@xberg-io/liter-llm-cli 1.15.0 → 1.17.3
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/bin/liter-llm.js +12 -17
- package/install.js +95 -133
- package/package.json +1 -1
package/bin/liter-llm.js
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import os from "node:os";
|
|
7
7
|
import path from "node:path";
|
|
8
|
-
import {fileURLToPath} from "node:url";
|
|
9
|
-
import {spawnSync} from "node:child_process";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { spawnSync } from "node:child_process";
|
|
10
10
|
|
|
11
11
|
const BIN_NAME = "liter-llm";
|
|
12
12
|
|
|
@@ -24,10 +24,8 @@ const binPath = path.join(__dirname, binaryName());
|
|
|
24
24
|
function isHealthy(file) {
|
|
25
25
|
try {
|
|
26
26
|
const stat = fs.statSync(file);
|
|
27
|
-
if (stat.size <= 0)
|
|
28
|
-
|
|
29
|
-
if (os.type() !== "Windows_NT" && (stat.mode & 0o111) === 0)
|
|
30
|
-
return false;
|
|
27
|
+
if (stat.size <= 0) return false;
|
|
28
|
+
if (os.type() !== "Windows_NT" && (stat.mode & 0o111) === 0) return false;
|
|
31
29
|
return true;
|
|
32
30
|
} catch {
|
|
33
31
|
return false;
|
|
@@ -35,22 +33,20 @@ function isHealthy(file) {
|
|
|
35
33
|
}
|
|
36
34
|
|
|
37
35
|
async function ensureBinary() {
|
|
38
|
-
if (fs.existsSync(binPath) && isHealthy(binPath))
|
|
39
|
-
|
|
40
|
-
process.stderr.write(
|
|
41
|
-
`${BIN_NAME}: binary missing or corrupt, attempting download...\n`);
|
|
36
|
+
if (fs.existsSync(binPath) && isHealthy(binPath)) return;
|
|
37
|
+
process.stderr.write(`${BIN_NAME}: binary missing or corrupt, attempting download...\n`);
|
|
42
38
|
// ~keep Call main() explicitly rather than relying on import side-effects:
|
|
43
39
|
// ESM ~keep caches modules, so the installer's top-level run is gated to
|
|
44
40
|
// direct ~keep invocation only and would not fire on import.
|
|
45
|
-
const {main} = await import("../install.js");
|
|
41
|
+
const { main } = await import("../install.js");
|
|
46
42
|
await main();
|
|
47
43
|
}
|
|
48
44
|
|
|
49
45
|
function printUnavailable() {
|
|
50
46
|
process.stderr.write(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
47
|
+
`${BIN_NAME} is not available for your platform yet. Install it with:\n` +
|
|
48
|
+
` brew install xberg-io/tap/liter-llm\n` +
|
|
49
|
+
` or use the Xberg plugin: /plugin marketplace add xberg-io/plugins\n`,
|
|
54
50
|
);
|
|
55
51
|
}
|
|
56
52
|
|
|
@@ -60,10 +56,9 @@ async function main() {
|
|
|
60
56
|
printUnavailable();
|
|
61
57
|
process.exit(1);
|
|
62
58
|
}
|
|
63
|
-
const result = spawnSync(binPath, process.argv.slice(2), {stdio
|
|
59
|
+
const result = spawnSync(binPath, process.argv.slice(2), { stdio: "inherit" });
|
|
64
60
|
if (result.error) {
|
|
65
|
-
process.stderr.write(
|
|
66
|
-
`${BIN_NAME}: failed to spawn binary: ${result.error.message}\n`);
|
|
61
|
+
process.stderr.write(`${BIN_NAME}: failed to spawn binary: ${result.error.message}\n`);
|
|
67
62
|
process.exit(1);
|
|
68
63
|
}
|
|
69
64
|
process.exit(result.status ?? 0);
|
package/install.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {execFileSync, spawnSync} from "node:child_process";
|
|
1
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
2
2
|
import crypto from "node:crypto";
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import https from "node:https";
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import path from "node:path";
|
|
7
|
-
import {fileURLToPath, pathToFileURL} from "node:url";
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
8
|
|
|
9
9
|
const REPO = "xberg-io/liter-llm";
|
|
10
10
|
const BIN_NAME = "liter-llm";
|
|
@@ -17,22 +17,17 @@ function targetTriple() {
|
|
|
17
17
|
const arch = os.arch();
|
|
18
18
|
|
|
19
19
|
if (type === "Windows_NT") {
|
|
20
|
-
if (arch === "x64")
|
|
21
|
-
return "x86_64-pc-windows-msvc";
|
|
20
|
+
if (arch === "x64") return "x86_64-pc-windows-msvc";
|
|
22
21
|
throw new Error(`unsupported Windows arch: ${arch}`);
|
|
23
22
|
}
|
|
24
23
|
if (type === "Linux") {
|
|
25
|
-
if (arch === "x64")
|
|
26
|
-
|
|
27
|
-
if (arch === "arm64")
|
|
28
|
-
return "aarch64-unknown-linux-gnu";
|
|
24
|
+
if (arch === "x64") return "x86_64-unknown-linux-gnu";
|
|
25
|
+
if (arch === "arm64") return "aarch64-unknown-linux-gnu";
|
|
29
26
|
throw new Error(`unsupported Linux arch: ${arch}`);
|
|
30
27
|
}
|
|
31
28
|
if (type === "Darwin") {
|
|
32
|
-
if (arch === "arm64")
|
|
33
|
-
|
|
34
|
-
if (arch === "x64")
|
|
35
|
-
return "x86_64-apple-darwin";
|
|
29
|
+
if (arch === "arm64") return "aarch64-apple-darwin";
|
|
30
|
+
if (arch === "x64") return "x86_64-apple-darwin";
|
|
36
31
|
throw new Error(`unsupported macOS arch: ${arch}`);
|
|
37
32
|
}
|
|
38
33
|
throw new Error(`unsupported platform: ${type} ${arch}`);
|
|
@@ -42,35 +37,30 @@ function binaryName() {
|
|
|
42
37
|
return os.type() === "Windows_NT" ? `${BIN_NAME}.exe` : BIN_NAME;
|
|
43
38
|
}
|
|
44
39
|
|
|
45
|
-
function httpGetBuffer(url, {headers = {}} = {}, maxRedirects = 5) {
|
|
40
|
+
function httpGetBuffer(url, { headers = {} } = {}, maxRedirects = 5) {
|
|
46
41
|
return new Promise((resolve, reject) => {
|
|
47
|
-
if (maxRedirects < 0)
|
|
48
|
-
return reject(new Error("too many redirects"));
|
|
42
|
+
if (maxRedirects < 0) return reject(new Error("too many redirects"));
|
|
49
43
|
if (!/^https:\/\//i.test(url)) {
|
|
50
44
|
return reject(new Error(`refusing non-https URL: ${url}`));
|
|
51
45
|
}
|
|
52
|
-
const req = https.get(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
res.on("data", (c) => chunks.push(c));
|
|
71
|
-
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
72
|
-
res.on("error", reject);
|
|
73
|
-
});
|
|
46
|
+
const req = https.get(url, { headers: { "User-Agent": USER_AGENT, ...headers } }, (res) => {
|
|
47
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
48
|
+
res.resume();
|
|
49
|
+
const next = res.headers.location;
|
|
50
|
+
if (!/^https:\/\//i.test(next)) {
|
|
51
|
+
return reject(new Error(`refusing non-https redirect to: ${next}`));
|
|
52
|
+
}
|
|
53
|
+
return httpGetBuffer(next, { headers }, maxRedirects - 1).then(resolve, reject);
|
|
54
|
+
}
|
|
55
|
+
if (res.statusCode !== 200) {
|
|
56
|
+
res.resume();
|
|
57
|
+
return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
|
|
58
|
+
}
|
|
59
|
+
const chunks = [];
|
|
60
|
+
res.on("data", (c) => chunks.push(c));
|
|
61
|
+
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
62
|
+
res.on("error", reject);
|
|
63
|
+
});
|
|
74
64
|
req.on("error", reject);
|
|
75
65
|
req.setTimeout(60000, () => {
|
|
76
66
|
req.destroy();
|
|
@@ -80,8 +70,7 @@ function httpGetBuffer(url, {headers = {}} = {}, maxRedirects = 5) {
|
|
|
80
70
|
}
|
|
81
71
|
|
|
82
72
|
async function httpGetJson(url) {
|
|
83
|
-
const buf = await httpGetBuffer(
|
|
84
|
-
url, {headers : {Accept : "application/vnd.github+json"}});
|
|
73
|
+
const buf = await httpGetBuffer(url, { headers: { Accept: "application/vnd.github+json" } });
|
|
85
74
|
return JSON.parse(buf.toString("utf8"));
|
|
86
75
|
}
|
|
87
76
|
|
|
@@ -117,24 +106,19 @@ export function isNonCliArtifact(name) {
|
|
|
117
106
|
export function assetScore(name) {
|
|
118
107
|
const n = (name || "").toLowerCase();
|
|
119
108
|
let score = 0;
|
|
120
|
-
if (n.includes("cli"))
|
|
121
|
-
|
|
122
|
-
if (n.includes(BIN_NAME.toLowerCase()))
|
|
123
|
-
score += 1;
|
|
109
|
+
if (n.includes("cli")) score += 2;
|
|
110
|
+
if (n.includes(BIN_NAME.toLowerCase())) score += 1;
|
|
124
111
|
return score;
|
|
125
112
|
}
|
|
126
113
|
|
|
127
114
|
export function selectArchiveName(names, triple) {
|
|
128
115
|
const survivors = (names || []).filter((name) => {
|
|
129
116
|
const n = (name || "").toLowerCase();
|
|
130
|
-
if (!n.includes(triple))
|
|
131
|
-
|
|
132
|
-
if (!(n.endsWith(".tar.gz") || n.endsWith(".zip")))
|
|
133
|
-
return false;
|
|
117
|
+
if (!n.includes(triple)) return false;
|
|
118
|
+
if (!(n.endsWith(".tar.gz") || n.endsWith(".zip"))) return false;
|
|
134
119
|
return !isNonCliArtifact(n);
|
|
135
120
|
});
|
|
136
|
-
if (survivors.length === 0)
|
|
137
|
-
return null;
|
|
121
|
+
if (survivors.length === 0) return null;
|
|
138
122
|
survivors.sort((a, b) => assetScore(b) - assetScore(a));
|
|
139
123
|
return survivors[0];
|
|
140
124
|
}
|
|
@@ -143,16 +127,15 @@ async function resolveRelease() {
|
|
|
143
127
|
const triple = targetTriple();
|
|
144
128
|
const pinned = process.env[VERSION_ENV];
|
|
145
129
|
const apiUrl = pinned
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
: `https://api.github.com/repos/${REPO}/releases/latest`;
|
|
130
|
+
? `https://api.github.com/repos/${REPO}/releases/tags/${encodeURIComponent(pinned)}`
|
|
131
|
+
: `https://api.github.com/repos/${REPO}/releases/latest`;
|
|
149
132
|
|
|
150
133
|
let release;
|
|
151
134
|
try {
|
|
152
135
|
release = await httpGetJson(apiUrl);
|
|
153
136
|
} catch (err) {
|
|
154
137
|
if (pinned && /HTTP 404/.test(err.message)) {
|
|
155
|
-
throw new Error(`release tag '${pinned}' not found`, {cause
|
|
138
|
+
throw new Error(`release tag '${pinned}' not found`, { cause: err });
|
|
156
139
|
}
|
|
157
140
|
throw err;
|
|
158
141
|
}
|
|
@@ -160,18 +143,16 @@ async function resolveRelease() {
|
|
|
160
143
|
const tag = release.tag_name || pinned || "latest";
|
|
161
144
|
|
|
162
145
|
const chosenName = selectArchiveName(
|
|
163
|
-
|
|
164
|
-
|
|
146
|
+
assets.map((a) => a.name),
|
|
147
|
+
triple,
|
|
165
148
|
);
|
|
166
149
|
if (!chosenName) {
|
|
167
|
-
throw new CliUnavailableError(`no standalone CLI asset for target triple "${
|
|
168
|
-
triple}" in ${REPO} release ${tag}`);
|
|
150
|
+
throw new CliUnavailableError(`no standalone CLI asset for target triple "${triple}" in ${REPO} release ${tag}`);
|
|
169
151
|
}
|
|
170
152
|
const archive = assets.find((a) => a.name === chosenName);
|
|
171
|
-
const checksums =
|
|
172
|
-
assets.find((a) => (a.name || "").toUpperCase().includes("SHA256SUMS"));
|
|
153
|
+
const checksums = assets.find((a) => (a.name || "").toUpperCase().includes("SHA256SUMS"));
|
|
173
154
|
|
|
174
|
-
return {tag, triple, archive, checksums};
|
|
155
|
+
return { tag, triple, archive, checksums };
|
|
175
156
|
}
|
|
176
157
|
|
|
177
158
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -180,14 +161,11 @@ const BIN_DIR = path.join(__dirname, "bin");
|
|
|
180
161
|
function expectedDigest(text, assetName) {
|
|
181
162
|
for (const raw of text.split(/\r?\n/)) {
|
|
182
163
|
const line = raw.trim();
|
|
183
|
-
if (!line)
|
|
184
|
-
continue;
|
|
164
|
+
if (!line) continue;
|
|
185
165
|
const parts = line.split(/\s+/);
|
|
186
|
-
if (parts.length < 2)
|
|
187
|
-
continue;
|
|
166
|
+
if (parts.length < 2) continue;
|
|
188
167
|
const name = parts[parts.length - 1].replace(/^\*/, "");
|
|
189
|
-
if (name === assetName)
|
|
190
|
-
return parts[0].toLowerCase();
|
|
168
|
+
if (name === assetName) return parts[0].toLowerCase();
|
|
191
169
|
}
|
|
192
170
|
return null;
|
|
193
171
|
}
|
|
@@ -195,58 +173,49 @@ function expectedDigest(text, assetName) {
|
|
|
195
173
|
async function verifyOrWarn(archiveBuf, archiveName, checksums) {
|
|
196
174
|
if (!checksums) {
|
|
197
175
|
process.stderr.write(
|
|
198
|
-
|
|
199
|
-
|
|
176
|
+
`WARNING: no SHA256SUMS asset found for ${archiveName}; ` +
|
|
177
|
+
`installing over HTTPS without checksum verification.\n`,
|
|
200
178
|
);
|
|
201
179
|
return;
|
|
202
180
|
}
|
|
203
|
-
const sumsText =
|
|
204
|
-
(await httpGetBuffer(checksums.browser_download_url)).toString("utf8");
|
|
181
|
+
const sumsText = (await httpGetBuffer(checksums.browser_download_url)).toString("utf8");
|
|
205
182
|
const expected = expectedDigest(sumsText, archiveName);
|
|
206
183
|
if (!expected) {
|
|
207
184
|
throw new Error(
|
|
208
|
-
|
|
209
|
-
checksums.name} — refusing to install unverified binary`,
|
|
185
|
+
`no checksum entry for ${archiveName} in ${checksums.name} — refusing to install unverified binary`,
|
|
210
186
|
);
|
|
211
187
|
}
|
|
212
|
-
const actual = crypto.createHash("sha256")
|
|
213
|
-
.update(archiveBuf)
|
|
214
|
-
.digest("hex")
|
|
215
|
-
.toLowerCase();
|
|
188
|
+
const actual = crypto.createHash("sha256").update(archiveBuf).digest("hex").toLowerCase();
|
|
216
189
|
if (actual !== expected) {
|
|
217
|
-
throw new Error(`checksum mismatch for ${archiveName} (expected ${
|
|
218
|
-
expected}, got ${actual})`);
|
|
190
|
+
throw new Error(`checksum mismatch for ${archiveName} (expected ${expected}, got ${actual})`);
|
|
219
191
|
}
|
|
220
192
|
process.stderr.write(`Checksum verified for ${archiveName}.\n`);
|
|
221
193
|
}
|
|
222
194
|
|
|
223
195
|
function isUnsafeEntry(name) {
|
|
224
196
|
const entry = String(name).replace(/\\/g, "/").trim();
|
|
225
|
-
if (!entry)
|
|
226
|
-
|
|
227
|
-
if (entry
|
|
228
|
-
|
|
229
|
-
if (/^[a-zA-Z]:/.test(entry))
|
|
230
|
-
return true;
|
|
231
|
-
if (entry.startsWith("//"))
|
|
232
|
-
return true;
|
|
197
|
+
if (!entry) return false;
|
|
198
|
+
if (entry.startsWith("/")) return true;
|
|
199
|
+
if (/^[a-zA-Z]:/.test(entry)) return true;
|
|
200
|
+
if (entry.startsWith("//")) return true;
|
|
233
201
|
return entry.split("/").some((part) => part === "..");
|
|
234
202
|
}
|
|
235
203
|
|
|
236
204
|
function listTarEntries(archivePath) {
|
|
237
|
-
const result = spawnSync("tar", [
|
|
205
|
+
const result = spawnSync("tar", ["-tzf", archivePath]);
|
|
238
206
|
if (result.status !== 0) {
|
|
239
207
|
const stderr = result.stderr ? result.stderr.toString() : "";
|
|
240
208
|
throw new Error(`tar listing failed: ${stderr || result.error}`);
|
|
241
209
|
}
|
|
242
|
-
return result.stdout
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
210
|
+
return result.stdout
|
|
211
|
+
.toString()
|
|
212
|
+
.split(/\r?\n/)
|
|
213
|
+
.map((s) => s.trim())
|
|
214
|
+
.filter(Boolean);
|
|
246
215
|
}
|
|
247
216
|
|
|
248
217
|
function extractTarGz(archivePath, destDir) {
|
|
249
|
-
const result = spawnSync("tar", [
|
|
218
|
+
const result = spawnSync("tar", ["-xzf", archivePath, "-C", destDir]);
|
|
250
219
|
if (result.status !== 0) {
|
|
251
220
|
const stderr = result.stderr ? result.stderr.toString() : "";
|
|
252
221
|
throw new Error(`tar extraction failed: ${stderr || result.error}`);
|
|
@@ -256,26 +225,28 @@ function extractTarGz(archivePath, destDir) {
|
|
|
256
225
|
function listZipEntries(archivePath) {
|
|
257
226
|
if (os.type() === "Windows_NT") {
|
|
258
227
|
const script =
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const out = execFileSync(
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
228
|
+
"$ErrorActionPreference='Stop';" +
|
|
229
|
+
"Add-Type -AssemblyName System.IO.Compression.FileSystem;" +
|
|
230
|
+
"[System.IO.Compression.ZipFile]::OpenRead($args[0]).Entries |" +
|
|
231
|
+
" ForEach-Object { $_.FullName }";
|
|
232
|
+
const out = execFileSync("powershell", ["-NoProfile", "-NonInteractive", "-Command", script, archivePath], {
|
|
233
|
+
encoding: "utf8",
|
|
234
|
+
});
|
|
235
|
+
return out
|
|
236
|
+
.split(/\r?\n/)
|
|
237
|
+
.map((s) => s.trim())
|
|
238
|
+
.filter(Boolean);
|
|
269
239
|
}
|
|
270
|
-
const result = spawnSync("unzip", [
|
|
240
|
+
const result = spawnSync("unzip", ["-Z1", archivePath]);
|
|
271
241
|
if (result.status !== 0) {
|
|
272
242
|
const stderr = result.stderr ? result.stderr.toString() : "";
|
|
273
243
|
throw new Error(`zip listing failed: ${stderr || result.error}`);
|
|
274
244
|
}
|
|
275
|
-
return result.stdout
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
245
|
+
return result.stdout
|
|
246
|
+
.toString()
|
|
247
|
+
.split(/\r?\n/)
|
|
248
|
+
.map((s) => s.trim())
|
|
249
|
+
.filter(Boolean);
|
|
279
250
|
}
|
|
280
251
|
|
|
281
252
|
function extractZip(archivePath, destDir) {
|
|
@@ -297,7 +268,7 @@ function extractZip(archivePath, destDir) {
|
|
|
297
268
|
}
|
|
298
269
|
return;
|
|
299
270
|
}
|
|
300
|
-
const result = spawnSync("unzip", [
|
|
271
|
+
const result = spawnSync("unzip", ["-o", archivePath, "-d", destDir]);
|
|
301
272
|
if (result.status !== 0) {
|
|
302
273
|
const stderr = result.stderr ? result.stderr.toString() : "";
|
|
303
274
|
throw new Error(`zip extraction failed: ${stderr || result.error}`);
|
|
@@ -305,12 +276,11 @@ function extractZip(archivePath, destDir) {
|
|
|
305
276
|
}
|
|
306
277
|
|
|
307
278
|
function findBinary(dir, name) {
|
|
308
|
-
for (const entry of fs.readdirSync(dir, {withFileTypes
|
|
279
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
309
280
|
const full = path.join(dir, entry.name);
|
|
310
281
|
if (entry.isDirectory()) {
|
|
311
282
|
const found = findBinary(full, name);
|
|
312
|
-
if (found)
|
|
313
|
-
return found;
|
|
283
|
+
if (found) return found;
|
|
314
284
|
} else if (entry.name === name) {
|
|
315
285
|
return full;
|
|
316
286
|
}
|
|
@@ -319,22 +289,18 @@ function findBinary(dir, name) {
|
|
|
319
289
|
}
|
|
320
290
|
|
|
321
291
|
function findDir(dir, name) {
|
|
322
|
-
for (const entry of fs.readdirSync(dir, {withFileTypes
|
|
323
|
-
if (!entry.isDirectory())
|
|
324
|
-
|
|
325
|
-
if (entry.name === name)
|
|
326
|
-
return path.join(dir, entry.name);
|
|
292
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
293
|
+
if (!entry.isDirectory()) continue;
|
|
294
|
+
if (entry.name === name) return path.join(dir, entry.name);
|
|
327
295
|
const found = findDir(path.join(dir, entry.name), name);
|
|
328
|
-
if (found)
|
|
329
|
-
return found;
|
|
296
|
+
if (found) return found;
|
|
330
297
|
}
|
|
331
298
|
return null;
|
|
332
299
|
}
|
|
333
300
|
|
|
334
301
|
function safeExtract(archivePath, archiveName, dest) {
|
|
335
302
|
const isZip = archiveName.toLowerCase().endsWith(".zip");
|
|
336
|
-
const entries =
|
|
337
|
-
isZip ? listZipEntries(archivePath) : listTarEntries(archivePath);
|
|
303
|
+
const entries = isZip ? listZipEntries(archivePath) : listTarEntries(archivePath);
|
|
338
304
|
for (const entry of entries) {
|
|
339
305
|
if (isUnsafeEntry(entry)) {
|
|
340
306
|
throw new Error(`refusing unsafe archive entry: ${entry}`);
|
|
@@ -352,19 +318,18 @@ function safeExtract(archivePath, archiveName, dest) {
|
|
|
352
318
|
const binName = binaryName();
|
|
353
319
|
const extractedBin = findBinary(tmpDir, binName);
|
|
354
320
|
if (!extractedBin) {
|
|
355
|
-
throw new CliUnavailableError(`archive ${
|
|
356
|
-
archiveName} did not contain expected CLI binary ${binName}`);
|
|
321
|
+
throw new CliUnavailableError(`archive ${archiveName} did not contain expected CLI binary ${binName}`);
|
|
357
322
|
}
|
|
358
323
|
const finalBin = path.join(dest, binName);
|
|
359
324
|
fs.copyFileSync(extractedBin, finalBin);
|
|
360
325
|
|
|
361
326
|
const libDir = findDir(tmpDir, "lib");
|
|
362
327
|
if (libDir) {
|
|
363
|
-
fs.cpSync(libDir, path.join(dest, "lib"), {recursive
|
|
328
|
+
fs.cpSync(libDir, path.join(dest, "lib"), { recursive: true });
|
|
364
329
|
}
|
|
365
330
|
return finalBin;
|
|
366
331
|
} finally {
|
|
367
|
-
fs.rmSync(tmpDir, {recursive
|
|
332
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
368
333
|
}
|
|
369
334
|
}
|
|
370
335
|
|
|
@@ -376,17 +341,14 @@ export async function main() {
|
|
|
376
341
|
const stat = fs.statSync(finalPath);
|
|
377
342
|
const sizeOk = stat.size > 0;
|
|
378
343
|
const execOk = os.type() === "Windows_NT" || (stat.mode & 0o111) !== 0;
|
|
379
|
-
if (sizeOk && execOk)
|
|
380
|
-
|
|
381
|
-
} catch {
|
|
382
|
-
}
|
|
344
|
+
if (sizeOk && execOk) return;
|
|
345
|
+
} catch {}
|
|
383
346
|
}
|
|
384
347
|
|
|
385
|
-
fs.mkdirSync(BIN_DIR, {recursive
|
|
348
|
+
fs.mkdirSync(BIN_DIR, { recursive: true });
|
|
386
349
|
|
|
387
|
-
const {tag, archive, checksums} = await resolveRelease();
|
|
388
|
-
process.stderr.write(
|
|
389
|
-
`Downloading ${BIN_NAME} ${tag} asset ${archive.name}...\n`);
|
|
350
|
+
const { tag, archive, checksums } = await resolveRelease();
|
|
351
|
+
process.stderr.write(`Downloading ${BIN_NAME} ${tag} asset ${archive.name}...\n`);
|
|
390
352
|
|
|
391
353
|
const archiveBuf = await httpGetBuffer(archive.browser_download_url);
|
|
392
354
|
await verifyOrWarn(archiveBuf, archive.name, checksums);
|
|
@@ -397,7 +359,7 @@ export async function main() {
|
|
|
397
359
|
fs.writeFileSync(archivePath, archiveBuf);
|
|
398
360
|
safeExtract(archivePath, archive.name, BIN_DIR);
|
|
399
361
|
} finally {
|
|
400
|
-
fs.rmSync(stageDir, {recursive
|
|
362
|
+
fs.rmSync(stageDir, { recursive: true, force: true });
|
|
401
363
|
}
|
|
402
364
|
|
|
403
365
|
if (os.type() !== "Windows_NT") {
|
package/package.json
CHANGED