worktree-bay 1.0.2 → 1.1.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/README.md CHANGED
@@ -27,12 +27,8 @@ npm i -g worktree-bay
27
27
  ## 快速上手
28
28
 
29
29
  ```bash
30
- # 占一个槽位
31
- worktree-bay claim drill-fix # 槽 1,端口块 6010
32
-
33
- # 把服务挂进这个功能的槽(开 worktree + 跑该服务的步骤)
34
- worktree-bay add drill-fix api feature/drill-fix # api 起在 6011
35
- worktree-bay add drill-fix lms feature/drill-ui # 前端自动接 6011
30
+ # 一条命令起整个功能:自动占槽 + 在 api/lms 上开 worktree(分支默认 = 功能名)
31
+ worktree-bay up drill-fix api lms
36
32
 
37
33
  # 看占用
38
34
  worktree-bay ls
@@ -40,11 +36,15 @@ worktree-bay ls
40
36
  # 在服务运行体里跑命令(透传)
41
37
  worktree-bay run drill-fix api test
42
38
 
43
- # 收尾
44
- worktree-bay rm drill-fix # 整槽拆除(默认查脏/未推保护,-f 强删)
45
- worktree-bay gc # 合并感知回收(默认 dry-run,--apply 实际执行)
39
+ # 拆除整个功能(默认查脏/未推保护,-f 强删)
40
+ worktree-bay down drill-fix
41
+
42
+ # 回收已合并的(默认 dry-run,--apply 实际执行)
43
+ worktree-bay gc
46
44
  ```
47
45
 
46
+ > 需要更细的控制:`claim <feature>` 单独占槽、`add <feature> <service> [branch]` 单加一个服务(branch 可自定义,省略则用功能名)、`rm <feature> [service]` 拆单个服务。
47
+
48
48
  ## 配置
49
49
 
50
50
  在工作区根放一份 `worktree-bay.config.json`,集中声明所有服务。工具运行时自下而上查找它(或用环境变量 `WORKTREE_BAY_CONFIG` 指定)。
@@ -106,10 +106,14 @@ worktree-bay gc # 合并感知回收(默认 dry-run,--app
106
106
 
107
107
  ## Shell 补全
108
108
 
109
+ 一条命令装好(自动探测 shell、幂等写入对应 rc;fish 直接写补全目录):
110
+
109
111
  ```bash
110
- worktree-bay completion bash >> ~/.bashrc # 或 zsh / fish
112
+ worktree-bay completion install
111
113
  ```
112
114
 
115
+ 执行 `source ~/.bashrc`(或重开终端)即可 tab 补全子命令、功能名、服务名。也可手动:`worktree-bay completion bash`(打印脚本,自行接入)。
116
+
113
117
  ## 许可证
114
118
 
115
119
  [MIT](./LICENSE)
package/dist/cli.js CHANGED
@@ -4,11 +4,11 @@ import { Command } from 'commander';
4
4
  import { loadConfig } from './config.js';
5
5
  import { claimCommand } from './commands/claim.js';
6
6
  import { lsCommand } from './commands/ls.js';
7
- import { addCommand } from './commands/add.js';
7
+ import { addCommand, upCommand } from './commands/add.js';
8
8
  import { runCommand, shCommand } from './commands/passthrough.js';
9
9
  import { rmCommand } from './commands/rm.js';
10
10
  import { gcCommand } from './commands/gc.js';
11
- import { complete, completionCommand } from './commands/completion.js';
11
+ import { complete, completionCommand, installCompletion } from './commands/completion.js';
12
12
  import { die } from './util/log.js';
13
13
  const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
14
14
  const program = new Command();
@@ -28,7 +28,14 @@ catch (e) {
28
28
  } });
29
29
  program.command('ls').description('列出所有槽位与占用状态')
30
30
  .action(() => sync(lsCommand));
