worktree-bay 2.3.9 → 2.4.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 CHANGED
@@ -7,6 +7,7 @@ import { lsCommand } from './commands/ls.js';
7
7
  import { addCommand, upCommand } from './commands/add.js';
8
8
  import { runCommand, shCommand, pathCommand } from './commands/passthrough.js';
9
9
  import { rmCommand } from './commands/rm.js';
10
+ import { startCommand, stopCommand, restartCommand } from './commands/lifecycle.js';
10
11
  import { gcCommand } from './commands/gc.js';
11
12
  import { doctorCommand } from './commands/doctor.js';
12
13
  import { complete, completionCommand, installCompletion } from './commands/completion.js';
@@ -82,6 +83,27 @@ program.command('run <feature> <service> <name> [args...]').description(t('在
82
83
  .action((f, s, n, args) => sync((c) => runCommand(c, f, s, n, args ?? [])));
83
84
  program.command('sh <feature> <service>').description(t('进入服务运行体的 shell', 'open a shell inside the service runtime'))
84
85
  .action((f, s) => sync((c) => shCommand(c, f, s)));
86
+ program.command('start <feature> [service]').description(t('启动该功能的 dev server(worktree 已在,只起 start 进程,不动 worktree)', 'start the feature\'s dev server(s) (worktree already exists; runs the start process only)'))
87
+ .action(async (f, s) => { try {
88
+ await startCommand(loadConfig(process.cwd()), f, s);
89
+ }
90
+ catch (e) {
91
+ die(e.message);
92
+ } });
93
+ program.command('stop <feature> [service]').description(t('停止该功能的 dev server(保留 worktree)', 'stop the feature\'s dev server(s) (keeps the worktree)'))
94
+ .action(async (f, s) => { try {
95
+ await stopCommand(loadConfig(process.cwd()), f, s);
96
+ }
97
+ catch (e) {
98
+ die(e.message);
99
+ } });
100
+ program.command('restart <feature> [service]').description(t('重启该功能的 dev server(停掉再起)', 'restart the feature\'s dev server(s) (stop then start)'))
101
+ .action(async (f, s) => { try {
102
+ await restartCommand(loadConfig(process.cwd()), f, s);
103
+ }
104
+ catch (e) {
105
+ die(e.message);
106
+ } });
85
107
  program.command('down <feature>').description(t('拆除整个功能的所有服务 worktree(= rm <feature>)', 'tear down all of a feature\'s service worktrees (= rm <feature>)')).option('-f, --force', t('跳过脏/未推检查强制删除', 'skip dirty/unpushed checks and force-remove'))
86
108
  .action(async (f, o) => { try {
87
109
  await rmCommand(loadConfig(process.cwd()), f, undefined, !!o.force);
@@ -4,7 +4,7 @@ import path from 'node:path';
4
4
  import { readLabels } from '../slots.js';
5
5
  import { log } from '../util/log.js';
6
6
  import { t } from '../i18n.js';
7
- const SUBCMDS = ['init', 'doctor', 'claim', 'up', 'add', 'ls', 'path', 'gc', 'down', 'rm', 'run', 'sh', 'completion', 'mcp', 'skill', 'version', 'help'];
7
+ const SUBCMDS = ['init', 'doctor', 'claim', 'up', 'add', 'ls', 'path', 'gc', 'down', 'rm', 'run', 'sh', 'start', 'stop', 'restart', 'completion', 'mcp', 'skill', 'version', 'help'];
8
8
  // words = 命令名 + 光标前已输入完的词(不含当前正在补的词)
9
9
  export function complete(cfg, words) {
10
10
  const prev = words.slice(1);
@@ -14,10 +14,10 @@ export function complete(cfg, words) {
14
14
  return []; // 无配置(不在工作区内):子命令已补全,feature/service 无从读取
15
15
  const sub = prev[0];
16
16
  const pos = prev.length;
17
- const featureSubs = ['up', 'add', 'rm', 'down', 'run', 'sh', 'path'];
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'].includes(sub) && pos === 2)
20
+ if (['add', 'run', 'sh', 'path', 'start', 'stop', 'restart'].includes(sub) && pos === 2)
21
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 命令名
@@ -0,0 +1,65 @@
1
+ import { repoPath } from '../config.js';
2
+ import { withLock } from '../lock.js';
3
+ import { scanOccupancy, slotOfFeature } from '../slots.js';
4
+ import { buildVars, ensureStarted } from '../engine.js';
5
+ import { stopManaged } from '../proc.js';
6
+ import { portInUse } from '../ports.js';
7
+ import { log, warn } from '../util/log.js';
8
+ import { color as c } from '../util/color.js';
9
+ import { t } from '../i18n.js';
10
+ // dev server 生命周期:start/stop/restart(只管 start 配置起的进程,不动 worktree)
11
+ function occupantsOf(cfg, feature, service) {
12
+ const slot = slotOfFeature(cfg, feature);
13
+ if (slot === undefined)
14
+ throw new Error(t(`功能「${feature}」未占槽。先 \`worktree-bay up ${feature} <服务...>\` 起它。`, `feature "${feature}" hasn't claimed a slot. Run \`worktree-bay up ${feature} <services...>\` first.`));
15
+ const all = scanOccupancy(cfg).get(slot) ?? [];
16
+ return service ? all.filter((o) => o.service === service) : all;
17
+ }
18
+ function ctxOf(cfg, o) {
19
+ const sp = cfg.services[o.service];
20
+ const base = { cfg, service: o.service, sp, slot: o.slot, slug: o.slug, dir: o.dir, repo: repoPath(cfg, o.service) };
21
+ return { ...base, vars: buildVars(cfg, base) };
22
+ }
23
+ export async function startCommand(cfg, feature, service) {
24
+ await withLock(cfg.workspaceRoot, async () => {
25
+ let any = false;
26
+ for (const o of occupantsOf(cfg, feature, service)) {
27
+ if (!cfg.services[o.service].start)
28
+ continue;
29
+ any = true;
30
+ log(c.bold(c.cyan(o.service)));
31
+ await ensureStarted(ctxOf(cfg, o));
32
+ }
33
+ if (!any)
34
+ warn(t('没有可启动的 dev server(相关服务未配置 start)', 'nothing to start (those services have no start configured)'));
35
+ });
36
+ }
37
+ export async function stopCommand(cfg, feature, service) {
38
+ await withLock(cfg.workspaceRoot, async () => {
39
+ let any = false;
40
+ for (const o of occupantsOf(cfg, feature, service)) {
41
+ const stopped = stopManaged(cfg.workspaceRoot, o.dir);
42
+ if (stopped) {
43
+ any = true;
44
+ log(`${c.green('✓')} ` + t(`已停止 ${o.service} dev server(pid ${stopped.pid})`, `stopped ${o.service} dev server (pid ${stopped.pid})`));
45
+ }
46
+ }
47
+ if (!any)
48
+ log(t('没有在跑的 dev server', 'no running dev server'));
49
+ });
50
+ }
51
+ export async function restartCommand(cfg, feature, service) {
52
+ await withLock(cfg.workspaceRoot, async () => {
53
+ for (const o of occupantsOf(cfg, feature, service)) {
54
+ const sp = cfg.services[o.service];
55
+ if (!sp.start)
56
+ continue;
57
+ log(c.bold(c.cyan(o.service)) + c.dim(t(' · 重启…', ' · restarting…')));
58
+ stopManaged(cfg.workspaceRoot, o.dir);
59
+ const port = Number(ctxOf(cfg, o).vars.port);
60
+ for (let i = 0; i < 40 && (await portInUse(port)); i++)
61
+ await new Promise((r) => setTimeout(r, 100)); // 等端口释放(最多 ~4s)
62
+ await ensureStarted(ctxOf(cfg, o));
63
+ }
64
+ });
65
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "worktree-bay",
3
- "version": "2.3.9",
3
+ "version": "2.4.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,6 +35,9 @@ 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> [service]` | 启动该功能的 dev server(worktree 已在,只起 `start` 进程,不动 worktree) |
39
+ | `worktree-bay stop <feature> [service]` | 停止该功能的 dev server(保留 worktree) |
40
+ | `worktree-bay restart <feature> [service]` | 重启 dev server(停掉再起;改了配置/端口卡住时用) |
38
41
  | `worktree-bay down <feature> [-f]` | 拆除整个功能的所有服务 worktree(= `rm <feature>`) |
39
42
  | `worktree-bay rm <feature> [service] [-f]` | 拆除某服务或整槽。默认查脏/未推保护,`-f` 强删 |
40
43
  | `worktree-bay gc [--apply]` | 合并感知回收:默认 dry-run 只列建议,`--apply` 才删「已合并且干净」的 |