cicy-code 2.1.47 → 2.1.49
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/cicy-code.js +94 -13
- package/package.json +1 -1
package/bin/cicy-code.js
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
3
|
-
// platform-specific optionalDependency (cicy-code-<os>-<cpu>) and execs
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
|
|
2
|
+
// Launcher for the npm distribution. Resolves the prebuilt binary that ships
|
|
3
|
+
// in the platform-specific optionalDependency (cicy-code-<os>-<cpu>) and execs
|
|
4
|
+
// it — no network, no postinstall download. ALL binary args/subcommands are
|
|
5
|
+
// passed straight through (skill <...>, --dev, --hot, --helper=1, --agents=…,
|
|
6
|
+
// --public, --cn, etc.), so `npx cicy-code <anything>` == the binary.
|
|
7
|
+
//
|
|
8
|
+
// Only when actually starting the server, it mirrors dev.py's startup: if PORT
|
|
9
|
+
// (default 8008) is held by a *cicy-code* process it kills it and waits, so a
|
|
10
|
+
// stale instance never blocks the new one. A non-cicy occupant is left alone
|
|
11
|
+
// and we abort. Utility invocations (--help/--version, `skill …`) skip the
|
|
12
|
+
// port dance entirely — `npx cicy-code --version` must never touch :8008.
|
|
13
|
+
const { spawn, execSync } = require('child_process');
|
|
8
14
|
const fs = require('fs');
|
|
9
15
|
|
|
10
|
-
const
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
const PORT = process.env.PORT || '8008';
|
|
11
18
|
|
|
19
|
+
const platformPkg = `cicy-code-${process.platform}-${process.arch}`;
|
|
12
20
|
let binPath;
|
|
13
21
|
try {
|
|
14
|
-
// require.resolve finds the binary inside the installed sub-package,
|
|
15
|
-
// wherever the package manager hoisted it.
|
|
16
22
|
binPath = require.resolve(`${platformPkg}/cicy-code`);
|
|
17
23
|
} catch {
|
|
18
24
|
console.error(`cicy-code: no prebuilt binary for ${process.platform}-${process.arch}.`);
|
|
@@ -22,13 +28,88 @@ try {
|
|
|
22
28
|
` (in China add --registry=https://registry.npmmirror.com)`);
|
|
23
29
|
process.exit(1);
|
|
24
30
|
}
|
|
25
|
-
|
|
26
|
-
// npm restores the 0755 mode from the tarball, but chmod defensively in case
|
|
27
|
-
// a mirror or extraction stripped the exec bit.
|
|
28
31
|
try { fs.chmodSync(binPath, 0o755); } catch {}
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
// Utility invocations don't start the server, so they must NOT kill :8008.
|
|
34
|
+
const isUtility =
|
|
35
|
+
args.some((a) => a === '-h' || a === '--help' || a === '-v' || a === '--version') ||
|
|
36
|
+
args[0] === 'skill';
|
|
37
|
+
|
|
38
|
+
if (!isUtility) ensurePortFree(PORT);
|
|
39
|
+
|
|
40
|
+
const child = spawn(binPath, args, {
|
|
41
|
+
stdio: 'inherit',
|
|
42
|
+
env: { ...process.env, PORT },
|
|
43
|
+
});
|
|
31
44
|
child.on('exit', (code, signal) => {
|
|
32
45
|
if (signal) process.kill(process.pid, signal);
|
|
33
46
|
else process.exit(code == null ? 0 : code);
|
|
34
47
|
});
|
|
48
|
+
|
|
49
|
+
// --- dev.py-style port hygiene --------------------------------------------
|
|
50
|
+
|
|
51
|
+
function ensurePortFree(port) {
|
|
52
|
+
const existing = pidOnPort(port);
|
|
53
|
+
if (!existing) return;
|
|
54
|
+
const cmd = processCommand(existing);
|
|
55
|
+
if (/cicy-code/.test(cmd)) {
|
|
56
|
+
console.log(`cicy-code: stopping existing instance on :${port} (pid=${existing})`);
|
|
57
|
+
if (!killPid(existing) || pidOnPort(port)) {
|
|
58
|
+
console.error(`cicy-code: port ${port} still in use after kill — aborting`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
console.error(`cicy-code: port ${port} is held by a non-cicy process (pid=${existing}): ${cmd}`);
|
|
63
|
+
console.error(`cicy-code: free it or set PORT=<other> — aborting`);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function pidOnPort(port) {
|
|
69
|
+
// lsof first (macOS + Linux), then ss (Linux without lsof).
|
|
70
|
+
try {
|
|
71
|
+
const out = execSync(`lsof -ti TCP:${port} -sTCP:LISTEN`, {
|
|
72
|
+
encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
|
|
73
|
+
}).trim();
|
|
74
|
+
if (out) return out.split('\n')[0].trim();
|
|
75
|
+
} catch {}
|
|
76
|
+
try {
|
|
77
|
+
const out = execSync(`ss -tlnp 'sport = :${port}'`, {
|
|
78
|
+
encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
|
|
79
|
+
});
|
|
80
|
+
const m = out.match(/pid=(\d+)/);
|
|
81
|
+
if (m) return m[1];
|
|
82
|
+
} catch {}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function processCommand(pid) {
|
|
87
|
+
try {
|
|
88
|
+
return execSync(`ps -p ${pid} -o command=`, {
|
|
89
|
+
encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
|
|
90
|
+
}).trim();
|
|
91
|
+
} catch { return ''; }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isAlive(pid) {
|
|
95
|
+
try { process.kill(Number(pid), 0); return true; }
|
|
96
|
+
catch (e) { return e.code === 'EPERM'; }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function waitExit(pid, timeoutMs) {
|
|
100
|
+
const start = Date.now();
|
|
101
|
+
while (Date.now() - start < timeoutMs) {
|
|
102
|
+
if (!isAlive(pid)) return true;
|
|
103
|
+
try { execSync('sleep 0.2'); } catch {}
|
|
104
|
+
}
|
|
105
|
+
return !isAlive(pid);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function killPid(pid) {
|
|
109
|
+
try { process.kill(Number(pid), 'SIGTERM'); }
|
|
110
|
+
catch (e) { if (e.code === 'ESRCH') return true; }
|
|
111
|
+
if (waitExit(pid, 6000)) return true;
|
|
112
|
+
try { process.kill(Number(pid), 'SIGKILL'); }
|
|
113
|
+
catch (e) { if (e.code === 'ESRCH') return true; }
|
|
114
|
+
return waitExit(pid, 2000);
|
|
115
|
+
}
|