31
- program.command('add <feature> <service> <branch> [base]').description('在某服务为功能开 worktree 并挂入其槽位')
31
+ program.command('up <feature> <services...>').description('一条命令为功能起多个服务(自动 claim + 各服务默认分支 = 功能名)')
32
+ .action(async (f, services) => { try {
33
+ await upCommand(loadConfig(process.cwd()), f, services);
34
+ }
35
+ catch (e) {
36
+ die(e.message);
37
+ } });
38
+ program.command('add <feature> <service> [branch] [base]').description('为功能在某服务开 worktree(branch 默认 = 功能名)')
32
39
  .action(async (f, s, b, base) => { try {
33
40
  await addCommand(loadConfig(process.cwd()), f, s, b, base);
34
41
  }
@@ -39,6 +46,13 @@ program.command('run <feature> <service> <name> [args...]').description('在服
39
46
  .action((f, s, n, args) => sync((c) => runCommand(c, f, s, n, args ?? [])));
40
47
  program.command('sh <feature> <service>').description('进入服务运行体的 shell')
41
48
  .action((f, s) => sync((c) => shCommand(c, f, s)));
49
+ program.command('down <feature>').description('拆除整个功能的所有服务 worktree(= rm <feature>)').option('-f, --force', '跳过脏/未推检查强制删除')
50
+ .action(async (f, o) => { try {
51
+ await rmCommand(loadConfig(process.cwd()), f, undefined, !!o.force);
52
+ }
53
+ catch (e) {
54
+ die(e.message);
55
+ } });
42
56
  program.command('rm <feature> [service]').description('拆除某服务或整槽的 worktree(默认查脏/未推保护)').option('-f, --force', '跳过脏/未推检查强制删除')
43
57
  .action(async (f, s, o) => { try {
44
58
  await rmCommand(loadConfig(process.cwd()), f, s, !!o.force);
@@ -53,9 +67,12 @@ program.command('gc').description('合并感知回收(默认 dry-run)').opti
53
67
  catch (e) {
54
68
  die(e.message);
55
69
  } });
56
- program.command('completion <shell>').description('打印 shell 补全脚本(bash|zsh|fish')
57
- .action((sh) => { try {
58
- completionCommand(sh);
70
+ program.command('completion <target> [shell]').description('install 一键装进 shell;或 bash|zsh|fish 打印补全脚本')
71
+ .action((target, shell) => { try {
72
+ if (target === 'install')
73
+ installCompletion(shell);
74
+ else
75
+ completionCommand(target);
59
76
  }
60
77
  catch (e) {
61
78
  die(e.message);
@@ -14,12 +14,18 @@ export function resolveAdd(cfg, feature, service, branch) {
14
14
  return { service, slot, slug, dir: path.join(repoPath(cfg, service), '.worktrees', slug), repo: repoPath(cfg, service) };
15
15
  }
16
16
  export async function addCommand(cfg, feature, service, branch, base) {
17
+ const br = branch || feature; // 默认分支 = 功能名
17
18
  await withLock(cfg.workspaceRoot, async () => {
18
- const p = resolveAdd(cfg, feature, service, branch);
19
+ const p = resolveAdd(cfg, feature, service, br);
19
20
  const sp = cfg.services[service];
20
21
  const ctxBase = { cfg, service, sp, slot: p.slot, slug: p.slug, dir: p.dir, repo: p.repo };
21
22
  const ctx = { ...ctxBase, vars: buildVars(cfg, ctxBase) };
22
- await bringUp(ctx, base ?? `origin/${mainBranch(p.repo)}`, branch);
23
- log(`✓ ${service} 挂入 "${feature}"(槽 ${p.slot},端口 ${ctx.vars.port})`);
23
+ await bringUp(ctx, base ?? `origin/${mainBranch(p.repo)}`, br);
24
+ log(`✓ ${service} 挂入 "${feature}"(槽 ${p.slot},端口 ${ctx.vars.port},分支 ${br})`);
24
25
  });
25
26
  }
27
+ // up: 一条命令为功能批量起多个服务(claim 自动 + 各服务默认分支)
28
+ export async function upCommand(cfg, feature, services, base) {
29
+ for (const service of services)
30
+ await addCommand(cfg, feature, service, undefined, base);
31
+ }
@@ -1,25 +1,55 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
1
4
  import { readLabels } from '../slots.js';
2
5
  import { log } from '../util/log.js';
3
- const SUBCMDS = ['claim', 'add', 'ls', 'gc', 'rm', 'run', 'sh', 'completion'];
6
+ const SUBCMDS = ['claim', 'up', 'add', 'ls', 'gc', 'down', 'rm', 'run', 'sh', 'completion'];
7
+ // words = 命令名 + 光标前已输入完的词(不含当前正在补的词)
4
8
  export function complete(cfg, words) {
5
- const args = words.slice(1);
6
- if (args.length === 0)
9
+ const prev = words.slice(1);
10
+ if (prev.length === 0)
7
11
  return SUBCMDS;
8
- const sub = args[0];
9
- const pos = args.length;
10
- if (['add', 'rm', 'run', 'sh'].includes(sub) && pos === 1)
12
+ const sub = prev[0];
13
+ const pos = prev.length;
14
+ const featureSubs = ['up', 'add', 'rm', 'down', 'run', 'sh'];
15
+ if (featureSubs.includes(sub) && pos === 1)
11
16
  return Object.values(readLabels(cfg));
12
17
  if (['add', 'run', 'sh'].includes(sub) && pos === 2)
13
18
  return Object.keys(cfg.services);
19
+ if (sub === 'up' && pos >= 2)
20
+ return Object.keys(cfg.services); // up 接变长服务列表
14
21
  return [];
15
22
  }
16
23
  export function completionScript(shell) {
24
+ // 脚本只传"光标前已完成的词",不含当前正在补的词,与 complete() 的模型一致
17
25
  if (shell === 'bash')
18
- return `_worktree_bay(){ COMPREPLY=( $(worktree-bay __complete -- "\${COMP_WORDS[@]}") ); }\ncomplete -F _worktree_bay worktree-bay`;
26
+ return `_worktree_bay(){ COMPREPLY=( $(worktree-bay __complete -- "\${COMP_WORDS[@]:0:\$COMP_CWORD}") ); }\ncomplete -F _worktree_bay worktree-bay`;
19
27
  if (shell === 'zsh')
20
- return `#compdef worktree-bay\n_worktree_bay(){ compadd -- $(worktree-bay __complete -- "\${words[@]}") }\ncompdef _worktree_bay worktree-bay`;
28
+ return `#compdef worktree-bay\n_worktree_bay(){ compadd -- $(worktree-bay __complete -- "\${(@)words[1,CURRENT-1]}") }\ncompdef _worktree_bay worktree-bay`;
21
29
  if (shell === 'fish')
22
- return `complete -c worktree-bay -a '(worktree-bay __complete -- (commandline -opc))'`;
30
+ return `complete -c worktree-bay -f -a '(worktree-bay __complete -- (commandline -opc))'`;
23
31
  throw new Error('unsupported shell: ' + shell);
24
32
  }
25
33
  export function completionCommand(shell) { log(completionScript(shell)); }
34
+ // 一键把补全装进当前 shell(幂等)。fish 直接写补全目录(零配置生效);bash/zsh 往 rc 加一行 eval。
35
+ export function installCompletion(shell) {
36
+ const sh = shell || path.basename(process.env.SHELL || 'bash');
37
+ if (sh === 'fish') {
38
+ const dir = path.join(os.homedir(), '.config', 'fish', 'completions');
39
+ fs.mkdirSync(dir, { recursive: true });
40
+ const file = path.join(dir, 'worktree-bay.fish');
41
+ fs.writeFileSync(file, completionScript('fish') + '\n');
42
+ log(`✓ fish 补全已写入 ${file}(新开 fish 即生效)`);
43
+ return;
44
+ }
45
+ const isZsh = sh === 'zsh';
46
+ const rc = path.join(os.homedir(), isZsh ? '.zshrc' : '.bashrc');
47
+ const line = `eval "$(worktree-bay completion ${isZsh ? 'zsh' : 'bash'})"`;
48
+ const cur = fs.existsSync(rc) ? fs.readFileSync(rc, 'utf8') : '';
49
+ if (cur.includes(line)) {
50
+ log(`✓ 补全已在 ${rc},无需重复安装`);
51
+ return;
52
+ }
53
+ fs.appendFileSync(rc, `\n# worktree-bay completion\n${line}\n`);
54
+ log(`✓ 补全已加入 ${rc},执行 'source ${rc}' 或重开终端即生效`);
55
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "worktree-bay",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "Config-driven git worktree slot + port orchestrator for parallel multi-service development",
5
5
  "keywords": [
6
6
  "git",