Merge remote-tracking branch 'origin/pr/1339' into work/v0.8.34
This commit is contained in:
@@ -985,10 +985,57 @@ async function verifyChecksum(filePath, assetName, checksums) {
|
||||
}
|
||||
}
|
||||
|
||||
async function checksumMatches(filePath, assetName, checksums) {
|
||||
const expected = checksums.get(assetName);
|
||||
if (!expected) {
|
||||
throw new NonRetryableError(`Checksum manifest is missing ${assetName}`);
|
||||
}
|
||||
const actual = await sha256File(filePath);
|
||||
return actual === expected;
|
||||
}
|
||||
|
||||
async function loadChecksums(version, repo, options = {}) {
|
||||
return parseChecksumManifest(await downloadText(checksumManifestUrl(version, repo), options));
|
||||
}
|
||||
|
||||
function existingBinaryCandidates(targetPath, assetName) {
|
||||
const candidates = [targetPath];
|
||||
const assetPath = path.join(path.dirname(targetPath), assetName);
|
||||
if (assetPath !== targetPath) {
|
||||
candidates.push(assetPath);
|
||||
}
|
||||
return candidates;
|
||||
}
|
||||
|
||||
async function adoptExistingBinaryIfValid(targetPath, assetName, version, getChecksums, marker) {
|
||||
const candidates = [];
|
||||
for (const candidate of existingBinaryCandidates(targetPath, assetName)) {
|
||||
if (await fileExists(candidate)) {
|
||||
candidates.push(candidate);
|
||||
}
|
||||
}
|
||||
if (candidates.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const checksums = await getChecksums();
|
||||
for (const candidate of candidates) {
|
||||
if (!(await checksumMatches(candidate, assetName, checksums))) {
|
||||
continue;
|
||||
}
|
||||
preflightGlibc(candidate);
|
||||
if (candidate !== targetPath) {
|
||||
await rename(candidate, targetPath);
|
||||
}
|
||||
if (process.platform !== "win32") {
|
||||
await chmod(targetPath, 0o755);
|
||||
}
|
||||
await writeFile(marker, String(version), "utf8");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function ensureBinary(targetPath, assetName, version, repo, getChecksums, options = {}) {
|
||||
const marker = `${targetPath}.version`;
|
||||
const downloadIfNeeded =
|
||||
@@ -1001,6 +1048,9 @@ async function ensureBinary(targetPath, assetName, version, repo, getChecksums,
|
||||
return targetPath;
|
||||
}
|
||||
}
|
||||
if (await adoptExistingBinaryIfValid(targetPath, assetName, version, getChecksums, marker)) {
|
||||
return targetPath;
|
||||
}
|
||||
}
|
||||
const checksums = await getChecksums();
|
||||
const url = releaseAssetUrl(assetName, version, repo);
|
||||
@@ -1077,9 +1127,11 @@ module.exports = {
|
||||
run,
|
||||
_internal: {
|
||||
isOptionalInstall,
|
||||
adoptExistingBinaryIfValid,
|
||||
shouldIgnoreInstallFailure,
|
||||
defaultTimeoutMs,
|
||||
defaultStallMs,
|
||||
ensureBinary,
|
||||
maxAttempts,
|
||||
withRetry,
|
||||
},
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
const assert = require("node:assert/strict");
|
||||
const crypto = require("node:crypto");
|
||||
const fs = require("node:fs");
|
||||
const os = require("node:os");
|
||||
const path = require("node:path");
|
||||
const test = require("node:test");
|
||||
|
||||
@@ -7,7 +9,45 @@ const installScript = fs.readFileSync(
|
||||
path.join(__dirname, "..", "scripts", "install.js"),
|
||||
"utf8",
|
||||
);
|
||||
const { installFailureHint } = require("../scripts/install");
|
||||
const { installFailureHint, _internal } = require("../scripts/install");
|
||||
|
||||
function sha256(content) {
|
||||
return crypto.createHash("sha256").update(content).digest("hex");
|
||||
}
|
||||
|
||||
async function makeTempDir(t) {
|
||||
const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "deepseek-install-test-"));
|
||||
t.after(() => fs.promises.rm(dir, { force: true, recursive: true }));
|
||||
return dir;
|
||||
}
|
||||
|
||||
async function exists(file) {
|
||||
return fs.promises.access(file).then(
|
||||
() => true,
|
||||
() => false,
|
||||
);
|
||||
}
|
||||
|
||||
async function withoutForcedDownload(callback) {
|
||||
const previousTui = process.env.DEEPSEEK_TUI_FORCE_DOWNLOAD;
|
||||
const previousLegacy = process.env.DEEPSEEK_FORCE_DOWNLOAD;
|
||||
delete process.env.DEEPSEEK_TUI_FORCE_DOWNLOAD;
|
||||
delete process.env.DEEPSEEK_FORCE_DOWNLOAD;
|
||||
try {
|
||||
return await callback();
|
||||
} finally {
|
||||
if (previousTui === undefined) {
|
||||
delete process.env.DEEPSEEK_TUI_FORCE_DOWNLOAD;
|
||||
} else {
|
||||
process.env.DEEPSEEK_TUI_FORCE_DOWNLOAD = previousTui;
|
||||
}
|
||||
if (previousLegacy === undefined) {
|
||||
delete process.env.DEEPSEEK_FORCE_DOWNLOAD;
|
||||
} else {
|
||||
process.env.DEEPSEEK_FORCE_DOWNLOAD = previousLegacy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test("install script checks Node support before loading helpers", () => {
|
||||
const guardIndex = installScript.indexOf("assertSupportedNode();");
|
||||
@@ -70,3 +110,71 @@ test("install failure hint checks configured release base when override is alrea
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("ensureBinary adopts a manually placed target binary after checksum validation", async (t) => {
|
||||
const dir = await makeTempDir(t);
|
||||
const target = path.join(dir, process.platform === "win32" ? "deepseek.exe" : "deepseek");
|
||||
const assetName = process.platform === "win32" ? "deepseek-windows-x64.exe" : "deepseek-linux-x64";
|
||||
const version = "0.8.25";
|
||||
const content = Buffer.from("manual deepseek binary");
|
||||
let checksumLoads = 0;
|
||||
|
||||
await fs.promises.writeFile(target, content, { mode: 0o600 });
|
||||
await fs.promises.writeFile(`${target}.version`, "0.8.24", "utf8");
|
||||
|
||||
const result = await withoutForcedDownload(() =>
|
||||
_internal.ensureBinary(target, assetName, version, "Hmbown/DeepSeek-TUI", async () => {
|
||||
checksumLoads += 1;
|
||||
return new Map([[assetName, sha256(content)]]);
|
||||
}),
|
||||
);
|
||||
|
||||
assert.equal(result, target);
|
||||
assert.equal(checksumLoads, 1);
|
||||
assert.equal(await fs.promises.readFile(`${target}.version`, "utf8"), version);
|
||||
if (process.platform !== "win32") {
|
||||
assert.notEqual((await fs.promises.stat(target)).mode & 0o111, 0);
|
||||
}
|
||||
});
|
||||
|
||||
test("ensureBinary adopts an official release-named binary placed in downloads", async (t) => {
|
||||
const dir = await makeTempDir(t);
|
||||
const target = path.join(dir, process.platform === "win32" ? "deepseek.exe" : "deepseek");
|
||||
const assetName = process.platform === "win32" ? "deepseek-windows-x64.exe" : "deepseek-linux-x64";
|
||||
const assetPath = path.join(dir, assetName);
|
||||
const version = "0.8.25";
|
||||
const content = Buffer.from("official release binary");
|
||||
|
||||
await fs.promises.writeFile(assetPath, content);
|
||||
|
||||
const result = await withoutForcedDownload(() =>
|
||||
_internal.ensureBinary(target, assetName, version, "Hmbown/DeepSeek-TUI", async () =>
|
||||
new Map([[assetName, sha256(content)]]),
|
||||
),
|
||||
);
|
||||
|
||||
assert.equal(result, target);
|
||||
assert.equal(await exists(target), true);
|
||||
assert.equal(await exists(assetPath), false);
|
||||
assert.equal(await fs.promises.readFile(`${target}.version`, "utf8"), version);
|
||||
});
|
||||
|
||||
test("manual binaries with mismatched checksums are not adopted", async (t) => {
|
||||
const dir = await makeTempDir(t);
|
||||
const target = path.join(dir, process.platform === "win32" ? "deepseek.exe" : "deepseek");
|
||||
const assetName = process.platform === "win32" ? "deepseek-windows-x64.exe" : "deepseek-linux-x64";
|
||||
const content = Buffer.from("wrong binary bytes");
|
||||
|
||||
await fs.promises.writeFile(target, content);
|
||||
|
||||
const adopted = await _internal.adoptExistingBinaryIfValid(
|
||||
target,
|
||||
assetName,
|
||||
"0.8.25",
|
||||
async () => new Map([[assetName, sha256(Buffer.from("different bytes"))]]),
|
||||
`${target}.version`,
|
||||
);
|
||||
|
||||
assert.equal(adopted, false);
|
||||
assert.equal(await exists(`${target}.version`), false);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user