worktree-bay 2.5.0 → 3.0.0
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/cli.js +13 -11
- package/dist/commands/completion.js +4 -4
- package/dist/commands/lifecycle.js +14 -8
- package/dist/commands/rm.js +12 -5
- package/dist/mcp.js +12 -12
- package/package.json +1 -1
- package/skill.md +5 -5
package/dist/cli.js
CHANGED
|
@@ -73,8 +73,10 @@ catch (e) {
|
|
|
73
73
|
die(e.message);
|
|
74
74
|
} });
|
|
75
75
|
program.command('add <feature> <service> [branch] [base]').description(t('为功能在某服务开 worktree(branch 默认 = 功能名)', 'open a worktree for a feature on one service (branch defaults to feature name)'))
|
|
76
|
-
.
|
|
77
|
-
|
|
76
|
+
.option('--branch <branch>', t('要创建的分支名(默认 = 功能名)', 'branch to create (default = feature name)'))
|
|
77
|
+
.option('--base <base>', t('分支基点(默认 = origin/<主分支>)', 'base ref for the branch (default = origin/<main>)'))
|
|
78
|
+
.action(async (f, s, b, base, o) => { try {
|
|
79
|
+
await addCommand(loadConfig(process.cwd()), f, s, o.branch ?? b, o.base ?? base);
|
|
78
80
|
}
|
|
79
81
|
catch (e) {
|
|
80
82
|
die(e.message);
|
|
@@ -83,35 +85,35 @@ program.command('run <feature> <service> <name> [args...]').description(t('在
|
|
|
83
85
|
.action((f, s, n, args) => sync((c) => runCommand(c, f, s, n, args ?? [])));
|
|
84
86
|
program.command('sh <feature> <service>').description(t('进入服务运行体的 shell', 'open a shell inside the service runtime'))
|
|
85
87
|
.action((f, s) => sync((c) => shCommand(c, f, s)));
|
|
86
|
-
program.command('start <feature> [
|
|
88
|
+
program.command('start <feature> [services...]').description(t('启动功能的运行体(docker 容器 + dev server);省略 service = 全部,可列多个。不动 worktree', 'start the feature\'s runtime (docker + dev server); omit services = all, or list several. Leaves the worktree untouched'))
|
|
87
89
|
.action(async (f, s) => { try {
|
|
88
|
-
await startCommand(loadConfig(process.cwd()), f, s);
|
|
90
|
+
await startCommand(loadConfig(process.cwd()), f, s ?? []);
|
|
89
91
|
}
|
|
90
92
|
catch (e) {
|
|
91
93
|
die(e.message);
|
|
92
94
|
} });
|
|
93
|
-
program.command('stop <feature> [
|
|
95
|
+
program.command('stop <feature> [services...]').description(t('停止功能的运行体(停 docker + 杀 dev server);省略 = 全部,可列多个。保留 worktree', 'stop the feature\'s runtime (stop docker + kill dev server); omit = all, or list several. Keeps the worktree'))
|
|
94
96
|
.action(async (f, s) => { try {
|
|
95
|
-
await stopCommand(loadConfig(process.cwd()), f, s);
|
|
97
|
+
await stopCommand(loadConfig(process.cwd()), f, s ?? []);
|
|
96
98
|
}
|
|
97
99
|
catch (e) {
|
|
98
100
|
die(e.message);
|
|
99
101
|
} });
|
|
100
|
-
program.command('restart <feature> [
|
|
102
|
+
program.command('restart <feature> [services...]').description(t('重启功能的运行体(停掉再起);省略 = 全部,可列多个', 'restart the feature\'s runtime (stop then start); omit = all, or list several'))
|
|
101
103
|
.action(async (f, s) => { try {
|
|
102
|
-
await restartCommand(loadConfig(process.cwd()), f, s);
|
|
104
|
+
await restartCommand(loadConfig(process.cwd()), f, s ?? []);
|
|
103
105
|
}
|
|
104
106
|
catch (e) {
|
|
105
107
|
die(e.message);
|
|
106
108
|
} });
|
|
107
|
-
program.command('down <feature>').description(t('
|
|
109
|
+
program.command('down <feature>').description(t('拆除整个功能(所有服务的 worktree)', 'tear down the whole feature (all its service worktrees)')).option('-f, --force', t('跳过脏/未推检查强制删除', 'skip dirty/unpushed checks and force-remove'))
|
|
108
110
|
.action(async (f, o) => { try {
|
|
109
|
-
await rmCommand(loadConfig(process.cwd()), f,
|
|
111
|
+
await rmCommand(loadConfig(process.cwd()), f, [], !!o.force);
|
|
110
112
|
}
|
|
111
113
|
catch (e) {
|
|
112
114
|
die(e.message);
|
|
113
115
|
} });
|
|
114
|
-
program.command('rm <feature>
|
|
116
|
+
program.command('rm <feature> <services...>').description(t('拆除功能下指定的一个或多个服务的 worktree(拆整功能用 down)。默认查脏/未推保护', 'remove the worktree(s) of the given service(s) of a feature (use down for the whole feature). Dirty/unpushed protected by default')).option('-f, --force', t('跳过脏/未推检查强制删除', 'skip dirty/unpushed checks and force-remove'))
|
|
115
117
|
.action(async (f, s, o) => { try {
|
|
116
118
|
await rmCommand(loadConfig(process.cwd()), f, s, !!o.force);
|
|
117
119
|
}
|
|
@@ -17,12 +17,12 @@ export function complete(cfg, words) {
|
|
|
17
17
|
const featureSubs = ['up', 'add', 'rm', 'down', 'run', 'sh', 'path', 'start', 'stop', 'restart'];
|
|
18
18
|
if (featureSubs.includes(sub) && pos === 1)
|
|
19
19
|
return Object.values(readLabels(cfg));
|
|
20
|
-
if (['add', 'run', 'sh', 'path'
|
|
21
|
-
return Object.keys(cfg.services);
|
|
20
|
+
if (['add', 'run', 'sh', 'path'].includes(sub) && pos === 2)
|
|
21
|
+
return Object.keys(cfg.services); // 单服务
|
|
22
22
|
if (sub === 'run' && pos === 3)
|
|
23
23
|
return Object.keys(cfg.services[prev[2]]?.run ?? {}); // run <feature> <service> <name>:补该服务的 run 命令名
|
|
24
|
-
if (
|
|
25
|
-
return Object.keys(cfg.services); //
|
|
24
|
+
if (['up', 'start', 'stop', 'restart', 'rm'].includes(sub) && pos >= 2)
|
|
25
|
+
return Object.keys(cfg.services); // 变长服务列表
|
|
26
26
|
return [];
|
|
27
27
|
}
|
|
28
28
|
export function completionScript(shell) {
|
|
@@ -7,12 +7,18 @@ import { log } from '../util/log.js';
|
|
|
7
7
|
import { color as c } from '../util/color.js';
|
|
8
8
|
import { t } from '../i18n.js';
|
|
9
9
|
// dev server + infra 生命周期:stop/start/restart 同时管 node(managed 进程)与 docker(stop 钩子 + setup 恢复),不动 worktree。
|
|
10
|
-
|
|
10
|
+
// services 为空 = 整功能;否则只这些服务
|
|
11
|
+
function occupantsOf(cfg, feature, services = []) {
|
|
11
12
|
const slot = slotOfFeature(cfg, feature);
|
|
12
13
|
if (slot === undefined)
|
|
13
14
|
throw new Error(t(`功能「${feature}」未占槽。先 \`worktree-bay up ${feature} <服务...>\` 起它。`, `feature "${feature}" hasn't claimed a slot. Run \`worktree-bay up ${feature} <services...>\` first.`));
|
|
14
15
|
const all = scanOccupancy(cfg).get(slot) ?? [];
|
|
15
|
-
|
|
16
|
+
if (!services.length)
|
|
17
|
+
return all;
|
|
18
|
+
for (const s of services)
|
|
19
|
+
if (!all.some((o) => o.service === s))
|
|
20
|
+
throw new Error(t(`服务「${s}」不在功能「${feature}」里。用 \`worktree-bay ls\` 看已起的服务。`, `service "${s}" is not in feature "${feature}". See \`worktree-bay ls\`.`));
|
|
21
|
+
return all.filter((o) => services.includes(o.service));
|
|
16
22
|
}
|
|
17
23
|
function ctxOf(cfg, o) {
|
|
18
24
|
const base = { cfg, service: o.service, sp: cfg.services[o.service], slot: o.slot, slug: o.slug, dir: o.dir, repo: repoPath(cfg, o.service) };
|
|
@@ -20,10 +26,10 @@ function ctxOf(cfg, o) {
|
|
|
20
26
|
}
|
|
21
27
|
// 该服务是否有「可停起的运行体」:managed dev server(start) 或可停的 infra(stop 钩子)
|
|
22
28
|
const hasRuntime = (cfg, service) => { const sp = cfg.services[service]; return !!(sp.start || sp.stop); };
|
|
23
|
-
export async function stopCommand(cfg, feature,
|
|
29
|
+
export async function stopCommand(cfg, feature, services = []) {
|
|
24
30
|
await withLock(cfg.workspaceRoot, async () => {
|
|
25
31
|
let any = false;
|
|
26
|
-
for (const o of occupantsOf(cfg, feature,
|
|
32
|
+
for (const o of occupantsOf(cfg, feature, services)) {
|
|
27
33
|
if (!hasRuntime(cfg, o.service))
|
|
28
34
|
continue;
|
|
29
35
|
any = true;
|
|
@@ -34,10 +40,10 @@ export async function stopCommand(cfg, feature, service) {
|
|
|
34
40
|
log(t('没有可停止的运行体(相关服务未配置 start/stop)', 'nothing to stop (no start/stop configured for those services)'));
|
|
35
41
|
});
|
|
36
42
|
}
|
|
37
|
-
export async function startCommand(cfg, feature,
|
|
43
|
+
export async function startCommand(cfg, feature, services = []) {
|
|
38
44
|
await withLock(cfg.workspaceRoot, async () => {
|
|
39
45
|
let any = false;
|
|
40
|
-
for (const o of occupantsOf(cfg, feature,
|
|
46
|
+
for (const o of occupantsOf(cfg, feature, services)) {
|
|
41
47
|
if (!hasRuntime(cfg, o.service))
|
|
42
48
|
continue;
|
|
43
49
|
any = true;
|
|
@@ -48,9 +54,9 @@ export async function startCommand(cfg, feature, service) {
|
|
|
48
54
|
log(t('没有可启动的运行体(相关服务未配置 start/stop)', 'nothing to start (no start/stop configured for those services)'));
|
|
49
55
|
});
|
|
50
56
|
}
|
|
51
|
-
export async function restartCommand(cfg, feature,
|
|
57
|
+
export async function restartCommand(cfg, feature, services = []) {
|
|
52
58
|
await withLock(cfg.workspaceRoot, async () => {
|
|
53
|
-
for (const o of occupantsOf(cfg, feature,
|
|
59
|
+
for (const o of occupantsOf(cfg, feature, services)) {
|
|
54
60
|
if (!hasRuntime(cfg, o.service))
|
|
55
61
|
continue;
|
|
56
62
|
const ctx = ctxOf(cfg, o);
|
package/dist/commands/rm.js
CHANGED
|
@@ -9,17 +9,24 @@ import { stopManaged } from '../proc.js';
|
|
|
9
9
|
import { log, warn } from '../util/log.js';
|
|
10
10
|
import { color as c } from '../util/color.js';
|
|
11
11
|
import { t } from '../i18n.js';
|
|
12
|
-
|
|
12
|
+
// services 为空 = 整功能;否则只这些服务(顺带校验服务名确实在该功能里)
|
|
13
|
+
export function resolveRm(cfg, feature, services = []) {
|
|
13
14
|
const slot = slotOfFeature(cfg, feature);
|
|
14
15
|
if (slot === undefined)
|
|
15
16
|
throw new Error(t(`功能「${feature}」未占槽,无需拆除。用 \`worktree-bay ls\` 看在用的功能。`, `feature "${feature}" has no slot — nothing to tear down. See \`worktree-bay ls\`.`));
|
|
16
17
|
const all = scanOccupancy(cfg).get(slot) ?? [];
|
|
17
|
-
|
|
18
|
+
if (!services.length)
|
|
19
|
+
return all;
|
|
20
|
+
for (const s of services)
|
|
21
|
+
if (!all.some((o) => o.service === s))
|
|
22
|
+
throw new Error(t(`服务「${s}」不在功能「${feature}」里。用 \`worktree-bay ls\` 看已起的服务。`, `service "${s}" is not in feature "${feature}". See \`worktree-bay ls\`.`));
|
|
23
|
+
return all.filter((o) => services.includes(o.service));
|
|
18
24
|
}
|
|
19
|
-
export async function rmCommand(cfg, feature,
|
|
25
|
+
export async function rmCommand(cfg, feature, services, force) {
|
|
20
26
|
await withLock(cfg.workspaceRoot, async () => {
|
|
21
27
|
let removed = 0;
|
|
22
|
-
const
|
|
28
|
+
const wholeFeature = services.length === 0;
|
|
29
|
+
const occs = resolveRm(cfg, feature, services);
|
|
23
30
|
for (const o of occs) {
|
|
24
31
|
const repo = repoPath(cfg, o.service);
|
|
25
32
|
const branch = currentBranch(o.dir);
|
|
@@ -41,7 +48,7 @@ export async function rmCommand(cfg, feature, service, force) {
|
|
|
41
48
|
removed++;
|
|
42
49
|
}
|
|
43
50
|
const slot = slotOfFeature(cfg, feature);
|
|
44
|
-
if (
|
|
51
|
+
if (wholeFeature && (scanOccupancy(cfg).get(slot) ?? []).length === 0) {
|
|
45
52
|
removeLabel(cfg, slot);
|
|
46
53
|
if (removed === 0)
|
|
47
54
|
log(`${c.green('✓')} ` + t(`释放空槽预约 "${feature}"(槽 ${slot})`, `released empty slot reservation "${feature}" (slot ${slot})`));
|
package/dist/mcp.js
CHANGED
|
@@ -52,18 +52,18 @@ export const TOOLS = [
|
|
|
52
52
|
{ name: 'worktree_bay_run', description: '在某功能某服务的运行体里跑预设命令(如 test),可透传额外参数',
|
|
53
53
|
inputSchema: { type: 'object', properties: { feature: str, service: str, name: str, args: { type: 'array', items: str } }, required: ['feature', 'service', 'name'] },
|
|
54
54
|
toArgs: (a) => ['run', String(a.feature), String(a.service), String(a.name), ...(a.args ?? [])] },
|
|
55
|
-
{ name: 'worktree_bay_start', description: '启动功能的运行体(docker 容器 + node dev server 一起),不动 worktree。
|
|
56
|
-
inputSchema: { type: 'object', properties: { feature: str,
|
|
57
|
-
toArgs: (a) => ['start', String(a.feature), ...(a.
|
|
58
|
-
{ name: 'worktree_bay_stop', description: '停止功能的运行体(停 docker 容器 + 杀 node dev server),保留 worktree。
|
|
59
|
-
inputSchema: { type: 'object', properties: { feature: str,
|
|
60
|
-
toArgs: (a) => ['stop', String(a.feature), ...(a.
|
|
61
|
-
{ name: 'worktree_bay_restart', description: '重启功能的运行体(停掉再起,docker + node 一起)。改了配置或端口卡住时用。
|
|
62
|
-
inputSchema: { type: 'object', properties: { feature: str,
|
|
63
|
-
toArgs: (a) => ['restart', String(a.feature), ...(a.
|
|
64
|
-
{ name: 'worktree_bay_down', description: '
|
|
65
|
-
inputSchema: { type: 'object', properties: { feature: str,
|
|
66
|
-
toArgs: (a) =>
|
|
55
|
+
{ name: 'worktree_bay_start', description: '启动功能的运行体(docker 容器 + node dev server 一起),不动 worktree。services 省略=该功能所有服务,也可列多个。',
|
|
56
|
+
inputSchema: { type: 'object', properties: { feature: str, services: { type: 'array', items: str } }, required: ['feature'] },
|
|
57
|
+
toArgs: (a) => ['start', String(a.feature), ...(a.services ?? [])] },
|
|
58
|
+
{ name: 'worktree_bay_stop', description: '停止功能的运行体(停 docker 容器 + 杀 node dev server),保留 worktree。services 省略=全部,也可列多个。',
|
|
59
|
+
inputSchema: { type: 'object', properties: { feature: str, services: { type: 'array', items: str } }, required: ['feature'] },
|
|
60
|
+
toArgs: (a) => ['stop', String(a.feature), ...(a.services ?? [])] },
|
|
61
|
+
{ name: 'worktree_bay_restart', description: '重启功能的运行体(停掉再起,docker + node 一起)。改了配置或端口卡住时用。services 省略=全部,也可列多个。',
|
|
62
|
+
inputSchema: { type: 'object', properties: { feature: str, services: { type: 'array', items: str } }, required: ['feature'] },
|
|
63
|
+
toArgs: (a) => ['restart', String(a.feature), ...(a.services ?? [])] },
|
|
64
|
+
{ name: 'worktree_bay_down', description: '拆除 worktree:省略 services 拆整个功能(所有服务),给 services 只拆这些服务(默认查脏/未推保护,force=true 强删)',
|
|
65
|
+
inputSchema: { type: 'object', properties: { feature: str, services: { type: 'array', items: str }, force: { type: 'boolean' } }, required: ['feature'] },
|
|
66
|
+
toArgs: (a) => { const s = a.services ?? []; const force = a.force ? ['-f'] : []; return s.length ? ['rm', String(a.feature), ...s, ...force] : ['down', String(a.feature), ...force]; } },
|
|
67
67
|
{ name: 'worktree_bay_gc', description: '合并感知回收:默认 dry-run 只列建议,apply=true 才实际删除「已合并且干净」的功能',
|
|
68
68
|
inputSchema: { type: 'object', properties: { apply: { type: 'boolean' } } },
|
|
69
69
|
toArgs: (a) => ['gc', ...(a.apply ? ['--apply'] : [])] },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "worktree-bay",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
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",
|
package/skill.md
CHANGED
|
@@ -35,11 +35,11 @@ worktree-bay completion install # 一键装 shell 补全(可选)
|
|
|
35
35
|
| `worktree-bay path <feature> <service>` | 打印某服务 worktree 的绝对路径(可 `cd $(worktree-bay path f api)`) |
|
|
36
36
|
| `worktree-bay run <feature> <service> <name> [args...]` | 在某服务运行体里跑配置的 `run.<name>`(如 test),透传 args |
|
|
37
37
|
| `worktree-bay sh <feature> <service>` | 进入某服务运行体的 shell |
|
|
38
|
-
| `worktree-bay start <feature> [
|
|
39
|
-
| `worktree-bay stop <feature> [
|
|
40
|
-
| `worktree-bay restart <feature> [
|
|
41
|
-
| `worktree-bay down <feature> [-f]` |
|
|
42
|
-
| `worktree-bay rm <feature>
|
|
38
|
+
| `worktree-bay start <feature> [services...]` | 启动功能的运行体(docker 容器 + node dev server 一起);**省略 = 全部**,也可列多个。不动 worktree |
|
|
39
|
+
| `worktree-bay stop <feature> [services...]` | 停止功能的运行体(停 docker + 杀 node dev server);省略 = 全部,可列多个。保留 worktree |
|
|
40
|
+
| `worktree-bay restart <feature> [services...]` | 重启运行体(停掉再起);省略 = 全部,可列多个 |
|
|
41
|
+
| `worktree-bay down <feature> [-f]` | 拆除**整个功能**(所有服务的 worktree)。`up` 的反操作 |
|
|
42
|
+
| `worktree-bay rm <feature> <services...> [-f]` | 拆除指定的**一个或多个服务**(拆整功能用 `down`)。`add` 的反操作。默认查脏/未推保护,`-f` 强删 |
|
|
43
43
|
| `worktree-bay gc [--apply]` | 合并感知回收:默认 dry-run 只列建议,`--apply` 才删「已合并且干净」的 |
|
|
44
44
|
| `worktree-bay completion <install\|bash\|zsh\|fish>` | `install` 一键装进 shell;或打印补全脚本 |
|
|
45
45
|
| `worktree-bay mcp` | 启动 MCP 服务(stdio,轻量脚本,客户端按需 spawn),供 AI 调用 |
|