deepseek-tui 0.8.33 → 0.8.35
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 +2 -2
- package/scripts/install.js +52 -0
- package/test/install.test.js +109 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deepseek-tui",
|
|
3
|
-
"version": "0.8.
|
|
4
|
-
"deepseekBinaryVersion": "0.8.
|
|
3
|
+
"version": "0.8.35",
|
|
4
|
+
"deepseekBinaryVersion": "0.8.35",
|
|
5
5
|
"description": "Install and run deepseek and deepseek-tui binaries from GitHub release artifacts.",
|
|
6
6
|
"author": "Hmbown",
|
|
7
7
|
"license": "MIT",
|
package/scripts/install.js
CHANGED
|
@@ -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
|
},
|
package/test/install.test.js
CHANGED
|
@@ -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
|
+
});
|