worktree-bay 2.3.0 → 2.3.2

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 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 } from './proc.js';
10
+ import { startDetached, recordedFor, pidAlive, stopManaged, 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');
@@ -86,8 +86,31 @@ export async function ensureStarted(ctx) {
86
86
  log(t(` • 端口 ${port} 已在监听,视为 ${service} dev server 在跑,跳过启动`, ` • port ${port} already listening; treating ${service} dev server as up, skip`));
87
87
  return;
88
88
  }
89
- const r = startDetached(ws, dir, service, slug, port, renderTemplate(sp.start, vars));
90
- log(t(` ▸ 已后台启动 ${service} dev server(pid ${r.pid},端口 ${port}) 日志: ${r.log}`, ` ▸ started ${service} dev server in background (pid ${r.pid}, port ${port}) log: ${r.log}`));
89
+ const cmd = renderTemplate(sp.start, vars);
90
+ const r = startDetached(ws, dir, service, slug, port, cmd);
91
+ // 验证没立刻挂:轮询 ~3s,端口起来=成功,pid 没了=启动失败(命令多半不对)
92
+ const alive = await waitStartup(r.pid, port, 3000);
93
+ if (alive) {
94
+ log(t(` ▸ 已后台启动 ${service} dev server(pid ${r.pid},端口 ${port}) 日志: ${r.log}`, ` ▸ started ${service} dev server in background (pid ${r.pid}, port ${port}) log: ${r.log}`));
95
+ }
96
+ else {
97
+ stopManaged(ws, dir); // 进程已死,清掉登记,别留假状态
98
+ const tail = readLogTail(r.log);
99
+ warn(t(` ✗ ${service} dev server 启动后立即退出——start 命令多半不对。\n 命令: ${cmd}\n 请在 worktree 里手动跑一遍排查: cd ${dir} && ${cmd}\n 日志(${r.log}):\n${tail || '(空)'}`, ` ✗ ${service} dev server exited immediately — the start command is likely wrong.\n command: ${cmd}\n reproduce in the worktree: cd ${dir} && ${cmd}\n log (${r.log}):\n${tail || '(empty)'}`));
100
+ }
101
+ }
102
+ // 端口起来→true(成功);pid 死了→false(崩了);超时仍存活→true(还在启动,如 vite 冷启动较慢)
103
+ async function waitStartup(pid, port, ms) {
104
+ const end = Date.now() + ms;
105
+ for (;;) {
106
+ if (!pidAlive(pid))
107
+ return false;
108
+ if (!(await isPortFree(port)))
109
+ return true;
110
+ if (Date.now() >= end)
111
+ return pidAlive(pid);
112
+ await new Promise((r) => setTimeout(r, 150));
113
+ }
91
114
  }
92
115
  export function execArgv(ctx, cmd) {
93
116
  const tpl = (ctx.sp.exec ?? ['sh', '-c', '{cmd...}']).map((el) => el === '{cmd...}' ? el : renderTemplate(el, ctx.vars));
package/dist/proc.js CHANGED
@@ -17,13 +17,26 @@ catch {
17
17
  return false;
18
18
  } }
19
19
  export function recordedFor(ws, dir) { return readProcs(ws).find((r) => r.dir === dir); }
20
+ export function readLogTail(file, lines = 15) {
21
+ try {
22
+ return fs.readFileSync(file, 'utf8').split(/\r?\n/).filter(Boolean).slice(-lines).join('\n');
23
+ }
24
+ catch {
25
+ return '';
26
+ }
27
+ }
20
28
  export function startDetached(ws, dir, service, slug, port, cmd) {
21
29
  const logDir = path.join(ws, '.worktree-bay', 'logs');
22
30
  fs.mkdirSync(logDir, { recursive: true });
23
31
  const log = path.join(logDir, `${slug}-${service}.log`);
24
32
  const fd = fs.openSync(log, 'a');
25
- // detached + unref:脱离本进程,CLI 退出后 dev server 继续跑;stdout/stderr 落日志文件。
26
- const child = spawn(cmd, { cwd: dir, shell: true, detached: true, stdio: ['ignore', fd, fd], windowsHide: true });
33
+ // 后台启动、CLI 退出后仍存活、且不弹窗:
34
+ // - Windows:不要 detached(detached 会新开控制台窗口、且与 windowsHide 冲突);用 windowsHide 抑制窗口,
35
+ // 子进程在父进程正常退出后不会被自动杀(Windows 默认不 kill-on-parent-exit),kill 时用 taskkill /T 杀进程树。
36
+ // - 类 Unix:detached 建新进程组,便于用 kill(-pid) 整组结束。
37
+ // 两边都把 stdout/stderr 落日志文件,并 unref 让本进程能直接退出。
38
+ const detached = process.platform !== 'win32';
39
+ const child = spawn(cmd, { cwd: dir, shell: true, detached, stdio: ['ignore', fd, fd], windowsHide: true });
27
40
  const pid = child.pid ?? -1;
28
41
  child.unref();
29
42
  const rec = { dir, service, port, pid, cmd, log, startedAt: Date.now() };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "worktree-bay",
3
- "version": "2.3.0",
3
+ "version": "2.3.2",
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",