@liwenkai/deepcode 0.4.8 → 0.4.10
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/deepcode +54 -8
- package/install.mjs +56 -10
- package/package.json +1 -1
package/bin/deepcode
CHANGED
|
@@ -9,15 +9,25 @@
|
|
|
9
9
|
// instead of leaving a command pointing at nothing.
|
|
10
10
|
'use strict';
|
|
11
11
|
|
|
12
|
-
const {
|
|
12
|
+
const { spawn } = require('node:child_process');
|
|
13
13
|
const { existsSync } = require('node:fs');
|
|
14
|
-
const { join } = require('node:path');
|
|
14
|
+
const { basename, join } = require('node:path');
|
|
15
|
+
const os = require('node:os');
|
|
15
16
|
|
|
16
17
|
const binary = join(
|
|
17
18
|
__dirname,
|
|
18
19
|
process.platform === 'win32' ? 'deepcode.exe' : 'deepcode-bin',
|
|
19
20
|
);
|
|
20
21
|
|
|
22
|
+
// The binary prints its own name in `--help`, `--version`, every usage line and
|
|
23
|
+
// every "Try `… --help`" error, and it reads that name from argv[0]. Spawned
|
|
24
|
+
// from here, argv[0] is `deepcode-bin` — the deliberately distinct download
|
|
25
|
+
// name, which is on nobody's PATH — so the first lines a new user reads told
|
|
26
|
+
// them to run a command that does not exist. This launcher is the only thing
|
|
27
|
+
// that knows the name npm actually linked, so it hands it over. `argv[1]` is
|
|
28
|
+
// this script's own path, which IS that name.
|
|
29
|
+
const invokedAs = basename(process.argv[1] || 'deepcode');
|
|
30
|
+
|
|
21
31
|
if (!existsSync(binary)) {
|
|
22
32
|
console.error('deepcode: native binary not found.');
|
|
23
33
|
console.error('Reinstall without --ignore-scripts, or grab a prebuilt binary from');
|
|
@@ -25,12 +35,48 @@ if (!existsSync(binary)) {
|
|
|
25
35
|
process.exit(1);
|
|
26
36
|
}
|
|
27
37
|
|
|
28
|
-
|
|
38
|
+
// `spawn`, not `spawnSync`: a supervisor (or `timeout`, or a closed shell) that
|
|
39
|
+
// sends SIGTERM/SIGHUP to THIS launcher would, with spawnSync, kill only Node —
|
|
40
|
+
// the native binary is reparented to init and keeps running, orphaning its job
|
|
41
|
+
// process groups (dev servers, watchers). Async spawn lets us forward those
|
|
42
|
+
// signals so the binary runs its own graceful shutdown. (SIGINT/Ctrl-C already
|
|
43
|
+
// reaches the whole foreground group; forwarding it too is harmless — the binary
|
|
44
|
+
// guards against a double interrupt — and covers a supervisor that signals only
|
|
45
|
+
// this pid.)
|
|
46
|
+
const child = spawn(binary, process.argv.slice(2), {
|
|
47
|
+
stdio: 'inherit',
|
|
48
|
+
env: { ...process.env, DEEP_CODE_PROGRAM_NAME: invokedAs },
|
|
49
|
+
});
|
|
29
50
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
51
|
+
const forward = (signal) => {
|
|
52
|
+
try {
|
|
53
|
+
child.kill(signal);
|
|
54
|
+
} catch {
|
|
55
|
+
/* child already gone — nothing to forward to */
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
// SIGINT is deliberately NOT forwarded on Windows: the child receives its own
|
|
59
|
+
// CTRL_C_EVENT from the console directly, and Node's `child.kill('SIGINT')`
|
|
60
|
+
// there is a forced TerminateProcess — it would win the race and abort the
|
|
61
|
+
// binary before its graceful Ctrl-C cancel (drain, JSON report, session tail)
|
|
62
|
+
// could run. On unix SIGINT already reaches the whole foreground group; the
|
|
63
|
+
// forward only additionally covers a supervisor signalling this pid alone.
|
|
64
|
+
const forwarded =
|
|
65
|
+
process.platform === 'win32' ? ['SIGTERM', 'SIGHUP'] : ['SIGINT', 'SIGTERM', 'SIGHUP'];
|
|
66
|
+
for (const signal of forwarded) {
|
|
67
|
+
process.on(signal, () => forward(signal));
|
|
33
68
|
}
|
|
34
69
|
|
|
35
|
-
|
|
36
|
-
|
|
70
|
+
child.on('error', (error) => {
|
|
71
|
+
console.error(`deepcode: failed to launch binary: ${error.message}`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
child.on('exit', (code, signal) => {
|
|
76
|
+
// Mirror the child's fate. A signal exit becomes the shell convention 128+n so
|
|
77
|
+
// the parent sees the real cause; a normal exit passes its code through.
|
|
78
|
+
if (signal) {
|
|
79
|
+
process.exit(128 + (os.constants.signals[signal] || 0));
|
|
80
|
+
}
|
|
81
|
+
process.exit(code === null ? 1 : code);
|
|
82
|
+
});
|
package/install.mjs
CHANGED
|
@@ -237,8 +237,24 @@ async function checksumMatches(assetName, binPath) {
|
|
|
237
237
|
const expected = parseChecksum(fs.readFileSync(sumsPath, 'utf8'), assetName);
|
|
238
238
|
if (!expected) return !checksumRequired();
|
|
239
239
|
return (await sha256OfFile(binPath)) === expected;
|
|
240
|
-
} catch {
|
|
241
|
-
|
|
240
|
+
} catch (error) {
|
|
241
|
+
// Distinguish "the server answered no" from "could not reach the server".
|
|
242
|
+
// A 404 / HTTP status means this VERSION's manifest or asset is genuinely
|
|
243
|
+
// absent (a wrong version left on disk), so fall through to re-download as
|
|
244
|
+
// before. But a connection failure/timeout is transient — offline
|
|
245
|
+
// `npm rebuild`, a blocked request — and the binary on disk was already
|
|
246
|
+
// verified at install time (download → verifyChecksum → rename), so deleting
|
|
247
|
+
// it and then failing the offline re-download is worse than keeping it.
|
|
248
|
+
const message = String(error?.message ?? error);
|
|
249
|
+
const serverRejected = message.includes('Binary not found') || message.startsWith('HTTP ');
|
|
250
|
+
if (serverRejected) {
|
|
251
|
+
return !checksumRequired();
|
|
252
|
+
}
|
|
253
|
+
console.warn(
|
|
254
|
+
'⚠️ deepcode: could not reach SHA256SUMS to re-verify the installed binary ' +
|
|
255
|
+
'(offline?); keeping the existing, install-time-verified binary.',
|
|
256
|
+
);
|
|
257
|
+
return true;
|
|
242
258
|
} finally {
|
|
243
259
|
fs.rmSync(sumsPath, { force: true });
|
|
244
260
|
}
|
|
@@ -266,7 +282,32 @@ function sha256OfFile(file) {
|
|
|
266
282
|
}
|
|
267
283
|
|
|
268
284
|
// ── 辅助函数 ──
|
|
269
|
-
|
|
285
|
+
// GitHub release assets always 302 to a signed URL, so the redirect branch runs
|
|
286
|
+
// on every install. Two things it must not do: follow forever, and throw
|
|
287
|
+
// synchronously out of the response callback (an `https.get` handler is not
|
|
288
|
+
// inside the promise executor, so a throw there is an uncaught exception, not a
|
|
289
|
+
// rejection — the install dies with an unrelated stack).
|
|
290
|
+
//
|
|
291
|
+
// `MAX_DOWNLOAD_REDIRECTS` bounds the chain: each hop opens its own socket and
|
|
292
|
+
// arms its own 5-minute timeout, so an unbounded chain is an unbounded hang, not
|
|
293
|
+
// merely a slow one.
|
|
294
|
+
const MAX_DOWNLOAD_REDIRECTS = 5;
|
|
295
|
+
|
|
296
|
+
// `fs.rmSync(..., { force: true })`, never `unlinkSync`: the write stream opens
|
|
297
|
+
// the file asynchronously, so on a fast response the path may not exist yet
|
|
298
|
+
// (`ENOENT`), and on Windows a file with a live handle refuses to unlink
|
|
299
|
+
// (`EBUSY`/`EPERM`). Both threw from inside the response callback. `force`
|
|
300
|
+
// swallows the missing-file case; the try/catch covers the locked-file one.
|
|
301
|
+
function discardPartial(file, dest) {
|
|
302
|
+
file.close();
|
|
303
|
+
try {
|
|
304
|
+
fs.rmSync(dest, { force: true });
|
|
305
|
+
} catch {
|
|
306
|
+
/* a locked or already-gone temp file must not mask the real error */
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function downloadFile(url, dest, redirectsLeft = MAX_DOWNLOAD_REDIRECTS) {
|
|
270
311
|
return new Promise((resolve, reject) => {
|
|
271
312
|
const file = fs.createWriteStream(dest, { mode: 0o755 });
|
|
272
313
|
|
|
@@ -275,18 +316,24 @@ function downloadFile(url, dest) {
|
|
|
275
316
|
if ([301, 302, 303, 307, 308].includes(response.statusCode)) {
|
|
276
317
|
const redirectUrl = response.headers.location;
|
|
277
318
|
if (!redirectUrl) {
|
|
319
|
+
discardPartial(file, dest);
|
|
278
320
|
reject(new Error(`Redirect with no location from ${url}`));
|
|
279
321
|
return;
|
|
280
322
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
323
|
+
if (redirectsLeft <= 0) {
|
|
324
|
+
discardPartial(file, dest);
|
|
325
|
+
reject(new Error(
|
|
326
|
+
`Too many redirects (more than ${MAX_DOWNLOAD_REDIRECTS}) starting at ${url}.`
|
|
327
|
+
));
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
discardPartial(file, dest);
|
|
331
|
+
downloadFile(redirectUrl, dest, redirectsLeft - 1).then(resolve).catch(reject);
|
|
284
332
|
return;
|
|
285
333
|
}
|
|
286
334
|
|
|
287
335
|
if (response.statusCode === 404) {
|
|
288
|
-
file
|
|
289
|
-
fs.unlinkSync(dest);
|
|
336
|
+
discardPartial(file, dest);
|
|
290
337
|
reject(new Error(
|
|
291
338
|
`Binary not found for ${platform()}-${arch()} (v${VERSION}). ` +
|
|
292
339
|
`Check https://github.com/${REPO}/releases for available versions.`
|
|
@@ -295,8 +342,7 @@ function downloadFile(url, dest) {
|
|
|
295
342
|
}
|
|
296
343
|
|
|
297
344
|
if (response.statusCode !== 200) {
|
|
298
|
-
file
|
|
299
|
-
fs.unlinkSync(dest);
|
|
345
|
+
discardPartial(file, dest);
|
|
300
346
|
reject(new Error(`HTTP ${response.statusCode} from ${url}`));
|
|
301
347
|
return;
|
|
302
348
|
}
|