worktree-bay 2.3.3 → 2.3.5
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/commands/ls.js +5 -5
- package/dist/engine.js +9 -7
- package/dist/ports.js +13 -0
- package/package.json +1 -1
package/dist/commands/ls.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { scanOccupancy, readLabels } from '../slots.js';
|
|
2
2
|
import { portOf } from '../ports.js';
|
|
3
3
|
import { log } from '../util/log.js';
|
|
4
|
-
import { recordedFor,
|
|
4
|
+
import { recordedFor, pidOnPort } from '../proc.js';
|
|
5
5
|
import { t } from '../i18n.js';
|
|
6
|
-
//
|
|
7
|
-
function running(cfg, dir) {
|
|
6
|
+
// 该服务的约定端口是否有进程在监听(按端口判,不受 shell/pnpm 让记录 pid 漂移的影响)
|
|
7
|
+
function running(cfg, dir, port) { return !!recordedFor(cfg.workspaceRoot, dir) && !!pidOnPort(port); }
|
|
8
8
|
export function renderSlots(cfg) {
|
|
9
9
|
const occ = scanOccupancy(cfg);
|
|
10
10
|
const labels = readLabels(cfg);
|
|
11
11
|
const slots = new Set([...occ.keys(), ...Object.keys(labels).map(Number)]);
|
|
12
12
|
const lines = [];
|
|
13
13
|
for (const n of [...slots].sort((a, b) => a - b)) {
|
|
14
|
-
const svc = (occ.get(n) ?? []).map((o) =>
|
|
14
|
+
const svc = (occ.get(n) ?? []).map((o) => { const p = portOf(cfg.services[o.service].port, n); return `${o.service}@${p}${running(cfg, o.dir, p) ? ' ▸run' : ''}`; });
|
|
15
15
|
lines.push(`slot ${n} ${labels[String(n)] ?? t('(未命名)', '(unnamed)')} [${svc.join(', ') || t('无 worktree', 'no worktree')}]`);
|
|
16
16
|
}
|
|
17
17
|
return lines.join('\n') || t('(无槽位在用)', '(no slots in use)');
|
|
@@ -22,7 +22,7 @@ export function slotsData(cfg) {
|
|
|
22
22
|
const slots = new Set([...occ.keys(), ...Object.keys(labels).map(Number)]);
|
|
23
23
|
return [...slots].sort((a, b) => a - b).map((n) => ({
|
|
24
24
|
slot: n, feature: labels[String(n)] ?? null,
|
|
25
|
-
services: (occ.get(n) ?? []).map((o) =>
|
|
25
|
+
services: (occ.get(n) ?? []).map((o) => { const p = portOf(cfg.services[o.service].port, n); return { service: o.service, port: p, dir: o.dir, running: running(cfg, o.dir, p) }; }),
|
|
26
26
|
}));
|
|
27
27
|
}
|
|
28
28
|
export function lsCommand(cfg, json = false) { log(json ? JSON.stringify(slotsData(cfg), null, 2) : renderSlots(cfg)); }
|
package/dist/engine.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { renderTemplate } from './config.js';
|
|
4
|
-
import { portOf,
|
|
4
|
+
import { portOf, portInUse } from './ports.js';
|
|
5
5
|
import { scanOccupancy } from './slots.js';
|
|
6
6
|
import { addWorktree } from './git.js';
|
|
7
7
|
import { runShellLive, run, spliceArgv, isTTY } from './util/exec.js';
|
|
@@ -41,7 +41,7 @@ export function buildVars(cfg, ctx) {
|
|
|
41
41
|
}
|
|
42
42
|
export async function bringUp(ctx, base, branch) {
|
|
43
43
|
const { sp, dir, repo, vars } = ctx;
|
|
44
|
-
if (
|
|
44
|
+
if (await portInUse(Number(vars.port)))
|
|
45
45
|
throw new Error(t(`端口 ${vars.port} 已被占用。先停掉占用它的进程,或用 \`worktree-bay gc\`/\`worktree-bay down <功能>\` 释放其它槽后重试。`, `port ${vars.port} is already in use. Stop whatever is using it, or free a slot with \`worktree-bay gc\`/\`worktree-bay down <feature>\`, then retry.`));
|
|
46
46
|
log(t(` → 创建 worktree(分支 ${branch})…`, ` → creating worktree (branch ${branch})…`));
|
|
47
47
|
addWorktree(repo, dir, branch, base);
|
|
@@ -82,14 +82,15 @@ export async function ensureStarted(ctx) {
|
|
|
82
82
|
log(t(` • ${service} dev server 已在跑(pid ${rec.pid},端口 ${port})`, ` • ${service} dev server already running (pid ${rec.pid}, port ${port})`));
|
|
83
83
|
return;
|
|
84
84
|
}
|
|
85
|
-
if (
|
|
85
|
+
if (await portInUse(port)) {
|
|
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
89
|
const cmd = renderTemplate(sp.start, vars);
|
|
90
90
|
const r = startDetached(ws, dir, service, slug, port, cmd);
|
|
91
|
-
// 等它在【约定端口】上监听(最多 ~
|
|
92
|
-
|
|
91
|
+
// 等它在【约定端口】上监听(最多 ~25s,给 vite 冷启动 + 偶发 restart 留足时间)。
|
|
92
|
+
// 起来后按端口查出真实 pid 回填(shell/pnpm 会让记录 pid 漂移)。
|
|
93
|
+
const up = await waitForListen(port, 25000);
|
|
93
94
|
if (up) {
|
|
94
95
|
const real = pidOnPort(port);
|
|
95
96
|
if (real && real > 0)
|
|
@@ -97,15 +98,16 @@ export async function ensureStarted(ctx) {
|
|
|
97
98
|
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}`));
|
|
98
99
|
}
|
|
99
100
|
else {
|
|
101
|
+
// 超时不等于失败:dev server 可能仍在启动/重启。给中性提示 + 日志末尾,让用户稍后 ls 复查或排查。
|
|
100
102
|
const tail = readLogTail(r.log);
|
|
101
|
-
warn(t(`
|
|
103
|
+
warn(t(` • ${service} dev server 已在后台启动,但 25s 内端口 ${port} 还没就绪——可能仍在启动/重启。\n 稍后用 \`worktree-bay ls\` 看是否 ▸run;若起不来,多半是 start 命令不对或端口被占退避(vite 建议加 --strictPort)。\n 命令: ${cmd} 日志: ${r.log}\n ${tail ? '日志末尾:\n' + tail : ''}`, ` • ${service} dev server launched, but port ${port} isn't ready within 25s — it may still be starting/restarting.\n Check \`worktree-bay ls\` shortly for ▸run; if it never comes up, the start command is likely wrong or it fell back to another port (add --strictPort for vite).\n command: ${cmd} log: ${r.log}\n ${tail ? 'log tail:\n' + tail : ''}`));
|
|
102
104
|
}
|
|
103
105
|
}
|
|
104
106
|
// 轮询直到约定端口被监听(true),或超时(false)
|
|
105
107
|
async function waitForListen(port, ms) {
|
|
106
108
|
const end = Date.now() + ms;
|
|
107
109
|
for (;;) {
|
|
108
|
-
if (
|
|
110
|
+
if (await portInUse(port))
|
|
109
111
|
return true;
|
|
110
112
|
if (Date.now() >= end)
|
|
111
113
|
return false;
|
package/dist/ports.js
CHANGED
|
@@ -9,3 +9,16 @@ export function isPortFree(port) {
|
|
|
9
9
|
srv.listen(port, '127.0.0.1');
|
|
10
10
|
});
|
|
11
11
|
}
|
|
12
|
+
// 端口是否已被监听(连接探测,同时试 IPv4 与 IPv6 回环)。
|
|
13
|
+
// 比 bind 探测可靠:Windows 上 vite 等常监听 IPv6(::),单纯在 127.0.0.1 上 bind 测试会漏判。
|
|
14
|
+
export function portInUse(port) {
|
|
15
|
+
const probe = (host) => new Promise((resolve) => {
|
|
16
|
+
const sock = net.connect({ port, host });
|
|
17
|
+
const finish = (v) => { sock.destroy(); resolve(v); };
|
|
18
|
+
sock.setTimeout(1500);
|
|
19
|
+
sock.once('connect', () => finish(true));
|
|
20
|
+
sock.once('timeout', () => finish(false));
|
|
21
|
+
sock.once('error', () => resolve(false));
|
|
22
|
+
});
|
|
23
|
+
return Promise.all([probe('127.0.0.1'), probe('::1')]).then((r) => r.some(Boolean));
|
|
24
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "worktree-bay",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.5",
|
|
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",
|