bingocode 1.0.31 → 1.0.32
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 -1
- package/scripts/preinstall-stop.cjs +164 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bingocode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.32",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"claude": "bin/claude-win.cjs",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"bingo": "bin/bingo-win.cjs"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
|
+
"preinstall": "node scripts/preinstall-stop.cjs",
|
|
12
13
|
"start": "bun run ./bin/bingo-win.cjs",
|
|
13
14
|
"bingo": "bun run ./bin/bingo-win.cjs",
|
|
14
15
|
"bingocode": "bun run ./bin/bingocode-win.cjs",
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* npm preinstall hook: gracefully stop running bingo/bingocode processes
|
|
5
|
+
* so that npm can overwrite files without EBUSY/EPERM on Windows.
|
|
6
|
+
*
|
|
7
|
+
* On Windows, running processes hold mandatory file locks on their loaded .js
|
|
8
|
+
* files and dependencies. When `npm install -g bingocode` tries to overwrite
|
|
9
|
+
* these files, it fails with EBUSY or EPERM. This script discovers all running
|
|
10
|
+
* bingo/bingocode processes via their PID files and stops them before npm
|
|
11
|
+
* writes any files.
|
|
12
|
+
*
|
|
13
|
+
* PID file locations (matching the runtime code):
|
|
14
|
+
* - Singleton server: ~/.claude-cli/runtime/server.lock.json { pid, port, ... }
|
|
15
|
+
* - Active sessions: ~/.claude/sessions/<pid>.json { pid, sessionId, ... }
|
|
16
|
+
*
|
|
17
|
+
* Must be CJS (.cjs) because npm executes lifecycle scripts with node,
|
|
18
|
+
* and package.json has "type": "module".
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const { spawnSync } = require('child_process');
|
|
22
|
+
const fs = require('fs');
|
|
23
|
+
const path = require('path');
|
|
24
|
+
const os = require('os');
|
|
25
|
+
|
|
26
|
+
// ── Paths (mirroring the runtime code) ──────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
const configDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
|
|
29
|
+
const runtimeDir = path.join(os.homedir(), '.claude-cli', 'runtime');
|
|
30
|
+
const serverLockPath = path.join(runtimeDir, 'server.lock.json');
|
|
31
|
+
const leasesDir = path.join(runtimeDir, 'leases');
|
|
32
|
+
const sessionsDir = path.join(configDir, 'sessions');
|
|
33
|
+
|
|
34
|
+
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
function isPidAlive(pid) {
|
|
37
|
+
try {
|
|
38
|
+
process.kill(pid, 0);
|
|
39
|
+
return true;
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function killPid(pid) {
|
|
46
|
+
try {
|
|
47
|
+
if (process.platform === 'win32') {
|
|
48
|
+
// /T = kill the entire process tree; /F = force
|
|
49
|
+
spawnSync('taskkill', ['/PID', String(pid), '/T', '/F'], {
|
|
50
|
+
stdio: 'ignore',
|
|
51
|
+
});
|
|
52
|
+
} else {
|
|
53
|
+
process.kill(pid, 'SIGTERM');
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
// Process may have already exited
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function readJsonSafe(filePath) {
|
|
61
|
+
try {
|
|
62
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function sleepMs(ms) {
|
|
69
|
+
// Cross-platform synchronous sleep via spawnSync
|
|
70
|
+
if (process.platform === 'win32') {
|
|
71
|
+
// "ping -n 2 127.0.0.1" sleeps ~1s (more reliable than timeout in non-interactive)
|
|
72
|
+
spawnSync('ping', ['-n', '2', '127.0.0.1'], { stdio: 'ignore' });
|
|
73
|
+
} else {
|
|
74
|
+
spawnSync('sleep', [String(ms / 1000)], { stdio: 'ignore' });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ── Collect PIDs ────────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
const pidsToKill = new Set();
|
|
81
|
+
|
|
82
|
+
// 1. Singleton server PID from server.lock.json
|
|
83
|
+
const serverLock = readJsonSafe(serverLockPath);
|
|
84
|
+
if (serverLock && serverLock.pid && isPidAlive(serverLock.pid)) {
|
|
85
|
+
pidsToKill.add(serverLock.pid);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 2. Active session PIDs from ~/.claude/sessions/<pid>.json
|
|
89
|
+
try {
|
|
90
|
+
const files = fs.readdirSync(sessionsDir);
|
|
91
|
+
for (const f of files) {
|
|
92
|
+
// Strict filename guard: only "<digits>.json" files are PID files
|
|
93
|
+
if (!/^\d+\.json$/.test(f)) continue;
|
|
94
|
+
const data = readJsonSafe(path.join(sessionsDir, f));
|
|
95
|
+
if (data && data.pid && isPidAlive(data.pid)) {
|
|
96
|
+
pidsToKill.add(data.pid);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
// sessionsDir may not exist yet — that's fine
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ── Nothing to do ───────────────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
if (pidsToKill.size === 0) {
|
|
106
|
+
// No running processes, npm can proceed safely
|
|
107
|
+
process.exit(0);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ── Kill processes ──────────────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
console.log(
|
|
113
|
+
`[bingocode] 检测到 ${pidsToKill.size} 个运行中的进程,正在停止以便安装...`
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
for (const pid of pidsToKill) {
|
|
117
|
+
console.log(` 停止 PID ${pid}`);
|
|
118
|
+
killPid(pid);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ── Wait for processes to exit (max 5 seconds) ─────────────────────────────
|
|
122
|
+
|
|
123
|
+
const MAX_WAIT_MS = 5000;
|
|
124
|
+
const deadline = Date.now() + MAX_WAIT_MS;
|
|
125
|
+
|
|
126
|
+
while (Date.now() < deadline) {
|
|
127
|
+
const alive = [...pidsToKill].filter(isPidAlive);
|
|
128
|
+
if (alive.length === 0) break;
|
|
129
|
+
sleepMs(500);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Check if any processes are still alive after timeout
|
|
133
|
+
const stillAlive = [...pidsToKill].filter(isPidAlive);
|
|
134
|
+
if (stillAlive.length > 0) {
|
|
135
|
+
console.warn(
|
|
136
|
+
`[bingocode] 警告:${stillAlive.length} 个进程未能在 ${MAX_WAIT_MS / 1000} 秒内停止 (PIDs: ${stillAlive.join(', ')})`
|
|
137
|
+
);
|
|
138
|
+
console.warn(
|
|
139
|
+
'[bingocode] 安装可能仍会因文件锁定而失败,请手动关闭这些进程后重试'
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── Clean up stale lock/lease files ─────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
fs.rmSync(serverLockPath, { force: true });
|
|
147
|
+
} catch {
|
|
148
|
+
// Ignore — file may not exist or may already be cleaned up
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const leaseFiles = fs.readdirSync(leasesDir);
|
|
153
|
+
for (const f of leaseFiles) {
|
|
154
|
+
try {
|
|
155
|
+
fs.rmSync(path.join(leasesDir, f), { force: true });
|
|
156
|
+
} catch {
|
|
157
|
+
// Best-effort cleanup
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
} catch {
|
|
161
|
+
// leasesDir may not exist
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.log('[bingocode] 进程已停止,继续安装...');
|