@fatdoge/wtree 0.1.10 → 0.2.1
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.en.md +66 -1
- package/README.md +60 -1
- package/api/cli/wtree.ts +248 -78
- package/dist-node/api/cli/wtree.js +266 -81
- package/dist-node/api/cli/wtui.js +0 -0
- package/package.json +16 -15
- package/skills/wtree/SKILL.md +162 -0
|
@@ -5,12 +5,32 @@ import inquirer from 'inquirer';
|
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import { execSync } from 'node:child_process';
|
|
7
7
|
import fs from 'node:fs';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
8
9
|
import { getRepoRoot } from '../core/git.js';
|
|
9
10
|
import { git, gitOrThrow } from '../core/git.js';
|
|
10
11
|
import { listWorktrees, parseWorktreePorcelain } from '../core/worktree.js';
|
|
11
12
|
import { openPath } from '../core/open.js';
|
|
12
13
|
import { readConfig, writeConfig, getConfigPaths } from '../core/config.js';
|
|
13
14
|
import { startUiDevServer } from '../ui/startUiDev.js';
|
|
15
|
+
function getVersion() {
|
|
16
|
+
try {
|
|
17
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
// 从 api/cli/ 或 dist-node/api/cli/ 向上查找 package.json
|
|
19
|
+
let dir = __dirname;
|
|
20
|
+
for (let i = 0; i < 5; i++) {
|
|
21
|
+
const pkgPath = path.join(dir, 'package.json');
|
|
22
|
+
if (fs.existsSync(pkgPath)) {
|
|
23
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
24
|
+
return pkg.version || 'unknown';
|
|
25
|
+
}
|
|
26
|
+
dir = path.dirname(dir);
|
|
27
|
+
}
|
|
28
|
+
return 'unknown';
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return 'unknown';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
14
34
|
function errMsg(e) {
|
|
15
35
|
return e instanceof Error ? e.message : String(e);
|
|
16
36
|
}
|
|
@@ -21,6 +41,15 @@ function parseArgs(argv) {
|
|
|
21
41
|
noOpen: false,
|
|
22
42
|
repo: '',
|
|
23
43
|
port: undefined,
|
|
44
|
+
json: false,
|
|
45
|
+
yes: false,
|
|
46
|
+
force: false,
|
|
47
|
+
dir: '',
|
|
48
|
+
base: '',
|
|
49
|
+
editor: undefined,
|
|
50
|
+
noEditor: false,
|
|
51
|
+
noInstall: false,
|
|
52
|
+
version: false,
|
|
24
53
|
};
|
|
25
54
|
const positional = [];
|
|
26
55
|
while (args.length) {
|
|
@@ -43,6 +72,42 @@ function parseArgs(argv) {
|
|
|
43
72
|
flags.port = v;
|
|
44
73
|
continue;
|
|
45
74
|
}
|
|
75
|
+
if (a === '--json') {
|
|
76
|
+
flags.json = true;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (a === '--yes' || a === '-y') {
|
|
80
|
+
flags.yes = true;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (a === '--force' || a === '-f') {
|
|
84
|
+
flags.force = true;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (a === '--dir') {
|
|
88
|
+
flags.dir = String(args.shift() || '');
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (a === '--base') {
|
|
92
|
+
flags.base = String(args.shift() || '');
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (a === '--editor') {
|
|
96
|
+
flags.editor = String(args.shift() || '');
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (a === '--no-editor') {
|
|
100
|
+
flags.noEditor = true;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (a === '--no-install') {
|
|
104
|
+
flags.noInstall = true;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (a === '--version' || a === '-v') {
|
|
108
|
+
flags.version = true;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
46
111
|
if (a.startsWith('--'))
|
|
47
112
|
continue;
|
|
48
113
|
positional.push(a);
|
|
@@ -80,8 +145,12 @@ function parseCommand(positional) {
|
|
|
80
145
|
}
|
|
81
146
|
return { command: 'interactive', rest: positional };
|
|
82
147
|
}
|
|
83
|
-
function printWorktreeList(rootDir) {
|
|
148
|
+
function printWorktreeList(rootDir, json = false) {
|
|
84
149
|
const items = listWorktrees(rootDir);
|
|
150
|
+
if (json) {
|
|
151
|
+
console.info(JSON.stringify(items, null, 2));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
85
154
|
if (items.length === 0) {
|
|
86
155
|
console.info('未读取到 worktree。');
|
|
87
156
|
return;
|
|
@@ -98,7 +167,7 @@ function printHelp() {
|
|
|
98
167
|
console.info(' wtree');
|
|
99
168
|
console.info(' wtree list');
|
|
100
169
|
console.info(' wtree create [branch]');
|
|
101
|
-
console.info(' wtree delete');
|
|
170
|
+
console.info(' wtree delete [branch|path ...]');
|
|
102
171
|
console.info(' wtree open [path|branch]');
|
|
103
172
|
console.info(' wtree lock [path|branch]');
|
|
104
173
|
console.info(' wtree unlock [path|branch]');
|
|
@@ -107,8 +176,25 @@ function printHelp() {
|
|
|
107
176
|
console.info(' wtree config get <key>');
|
|
108
177
|
console.info(' wtree config set <key> <value>');
|
|
109
178
|
console.info(' wtree --ui [--repo <path>] [--no-open] [--port <number>]');
|
|
179
|
+
console.info(' wtree --version, -v');
|
|
180
|
+
console.info('');
|
|
181
|
+
console.info('选项:');
|
|
182
|
+
console.info(' --json 以 JSON 格式输出 (适合脚本/agent 使用)');
|
|
183
|
+
console.info(' --yes, -y 自动确认所有提示');
|
|
184
|
+
console.info(' --force, -f 强制操作 (如强制删除有未提交更改的 worktree)');
|
|
185
|
+
console.info(' --dir <path> 指定 worktree 目录路径 (相对于 git 根目录)');
|
|
186
|
+
console.info(' --base <ref> 创建新分支时的基准引用 (如 main, origin/main)');
|
|
187
|
+
console.info(' --editor <name> 创建后使用指定编辑器打开 (trae, cursor, code, none)');
|
|
188
|
+
console.info(' --no-editor 创建后不打开编辑器');
|
|
189
|
+
console.info(' --no-install 创建后不自动安装依赖');
|
|
110
190
|
console.info('');
|
|
111
191
|
console.info('可用配置 key: baseDir, openCommand, editorCommand');
|
|
192
|
+
console.info('');
|
|
193
|
+
console.info('非交互示例:');
|
|
194
|
+
console.info(' wtree list --json');
|
|
195
|
+
console.info(' wtree create feat/x --yes --no-editor --no-install --json');
|
|
196
|
+
console.info(' wtree create feat/new --base main --yes --dir worktrees/feat-new --json');
|
|
197
|
+
console.info(' wtree delete feat/old --yes --force --json');
|
|
112
198
|
}
|
|
113
199
|
function resolveWorktree(rootDir, key) {
|
|
114
200
|
const items = listWorktrees(rootDir);
|
|
@@ -258,6 +344,10 @@ async function pruneWorktrees(rootDir) {
|
|
|
258
344
|
}
|
|
259
345
|
async function main() {
|
|
260
346
|
const { flags, positional } = parseArgs(process.argv.slice(2));
|
|
347
|
+
if (flags.version || positional[0] === 'version') {
|
|
348
|
+
console.info(getVersion());
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
261
351
|
const cwd = flags.repo ? path.resolve(flags.repo) : process.cwd();
|
|
262
352
|
const rootDir = getRepoRoot(cwd);
|
|
263
353
|
if (flags.ui) {
|
|
@@ -276,18 +366,20 @@ async function main() {
|
|
|
276
366
|
process.on('SIGTERM', close);
|
|
277
367
|
return;
|
|
278
368
|
}
|
|
279
|
-
|
|
369
|
+
if (!flags.json) {
|
|
370
|
+
console.info(chalk.blue(`检测到git repo根目录 ${rootDir},将在这里运行git命令`));
|
|
371
|
+
}
|
|
280
372
|
const { command, rest } = parseCommand(positional);
|
|
281
373
|
if (command === 'list') {
|
|
282
|
-
printWorktreeList(rootDir);
|
|
374
|
+
printWorktreeList(rootDir, flags.json);
|
|
283
375
|
return;
|
|
284
376
|
}
|
|
285
377
|
if (command === 'create') {
|
|
286
|
-
await createWorktree({ rootDir }, rest[0]);
|
|
378
|
+
await createWorktree({ rootDir, flags }, rest[0]);
|
|
287
379
|
return;
|
|
288
380
|
}
|
|
289
381
|
if (command === 'delete') {
|
|
290
|
-
await deleteWorktree({ rootDir });
|
|
382
|
+
await deleteWorktree({ rootDir, flags }, rest);
|
|
291
383
|
return;
|
|
292
384
|
}
|
|
293
385
|
if (command === 'open') {
|
|
@@ -329,7 +421,7 @@ async function main() {
|
|
|
329
421
|
}
|
|
330
422
|
const directBranch = rest[0];
|
|
331
423
|
const action = await getUserAction(directBranch);
|
|
332
|
-
const ctx = { rootDir };
|
|
424
|
+
const ctx = { rootDir, flags };
|
|
333
425
|
if (action === 'create') {
|
|
334
426
|
await createWorktree(ctx, directBranch);
|
|
335
427
|
}
|
|
@@ -374,21 +466,28 @@ async function getUserAction(directBranch) {
|
|
|
374
466
|
return action;
|
|
375
467
|
}
|
|
376
468
|
async function createWorktree(ctx, directBranch) {
|
|
377
|
-
const { rootDir } = ctx;
|
|
469
|
+
const { rootDir, flags } = ctx;
|
|
378
470
|
const defaultBranch = git(rootDir, ['symbolic-ref', '--short', 'refs/remotes/origin/HEAD']).stdout
|
|
379
471
|
.replace(/^origin\//, '')
|
|
380
472
|
.trim() || 'master';
|
|
381
473
|
const { sourceType, selection } = await selectSource(rootDir, directBranch, defaultBranch);
|
|
382
|
-
const { targetBranch, baseRef, isNewBranch } = await resolveBranchInfo(rootDir, sourceType, selection, directBranch, defaultBranch);
|
|
383
|
-
const { targetDir, dirName } = await selectTargetDir(rootDir, targetBranch);
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
474
|
+
const { targetBranch, baseRef, isNewBranch } = await resolveBranchInfo(rootDir, sourceType, selection, directBranch, defaultBranch, flags);
|
|
475
|
+
const { targetDir, dirName } = await selectTargetDir(rootDir, targetBranch, flags);
|
|
476
|
+
if (!flags.json) {
|
|
477
|
+
console.info(chalk.green(`\n准备创建 Worktree:`));
|
|
478
|
+
console.info(` 分支: ${targetBranch}`);
|
|
479
|
+
console.info(` 目录: ${targetDir}`);
|
|
480
|
+
console.info(` 来源: ${baseRef || 'Existing Local'}`);
|
|
481
|
+
}
|
|
388
482
|
await createGitWorktree(rootDir, targetDir, targetBranch, baseRef, isNewBranch, sourceType, defaultBranch);
|
|
389
483
|
await setupWorktreeEnv(rootDir, targetDir, dirName);
|
|
390
|
-
await installDependencies(targetDir);
|
|
391
|
-
await openInIDE(targetDir);
|
|
484
|
+
await installDependencies(targetDir, flags.noInstall);
|
|
485
|
+
await openInIDE(targetDir, flags);
|
|
486
|
+
if (flags.json) {
|
|
487
|
+
const items = listWorktrees(rootDir);
|
|
488
|
+
const created = items.find(x => path.resolve(x.path) === path.resolve(targetDir));
|
|
489
|
+
console.info(JSON.stringify({ ok: true, data: created || null }));
|
|
490
|
+
}
|
|
392
491
|
}
|
|
393
492
|
async function selectSource(rootDir, directBranch, defaultBranch) {
|
|
394
493
|
let sourceType;
|
|
@@ -430,7 +529,7 @@ async function selectSource(rootDir, directBranch, defaultBranch) {
|
|
|
430
529
|
}
|
|
431
530
|
return { sourceType, selection };
|
|
432
531
|
}
|
|
433
|
-
async function resolveBranchInfo(rootDir, sourceType, selection, directBranch, defaultBranch) {
|
|
532
|
+
async function resolveBranchInfo(rootDir, sourceType, selection, directBranch, defaultBranch, flags) {
|
|
434
533
|
let targetBranch = '';
|
|
435
534
|
let baseRef = '';
|
|
436
535
|
let isNewBranch = false;
|
|
@@ -470,20 +569,26 @@ async function resolveBranchInfo(rootDir, sourceType, selection, directBranch, d
|
|
|
470
569
|
isNewBranch = true;
|
|
471
570
|
}
|
|
472
571
|
else {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
type: 'confirm',
|
|
476
|
-
name: 'createNew',
|
|
477
|
-
message: `分支 ${targetBranch} 不存在。是否基于 ${defaultBranch} 创建新分支?`,
|
|
478
|
-
default: true,
|
|
479
|
-
},
|
|
480
|
-
]);
|
|
481
|
-
if (createNew) {
|
|
482
|
-
baseRef = defaultBranch;
|
|
572
|
+
if (flags.yes) {
|
|
573
|
+
baseRef = flags.base || defaultBranch;
|
|
483
574
|
isNewBranch = true;
|
|
484
575
|
}
|
|
485
576
|
else {
|
|
486
|
-
|
|
577
|
+
const { createNew } = await inquirer.prompt([
|
|
578
|
+
{
|
|
579
|
+
type: 'confirm',
|
|
580
|
+
name: 'createNew',
|
|
581
|
+
message: `分支 ${targetBranch} 不存在。是否基于 ${defaultBranch} 创建新分支?`,
|
|
582
|
+
default: true,
|
|
583
|
+
},
|
|
584
|
+
]);
|
|
585
|
+
if (createNew) {
|
|
586
|
+
baseRef = defaultBranch;
|
|
587
|
+
isNewBranch = true;
|
|
588
|
+
}
|
|
589
|
+
else {
|
|
590
|
+
process.exit(1);
|
|
591
|
+
}
|
|
487
592
|
}
|
|
488
593
|
}
|
|
489
594
|
}
|
|
@@ -539,16 +644,26 @@ async function resolveBranchInfo(rootDir, sourceType, selection, directBranch, d
|
|
|
539
644
|
}
|
|
540
645
|
return { targetBranch, baseRef, isNewBranch };
|
|
541
646
|
}
|
|
542
|
-
async function selectTargetDir(rootDir, targetBranch) {
|
|
647
|
+
async function selectTargetDir(rootDir, targetBranch, flags) {
|
|
543
648
|
const defaultDirName = `worktrees/${targetBranch.split('/').join('-')}`;
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
649
|
+
let dirName;
|
|
650
|
+
if (flags.dir) {
|
|
651
|
+
dirName = flags.dir;
|
|
652
|
+
}
|
|
653
|
+
else if (flags.yes) {
|
|
654
|
+
dirName = defaultDirName;
|
|
655
|
+
}
|
|
656
|
+
else {
|
|
657
|
+
const result = await inquirer.prompt([
|
|
658
|
+
{
|
|
659
|
+
type: 'input',
|
|
660
|
+
name: 'dirName',
|
|
661
|
+
message: `请输入 Worktree 目录路径 (相对于 Git 根目录, 默认: ${defaultDirName}):`,
|
|
662
|
+
default: defaultDirName,
|
|
663
|
+
},
|
|
664
|
+
]);
|
|
665
|
+
dirName = result.dirName;
|
|
666
|
+
}
|
|
552
667
|
const targetDir = path.resolve(rootDir, dirName);
|
|
553
668
|
if (fs.existsSync(targetDir)) {
|
|
554
669
|
console.error(chalk.red(`目录 ${targetDir} 已存在!`));
|
|
@@ -594,7 +709,9 @@ async function setupWorktreeEnv(rootDir, targetDir, dirName) {
|
|
|
594
709
|
}
|
|
595
710
|
}
|
|
596
711
|
}
|
|
597
|
-
async function installDependencies(targetDir) {
|
|
712
|
+
async function installDependencies(targetDir, skip = false) {
|
|
713
|
+
if (skip)
|
|
714
|
+
return;
|
|
598
715
|
if (!fs.existsSync(path.join(targetDir, 'package.json')))
|
|
599
716
|
return;
|
|
600
717
|
try {
|
|
@@ -615,7 +732,20 @@ function hasCommand(cmd) {
|
|
|
615
732
|
return false;
|
|
616
733
|
}
|
|
617
734
|
}
|
|
618
|
-
async function openInIDE(targetDir) {
|
|
735
|
+
async function openInIDE(targetDir, flags) {
|
|
736
|
+
if (flags.noEditor)
|
|
737
|
+
return;
|
|
738
|
+
if (flags.editor !== undefined) {
|
|
739
|
+
if (flags.editor === 'none' || flags.editor === '')
|
|
740
|
+
return;
|
|
741
|
+
try {
|
|
742
|
+
execSync(`${flags.editor} "${targetDir}"`, { stdio: 'ignore' });
|
|
743
|
+
}
|
|
744
|
+
catch (e) {
|
|
745
|
+
void e;
|
|
746
|
+
}
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
619
749
|
const editors = [];
|
|
620
750
|
if (hasCommand('trae'))
|
|
621
751
|
editors.push({ name: `在 Trae 中打开 (trae ${targetDir})`, value: 'trae' });
|
|
@@ -644,34 +774,63 @@ async function openInIDE(targetDir) {
|
|
|
644
774
|
void e;
|
|
645
775
|
}
|
|
646
776
|
}
|
|
647
|
-
async function deleteWorktree(ctx) {
|
|
648
|
-
const
|
|
649
|
-
const
|
|
777
|
+
async function deleteWorktree(ctx, targets = []) {
|
|
778
|
+
const { rootDir, flags } = ctx;
|
|
779
|
+
const worktrees = getWorktreeList(rootDir);
|
|
780
|
+
const choices = getDeletableWorktrees(rootDir, worktrees);
|
|
650
781
|
if (choices.length === 0) {
|
|
651
|
-
|
|
782
|
+
if (flags.json) {
|
|
783
|
+
console.info(JSON.stringify({ ok: true, data: [], message: 'No deletable worktrees' }));
|
|
784
|
+
}
|
|
785
|
+
else {
|
|
786
|
+
console.warn(chalk.yellow('没有可删除的 Worktree (除了主 Worktree)'));
|
|
787
|
+
}
|
|
652
788
|
return;
|
|
653
789
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
790
|
+
let targetPaths;
|
|
791
|
+
if (targets.length > 0) {
|
|
792
|
+
// Non-interactive: resolve each target to a worktree path
|
|
793
|
+
targetPaths = [];
|
|
794
|
+
for (const key of targets) {
|
|
795
|
+
const wt = resolveWorktree(rootDir, key);
|
|
796
|
+
if (!wt) {
|
|
797
|
+
console.error(chalk.red(`未找到 worktree: ${key}`));
|
|
798
|
+
process.exit(1);
|
|
799
|
+
}
|
|
800
|
+
if (path.resolve(wt.path) === path.resolve(rootDir)) {
|
|
801
|
+
console.error(chalk.red(`不能删除主 worktree: ${key}`));
|
|
802
|
+
process.exit(1);
|
|
803
|
+
}
|
|
804
|
+
targetPaths.push(wt.path);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
else {
|
|
808
|
+
// Interactive: checkbox prompt
|
|
809
|
+
const result = await inquirer.prompt([
|
|
810
|
+
{
|
|
811
|
+
type: 'checkbox',
|
|
812
|
+
name: 'targetPaths',
|
|
813
|
+
message: '请选择要删除的 Worktree:',
|
|
814
|
+
choices,
|
|
815
|
+
validate: (answer) => (answer.length > 0 ? true : '请至少选择一个'),
|
|
816
|
+
},
|
|
817
|
+
]);
|
|
818
|
+
targetPaths = result.targetPaths;
|
|
819
|
+
}
|
|
820
|
+
if (!flags.yes) {
|
|
821
|
+
const { confirmDelete } = await inquirer.prompt([
|
|
822
|
+
{
|
|
823
|
+
type: 'confirm',
|
|
824
|
+
name: 'confirmDelete',
|
|
825
|
+
message: `确定要删除这 ${targetPaths.length} 个 Worktree 吗?`,
|
|
826
|
+
default: false,
|
|
827
|
+
},
|
|
828
|
+
]);
|
|
829
|
+
if (!confirmDelete)
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
673
832
|
for (const targetPath of targetPaths) {
|
|
674
|
-
await deleteSingleWorktree(
|
|
833
|
+
await deleteSingleWorktree(rootDir, targetPath, flags);
|
|
675
834
|
}
|
|
676
835
|
}
|
|
677
836
|
function getWorktreeList(rootDir) {
|
|
@@ -690,29 +849,55 @@ function getDeletableWorktrees(rootDir, worktrees) {
|
|
|
690
849
|
return { name: `${wt.branch || 'HEAD'} (${relativePath})`, value: wt.path };
|
|
691
850
|
});
|
|
692
851
|
}
|
|
693
|
-
async function deleteSingleWorktree(rootDir, targetPath) {
|
|
852
|
+
async function deleteSingleWorktree(rootDir, targetPath, flags) {
|
|
694
853
|
try {
|
|
695
854
|
gitOrThrow(rootDir, ['worktree', 'remove', targetPath], 'WORKTREE_REMOVE');
|
|
696
|
-
|
|
855
|
+
if (flags.json) {
|
|
856
|
+
console.info(JSON.stringify({ ok: true, removed: targetPath }));
|
|
857
|
+
}
|
|
858
|
+
else {
|
|
859
|
+
console.info(chalk.green(`成功删除: ${targetPath}`));
|
|
860
|
+
}
|
|
697
861
|
}
|
|
698
862
|
catch (e) {
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
863
|
+
if (flags.force) {
|
|
864
|
+
try {
|
|
865
|
+
gitOrThrow(rootDir, ['worktree', 'remove', '--force', targetPath], 'WORKTREE_REMOVE_FORCE');
|
|
866
|
+
if (flags.json) {
|
|
867
|
+
console.info(JSON.stringify({ ok: true, removed: targetPath, forced: true }));
|
|
868
|
+
}
|
|
869
|
+
else {
|
|
870
|
+
console.info(chalk.green(`成功强制删除: ${targetPath}`));
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
catch (forceErr) {
|
|
874
|
+
if (flags.json) {
|
|
875
|
+
console.error(JSON.stringify({ ok: false, error: errMsg(forceErr), path: targetPath }));
|
|
876
|
+
}
|
|
877
|
+
else {
|
|
878
|
+
console.error(chalk.red(`强制删除也失败了: ${errMsg(forceErr)}`));
|
|
879
|
+
}
|
|
880
|
+
}
|
|
713
881
|
}
|
|
714
|
-
|
|
715
|
-
console.error(chalk.red(
|
|
882
|
+
else {
|
|
883
|
+
console.error(chalk.red(`删除失败: ${errMsg(e)}`));
|
|
884
|
+
const { force } = await inquirer.prompt([
|
|
885
|
+
{
|
|
886
|
+
type: 'confirm',
|
|
887
|
+
name: 'force',
|
|
888
|
+
message: '删除失败 (可能有未提交的更改). 强制删除吗?',
|
|
889
|
+
default: false,
|
|
890
|
+
},
|
|
891
|
+
]);
|
|
892
|
+
if (!force)
|
|
893
|
+
return;
|
|
894
|
+
try {
|
|
895
|
+
gitOrThrow(rootDir, ['worktree', 'remove', '--force', targetPath], 'WORKTREE_REMOVE_FORCE');
|
|
896
|
+
console.info(chalk.green(`成功强制删除: ${targetPath}`));
|
|
897
|
+
}
|
|
898
|
+
catch (forceErr) {
|
|
899
|
+
console.error(chalk.red(`强制删除也失败了: ${errMsg(forceErr)}`));
|
|
900
|
+
}
|
|
716
901
|
}
|
|
717
902
|
}
|
|
718
903
|
}
|
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fatdoge/wtree",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.1
|
|
4
|
+
"version": "0.2.1",
|
|
5
5
|
"description": "CLI + UI tool for managing git worktrees",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"git",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"api",
|
|
31
31
|
"src",
|
|
32
32
|
"shared",
|
|
33
|
+
"skills",
|
|
33
34
|
"index.html",
|
|
34
35
|
"vite.config.ts",
|
|
35
36
|
"tailwind.config.js",
|
|
@@ -37,6 +38,19 @@
|
|
|
37
38
|
"README.md",
|
|
38
39
|
"LICENSE"
|
|
39
40
|
],
|
|
41
|
+
"scripts": {
|
|
42
|
+
"client:dev": "vite",
|
|
43
|
+
"build:ui": "vite build",
|
|
44
|
+
"build:cli": "tsc -p tsconfig.node.json",
|
|
45
|
+
"build": "pnpm run build:cli && pnpm run build:ui",
|
|
46
|
+
"lint": "eslint .",
|
|
47
|
+
"preview": "vite preview",
|
|
48
|
+
"check": "tsc --noEmit && tsc -p tsconfig.node.json --noEmit",
|
|
49
|
+
"server:dev": "nodemon",
|
|
50
|
+
"dev": "concurrently \"pnpm run client:dev\" \"pnpm run server:dev\"",
|
|
51
|
+
"wtree": "tsx api/cli/wtree.ts",
|
|
52
|
+
"test": "vitest run"
|
|
53
|
+
},
|
|
40
54
|
"dependencies": {
|
|
41
55
|
"@vitejs/plugin-react": "^4.4.1",
|
|
42
56
|
"autoprefixer": "^10.4.21",
|
|
@@ -82,18 +96,5 @@
|
|
|
82
96
|
"typescript": "~5.8.3",
|
|
83
97
|
"typescript-eslint": "^8.30.1",
|
|
84
98
|
"vitest": "^2.1.9"
|
|
85
|
-
},
|
|
86
|
-
"scripts": {
|
|
87
|
-
"client:dev": "vite",
|
|
88
|
-
"build:ui": "vite build",
|
|
89
|
-
"build:cli": "tsc -p tsconfig.node.json",
|
|
90
|
-
"build": "pnpm run build:cli && pnpm run build:ui",
|
|
91
|
-
"lint": "eslint .",
|
|
92
|
-
"preview": "vite preview",
|
|
93
|
-
"check": "tsc --noEmit && tsc -p tsconfig.node.json --noEmit",
|
|
94
|
-
"server:dev": "nodemon",
|
|
95
|
-
"dev": "concurrently \"pnpm run client:dev\" \"pnpm run server:dev\"",
|
|
96
|
-
"wtree": "tsx api/cli/wtree.ts",
|
|
97
|
-
"test": "vitest run"
|
|
98
99
|
}
|
|
99
|
-
}
|
|
100
|
+
}
|