worktree-bay 2.3.1 → 2.3.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/dist/engine.js +13 -13
- package/dist/git.js +3 -2
- package/dist/proc.js +48 -5
- package/package.json +1 -1
package/dist/engine.js
CHANGED
|
@@ -7,7 +7,7 @@ import { addWorktree } from './git.js';
|
|
|
7
7
|
import { runShellLive, run, spliceArgv, isTTY } from './util/exec.js';
|
|
8
8
|
import { warn, log } from './util/log.js';
|
|
9
9
|
import { withProgress } from './util/progress.js';
|
|
10
|
-
import { startDetached, recordedFor, pidAlive,
|
|
10
|
+
import { startDetached, recordedFor, pidAlive, setPid, pidOnPort, readLogTail } from './proc.js';
|
|
11
11
|
import { t } from './i18n.js';
|
|
12
12
|
export function mergeEnvText(text, kv) {
|
|
13
13
|
const lines = text.split('\n');
|
|
@@ -88,28 +88,28 @@ export async function ensureStarted(ctx) {
|
|
|
88
88
|
}
|
|
89
89
|
const cmd = renderTemplate(sp.start, vars);
|
|
90
90
|
const r = startDetached(ws, dir, service, slug, port, cmd);
|
|
91
|
-
//
|
|
92
|
-
const
|
|
93
|
-
if (
|
|
94
|
-
|
|
91
|
+
// 等它在【约定端口】上监听(最多 ~12s)。起来后按端口查出真实 pid 回填(shell/pnpm 会让记录 pid 漂移)。
|
|
92
|
+
const up = await waitForListen(port, 12000);
|
|
93
|
+
if (up) {
|
|
94
|
+
const real = pidOnPort(port);
|
|
95
|
+
if (real && real > 0)
|
|
96
|
+
setPid(ws, dir, real);
|
|
97
|
+
log(t(` ▸ 已后台启动 ${service} dev server(pid ${real || r.pid},端口 ${port}) 日志: ${r.log}`, ` ▸ started ${service} dev server in background (pid ${real || r.pid}, port ${port}) log: ${r.log}`));
|
|
95
98
|
}
|
|
96
99
|
else {
|
|
97
|
-
stopManaged(ws, dir); // 进程已死,清掉登记,别留假状态
|
|
98
100
|
const tail = readLogTail(r.log);
|
|
99
|
-
warn(t(` ✗ ${service} dev server
|
|
101
|
+
warn(t(` ✗ ${service} dev server 未在约定端口 ${port} 上监听(12s 内)。多半是 start 命令不对,或端口被占后退避了——建议给 vite 加 --strictPort。\n 命令: ${cmd}\n 手动排查: cd ${dir} && ${cmd}\n 日志(${r.log}):\n${tail || '(空)'}`, ` ✗ ${service} dev server is not listening on the expected port ${port} (within 12s). The start command may be wrong, or it fell back to another port — add --strictPort for vite.\n command: ${cmd}\n reproduce: cd ${dir} && ${cmd}\n log (${r.log}):\n${tail || '(empty)'}`));
|
|
100
102
|
}
|
|
101
103
|
}
|
|
102
|
-
//
|
|
103
|
-
async function
|
|
104
|
+
// 轮询直到约定端口被监听(true),或超时(false)
|
|
105
|
+
async function waitForListen(port, ms) {
|
|
104
106
|
const end = Date.now() + ms;
|
|
105
107
|
for (;;) {
|
|
106
|
-
if (!pidAlive(pid))
|
|
107
|
-
return false;
|
|
108
108
|
if (!(await isPortFree(port)))
|
|
109
109
|
return true;
|
|
110
110
|
if (Date.now() >= end)
|
|
111
|
-
return
|
|
112
|
-
await new Promise((r) => setTimeout(r,
|
|
111
|
+
return false;
|
|
112
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
export function execArgv(ctx, cmd) {
|
package/dist/git.js
CHANGED
|
@@ -20,9 +20,10 @@ export async function removeWorktree(repo, dir, force) {
|
|
|
20
20
|
git(repo, ...a); // 尽力移除;残留交给下面兜底(调用方已先过脏/未推保护,到这里删除是安全的)
|
|
21
21
|
// git worktree remove 不会删被忽略的文件(前端 node_modules 等)→ 目录残留报 "Directory not empty";
|
|
22
22
|
// 它还可能先摘了登记再删目录失败,留下「git 不认但磁盘还在」的孤儿。统一兜底:物理删目录 + prune 同步元数据。
|
|
23
|
-
// 用异步 rm,让删 node_modules(可能数秒)时上层 spinner
|
|
23
|
+
// 用异步 rm,让删 node_modules(可能数秒)时上层 spinner 能转;
|
|
24
|
+
// maxRetries 兜底 Windows 上刚杀进程、句柄尚未释放导致的 EBUSY/ENOTEMPTY。
|
|
24
25
|
if (fs.existsSync(dir))
|
|
25
|
-
await fs.promises.rm(dir, { recursive: true, force: true });
|
|
26
|
+
await fs.promises.rm(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 300 });
|
|
26
27
|
git(repo, 'worktree', 'prune');
|
|
27
28
|
}
|
|
28
29
|
export function isDirty(dir) { return ok(dir, 'status', '--porcelain').trim().length > 0; }
|
package/dist/proc.js
CHANGED
|
@@ -9,7 +9,8 @@ catch {
|
|
|
9
9
|
return [];
|
|
10
10
|
} }
|
|
11
11
|
function writeProcs(ws, recs) { fs.mkdirSync(path.join(ws, '.worktree-bay'), { recursive: true }); fs.writeFileSync(regPath(ws), JSON.stringify(recs, null, 2) + '\n'); }
|
|
12
|
-
export function pidAlive(pid) {
|
|
12
|
+
export function pidAlive(pid) { if (!pid || pid < 1)
|
|
13
|
+
return false; try {
|
|
13
14
|
process.kill(pid, 0);
|
|
14
15
|
return true;
|
|
15
16
|
}
|
|
@@ -17,6 +18,10 @@ catch {
|
|
|
17
18
|
return false;
|
|
18
19
|
} }
|
|
19
20
|
export function recordedFor(ws, dir) { return readProcs(ws).find((r) => r.dir === dir); }
|
|
21
|
+
export function setPid(ws, dir, pid) { const recs = readProcs(ws); const r = recs.find((x) => x.dir === dir); if (r) {
|
|
22
|
+
r.pid = pid;
|
|
23
|
+
writeProcs(ws, recs);
|
|
24
|
+
} }
|
|
20
25
|
export function readLogTail(file, lines = 15) {
|
|
21
26
|
try {
|
|
22
27
|
return fs.readFileSync(file, 'utf8').split(/\r?\n/).filter(Boolean).slice(-lines).join('\n');
|
|
@@ -25,13 +30,43 @@ export function readLogTail(file, lines = 15) {
|
|
|
25
30
|
return '';
|
|
26
31
|
}
|
|
27
32
|
}
|
|
33
|
+
// 找出监听某端口的进程 pid(shell/pnpm 等中间层会让记录的 pid 漂移,按端口查最可靠)。
|
|
34
|
+
export function pidOnPort(port) {
|
|
35
|
+
if (process.platform === 'win32') {
|
|
36
|
+
const r = spawnSync('netstat', ['-ano', '-p', 'tcp'], { encoding: 'utf8' });
|
|
37
|
+
for (const line of (r.stdout || '').split(/\r?\n/)) {
|
|
38
|
+
const m = new RegExp(`[:.]${port}\\s+\\S+\\s+LISTENING\\s+(\\d+)`, 'i').exec(line);
|
|
39
|
+
if (m)
|
|
40
|
+
return Number(m[1]);
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
let r = spawnSync('lsof', ['-ti', `tcp:${port}`, '-sTCP:LISTEN'], { encoding: 'utf8' });
|
|
45
|
+
if (r.status === 0 && r.stdout.trim()) {
|
|
46
|
+
const pid = parseInt(r.stdout.trim().split(/\s+/)[0], 10);
|
|
47
|
+
if (pid)
|
|
48
|
+
return pid;
|
|
49
|
+
}
|
|
50
|
+
r = spawnSync('ss', ['-ltnp'], { encoding: 'utf8' });
|
|
51
|
+
if (r.status === 0) {
|
|
52
|
+
const m = new RegExp(`:${port}\\s.*pid=(\\d+)`).exec(r.stdout || '');
|
|
53
|
+
if (m)
|
|
54
|
+
return Number(m[1]);
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
28
58
|
export function startDetached(ws, dir, service, slug, port, cmd) {
|
|
29
59
|
const logDir = path.join(ws, '.worktree-bay', 'logs');
|
|
30
60
|
fs.mkdirSync(logDir, { recursive: true });
|
|
31
61
|
const log = path.join(logDir, `${slug}-${service}.log`);
|
|
32
62
|
const fd = fs.openSync(log, 'a');
|
|
33
|
-
//
|
|
34
|
-
|
|
63
|
+
// 后台启动、CLI 退出后仍存活、且不弹窗:
|
|
64
|
+
// - Windows:不要 detached(detached 会新开控制台窗口、且与 windowsHide 冲突);用 windowsHide 抑制窗口,
|
|
65
|
+
// 子进程在父进程正常退出后不会被自动杀(Windows 默认不 kill-on-parent-exit),kill 时用 taskkill /T 杀进程树。
|
|
66
|
+
// - 类 Unix:detached 建新进程组,便于用 kill(-pid) 整组结束。
|
|
67
|
+
// 两边都把 stdout/stderr 落日志文件,并 unref 让本进程能直接退出。
|
|
68
|
+
const detached = process.platform !== 'win32';
|
|
69
|
+
const child = spawn(cmd, { cwd: dir, shell: true, detached, stdio: ['ignore', fd, fd], windowsHide: true });
|
|
35
70
|
const pid = child.pid ?? -1;
|
|
36
71
|
child.unref();
|
|
37
72
|
const rec = { dir, service, port, pid, cmd, log, startedAt: Date.now() };
|
|
@@ -54,13 +89,21 @@ function killTree(pid) {
|
|
|
54
89
|
}
|
|
55
90
|
}
|
|
56
91
|
// 停掉某 worktree 的托管进程(含进程树),并从账本移除。返回被停的记录(无则 undefined)。
|
|
92
|
+
// 同时按「记录 pid」和「当前端口占用 pid」双杀——shell/pnpm 中间层会让记录 pid 漂移,按端口兜底最稳。
|
|
57
93
|
export function stopManaged(ws, dir) {
|
|
58
94
|
const recs = readProcs(ws);
|
|
59
95
|
const rec = recs.find((r) => r.dir === dir);
|
|
60
96
|
if (!rec)
|
|
61
97
|
return undefined;
|
|
62
|
-
|
|
63
|
-
|
|
98
|
+
const targets = new Set();
|
|
99
|
+
if (rec.pid > 0)
|
|
100
|
+
targets.add(rec.pid);
|
|
101
|
+
const onPort = pidOnPort(rec.port);
|
|
102
|
+
if (onPort)
|
|
103
|
+
targets.add(onPort);
|
|
104
|
+
for (const pid of targets)
|
|
105
|
+
if (pidAlive(pid))
|
|
106
|
+
killTree(pid);
|
|
64
107
|
writeProcs(ws, recs.filter((r) => r.dir !== dir));
|
|
65
108
|
return rec;
|
|
66
109
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "worktree-bay",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.3",
|
|
4
4
|
"description": "Per-feature git worktree + port slots for parallel multi-service development: auto deps, env wiring, frontend-to-backend, merge-aware reclaim, plus an MCP server for AI agents.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"git",
|