deepseek-tui 0.8.32 → 0.8.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "deepseek-tui",
3
- "version": "0.8.32",
4
- "deepseekBinaryVersion": "0.8.32",
3
+ "version": "0.8.34",
4
+ "deepseekBinaryVersion": "0.8.34",
5
5
  "description": "Install and run deepseek and deepseek-tui binaries from GitHub release artifacts.",
6
6
  "author": "Hmbown",
7
7
  "license": "MIT",
@@ -985,10 +985,57 @@ async function verifyChecksum(filePath, assetName, checksums) {
985
985
  }
986
986
  }
987
987
 
988
+ async function checksumMatches(filePath, assetName, checksums) {
989
+ const expected = checksums.get(assetName);
990
+ if (!expected) {
991
+ throw new NonRetryableError(`Checksum manifest is missing ${assetName}`);
992
+ }
993
+ const actual = await sha256File(filePath);
994
+ return actual === expected;
995
+ }
996
+
988
997
  async function loadChecksums(version, repo, options = {}) {
989
998
  return parseChecksumManifest(await downloadText(checksumManifestUrl(version, repo), options));
990
999
  }
991
1000
 
1001
+ function existingBinaryCandidates(targetPath, assetName) {
1002
+ const candidates = [targetPath];
1003
+ const assetPath = path.join(path.dirname(targetPath), assetName);
1004
+ if (assetPath !== targetPath) {
1005
+ candidates.push(assetPath);
1006
+ }
1007
+ return candidates;
1008
+ }
1009
+
1010
+ async function adoptExistingBinaryIfValid(targetPath, assetName, version, getChecksums, marker) {
1011
+ const candidates = [];
1012
+ for (const candidate of existingBinaryCandidates(targetPath, assetName)) {
1013
+ if (await fileExists(candidate)) {
1014
+ candidates.push(candidate);
1015
+ }
1016
+ }
1017
+ if (candidates.length === 0) {
1018
+ return false;
1019
+ }
1020
+
1021
+ const checksums = await getChecksums();
1022
+ for (const candidate of candidates) {
1023
+ if (!(await checksumMatches(candidate, assetName, checksums))) {
1024
+ continue;
1025
+ }
1026
+ preflightGlibc(candidate);
1027
+ if (candidate !== targetPath) {
1028
+ await rename(candidate, targetPath);
1029
+ }
1030
+ if (process.platform !== "win32") {
1031
+ await chmod(targetPath, 0o755);
1032
+ }
1033
+ await writeFile(marker, String(version), "utf8");
1034
+ return true;
1035
+ }
1036
+ return false;
1037
+ }
1038
+
992
1039
  async function ensureBinary(targetPath, assetName, version, repo, getChecksums, options = {}) {
993
1040
  const marker = `${targetPath}.version`;
994
1041
  const downloadIfNeeded =
@@ -1001,6 +1048,9 @@ async function ensureBinary(targetPath, assetName, version, repo, getChecksums,
1001
1048
  return targetPath;
1002
1049
  }
1003
1050
  }
1051
+ if (await adoptExistingBinaryIfValid(targetPath, assetName, version, getChecksums, marker)) {
1052
+ return targetPath;
1053
+ }
1004
1054
  }
1005
1055
  const checksums = await getChecksums();
1006
1056
  const url = releaseAssetUrl(assetName, version, repo);
@@ -1077,9 +1127,11 @@ module.exports = {
1077
1127
  run,
1078
1128
  _internal: {
1079
1129
  isOptionalInstall,
1130
+ adoptExistingBinaryIfValid,
1080
1131
  shouldIgnoreInstallFailure,
1081
1132
  defaultTimeoutMs,
1082
1133
  defaultStallMs,
1134
+ ensureBinary,
1083
1135
  maxAttempts,
1084
1136
  withRetry,
1085
1137
  },
@@ -1,5 +1,7 @@
1
1
  const assert = require("node:assert/strict");
2
+ const crypto = require("node:crypto");
2
3
  const fs = require("node:fs");
4
+ const os = require("node:os");
3
5
  const path = require("node:path");
4
6
  const test = require("node:test");
5
7
 
@@ -7,7 +9,45 @@ const installScript = fs.readFileSync(
7
9
  path.join(__dirname, "..", "scripts", "install.js"),
8
10
  "utf8",
9
11
  );
10
- const { installFailureHint } = require("../scripts/install");
12
+ const { installFailureHint, _internal } = require("../scripts/install");
13
+
14
+ function sha256(content) {
15
+ return crypto.createHash("sha256").update(content).digest("hex");
16
+ }
17
+
18
+ async function makeTempDir(t) {
19
+ const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "deepseek-install-test-"));
20
+ t.after(() => fs.promises.rm(dir, { force: true, recursive: true }));
21
+ return dir;
22
+ }
23
+
24
+ async function exists(file) {
25
+ return fs.promises.access(file).then(
26
+ () => true,
27
+ () => false,
28
+ );
29
+ }
30
+
31
+ async function withoutForcedDownload(callback) {
32
+ const previousTui = process.env.DEEPSEEK_TUI_FORCE_DOWNLOAD;
33
+ const previousLegacy = process.env.DEEPSEEK_FORCE_DOWNLOAD;
34
+ delete process.env.DEEPSEEK_TUI_FORCE_DOWNLOAD;
35
+ delete process.env.DEEPSEEK_FORCE_DOWNLOAD;
36
+ try {
37
+ return await callback();
38
+ } finally {
39
+ if (previousTui === undefined) {
40
+ delete process.env.DEEPSEEK_TUI_FORCE_DOWNLOAD;
41
+ } else {
42
+ process.env.DEEPSEEK_TUI_FORCE_DOWNLOAD = previousTui;
43
+ }
44
+ if (previousLegacy === undefined) {
45
+ delete process.env.DEEPSEEK_FORCE_DOWNLOAD;
46
+ } else {
47
+ process.env.DEEPSEEK_FORCE_DOWNLOAD = previousLegacy;
48
+ }
49
+ }
50
+ }
11
51
 
