worktree-bay 2.2.1 → 2.2.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/commands/gc.js +3 -2
- package/dist/commands/rm.js +3 -2
- package/dist/engine.js +3 -4
- package/dist/util/exec.js +45 -1
- package/package.json +1 -1
package/dist/commands/gc.js
CHANGED
|
@@ -3,7 +3,7 @@ import { withLock } from '../lock.js';
|
|
|
3
3
|
import { scanOccupancy, pruneEmptyLabels, removeLabel } from '../slots.js';
|
|
4
4
|
import { buildVars } from '../engine.js';
|
|
5
5
|
import { currentBranch, isDirty, hasUnpushed, isMergedToMain, remoteBranchGone, removeWorktree } from '../git.js';
|
|
6
|
-
import {
|
|
6
|
+
import { runShellLive } from '../util/exec.js';
|
|
7
7
|
import { log, warn } from '../util/log.js';
|
|
8
8
|
import { t } from '../i18n.js';
|
|
9
9
|
export function classifyForGc(s) {
|
|
@@ -26,7 +26,8 @@ export async function gcCommand(cfg, apply) {
|
|
|
26
26
|
const sp = cfg.services[o.service];
|
|
27
27
|
if (sp.teardown) {
|
|
28
28
|
const vars = buildVars(cfg, { cfg, service: o.service, sp, slot, slug: o.slug, dir: o.dir, repo });
|
|
29
|
-
|
|
29
|
+
const cmd = renderTemplate(sp.teardown, vars);
|
|
30
|
+
await runShellLive(cmd, { cwd: repo }, t(`teardown ${o.service}:${cmd}`, `teardown ${o.service}: ${cmd}`));
|
|
30
31
|
}
|
|
31
32
|
removeWorktree(repo, o.dir, false);
|
|
32
33
|
}
|
package/dist/commands/rm.js
CHANGED
|
@@ -3,7 +3,7 @@ import { withLock } from '../lock.js';
|
|
|
3
3
|
import { scanOccupancy, slotOfFeature, removeLabel } from '../slots.js';
|
|
4
4
|
import { buildVars } from '../engine.js';
|
|
5
5
|
import { isDirty, hasUnpushed, currentBranch, removeWorktree } from '../git.js';
|
|
6
|
-
import {
|
|
6
|
+
import { runShellLive } from '../util/exec.js';
|
|
7
7
|
import { log, warn } from '../util/log.js';
|
|
8
8
|
import { t } from '../i18n.js';
|
|
9
9
|
export function resolveRm(cfg, feature, service) {
|
|
@@ -26,7 +26,8 @@ export async function rmCommand(cfg, feature, service, force) {
|
|
|
26
26
|
const sp = cfg.services[o.service];
|
|
27
27
|
if (sp.teardown) {
|
|
28
28
|
const vars = buildVars(cfg, { cfg, service: o.service, sp, slot: o.slot, slug: o.slug, dir: o.dir, repo });
|
|
29
|
-
|
|
29
|
+
const cmd = renderTemplate(sp.teardown, vars);
|
|
30
|
+
await runShellLive(cmd, { cwd: repo }, t(`teardown ${o.service}:${cmd}`, `teardown ${o.service}: ${cmd}`));
|
|
30
31
|
}
|
|
31
32
|
removeWorktree(repo, o.dir, force);
|
|
32
33
|
log(t(`✓ 移除 ${o.service}`, `✓ removed ${o.service}`));
|
package/dist/engine.js
CHANGED
|
@@ -4,7 +4,7 @@ import { renderTemplate } from './config.js';
|
|
|
4
4
|
import { portOf, isPortFree } from './ports.js';
|
|
5
5
|
import { scanOccupancy } from './slots.js';
|
|
6
6
|
import { addWorktree } from './git.js';
|
|
7
|
-
import {
|
|
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
10
|
import { t } from './i18n.js';
|
|
@@ -64,10 +64,9 @@ export async function bringUp(ctx, base, branch) {
|
|
|
64
64
|
}
|
|
65
65
|
if (sp.setup) {
|
|
66
66
|
const cmd = renderTemplate(sp.setup, vars);
|
|
67
|
-
|
|
68
|
-
const r = runShell(cmd, { cwd: dir });
|
|
67
|
+
const r = await runShellLive(cmd, { cwd: dir }, t(`setup:${cmd}`, `setup: ${cmd}`));
|
|
69
68
|
if (r.code !== 0)
|
|
70
|
-
throw new Error(t(`setup 命令失败(退出码 ${r.code}
|
|
69
|
+
throw new Error(t(`setup 命令失败(退出码 ${r.code})。完整输出见上;修好后可重跑 add(已建的 worktree 会被复用,不会重复创建)。`, `setup command failed (exit code ${r.code}). Full output is above; after fixing, re-run add (the existing worktree is reused, not recreated).`));
|
|
71
70
|
}
|
|
72
71
|
if (sp.start)
|
|
73
72
|
log(t(` 启动: (cd ${dir} && ${renderTemplate(sp.start, vars)})`, ` start: (cd ${dir} && ${renderTemplate(sp.start, vars)})`));
|
package/dist/util/exec.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process';
|
|
1
|
+
import { spawnSync, spawn } from 'node:child_process';
|
|
2
|
+
import { t } from '../i18n.js';
|
|
2
3
|
export function run(cmd, args, opts = {}) { const r = spawnSync(cmd, args, { cwd: opts.cwd, stdio: 'inherit', shell: false }); return { code: r.status ?? 1 }; }
|
|
3
4
|
export function runShell(line, opts = {}) { const r = spawnSync(line, { cwd: opts.cwd, stdio: 'inherit', shell: true }); return { code: r.status ?? 1 }; }
|
|
4
5
|
export function spliceArgv(template, cmd) { const out = []; for (const el of template) {
|
|
@@ -8,3 +9,46 @@ export function spliceArgv(template, cmd) { const out = []; for (const el of tem
|
|
|
8
9
|
out.push(el);
|
|
9
10
|
} return out; }
|
|
10
11
|
export function isTTY() { return Boolean(process.stdout.isTTY && process.stdin.isTTY); }
|
|
12
|
+
const SPIN = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
13
|
+
// 折叠式执行 setup/teardown 这类外部命令:TTY 下把输出收进单行临时进度(spinner + 秒数 + 最后一行),
|
|
14
|
+
// 成功收成「✓ label(Ns)」,失败才吐完整日志便于排查;非 TTY(CI/管道/MCP)原样透传保留完整日志。
|
|
15
|
+
export function runShellLive(line, opts, label) {
|
|
16
|
+
if (!process.stderr.isTTY) {
|
|
17
|
+
const r = spawnSync(line, { cwd: opts.cwd, stdio: 'inherit', shell: true });
|
|
18
|
+
return Promise.resolve({ code: r.status ?? 1 });
|
|
19
|
+
}
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
|
+
const t0 = Date.now();
|
|
22
|
+
const child = spawn(line, { cwd: opts.cwd, shell: true });
|
|
23
|
+
const buf = [];
|
|
24
|
+
let last = '';
|
|
25
|
+
let fi = 0;
|
|
26
|
+
const render = () => {
|
|
27
|
+
const secs = Math.floor((Date.now() - t0) / 1000);
|
|
28
|
+
const head = ` ${SPIN[fi++ % SPIN.length]} ${label} ${secs}s `;
|
|
29
|
+
const room = Math.max(0, (process.stderr.columns || 80) - head.length - 1);
|
|
30
|
+
process.stderr.write('\r\x1b[2K' + head + last.slice(0, room));
|
|
31
|
+
};
|
|
32
|
+
const timer = setInterval(render, 120);
|
|
33
|
+
const onData = (d) => {
|
|
34
|
+
const s = d.toString();
|
|
35
|
+
buf.push(s);
|
|
36
|
+
const lines = s.split(/[\r\n]+/).map((l) => l.trim()).filter(Boolean);
|
|
37
|
+
if (lines.length)
|
|
38
|
+
last = lines[lines.length - 1];
|
|
39
|
+
};
|
|
40
|
+
child.stdout?.on('data', onData);
|
|
41
|
+
child.stderr?.on('data', onData);
|
|
42
|
+
child.on('error', () => { clearInterval(timer); process.stderr.write('\r\x1b[2K'); resolve({ code: 1 }); });
|
|
43
|
+
child.on('close', (code) => {
|
|
44
|
+
clearInterval(timer);
|
|
45
|
+
const secs = ((Date.now() - t0) / 1000).toFixed(1);
|
|
46
|
+
process.stderr.write('\r\x1b[2K');
|
|
47
|
+
if (code === 0)
|
|
48
|
+
process.stderr.write(t(` ✓ ${label}(${secs}s)\n`, ` ✓ ${label} (${secs}s)\n`));
|
|
49
|
+
else
|
|
50
|
+
process.stderr.write(t(` ✗ ${label} 失败(退出码 ${code},${secs}s)↓\n`, ` ✗ ${label} failed (exit ${code}, ${secs}s) ↓\n`) + buf.join(''));
|
|
51
|
+
resolve({ code: code ?? 1 });
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "worktree-bay",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.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",
|