clawt 3.4.2 → 3.4.3
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/index.js +72 -53
- package/dist/postinstall.js +2 -0
- package/package.json +1 -1
- package/src/commands/cover-validate.ts +2 -4
- package/src/commands/create.ts +2 -2
- package/src/commands/home.ts +2 -5
- package/src/commands/init.ts +7 -3
- package/src/commands/list.ts +2 -2
- package/src/commands/merge.ts +4 -2
- package/src/commands/remove.ts +2 -4
- package/src/commands/reset.ts +2 -4
- package/src/commands/resume.ts +2 -2
- package/src/commands/run.ts +2 -2
- package/src/commands/status.ts +2 -2
- package/src/commands/sync.ts +2 -4
- package/src/commands/validate.ts +3 -7
- package/src/constants/messages/common.ts +2 -0
- package/src/utils/index.ts +1 -1
- package/src/utils/validation.ts +50 -0
- package/tests/unit/commands/cover-validate.test.ts +1 -1
- package/tests/unit/commands/create.test.ts +5 -5
- package/tests/unit/commands/init.test.ts +2 -1
- package/tests/unit/commands/list.test.ts +5 -5
- package/tests/unit/commands/merge.test.ts +1 -1
- package/tests/unit/commands/remove.test.ts +3 -3
- package/tests/unit/commands/reset.test.ts +1 -1
- package/tests/unit/commands/resume.test.ts +5 -5
- package/tests/unit/commands/run.test.ts +1 -1
- package/tests/unit/commands/status.test.ts +1 -1
- package/tests/unit/commands/sync.test.ts +1 -1
- package/tests/unit/commands/validate.test.ts +1 -1
- package/tests/unit/utils/validation.test.ts +43 -1
package/dist/index.js
CHANGED
|
@@ -30,6 +30,8 @@ var COMMON_MESSAGES = {
|
|
|
30
30
|
GIT_NOT_INSTALLED: "Git \u672A\u5B89\u88C5\u6216\u4E0D\u5728 PATH \u4E2D\uFF0C\u8BF7\u5148\u5B89\u88C5 Git",
|
|
31
31
|
/** Claude Code CLI 未安装 */
|
|
32
32
|
CLAUDE_NOT_INSTALLED: "Claude Code CLI \u672A\u5B89\u88C5\uFF0C\u8BF7\u5148\u5B89\u88C5\uFF1Anpm install -g @anthropic-ai/claude-code",
|
|
33
|
+
/** HEAD 不存在(仓库无任何 commit) */
|
|
34
|
+
HEAD_NOT_FOUND: "\u5F53\u524D\u4ED3\u5E93\u5C1A\u672A\u521B\u5EFA\u4EFB\u4F55\u63D0\u4EA4\uFF0C\u8BF7\u5148\u6267\u884C git commit \u521B\u5EFA\u9996\u6B21\u63D0\u4EA4\u540E\u518D\u4F7F\u7528 clawt",
|
|
33
35
|
/** 分支已存在 */
|
|
34
36
|
BRANCH_EXISTS: (name) => `\u5206\u652F ${name} \u5DF2\u5B58\u5728\uFF0C\u65E0\u6CD5\u521B\u5EFA`,
|
|
35
37
|
/** 分支名清理后为空 */
|
|
@@ -1229,31 +1231,9 @@ function validateBranchesNotExist(branchNames) {
|
|
|
1229
1231
|
}
|
|
1230
1232
|
}
|
|
1231
1233
|
|
|
1232
|
-
// src/utils/
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
const gitCommonDir = getGitCommonDir();
|
|
1236
|
-
if (gitCommonDir !== ".git") {
|
|
1237
|
-
throw new ClawtError(MESSAGES.NOT_MAIN_WORKTREE);
|
|
1238
|
-
}
|
|
1239
|
-
} catch (error) {
|
|
1240
|
-
if (error instanceof ClawtError) {
|
|
1241
|
-
throw error;
|
|
1242
|
-
}
|
|
1243
|
-
throw new ClawtError(MESSAGES.NOT_MAIN_WORKTREE);
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1246
|
-
function validateClaudeCodeInstalled() {
|
|
1247
|
-
try {
|
|
1248
|
-
execCommand("claude --version");
|
|
1249
|
-
} catch {
|
|
1250
|
-
throw new ClawtError(MESSAGES.CLAUDE_NOT_INSTALLED);
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
// src/utils/worktree.ts
|
|
1255
|
-
import { join as join4 } from "path";
|
|
1256
|
-
import { existsSync as existsSync4, readdirSync as readdirSync2 } from "fs";
|
|
1234
|
+
// src/utils/project-config.ts
|
|
1235
|
+
import { existsSync as existsSync3, readFileSync, writeFileSync } from "fs";
|
|
1236
|
+
import { join as join3 } from "path";
|
|
1257
1237
|
|
|
1258
1238
|
// src/utils/fs.ts
|
|
1259
1239
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, rmdirSync, statSync } from "fs";
|
|
@@ -1294,12 +1274,7 @@ function calculateDirSize(dirPath) {
|
|
|
1294
1274
|
return totalSize;
|
|
1295
1275
|
}
|
|
1296
1276
|
|
|
1297
|
-
// src/utils/validate-branch.ts
|
|
1298
|
-
import Enquirer from "enquirer";
|
|
1299
|
-
|
|
1300
1277
|
// src/utils/project-config.ts
|
|
1301
|
-
import { existsSync as existsSync3, readFileSync, writeFileSync } from "fs";
|
|
1302
|
-
import { join as join3 } from "path";
|
|
1303
1278
|
function getProjectConfigPath(projectName) {
|
|
1304
1279
|
return join3(PROJECTS_CONFIG_DIR, projectName, "config.json");
|
|
1305
1280
|
}
|
|
@@ -1364,7 +1339,55 @@ function getValidateRunCommand() {
|
|
|
1364
1339
|
return config2?.validateRunCommand || void 0;
|
|
1365
1340
|
}
|
|
1366
1341
|
|
|
1342
|
+
// src/utils/validation.ts
|
|
1343
|
+
function validateMainWorktree() {
|
|
1344
|
+
try {
|
|
1345
|
+
const gitCommonDir = getGitCommonDir();
|
|
1346
|
+
if (gitCommonDir !== ".git") {
|
|
1347
|
+
throw new ClawtError(MESSAGES.NOT_MAIN_WORKTREE);
|
|
1348
|
+
}
|
|
1349
|
+
} catch (error) {
|
|
1350
|
+
if (error instanceof ClawtError) {
|
|
1351
|
+
throw error;
|
|
1352
|
+
}
|
|
1353
|
+
throw new ClawtError(MESSAGES.NOT_MAIN_WORKTREE);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
function validateClaudeCodeInstalled() {
|
|
1357
|
+
try {
|
|
1358
|
+
execCommand("claude --version");
|
|
1359
|
+
} catch {
|
|
1360
|
+
throw new ClawtError(MESSAGES.CLAUDE_NOT_INSTALLED);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
function validateHeadExists() {
|
|
1364
|
+
try {
|
|
1365
|
+
execCommand("git rev-parse --verify HEAD");
|
|
1366
|
+
} catch {
|
|
1367
|
+
throw new ClawtError(MESSAGES.HEAD_NOT_FOUND);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
function runPreChecks(options) {
|
|
1371
|
+
if (options.mainWorktree) {
|
|
1372
|
+
validateMainWorktree();
|
|
1373
|
+
}
|
|
1374
|
+
if (options.headExists) {
|
|
1375
|
+
validateHeadExists();
|
|
1376
|
+
}
|
|
1377
|
+
if (options.projectConfig) {
|
|
1378
|
+
requireProjectConfig();
|
|
1379
|
+
}
|
|
1380
|
+
if (options.branchExists) {
|
|
1381
|
+
guardMainWorkBranchExists();
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
// src/utils/worktree.ts
|
|
1386
|
+
import { join as join4 } from "path";
|
|
1387
|
+
import { existsSync as existsSync4, readdirSync as readdirSync2 } from "fs";
|
|
1388
|
+
|
|
1367
1389
|
// src/utils/validate-branch.ts
|
|
1390
|
+
import Enquirer from "enquirer";
|
|
1368
1391
|
function getValidateBranchName(branchName) {
|
|
1369
1392
|
return `${VALIDATE_BRANCH_PREFIX}${branchName}`;
|
|
1370
1393
|
}
|
|
@@ -3795,7 +3818,7 @@ function registerListCommand(program2) {
|
|
|
3795
3818
|
});
|
|
3796
3819
|
}
|
|
3797
3820
|
function handleList(options) {
|
|
3798
|
-
|
|
3821
|
+
runPreChecks({ mainWorktree: true });
|
|
3799
3822
|
const projectName = getProjectName();
|
|
3800
3823
|
const worktrees = getProjectWorktrees();
|
|
3801
3824
|
logger.info(`list \u547D\u4EE4\u6267\u884C\uFF0C\u9879\u76EE: ${projectName}\uFF0C\u5171 ${worktrees.length} \u4E2A worktree`);
|
|
@@ -3845,7 +3868,7 @@ function registerCreateCommand(program2) {
|
|
|
3845
3868
|
});
|
|
3846
3869
|
}
|
|
3847
3870
|
async function handleCreate(options) {
|
|
3848
|
-
|
|
3871
|
+
runPreChecks({ mainWorktree: true, headExists: true });
|
|
3849
3872
|
await guardMainWorkBranch();
|
|
3850
3873
|
await ensureOnMainWorkBranch();
|
|
3851
3874
|
const count = Number(options.number);
|
|
@@ -3881,8 +3904,7 @@ function registerRemoveCommand(program2) {
|
|
|
3881
3904
|
});
|
|
3882
3905
|
}
|
|
3883
3906
|
async function handleRemove(options) {
|
|
3884
|
-
|
|
3885
|
-
requireProjectConfig();
|
|
3907
|
+
runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
|
|
3886
3908
|
const projectName = getProjectName();
|
|
3887
3909
|
logger.info(`remove \u547D\u4EE4\u6267\u884C\uFF0C\u9879\u76EE: ${projectName}`);
|
|
3888
3910
|
const allWorktrees = getProjectWorktrees();
|
|
@@ -3988,7 +4010,7 @@ function handleDryRunFromFile(options) {
|
|
|
3988
4010
|
printDryRunPreview(branchNames, tasks, concurrency);
|
|
3989
4011
|
}
|
|
3990
4012
|
async function handleRun(options) {
|
|
3991
|
-
|
|
4013
|
+
runPreChecks({ mainWorktree: true, headExists: true });
|
|
3992
4014
|
if (!options.dryRun) {
|
|
3993
4015
|
await guardMainWorkBranch();
|
|
3994
4016
|
await ensureOnMainWorkBranch();
|
|
@@ -4053,7 +4075,7 @@ function registerResumeCommand(program2) {
|
|
|
4053
4075
|
});
|
|
4054
4076
|
}
|
|
4055
4077
|
async function handleResume(options) {
|
|
4056
|
-
|
|
4078
|
+
runPreChecks({ mainWorktree: true, headExists: true });
|
|
4057
4079
|
validateClaudeCodeInstalled();
|
|
4058
4080
|
logger.info(`resume \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F\u8FC7\u6EE4: ${options.branch ?? "(\u65E0)"}`);
|
|
4059
4081
|
const worktrees = getProjectWorktrees();
|
|
@@ -4154,8 +4176,7 @@ async function executeSyncForBranch(targetWorktreePath, branch) {
|
|
|
4154
4176
|
return { success: true, hasConflict: false };
|
|
4155
4177
|
}
|
|
4156
4178
|
async function handleSync(options) {
|
|
4157
|
-
|
|
4158
|
-
requireProjectConfig();
|
|
4179
|
+
runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
|
|
4159
4180
|
await guardMainWorkBranch();
|
|
4160
4181
|
logger.info(`sync \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch ?? "(\u672A\u6307\u5B9A)"}`);
|
|
4161
4182
|
const worktrees = getProjectWorktrees();
|
|
@@ -4189,8 +4210,7 @@ async function handlePatchApplyFailure(targetWorktreePath, branchName) {
|
|
|
4189
4210
|
const syncResult = await executeSyncForBranch(targetWorktreePath, branchName);
|
|
4190
4211
|
}
|
|
4191
4212
|
async function handleValidateClean(options) {
|
|
4192
|
-
|
|
4193
|
-
requireProjectConfig();
|
|
4213
|
+
runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
|
|
4194
4214
|
const projectName = getProjectName();
|
|
4195
4215
|
const mainWorktreePath = getGitTopLevel();
|
|
4196
4216
|
const worktrees = getProjectWorktrees();
|
|
@@ -4275,8 +4295,7 @@ async function handleValidate(options) {
|
|
|
4275
4295
|
await handleValidateClean(options);
|
|
4276
4296
|
return;
|
|
4277
4297
|
}
|
|
4278
|
-
|
|
4279
|
-
requireProjectConfig();
|
|
4298
|
+
runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
|
|
4280
4299
|
const projectName = getProjectName();
|
|
4281
4300
|
const mainWorktreePath = getGitTopLevel();
|
|
4282
4301
|
const worktrees = getProjectWorktrees();
|
|
@@ -4341,8 +4360,7 @@ function computeIncrementalPatch(snapshotTreeHash, mainWorktreePath) {
|
|
|
4341
4360
|
return { patch, currentTreeHash };
|
|
4342
4361
|
}
|
|
4343
4362
|
async function handleCoverValidate() {
|
|
4344
|
-
|
|
4345
|
-
requireProjectConfig();
|
|
4363
|
+
runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
|
|
4346
4364
|
const projectName = getProjectName();
|
|
4347
4365
|
const mainWorktreePath = getGitTopLevel();
|
|
4348
4366
|
const currentBranch = getCurrentBranch(mainWorktreePath);
|
|
@@ -4421,7 +4439,8 @@ function cleanupWorktreeAndBranch(worktreePath, branchName) {
|
|
|
4421
4439
|
printSuccess(MESSAGES.WORKTREE_CLEANED(branchName));
|
|
4422
4440
|
}
|
|
4423
4441
|
async function handleMerge(options) {
|
|
4424
|
-
|
|
4442
|
+
runPreChecks({ mainWorktree: true, headExists: true });
|
|
4443
|
+
await guardMainWorkBranch();
|
|
4425
4444
|
await guardMainWorkBranch();
|
|
4426
4445
|
const mainWorktreePath = getGitTopLevel();
|
|
4427
4446
|
await ensureOnMainWorkBranch(mainWorktreePath);
|
|
@@ -4583,8 +4602,7 @@ function registerResetCommand(program2) {
|
|
|
4583
4602
|
});
|
|
4584
4603
|
}
|
|
4585
4604
|
async function handleReset() {
|
|
4586
|
-
|
|
4587
|
-
requireProjectConfig();
|
|
4605
|
+
runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
|
|
4588
4606
|
const mainWorktreePath = getGitTopLevel();
|
|
4589
4607
|
logger.info("reset \u547D\u4EE4\u6267\u884C");
|
|
4590
4608
|
if (!isWorkingDirClean(mainWorktreePath)) {
|
|
@@ -4614,7 +4632,7 @@ function registerStatusCommand(program2) {
|
|
|
4614
4632
|
});
|
|
4615
4633
|
}
|
|
4616
4634
|
async function handleStatus(options) {
|
|
4617
|
-
|
|
4635
|
+
runPreChecks({ mainWorktree: true, headExists: true });
|
|
4618
4636
|
if (options.interactive) {
|
|
4619
4637
|
const panel = new InteractivePanel(collectStatus);
|
|
4620
4638
|
await panel.start();
|
|
@@ -5294,7 +5312,7 @@ function registerInitCommand(program2) {
|
|
|
5294
5312
|
);
|
|
5295
5313
|
}
|
|
5296
5314
|
async function handleInitShow() {
|
|
5297
|
-
|
|
5315
|
+
runPreChecks({ mainWorktree: true, projectConfig: true });
|
|
5298
5316
|
const config2 = requireProjectConfig();
|
|
5299
5317
|
logger.info("init show \u547D\u4EE4\u6267\u884C\uFF0C\u8FDB\u5165\u4EA4\u4E92\u5F0F\u9879\u76EE\u914D\u7F6E");
|
|
5300
5318
|
const { key, newValue } = await interactiveConfigEditor(
|
|
@@ -5307,8 +5325,11 @@ async function handleInitShow() {
|
|
|
5307
5325
|
printSuccess(MESSAGES.INIT_SET_SUCCESS(key, String(newValue)));
|
|
5308
5326
|
}
|
|
5309
5327
|
async function handleInit(options) {
|
|
5310
|
-
|
|
5328
|
+
runPreChecks({ mainWorktree: true });
|
|
5311
5329
|
const existingConfig = loadProjectConfig();
|
|
5330
|
+
if (!options.branch) {
|
|
5331
|
+
validateHeadExists();
|
|
5332
|
+
}
|
|
5312
5333
|
const branchName = options.branch || getCurrentBranch();
|
|
5313
5334
|
logger.info(`init \u547D\u4EE4\u6267\u884C\uFF0C\u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}`);
|
|
5314
5335
|
saveProjectConfig({ clawtMainWorkBranch: branchName });
|
|
@@ -5326,9 +5347,7 @@ function registerHomeCommand(program2) {
|
|
|
5326
5347
|
});
|
|
5327
5348
|
}
|
|
5328
5349
|
async function handleHome() {
|
|
5329
|
-
|
|
5330
|
-
requireProjectConfig();
|
|
5331
|
-
guardMainWorkBranchExists();
|
|
5350
|
+
runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true, branchExists: true });
|
|
5332
5351
|
const mainBranch = getMainWorkBranch();
|
|
5333
5352
|
const currentBranch = getCurrentBranch();
|
|
5334
5353
|
if (currentBranch === mainBranch) {
|
package/dist/postinstall.js
CHANGED
|
@@ -21,6 +21,8 @@ var COMMON_MESSAGES = {
|
|
|
21
21
|
GIT_NOT_INSTALLED: "Git \u672A\u5B89\u88C5\u6216\u4E0D\u5728 PATH \u4E2D\uFF0C\u8BF7\u5148\u5B89\u88C5 Git",
|
|
22
22
|
/** Claude Code CLI 未安装 */
|
|
23
23
|
CLAUDE_NOT_INSTALLED: "Claude Code CLI \u672A\u5B89\u88C5\uFF0C\u8BF7\u5148\u5B89\u88C5\uFF1Anpm install -g @anthropic-ai/claude-code",
|
|
24
|
+
/** HEAD 不存在(仓库无任何 commit) */
|
|
25
|
+
HEAD_NOT_FOUND: "\u5F53\u524D\u4ED3\u5E93\u5C1A\u672A\u521B\u5EFA\u4EFB\u4F55\u63D0\u4EA4\uFF0C\u8BF7\u5148\u6267\u884C git commit \u521B\u5EFA\u9996\u6B21\u63D0\u4EA4\u540E\u518D\u4F7F\u7528 clawt",
|
|
24
26
|
/** 分支已存在 */
|
|
25
27
|
BRANCH_EXISTS: (name) => `\u5206\u652F ${name} \u5DF2\u5B58\u5728\uFF0C\u65E0\u6CD5\u521B\u5EFA`,
|
|
26
28
|
/** 分支名清理后为空 */
|
package/package.json
CHANGED
|
@@ -3,8 +3,7 @@ import { logger } from '../logger/index.js';
|
|
|
3
3
|
import { ClawtError } from '../errors/index.js';
|
|
4
4
|
import { MESSAGES, VALIDATE_BRANCH_PREFIX } from '../constants/index.js';
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
requireProjectConfig,
|
|
6
|
+
runPreChecks,
|
|
8
7
|
getProjectName,
|
|
9
8
|
getGitTopLevel,
|
|
10
9
|
getCurrentBranch,
|
|
@@ -98,8 +97,7 @@ export function computeIncrementalPatch(snapshotTreeHash: string, mainWorktreePa
|
|
|
98
97
|
*/
|
|
99
98
|
async function handleCoverValidate(): Promise<void> {
|
|
100
99
|
// 步骤 1:前置校验
|
|
101
|
-
|
|
102
|
-
requireProjectConfig();
|
|
100
|
+
runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
|
|
103
101
|
const projectName = getProjectName();
|
|
104
102
|
const mainWorktreePath = getGitTopLevel();
|
|
105
103
|
const currentBranch = getCurrentBranch(mainWorktreePath);
|
package/src/commands/create.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { ClawtError } from '../errors/index.js';
|
|
|
4
4
|
import { logger } from '../logger/index.js';
|
|
5
5
|
import type { CreateOptions } from '../types/index.js';
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
runPreChecks,
|
|
8
8
|
createWorktrees,
|
|
9
9
|
ensureOnMainWorkBranch,
|
|
10
10
|
getValidateBranchName,
|
|
@@ -34,7 +34,7 @@ export function registerCreateCommand(program: Command): void {
|
|
|
34
34
|
* @param {CreateOptions} options - 命令选项
|
|
35
35
|
*/
|
|
36
36
|
async function handleCreate(options: CreateOptions): Promise<void> {
|
|
37
|
-
|
|
37
|
+
runPreChecks({ mainWorktree: true, headExists: true });
|
|
38
38
|
|
|
39
39
|
await guardMainWorkBranch();
|
|
40
40
|
|
package/src/commands/home.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
2
|
import { MESSAGES } from '../constants/index.js';
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
requireProjectConfig,
|
|
4
|
+
runPreChecks,
|
|
6
5
|
ensureOnMainWorkBranch,
|
|
7
6
|
getCurrentBranch,
|
|
8
7
|
getMainWorkBranch,
|
|
@@ -28,9 +27,7 @@ export function registerHomeCommand(program: Command): void {
|
|
|
28
27
|
* 执行 home 命令:切换回主工作分支
|
|
29
28
|
*/
|
|
30
29
|
async function handleHome(): Promise<void> {
|
|
31
|
-
|
|
32
|
-
requireProjectConfig();
|
|
33
|
-
guardMainWorkBranchExists();
|
|
30
|
+
runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true, branchExists: true });
|
|
34
31
|
|
|
35
32
|
const mainBranch = getMainWorkBranch();
|
|
36
33
|
const currentBranch = getCurrentBranch();
|
package/src/commands/init.ts
CHANGED
|
@@ -4,7 +4,8 @@ import { logger } from '../logger/index.js';
|
|
|
4
4
|
import { MESSAGES, PROJECT_CONFIG_DEFINITIONS } from '../constants/index.js';
|
|
5
5
|
import type { InitOptions, ProjectConfig } from '../types/index.js';
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
runPreChecks,
|
|
8
|
+
validateHeadExists,
|
|
8
9
|
getCurrentBranch,
|
|
9
10
|
loadProjectConfig,
|
|
10
11
|
saveProjectConfig,
|
|
@@ -40,7 +41,7 @@ export function registerInitCommand(program: Command): void {
|
|
|
40
41
|
* 处理 init show 子命令:交互式面板展示和修改项目配置
|
|
41
42
|
*/
|
|
42
43
|
async function handleInitShow(): Promise<void> {
|
|
43
|
-
|
|
44
|
+
runPreChecks({ mainWorktree: true, projectConfig: true });
|
|
44
45
|
const config = requireProjectConfig();
|
|
45
46
|
|
|
46
47
|
logger.info('init show 命令执行,进入交互式项目配置');
|
|
@@ -66,11 +67,14 @@ async function handleInitShow(): Promise<void> {
|
|
|
66
67
|
* @param {InitOptions} options - 命令选项
|
|
67
68
|
*/
|
|
68
69
|
async function handleInit(options: InitOptions): Promise<void> {
|
|
69
|
-
|
|
70
|
+
runPreChecks({ mainWorktree: true });
|
|
70
71
|
|
|
71
72
|
const existingConfig = loadProjectConfig();
|
|
72
73
|
|
|
73
74
|
// 确定分支名:优先使用 -b 参数,否则使用当前分支
|
|
75
|
+
if (!options.branch) {
|
|
76
|
+
validateHeadExists();
|
|
77
|
+
}
|
|
74
78
|
const branchName = options.branch || getCurrentBranch();
|
|
75
79
|
|
|
76
80
|
logger.info(`init 命令执行,主工作分支: ${branchName}`);
|
package/src/commands/list.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { MESSAGES } from '../constants/index.js';
|
|
|
4
4
|
import { logger } from '../logger/index.js';
|
|
5
5
|
import type { ListOptions } from '../types/index.js';
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
runPreChecks,
|
|
8
8
|
getProjectName,
|
|
9
9
|
getProjectWorktrees,
|
|
10
10
|
getWorktreeStatus,
|
|
@@ -33,7 +33,7 @@ export function registerListCommand(program: Command): void {
|
|
|
33
33
|
* @param {ListOptions} options - 命令选项
|
|
34
34
|
*/
|
|
35
35
|
function handleList(options: ListOptions): void {
|
|
36
|
-
|
|
36
|
+
runPreChecks({ mainWorktree: true });
|
|
37
37
|
|
|
38
38
|
const projectName = getProjectName();
|
|
39
39
|
const worktrees = getProjectWorktrees();
|
package/src/commands/merge.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { ClawtError } from '../errors/index.js';
|
|
|
4
4
|
import { MESSAGES, AUTO_SAVE_COMMIT_MESSAGE } from '../constants/index.js';
|
|
5
5
|
import type { MergeOptions } from '../types/index.js';
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
runPreChecks,
|
|
8
8
|
getProjectName,
|
|
9
9
|
getGitTopLevel,
|
|
10
10
|
getProjectWorktrees,
|
|
@@ -136,7 +136,9 @@ function cleanupWorktreeAndBranch(worktreePath: string, branchName: string): voi
|
|
|
136
136
|
* @param {MergeOptions} options - 命令选项
|
|
137
137
|
*/
|
|
138
138
|
async function handleMerge(options: MergeOptions): Promise<void> {
|
|
139
|
-
|
|
139
|
+
runPreChecks({ mainWorktree: true, headExists: true });
|
|
140
|
+
|
|
141
|
+
await guardMainWorkBranch();
|
|
140
142
|
|
|
141
143
|
await guardMainWorkBranch();
|
|
142
144
|
|
package/src/commands/remove.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { ClawtError } from '../errors/index.js';
|
|
|
4
4
|
import { MESSAGES } from '../constants/index.js';
|
|
5
5
|
import type { RemoveOptions } from '../types/index.js';
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
runPreChecks,
|
|
8
8
|
getProjectName,
|
|
9
9
|
getProjectWorktreeDir,
|
|
10
10
|
getProjectWorktrees,
|
|
@@ -23,7 +23,6 @@ import {
|
|
|
23
23
|
resolveTargetWorktrees,
|
|
24
24
|
getValidateBranchName,
|
|
25
25
|
deleteValidateBranch,
|
|
26
|
-
requireProjectConfig,
|
|
27
26
|
getCurrentBranch,
|
|
28
27
|
} from '../utils/index.js';
|
|
29
28
|
import type { WorktreeMultiResolveMessages } from '../utils/index.js';
|
|
@@ -56,8 +55,7 @@ export function registerRemoveCommand(program: Command): void {
|
|
|
56
55
|
* @param {RemoveOptions} options - 命令选项
|
|
57
56
|
*/
|
|
58
57
|
async function handleRemove(options: RemoveOptions): Promise<void> {
|
|
59
|
-
|
|
60
|
-
requireProjectConfig();
|
|
58
|
+
runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
|
|
61
59
|
|
|
62
60
|
const projectName = getProjectName();
|
|
63
61
|
logger.info(`remove 命令执行,项目: ${projectName}`);
|
package/src/commands/reset.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { Command } from 'commander';
|
|
|
2
2
|
import { logger } from '../logger/index.js';
|
|
3
3
|
import { MESSAGES } from '../constants/index.js';
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
runPreChecks,
|
|
6
6
|
getGitTopLevel,
|
|
7
7
|
getConfigValue,
|
|
8
8
|
isWorkingDirClean,
|
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
confirmDestructiveAction,
|
|
12
12
|
printSuccess,
|
|
13
13
|
printInfo,
|
|
14
|
-
requireProjectConfig,
|
|
15
14
|
} from '../utils/index.js';
|
|
16
15
|
|
|
17
16
|
/**
|
|
@@ -31,8 +30,7 @@ export function registerResetCommand(program: Command): void {
|
|
|
31
30
|
* 执行 reset 命令:重置主 worktree 工作区和暂存区
|
|
32
31
|
*/
|
|
33
32
|
async function handleReset(): Promise<void> {
|
|
34
|
-
|
|
35
|
-
requireProjectConfig();
|
|
33
|
+
runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
|
|
36
34
|
|
|
37
35
|
const mainWorktreePath = getGitTopLevel();
|
|
38
36
|
logger.info('reset 命令执行');
|
package/src/commands/resume.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { MESSAGES } from '../constants/index.js';
|
|
|
4
4
|
import type { ResumeOptions } from '../types/index.js';
|
|
5
5
|
import type { WorktreeInfo } from '../types/index.js';
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
runPreChecks,
|
|
8
8
|
validateClaudeCodeInstalled,
|
|
9
9
|
getProjectWorktrees,
|
|
10
10
|
launchInteractiveClaude,
|
|
@@ -47,7 +47,7 @@ export function registerResumeCommand(program: Command): void {
|
|
|
47
47
|
* @param {ResumeOptions} options - 命令选项
|
|
48
48
|
*/
|
|
49
49
|
async function handleResume(options: ResumeOptions): Promise<void> {
|
|
50
|
-
|
|
50
|
+
runPreChecks({ mainWorktree: true, headExists: true });
|
|
51
51
|
validateClaudeCodeInstalled();
|
|
52
52
|
|
|
53
53
|
logger.info(`resume 命令执行,分支过滤: ${options.branch ?? '(无)'}`);
|
package/src/commands/run.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { ClawtError } from '../errors/index.js';
|
|
|
4
4
|
import { MESSAGES } from '../constants/index.js';
|
|
5
5
|
import type { RunOptions, WorktreeInfo } from '../types/index.js';
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
runPreChecks,
|
|
8
8
|
validateClaudeCodeInstalled,
|
|
9
9
|
createWorktrees,
|
|
10
10
|
createWorktreesByBranches,
|
|
@@ -120,7 +120,7 @@ function handleDryRunFromFile(options: RunOptions): void {
|
|
|
120
120
|
* @param {RunOptions} options - 命令选项
|
|
121
121
|
*/
|
|
122
122
|
async function handleRun(options: RunOptions): Promise<void> {
|
|
123
|
-
|
|
123
|
+
runPreChecks({ mainWorktree: true, headExists: true });
|
|
124
124
|
|
|
125
125
|
// dry-run 模式跳过项目配置前置校验
|
|
126
126
|
if (!options.dryRun) {
|
package/src/commands/status.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { MESSAGES, VALIDATE_BRANCH_PREFIX } from '../constants/index.js';
|
|
|
4
4
|
import { logger } from '../logger/index.js';
|
|
5
5
|
import type { StatusOptions, WorktreeDetailedStatus, MainWorktreeStatus, SnapshotSummary, StatusResult, WorktreeInfo } from '../types/index.js';
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
runPreChecks,
|
|
8
8
|
getProjectName,
|
|
9
9
|
getCurrentBranch,
|
|
10
10
|
isWorkingDirClean,
|
|
@@ -46,7 +46,7 @@ export function registerStatusCommand(program: Command): void {
|
|
|
46
46
|
* @param {StatusOptions} options - 命令选项
|
|
47
47
|
*/
|
|
48
48
|
async function handleStatus(options: StatusOptions): Promise<void> {
|
|
49
|
-
|
|
49
|
+
runPreChecks({ mainWorktree: true, headExists: true });
|
|
50
50
|
|
|
51
51
|
// 交互式面板模式
|
|
52
52
|
if (options.interactive) {
|
package/src/commands/sync.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { ClawtError } from '../errors/index.js';
|
|
|
4
4
|
import { MESSAGES, AUTO_SAVE_COMMIT_MESSAGE } from '../constants/index.js';
|
|
5
5
|
import type { SyncOptions } from '../types/index.js';
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
runPreChecks,
|
|
8
8
|
getGitTopLevel,
|
|
9
9
|
getProjectWorktrees,
|
|
10
10
|
isWorkingDirClean,
|
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
printInfo,
|
|
17
17
|
printWarning,
|
|
18
18
|
resolveTargetWorktree,
|
|
19
|
-
requireProjectConfig,
|
|
20
19
|
getMainWorkBranch,
|
|
21
20
|
rebuildValidateBranch,
|
|
22
21
|
getValidateBranchName,
|
|
@@ -128,8 +127,7 @@ export async function executeSyncForBranch(targetWorktreePath: string, branch: s
|
|
|
128
127
|
* @param {SyncOptions} options - 命令选项
|
|
129
128
|
*/
|
|
130
129
|
async function handleSync(options: SyncOptions): Promise<void> {
|
|
131
|
-
|
|
132
|
-
requireProjectConfig();
|
|
130
|
+
runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
|
|
133
131
|
await guardMainWorkBranch();
|
|
134
132
|
|
|
135
133
|
logger.info(`sync 命令执行,分支: ${options.branch ?? '(未指定)'}`);
|
package/src/commands/validate.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { MESSAGES } from '../constants/index.js';
|
|
|
4
4
|
import type { ValidateOptions } from '../types/index.js';
|
|
5
5
|
import { executeSyncForBranch } from './sync.js';
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
runPreChecks,
|
|
8
8
|
getProjectName,
|
|
9
9
|
getGitTopLevel,
|
|
10
10
|
getProjectWorktrees,
|
|
@@ -25,7 +25,6 @@ import {
|
|
|
25
25
|
printWarning,
|
|
26
26
|
printInfo,
|
|
27
27
|
resolveTargetWorktree,
|
|
28
|
-
requireProjectConfig,
|
|
29
28
|
ensureOnMainWorkBranch,
|
|
30
29
|
handleDirtyWorkingDir,
|
|
31
30
|
getValidateRunCommand,
|
|
@@ -98,9 +97,7 @@ async function handlePatchApplyFailure(targetWorktreePath: string, branchName: s
|
|
|
98
97
|
* @param {ValidateOptions} options - 命令选项
|
|
99
98
|
*/
|
|
100
99
|
async function handleValidateClean(options: ValidateOptions): Promise<void> {
|
|
101
|
-
|
|
102
|
-
// 显式前置校验:确保项目已初始化
|
|
103
|
-
requireProjectConfig();
|
|
100
|
+
runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
|
|
104
101
|
|
|
105
102
|
const projectName = getProjectName();
|
|
106
103
|
const mainWorktreePath = getGitTopLevel();
|
|
@@ -264,8 +261,7 @@ async function handleValidate(options: ValidateOptions): Promise<void> {
|
|
|
264
261
|
return;
|
|
265
262
|
}
|
|
266
263
|
|
|
267
|
-
|
|
268
|
-
requireProjectConfig();
|
|
264
|
+
runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
|
|
269
265
|
|
|
270
266
|
const projectName = getProjectName();
|
|
271
267
|
const mainWorktreePath = getGitTopLevel();
|
|
@@ -6,6 +6,8 @@ export const COMMON_MESSAGES = {
|
|
|
6
6
|
GIT_NOT_INSTALLED: 'Git 未安装或不在 PATH 中,请先安装 Git',
|
|
7
7
|
/** Claude Code CLI 未安装 */
|
|
8
8
|
CLAUDE_NOT_INSTALLED: 'Claude Code CLI 未安装,请先安装:npm install -g @anthropic-ai/claude-code',
|
|
9
|
+
/** HEAD 不存在(仓库无任何 commit) */
|
|
10
|
+
HEAD_NOT_FOUND: '当前仓库尚未创建任何提交,请先执行 git commit 创建首次提交后再使用 clawt',
|
|
9
11
|
/** 分支已存在 */
|
|
10
12
|
BRANCH_EXISTS: (name: string) => `分支 ${name} 已存在,无法创建`,
|
|
11
13
|
/** 分支名清理后为空 */
|
package/src/utils/index.ts
CHANGED
|
@@ -50,7 +50,7 @@ export {
|
|
|
50
50
|
createBranch,
|
|
51
51
|
} from './git.js';
|
|
52
52
|
export { sanitizeBranchName, generateBranchNames, validateBranchesNotExist } from './branch.js';
|
|
53
|
-
export { validateMainWorktree, validateGitInstalled, validateClaudeCodeInstalled } from './validation.js';
|
|
53
|
+
export { validateMainWorktree, validateGitInstalled, validateClaudeCodeInstalled, validateHeadExists, runPreChecks } from './validation.js';
|
|
54
54
|
export { createWorktrees, getProjectWorktrees, getProjectWorktreeDir, cleanupWorktrees, getWorktreeStatus, createWorktreesByBranches } from './worktree.js';
|
|
55
55
|
export { loadConfig, writeDefaultConfig, writeConfig, saveConfig, getConfigValue, ensureClawtDirs, parseConcurrency } from './config.js';
|
|
56
56
|
export { printSuccess, printError, printWarning, printInfo, printHint, printSeparator, printDoubleSeparator, confirmAction, confirmDestructiveAction, formatWorktreeStatus, isWorktreeIdle, formatDuration, formatRelativeTime, formatDiskSize, formatLocalISOString } from './formatter.js';
|
package/src/utils/validation.ts
CHANGED
|
@@ -2,6 +2,19 @@ import { MESSAGES } from '../constants/index.js';
|
|
|
2
2
|
import { ClawtError } from '../errors/index.js';
|
|
3
3
|
import { execCommand } from './shell.js';
|
|
4
4
|
import { getGitCommonDir } from './git.js';
|
|
5
|
+
import { requireProjectConfig, guardMainWorkBranchExists } from './project-config.js';
|
|
6
|
+
|
|
7
|
+
/** 统一前置校验选项 */
|
|
8
|
+
interface PreCheckOptions {
|
|
9
|
+
/** 校验是否在主 worktree 根目录 */
|
|
10
|
+
mainWorktree?: boolean;
|
|
11
|
+
/** 校验 HEAD 是否存在(仓库有至少一次 commit) */
|
|
12
|
+
headExists?: boolean;
|
|
13
|
+
/** 校验项目是否已初始化(配置文件存在) */
|
|
14
|
+
projectConfig?: boolean;
|
|
15
|
+
/** 校验配置的主工作分支是否存在 */
|
|
16
|
+
branchExists?: boolean;
|
|
17
|
+
}
|
|
5
18
|
|
|
6
19
|
/**
|
|
7
20
|
* 校验当前目录是否为主 worktree 的根目录
|
|
@@ -46,3 +59,40 @@ export function validateClaudeCodeInstalled(): void {
|
|
|
46
59
|
throw new ClawtError(MESSAGES.CLAUDE_NOT_INSTALLED);
|
|
47
60
|
}
|
|
48
61
|
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 校验 HEAD 是否存在(仓库是否有至少一次 commit)
|
|
65
|
+
* git init 后未做任何 commit 时,HEAD 不指向有效引用
|
|
66
|
+
* @throws {ClawtError} HEAD 不存在时抛出
|
|
67
|
+
*/
|
|
68
|
+
export function validateHeadExists(): void {
|
|
69
|
+
try {
|
|
70
|
+
execCommand('git rev-parse --verify HEAD');
|
|
71
|
+
} catch {
|
|
72
|
+
throw new ClawtError(MESSAGES.HEAD_NOT_FOUND);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 统一前置校验入口,按需执行各项校验
|
|
78
|
+
* @param {PreCheckOptions} options - 校验选项
|
|
79
|
+
* @param {boolean} [options.mainWorktree] - 校验是否在主 worktree 根目录
|
|
80
|
+
* @param {boolean} [options.headExists] - 校验 HEAD 是否存在
|
|
81
|
+
* @param {boolean} [options.projectConfig] - 校验项目是否已初始化
|
|
82
|
+
* @param {boolean} [options.branchExists] - 校验配置的主工作分支是否存在
|
|
83
|
+
* @throws {ClawtError} 任一校验未通过时抛出
|
|
84
|
+
*/
|
|
85
|
+
export function runPreChecks(options: PreCheckOptions): void {
|
|
86
|
+
if (options.mainWorktree) {
|
|
87
|
+
validateMainWorktree();
|
|
88
|
+
}
|
|
89
|
+
if (options.headExists) {
|
|
90
|
+
validateHeadExists();
|
|
91
|
+
}
|
|
92
|
+
if (options.projectConfig) {
|
|
93
|
+
requireProjectConfig();
|
|
94
|
+
}
|
|
95
|
+
if (options.branchExists) {
|
|
96
|
+
guardMainWorkBranchExists();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -29,7 +29,7 @@ vi.mock('../../../src/constants/index.js', () => ({
|
|
|
29
29
|
}));
|
|
30
30
|
|
|
31
31
|
vi.mock('../../../src/utils/index.js', () => ({
|
|
32
|
-
|
|
32
|
+
runPreChecks: vi.fn(),
|
|
33
33
|
requireProjectConfig: vi.fn(),
|
|
34
34
|
getProjectName: vi.fn().mockReturnValue('test-project'),
|
|
35
35
|
getGitTopLevel: vi.fn().mockReturnValue('/repo'),
|
|
@@ -24,7 +24,7 @@ vi.mock('../../../src/constants/index.js', () => ({
|
|
|
24
24
|
}));
|
|
25
25
|
|
|
26
26
|
vi.mock('../../../src/utils/index.js', () => ({
|
|
27
|
-
|
|
27
|
+
runPreChecks: vi.fn(),
|
|
28
28
|
createWorktrees: vi.fn(),
|
|
29
29
|
getConfigValue: vi.fn().mockReturnValue(true),
|
|
30
30
|
requireProjectConfig: vi.fn().mockReturnValue({ clawtMainWorkBranch: 'main' }),
|
|
@@ -38,14 +38,14 @@ vi.mock('../../../src/utils/index.js', () => ({
|
|
|
38
38
|
}));
|
|
39
39
|
|
|
40
40
|
import { registerCreateCommand } from '../../../src/commands/create.js';
|
|
41
|
-
import {
|
|
41
|
+
import { runPreChecks, createWorktrees, printSuccess } from '../../../src/utils/index.js';
|
|
42
42
|
|
|
43
|
-
const
|
|
43
|
+
const mockedRunPreChecks = vi.mocked(runPreChecks);
|
|
44
44
|
const mockedCreateWorktrees = vi.mocked(createWorktrees);
|
|
45
45
|
const mockedPrintSuccess = vi.mocked(printSuccess);
|
|
46
46
|
|
|
47
47
|
beforeEach(() => {
|
|
48
|
-
|
|
48
|
+
mockedRunPreChecks.mockReset();
|
|
49
49
|
mockedCreateWorktrees.mockReset();
|
|
50
50
|
mockedPrintSuccess.mockReset();
|
|
51
51
|
});
|
|
@@ -70,7 +70,7 @@ describe('handleCreate', () => {
|
|
|
70
70
|
registerCreateCommand(program);
|
|
71
71
|
await program.parseAsync(['create', '-b', 'feature'], { from: 'user' });
|
|
72
72
|
|
|
73
|
-
expect(
|
|
73
|
+
expect(mockedRunPreChecks).toHaveBeenCalled();
|
|
74
74
|
expect(mockedCreateWorktrees).toHaveBeenCalledWith('feature', 1);
|
|
75
75
|
expect(mockedPrintSuccess).toHaveBeenCalled();
|
|
76
76
|
});
|
|
@@ -22,7 +22,8 @@ vi.mock('../../../src/constants/index.js', () => ({
|
|
|
22
22
|
}));
|
|
23
23
|
|
|
24
24
|
vi.mock('../../../src/utils/index.js', () => ({
|
|
25
|
-
|
|
25
|
+
runPreChecks: vi.fn(),
|
|
26
|
+
validateHeadExists: vi.fn(),
|
|
26
27
|
getCurrentBranch: vi.fn().mockReturnValue('main'),
|
|
27
28
|
loadProjectConfig: vi.fn(),
|
|
28
29
|
saveProjectConfig: vi.fn(),
|
|
@@ -13,7 +13,7 @@ vi.mock('../../../src/constants/index.js', () => ({
|
|
|
13
13
|
}));
|
|
14
14
|
|
|
15
15
|
vi.mock('../../../src/utils/index.js', () => ({
|
|
16
|
-
|
|
16
|
+
runPreChecks: vi.fn(),
|
|
17
17
|
getProjectName: vi.fn(),
|
|
18
18
|
getProjectWorktrees: vi.fn(),
|
|
19
19
|
getWorktreeStatus: vi.fn(),
|
|
@@ -23,16 +23,16 @@ vi.mock('../../../src/utils/index.js', () => ({
|
|
|
23
23
|
}));
|
|
24
24
|
|
|
25
25
|
import { registerListCommand } from '../../../src/commands/list.js';
|
|
26
|
-
import {
|
|
26
|
+
import { runPreChecks, getProjectName, getProjectWorktrees, getWorktreeStatus, printInfo } from '../../../src/utils/index.js';
|
|
27
27
|
|
|
28
|
-
const
|
|
28
|
+
const mockedRunPreChecks = vi.mocked(runPreChecks);
|
|
29
29
|
const mockedGetProjectName = vi.mocked(getProjectName);
|
|
30
30
|
const mockedGetProjectWorktrees = vi.mocked(getProjectWorktrees);
|
|
31
31
|
const mockedGetWorktreeStatus = vi.mocked(getWorktreeStatus);
|
|
32
32
|
const mockedPrintInfo = vi.mocked(printInfo);
|
|
33
33
|
|
|
34
34
|
beforeEach(() => {
|
|
35
|
-
|
|
35
|
+
mockedRunPreChecks.mockReset();
|
|
36
36
|
mockedGetProjectName.mockReset();
|
|
37
37
|
mockedGetProjectWorktrees.mockReset();
|
|
38
38
|
mockedGetWorktreeStatus.mockReset();
|
|
@@ -58,7 +58,7 @@ describe('handleList', () => {
|
|
|
58
58
|
registerListCommand(program);
|
|
59
59
|
program.parse(['list'], { from: 'user' });
|
|
60
60
|
|
|
61
|
-
expect(
|
|
61
|
+
expect(mockedRunPreChecks).toHaveBeenCalled();
|
|
62
62
|
expect(mockedPrintInfo).toHaveBeenCalled();
|
|
63
63
|
});
|
|
64
64
|
|
|
@@ -40,7 +40,7 @@ vi.mock('../../../src/constants/index.js', () => ({
|
|
|
40
40
|
}));
|
|
41
41
|
|
|
42
42
|
vi.mock('../../../src/utils/index.js', () => ({
|
|
43
|
-
|
|
43
|
+
runPreChecks: vi.fn(),
|
|
44
44
|
getProjectName: vi.fn(),
|
|
45
45
|
getGitTopLevel: vi.fn(),
|
|
46
46
|
getProjectWorktrees: vi.fn(),
|
|
@@ -33,7 +33,7 @@ vi.mock('../../../src/constants/index.js', () => ({
|
|
|
33
33
|
}));
|
|
34
34
|
|
|
35
35
|
vi.mock('../../../src/utils/index.js', () => ({
|
|
36
|
-
|
|
36
|
+
runPreChecks: vi.fn(),
|
|
37
37
|
getProjectName: vi.fn(),
|
|
38
38
|
getProjectWorktreeDir: vi.fn(),
|
|
39
39
|
getProjectWorktrees: vi.fn(),
|
|
@@ -60,7 +60,7 @@ vi.mock('../../../src/utils/index.js', () => ({
|
|
|
60
60
|
|
|
61
61
|
import { registerRemoveCommand } from '../../../src/commands/remove.js';
|
|
62
62
|
import {
|
|
63
|
-
|
|
63
|
+
runPreChecks,
|
|
64
64
|
getProjectName,
|
|
65
65
|
getProjectWorktrees,
|
|
66
66
|
removeWorktreeByPath,
|
|
@@ -91,7 +91,7 @@ const mockedResolveTargetWorktrees = vi.mocked(resolveTargetWorktrees);
|
|
|
91
91
|
const mockedGetCurrentBranch = vi.mocked(getCurrentBranch);
|
|
92
92
|
|
|
93
93
|
beforeEach(() => {
|
|
94
|
-
vi.mocked(
|
|
94
|
+
vi.mocked(runPreChecks).mockReset();
|
|
95
95
|
mockedGetProjectName.mockReturnValue('test-project');
|
|
96
96
|
mockedGetProjectWorktrees.mockReset();
|
|
97
97
|
mockedRemoveWorktreeByPath.mockReset();
|
|
@@ -14,7 +14,7 @@ vi.mock('../../../src/constants/index.js', () => ({
|
|
|
14
14
|
}));
|
|
15
15
|
|
|
16
16
|
vi.mock('../../../src/utils/index.js', () => ({
|
|
17
|
-
|
|
17
|
+
runPreChecks: vi.fn(),
|
|
18
18
|
getGitTopLevel: vi.fn(),
|
|
19
19
|
getConfigValue: vi.fn(),
|
|
20
20
|
isWorkingDirClean: vi.fn(),
|
|
@@ -17,7 +17,7 @@ vi.mock('../../../src/constants/index.js', () => ({
|
|
|
17
17
|
}));
|
|
18
18
|
|
|
19
19
|
vi.mock('../../../src/utils/index.js', () => ({
|
|
20
|
-
|
|
20
|
+
runPreChecks: vi.fn(),
|
|
21
21
|
validateClaudeCodeInstalled: vi.fn(),
|
|
22
22
|
getProjectWorktrees: vi.fn(),
|
|
23
23
|
launchInteractiveClaude: vi.fn(),
|
|
@@ -33,7 +33,7 @@ vi.mock('../../../src/utils/index.js', () => ({
|
|
|
33
33
|
|
|
34
34
|
import { registerResumeCommand } from '../../../src/commands/resume.js';
|
|
35
35
|
import {
|
|
36
|
-
|
|
36
|
+
runPreChecks,
|
|
37
37
|
validateClaudeCodeInstalled,
|
|
38
38
|
getProjectWorktrees,
|
|
39
39
|
launchInteractiveClaude,
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
getConfigValue,
|
|
46
46
|
} from '../../../src/utils/index.js';
|
|
47
47
|
|
|
48
|
-
const
|
|
48
|
+
const mockedRunPreChecks = vi.mocked(runPreChecks);
|
|
49
49
|
const mockedValidateClaudeCodeInstalled = vi.mocked(validateClaudeCodeInstalled);
|
|
50
50
|
const mockedGetProjectWorktrees = vi.mocked(getProjectWorktrees);
|
|
51
51
|
const mockedLaunchInteractiveClaude = vi.mocked(launchInteractiveClaude);
|
|
@@ -57,7 +57,7 @@ const mockedConfirmAction = vi.mocked(confirmAction);
|
|
|
57
57
|
const mockedGetConfigValue = vi.mocked(getConfigValue);
|
|
58
58
|
|
|
59
59
|
beforeEach(() => {
|
|
60
|
-
|
|
60
|
+
mockedRunPreChecks.mockReset();
|
|
61
61
|
mockedValidateClaudeCodeInstalled.mockReset();
|
|
62
62
|
mockedGetProjectWorktrees.mockReset();
|
|
63
63
|
mockedLaunchInteractiveClaude.mockReset();
|
|
@@ -90,7 +90,7 @@ describe('handleResume', () => {
|
|
|
90
90
|
registerResumeCommand(program);
|
|
91
91
|
await program.parseAsync(['resume', '-b', 'feature'], { from: 'user' });
|
|
92
92
|
|
|
93
|
-
expect(
|
|
93
|
+
expect(mockedRunPreChecks).toHaveBeenCalled();
|
|
94
94
|
expect(mockedValidateClaudeCodeInstalled).toHaveBeenCalled();
|
|
95
95
|
expect(mockedResolveTargetWorktrees).toHaveBeenCalled();
|
|
96
96
|
expect(mockedPromptGroupedMultiSelectBranches).not.toHaveBeenCalled();
|
|
@@ -50,7 +50,7 @@ vi.mock('../../../src/utils/index.js', async (importOriginal) => {
|
|
|
50
50
|
const actual = await importOriginal<typeof import('../../../src/utils/index.js')>();
|
|
51
51
|
return {
|
|
52
52
|
...actual,
|
|
53
|
-
|
|
53
|
+
runPreChecks: vi.fn(),
|
|
54
54
|
validateClaudeCodeInstalled: vi.fn(),
|
|
55
55
|
createWorktrees: vi.fn(),
|
|
56
56
|
sanitizeBranchName: vi.fn(),
|
|
@@ -30,7 +30,7 @@ vi.mock('../../../src/constants/index.js', () => ({
|
|
|
30
30
|
}));
|
|
31
31
|
|
|
32
32
|
vi.mock('../../../src/utils/index.js', () => ({
|
|
33
|
-
|
|
33
|
+
runPreChecks: vi.fn(),
|
|
34
34
|
getProjectName: vi.fn(),
|
|
35
35
|
getCurrentBranch: vi.fn(),
|
|
36
36
|
isWorkingDirClean: vi.fn(),
|
|
@@ -31,7 +31,7 @@ vi.mock('../../../src/constants/index.js', () => ({
|
|
|
31
31
|
}));
|
|
32
32
|
|
|
33
33
|
vi.mock('../../../src/utils/index.js', () => ({
|
|
34
|
-
|
|
34
|
+
runPreChecks: vi.fn(),
|
|
35
35
|
getGitTopLevel: vi.fn(),
|
|
36
36
|
getProjectWorktrees: vi.fn(),
|
|
37
37
|
isWorkingDirClean: vi.fn(),
|
|
@@ -10,6 +10,12 @@ vi.mock('../../../src/utils/git.js', () => ({
|
|
|
10
10
|
getGitCommonDir: vi.fn(),
|
|
11
11
|
}));
|
|
12
12
|
|
|
13
|
+
// mock project-config(runPreChecks 依赖 requireProjectConfig 和 guardMainWorkBranchExists)
|
|
14
|
+
vi.mock('../../../src/utils/project-config.js', () => ({
|
|
15
|
+
requireProjectConfig: vi.fn(),
|
|
16
|
+
guardMainWorkBranchExists: vi.fn(),
|
|
17
|
+
}));
|
|
18
|
+
|
|
13
19
|
// mock logger
|
|
14
20
|
vi.mock('../../../src/logger/index.js', () => ({
|
|
15
21
|
logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
|
|
@@ -17,11 +23,14 @@ vi.mock('../../../src/logger/index.js', () => ({
|
|
|
17
23
|
|
|
18
24
|
import { execCommand } from '../../../src/utils/shell.js';
|
|
19
25
|
import { getGitCommonDir } from '../../../src/utils/git.js';
|
|
20
|
-
import { validateMainWorktree, validateGitInstalled, validateClaudeCodeInstalled } from '../../../src/utils/validation.js';
|
|
26
|
+
import { validateMainWorktree, validateGitInstalled, validateClaudeCodeInstalled, validateHeadExists, runPreChecks } from '../../../src/utils/validation.js';
|
|
27
|
+
import { requireProjectConfig, guardMainWorkBranchExists } from '../../../src/utils/project-config.js';
|
|
21
28
|
import { ClawtError } from '../../../src/errors/index.js';
|
|
22
29
|
|
|
23
30
|
const mockedExecCommand = vi.mocked(execCommand);
|
|
24
31
|
const mockedGetGitCommonDir = vi.mocked(getGitCommonDir);
|
|
32
|
+
const mockedRequireProjectConfig = vi.mocked(requireProjectConfig);
|
|
33
|
+
const mockedGuardMainWorkBranchExists = vi.mocked(guardMainWorkBranchExists);
|
|
25
34
|
|
|
26
35
|
describe('validateMainWorktree', () => {
|
|
27
36
|
it('.git 返回时正常通过', () => {
|
|
@@ -63,3 +72,36 @@ describe('validateClaudeCodeInstalled', () => {
|
|
|
63
72
|
expect(() => validateClaudeCodeInstalled()).toThrow(ClawtError);
|
|
64
73
|
});
|
|
65
74
|
});
|
|
75
|
+
|
|
76
|
+
describe('validateHeadExists', () => {
|
|
77
|
+
it('HEAD 存在时正常通过', () => {
|
|
78
|
+
mockedExecCommand.mockReturnValue('abc1234');
|
|
79
|
+
expect(() => validateHeadExists()).not.toThrow();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('HEAD 不存在时抛出 ClawtError', () => {
|
|
83
|
+
mockedExecCommand.mockImplementation(() => { throw new Error('fatal: ambiguous argument HEAD'); });
|
|
84
|
+
expect(() => validateHeadExists()).toThrow(ClawtError);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('runPreChecks', () => {
|
|
89
|
+
it('按选项组合调用对应校验', () => {
|
|
90
|
+
mockedGetGitCommonDir.mockReturnValue('.git');
|
|
91
|
+
mockedExecCommand.mockReturnValue('abc1234');
|
|
92
|
+
mockedRequireProjectConfig.mockReturnValue({ clawtMainWorkBranch: 'main' });
|
|
93
|
+
mockedGuardMainWorkBranchExists.mockReturnValue(undefined);
|
|
94
|
+
|
|
95
|
+
expect(() => runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true, branchExists: true })).not.toThrow();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('未设置的选项不触发校验', () => {
|
|
99
|
+
// 不设置任何选项,不应该调用任何校验函数
|
|
100
|
+
mockedGetGitCommonDir.mockImplementation(() => { throw new Error('should not be called'); });
|
|
101
|
+
mockedExecCommand.mockImplementation(() => { throw new Error('should not be called'); });
|
|
102
|
+
mockedRequireProjectConfig.mockImplementation(() => { throw new Error('should not be called'); });
|
|
103
|
+
mockedGuardMainWorkBranchExists.mockImplementation(() => { throw new Error('should not be called'); });
|
|
104
|
+
|
|
105
|
+
expect(() => runPreChecks({})).not.toThrow();
|
|
106
|
+
});
|
|
107
|
+
});
|