12
52
  test("install script checks Node support before loading helpers", () => {
13
53
  const guardIndex = installScript.indexOf("assertSupportedNode();");
@@ -70,3 +110,71 @@ test("install failure hint checks configured release base when override is alrea
70
110
  }
71
111
  }
72
112
  });
113
+
114
+ test("ensureBinary adopts a manually placed target binary after checksum validation", async (t) => {
115
+ const dir = await makeTempDir(t);
116
+ const target = path.join(dir, process.platform === "win32" ? "deepseek.exe" : "deepseek");
117
+ const assetName = process.platform === "win32" ? "deepseek-windows-x64.exe" : "deepseek-linux-x64";
118
+ const version = "0.8.25";
119
+ const content = Buffer.from("manual deepseek binary");
120
+ let checksumLoads = 0;
121
+
122
+ await fs.promises.writeFile(target, content, { mode: 0o600 });
123
+ await fs.promises.writeFile(`${target}.version`, "0.8.24", "utf8");
124
+
125
+ const result = await withoutForcedDownload(() =>
126
+ _internal.ensureBinary(target, assetName, version, "Hmbown/DeepSeek-TUI", async () => {
127
+ checksumLoads += 1;
128
+ return new Map([[assetName, sha256(content)]]);
129
+ }),
130
+ );
131
+
132
+ assert.equal(result, target);
133
+ assert.equal(checksumLoads, 1);
134
+ assert.equal(await fs.promises.readFile(`${target}.version`, "utf8"), version);
135
+ if (process.platform !== "win32") {
136
+ assert.notEqual((await fs.promises.stat(target)).mode & 0o111, 0);
137
+ }
138
+ });
139
+
140
+ test("ensureBinary adopts an official release-named binary placed in downloads", async (t) => {
141
+ const dir = await makeTempDir(t);
142
+ const target = path.join(dir, process.platform === "win32" ? "deepseek.exe" : "deepseek");
143
+ const assetName = process.platform === "win32" ? "deepseek-windows-x64.exe" : "deepseek-linux-x64";
144
+ const assetPath = path.join(dir, assetName);
145
+ const version = "0.8.25";
146
+ const content = Buffer.from("official release binary");
147
+
148
+ await fs.promises.writeFile(assetPath, content);
149
+
150
+ const result = await withoutForcedDownload(() =>
151
+ _internal.ensureBinary(target, assetName, version, "Hmbown/DeepSeek-TUI", async () =>
152
+ new Map([[assetName, sha256(content)]]),
153
+ ),
154
+ );
155
+
156
+ assert.equal(result, target);
157
+ assert.equal(await exists(target), true);
158
+ assert.equal(await exists(assetPath), false);
159
+ assert.equal(await fs.promises.readFile(`${target}.version`, "utf8"), version);
160
+ });
161
+
162
+ test("manual binaries with mismatched checksums are not adopted", async (t) => {
163
+ const dir = await makeTempDir(t);
164
+ const target = path.join(dir, process.platform === "win32" ? "deepseek.exe" : "deepseek");
165
+ const assetName = process.platform === "win32" ? "deepseek-windows-x64.exe" : "deepseek-linux-x64";
166
+ const content = Buffer.from("wrong binary bytes");
167
+
168
+ await fs.promises.writeFile(target, content);
169
+
170
+ const adopted = await _internal.adoptExistingBinaryIfValid(
171
+ target,
172
+ assetName,
173
+ "0.8.25",
174
+ async () => new Map([[assetName, sha256(Buffer.from("different bytes"))]]),
175
+ `${target}.version`,
176
+ );
177
+
178
+ assert.equal(adopted, false);
179
+ assert.equal(await exists(`${target}.version`), false);
180
+ });