From e61a40f39de2705bb8acf7ab014d5a869e9d4bcf Mon Sep 17 00:00:00 2001 From: lixia Date: Sun, 10 May 2026 11:26:08 +0800 Subject: [PATCH] fix: adopt verified npm binaries from downloads --- npm/deepseek-tui/scripts/install.js | 52 ++++++++++++ npm/deepseek-tui/test/install.test.js | 109 +++++++++++++++++++++++++- 2 files changed, 160 insertions(+), 1 deletion(-) diff --git a/npm/deepseek-tui/scripts/install.js b/npm/deepseek-tui/scripts/install.js index 884aa311..3a477f59 100644 --- a/npm/deepseek-tui/scripts/install.js +++ b/npm/deepseek-tui/scripts/install.js @@ -979,10 +979,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) { + 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(`${targetPath}.version`, String(version), "utf8"); + return true; + } + return false; +} + async function ensureBinary(targetPath, assetName, version, repo, getChecksums, options = {}) { const marker = `${targetPath}.version`; const downloadIfNeeded = @@ -995,6 +1042,9 @@ async function ensureBinary(targetPath, assetName, version, repo, getChecksums, return targetPath; } } + if (await adoptExistingBinaryIfValid(targetPath, assetName, version, getChecksums)) { + return targetPath; + } } const checksums = await getChecksums(); const url = releaseAssetUrl(assetName, version, repo); @@ -1071,9 +1121,11 @@ module.exports = { run, _internal: { isOptionalInstall, + adoptExistingBinaryIfValid, shouldIgnoreInstallFailure, defaultTimeoutMs, defaultStallMs, + ensureBinary, maxAttempts, withRetry, }, diff --git a/npm/deepseek-tui/test/install.test.js b/npm/deepseek-tui/test/install.test.js index 3753f015..3c8ee47d 100644 --- a/npm/deepseek-tui/test/install.test.js +++ b/npm/deepseek-tui/test/install.test.js @@ -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();"); @@ -69,3 +109,70 @@ 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"))]]), + ); + + assert.equal(adopted, false); + assert.equal(await exists(`${target}.version`), false); +});