clawt 3.1.2 → 3.1.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/README.md +3 -3
- package/dist/index.js +128 -113
- package/dist/postinstall.js +31 -1
- package/docs/config.md +2 -1
- package/docs/init.md +16 -7
- package/docs/project-config.md +132 -0
- package/docs/spec.md +31 -22
- package/docs/validate.md +4 -3
- package/package.json +1 -1
- package/src/commands/config.ts +14 -28
- package/src/commands/init.ts +23 -12
- package/src/commands/validate.ts +17 -3
- package/src/constants/index.ts +1 -0
- package/src/constants/messages/init.ts +4 -0
- package/src/constants/project-config.ts +46 -0
- package/src/types/index.ts +1 -1
- package/src/types/projectConfig.ts +17 -0
- package/src/utils/config-strategy.ts +68 -20
- package/src/utils/index.ts +2 -2
- package/src/utils/project-config.ts +9 -0
- package/tests/unit/commands/config.test.ts +1 -0
- package/tests/unit/commands/init.test.ts +41 -6
- package/tests/unit/commands/validate.test.ts +63 -0
- package/tests/unit/utils/config-strategy.test.ts +77 -1
- package/tests/unit/utils/project-config.test.ts +32 -0
package/README.md
CHANGED
|
@@ -43,10 +43,10 @@ clawt merge -b branch-1 -m "feat: 实现xxx功能"
|
|
|
43
43
|
```bash
|
|
44
44
|
clawt init # 以当前分支作为主工作分支进行初始化
|
|
45
45
|
clawt init -b <branch> # 指定主工作分支名
|
|
46
|
-
clawt init show #
|
|
46
|
+
clawt init show # 交互式查看和修改项目配置
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
设置项目的主工作分支。重复执行会更新主工作分支配置。`init show` 提供交互式面板,可查看和修改项目配置项(如 validate 成功后自动执行的命令)。
|
|
50
50
|
|
|
51
51
|
### `clawt run` — 创建 worktree 并执行任务
|
|
52
52
|
|
|
@@ -140,7 +140,7 @@ clawt validate -b <branch> -r "pnpm test & pnpm build" # 并行执行多个命
|
|
|
140
140
|
|
|
141
141
|
当 patch apply 失败(目标分支与主分支差异过大)时,会自动询问是否执行 `sync` 同步主分支到目标 worktree,无需手动操作。
|
|
142
142
|
|
|
143
|
-
`-r, --run` 选项可在 validate 成功后自动在主 worktree 中执行指定命令(如测试、构建等),命令执行失败不影响 validate
|
|
143
|
+
`-r, --run` 选项可在 validate 成功后自动在主 worktree 中执行指定命令(如测试、构建等),命令执行失败不影响 validate 结果。不传 `-r` 时会自动从项目配置的 `validateRunCommand` 读取(可通过 `clawt init show` 设置)。支持用 `&` 分隔多个命令并行执行:
|
|
144
144
|
|
|
145
145
|
| 用法 | 行为 |
|
|
146
146
|
| ---- | ---- |
|
package/dist/index.js
CHANGED
|
@@ -460,7 +460,11 @@ var INIT_MESSAGES = {
|
|
|
460
460
|
/** 项目未初始化(requireProjectConfig 使用) */
|
|
461
461
|
PROJECT_NOT_INITIALIZED: "\u9879\u76EE\u5C1A\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u6267\u884C clawt init \u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F",
|
|
462
462
|
/** 项目配置缺少 clawtMainWorkBranch 字段 */
|
|
463
|
-
PROJECT_CONFIG_MISSING_BRANCH: "\u9879\u76EE\u914D\u7F6E\u7F3A\u5C11\u4E3B\u5DE5\u4F5C\u5206\u652F\u4FE1\u606F\uFF0C\u8BF7\u91CD\u65B0\u6267\u884C clawt init \u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F"
|
|
463
|
+
PROJECT_CONFIG_MISSING_BRANCH: "\u9879\u76EE\u914D\u7F6E\u7F3A\u5C11\u4E3B\u5DE5\u4F5C\u5206\u652F\u4FE1\u606F\uFF0C\u8BF7\u91CD\u65B0\u6267\u884C clawt init \u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F",
|
|
464
|
+
/** init show 交互式面板选择配置项提示 */
|
|
465
|
+
INIT_SELECT_PROMPT: "\u9009\u62E9\u8981\u4FEE\u6539\u7684\u9879\u76EE\u914D\u7F6E\u9879",
|
|
466
|
+
/** init show 交互式面板配置项修改成功 */
|
|
467
|
+
INIT_SET_SUCCESS: (key, value) => `\u2713 \u9879\u76EE\u914D\u7F6E ${key} \u5DF2\u8BBE\u7F6E\u4E3A ${value}`
|
|
464
468
|
};
|
|
465
469
|
|
|
466
470
|
// src/constants/messages/interactive-panel.ts
|
|
@@ -608,6 +612,32 @@ function deriveConfigDescriptions(definitions) {
|
|
|
608
612
|
var DEFAULT_CONFIG = deriveDefaultConfig(CONFIG_DEFINITIONS);
|
|
609
613
|
var CONFIG_DESCRIPTIONS = deriveConfigDescriptions(CONFIG_DEFINITIONS);
|
|
610
614
|
|
|
615
|
+
// src/constants/project-config.ts
|
|
616
|
+
var PROJECT_CONFIG_DEFINITIONS = {
|
|
617
|
+
clawtMainWorkBranch: {
|
|
618
|
+
defaultValue: "",
|
|
619
|
+
description: "\u4E3B worktree \u7684\u5DE5\u4F5C\u5206\u652F\u540D"
|
|
620
|
+
},
|
|
621
|
+
validateRunCommand: {
|
|
622
|
+
defaultValue: void 0,
|
|
623
|
+
description: "validate \u6210\u529F\u540E\u81EA\u52A8\u6267\u884C\u7684\u547D\u4EE4\uFF08-r \u7684\u9ED8\u8BA4\u503C\uFF09"
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
function deriveDefaultConfig2(definitions) {
|
|
627
|
+
const entries = Object.entries(definitions).map(
|
|
628
|
+
([key, def]) => [key, def.defaultValue]
|
|
629
|
+
);
|
|
630
|
+
return Object.fromEntries(entries);
|
|
631
|
+
}
|
|
632
|
+
function deriveConfigDescriptions2(definitions) {
|
|
633
|
+
const entries = Object.entries(definitions).map(
|
|
634
|
+
([key, def]) => [key, def.description]
|
|
635
|
+
);
|
|
636
|
+
return Object.fromEntries(entries);
|
|
637
|
+
}
|
|
638
|
+
var PROJECT_DEFAULT_CONFIG = deriveDefaultConfig2(PROJECT_CONFIG_DEFINITIONS);
|
|
639
|
+
var PROJECT_CONFIG_DESCRIPTIONS = deriveConfigDescriptions2(PROJECT_CONFIG_DEFINITIONS);
|
|
640
|
+
|
|
611
641
|
// src/constants/git.ts
|
|
612
642
|
var AUTO_SAVE_COMMIT_MESSAGE = "chore: auto-save before sync";
|
|
613
643
|
|
|
@@ -1259,6 +1289,10 @@ function getMainWorkBranch() {
|
|
|
1259
1289
|
const config2 = requireProjectConfig();
|
|
1260
1290
|
return config2.clawtMainWorkBranch;
|
|
1261
1291
|
}
|
|
1292
|
+
function getValidateRunCommand() {
|
|
1293
|
+
const config2 = loadProjectConfig();
|
|
1294
|
+
return config2?.validateRunCommand || void 0;
|
|
1295
|
+
}
|
|
1262
1296
|
|
|
1263
1297
|
// src/utils/validate-branch.ts
|
|
1264
1298
|
function getValidateBranchName(branchName) {
|
|
@@ -2717,7 +2751,7 @@ function parseConfigValue(key, rawValue) {
|
|
|
2717
2751
|
}
|
|
2718
2752
|
return { success: true, value: rawValue };
|
|
2719
2753
|
}
|
|
2720
|
-
async function promptConfigValue(key, currentValue) {
|
|
2754
|
+
async function promptConfigValue(key, currentValue, allowedValues) {
|
|
2721
2755
|
const expectedType = typeof currentValue;
|
|
2722
2756
|
if (expectedType === "boolean") {
|
|
2723
2757
|
return promptBooleanValue(key, currentValue);
|
|
@@ -2725,18 +2759,43 @@ async function promptConfigValue(key, currentValue) {
|
|
|
2725
2759
|
if (expectedType === "number") {
|
|
2726
2760
|
return promptNumberValue(key, currentValue);
|
|
2727
2761
|
}
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
return promptEnumValue(key, currentValue, definition.allowedValues);
|
|
2762
|
+
if (allowedValues) {
|
|
2763
|
+
return promptEnumValue(key, currentValue, allowedValues);
|
|
2731
2764
|
}
|
|
2732
2765
|
return promptStringValue(key, currentValue);
|
|
2733
2766
|
}
|
|
2734
2767
|
function formatConfigValue(value) {
|
|
2768
|
+
if (value === void 0 || value === null) {
|
|
2769
|
+
return chalk7.dim("(\u672A\u8BBE\u7F6E)");
|
|
2770
|
+
}
|
|
2735
2771
|
if (typeof value === "boolean") {
|
|
2736
2772
|
return value ? chalk7.green("true") : chalk7.yellow("false");
|
|
2737
2773
|
}
|
|
2738
2774
|
return chalk7.cyan(String(value));
|
|
2739
2775
|
}
|
|
2776
|
+
async function interactiveConfigEditor(config2, definitions, options) {
|
|
2777
|
+
const keys = Object.keys(definitions);
|
|
2778
|
+
const disabledKeys = options?.disabledKeys ?? {};
|
|
2779
|
+
const configRecord = config2;
|
|
2780
|
+
const choices = keys.map((k) => {
|
|
2781
|
+
const isDisabled = k in disabledKeys;
|
|
2782
|
+
const value = configRecord[k];
|
|
2783
|
+
const isObject = typeof value === "object" && value !== null;
|
|
2784
|
+
return {
|
|
2785
|
+
name: k,
|
|
2786
|
+
message: `${k}: ${isObject || isDisabled ? chalk7.dim(isObject ? JSON.stringify(value) : String(value ?? "")) : formatConfigValue(value)} ${chalk7.dim(`\u2014 ${definitions[k].description}`)}`,
|
|
2787
|
+
...isDisabled && { disabled: disabledKeys[k] }
|
|
2788
|
+
};
|
|
2789
|
+
});
|
|
2790
|
+
const selectedKey = await new Enquirer4.Select({
|
|
2791
|
+
message: options?.selectPrompt ?? MESSAGES.CONFIG_SELECT_PROMPT,
|
|
2792
|
+
choices
|
|
2793
|
+
}).run();
|
|
2794
|
+
const currentValue = configRecord[selectedKey];
|
|
2795
|
+
const definition = definitions[selectedKey];
|
|
2796
|
+
const newValue = await promptConfigValue(selectedKey, currentValue, definition.allowedValues);
|
|
2797
|
+
return { key: selectedKey, newValue };
|
|
2798
|
+
}
|
|
2740
2799
|
async function promptBooleanValue(key, currentValue) {
|
|
2741
2800
|
const choices = [
|
|
2742
2801
|
{ name: "true", message: "true" },
|
|
@@ -2774,7 +2833,7 @@ async function promptEnumValue(key, currentValue, allowedValues) {
|
|
|
2774
2833
|
async function promptStringValue(key, currentValue) {
|
|
2775
2834
|
return await new Enquirer4.Input({
|
|
2776
2835
|
message: MESSAGES.CONFIG_INPUT_PROMPT(key),
|
|
2777
|
-
initial: currentValue
|
|
2836
|
+
initial: currentValue || ""
|
|
2778
2837
|
}).run();
|
|
2779
2838
|
}
|
|
2780
2839
|
|
|
@@ -2909,56 +2968,6 @@ async function checkForUpdates(currentVersion) {
|
|
|
2909
2968
|
}
|
|
2910
2969
|
}
|
|
2911
2970
|
|
|
2912
|
-
// src/utils/json.ts
|
|
2913
|
-
function primitiveToString(value) {
|
|
2914
|
-
if (value === void 0) {
|
|
2915
|
-
return "undefined";
|
|
2916
|
-
}
|
|
2917
|
-
if (value === null) {
|
|
2918
|
-
return "null";
|
|
2919
|
-
}
|
|
2920
|
-
if (typeof value === "symbol") {
|
|
2921
|
-
return value.toString();
|
|
2922
|
-
}
|
|
2923
|
-
if (typeof value === "function") {
|
|
2924
|
-
return `[Function: ${value.name || "anonymous"}]`;
|
|
2925
|
-
}
|
|
2926
|
-
return String(value);
|
|
2927
|
-
}
|
|
2928
|
-
function safeStringify(value, indent = 2) {
|
|
2929
|
-
if (value === null || typeof value !== "object") {
|
|
2930
|
-
return primitiveToString(value);
|
|
2931
|
-
}
|
|
2932
|
-
try {
|
|
2933
|
-
const seen = /* @__PURE__ */ new WeakSet();
|
|
2934
|
-
return JSON.stringify(
|
|
2935
|
-
value,
|
|
2936
|
-
(_key, val) => {
|
|
2937
|
-
if (typeof val === "bigint") {
|
|
2938
|
-
return val.toString();
|
|
2939
|
-
}
|
|
2940
|
-
if (typeof val === "undefined" || typeof val === "function" || typeof val === "symbol") {
|
|
2941
|
-
return primitiveToString(val);
|
|
2942
|
-
}
|
|
2943
|
-
if (typeof val === "object" && val !== null) {
|
|
2944
|
-
if (seen.has(val)) {
|
|
2945
|
-
return "[Circular]";
|
|
2946
|
-
}
|
|
2947
|
-
seen.add(val);
|
|
2948
|
-
}
|
|
2949
|
-
return val;
|
|
2950
|
-
},
|
|
2951
|
-
indent
|
|
2952
|
-
);
|
|
2953
|
-
} catch {
|
|
2954
|
-
try {
|
|
2955
|
-
return JSON.stringify(String(value), null, indent);
|
|
2956
|
-
} catch {
|
|
2957
|
-
return "[Unserializable]";
|
|
2958
|
-
}
|
|
2959
|
-
}
|
|
2960
|
-
}
|
|
2961
|
-
|
|
2962
2971
|
// src/utils/interactive-panel.ts
|
|
2963
2972
|
import { createInterface as createInterface2 } from "readline";
|
|
2964
2973
|
|
|
@@ -4116,6 +4125,12 @@ async function executeRunCommand(command, mainWorktreePath) {
|
|
|
4116
4125
|
await executeParallelCommands(commands, mainWorktreePath);
|
|
4117
4126
|
}
|
|
4118
4127
|
}
|
|
4128
|
+
function resolveRunCommand(optionRun) {
|
|
4129
|
+
if (optionRun) {
|
|
4130
|
+
return optionRun;
|
|
4131
|
+
}
|
|
4132
|
+
return getValidateRunCommand();
|
|
4133
|
+
}
|
|
4119
4134
|
async function handleValidate(options) {
|
|
4120
4135
|
if (options.clean) {
|
|
4121
4136
|
await handleValidateClean(options);
|
|
@@ -4148,8 +4163,9 @@ async function handleValidate(options) {
|
|
|
4148
4163
|
}
|
|
4149
4164
|
await handleFirstValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted);
|
|
4150
4165
|
}
|
|
4151
|
-
|
|
4152
|
-
|
|
4166
|
+
const runCommand = resolveRunCommand(options.run);
|
|
4167
|
+
if (runCommand) {
|
|
4168
|
+
await executeRunCommand(runCommand, mainWorktreePath);
|
|
4153
4169
|
}
|
|
4154
4170
|
}
|
|
4155
4171
|
|
|
@@ -4274,8 +4290,6 @@ async function handleMerge(options) {
|
|
|
4274
4290
|
}
|
|
4275
4291
|
|
|
4276
4292
|
// src/commands/config.ts
|
|
4277
|
-
import chalk11 from "chalk";
|
|
4278
|
-
import Enquirer5 from "enquirer";
|
|
4279
4293
|
function registerConfigCommand(program2) {
|
|
4280
4294
|
const configCmd = program2.command("config").description("\u4EA4\u4E92\u5F0F\u67E5\u770B\u548C\u4FEE\u6539\u5168\u5C40\u914D\u7F6E").action(async () => {
|
|
4281
4295
|
await handleConfigSet();
|
|
@@ -4329,25 +4343,19 @@ async function handleConfigSet(key, value) {
|
|
|
4329
4343
|
}
|
|
4330
4344
|
async function handleInteractiveConfigSet() {
|
|
4331
4345
|
const config2 = loadConfig();
|
|
4332
|
-
const keys = Object.keys(DEFAULT_CONFIG);
|
|
4333
4346
|
logger.info("config set \u547D\u4EE4\u6267\u884C\uFF0C\u8FDB\u5165\u4EA4\u4E92\u5F0F\u914D\u7F6E");
|
|
4334
|
-
const
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4347
|
+
const disabledKeys = {};
|
|
4348
|
+
for (const k of Object.keys(DEFAULT_CONFIG)) {
|
|
4349
|
+
if (typeof DEFAULT_CONFIG[k] === "object") {
|
|
4350
|
+
disabledKeys[k] = CONFIG_ALIAS_DISABLED_HINT;
|
|
4351
|
+
}
|
|
4352
|
+
}
|
|
4353
|
+
const { key, newValue } = await interactiveConfigEditor(config2, CONFIG_DEFINITIONS, {
|
|
4354
|
+
disabledKeys
|
|
4341
4355
|
});
|
|
4342
|
-
|
|
4343
|
-
message: MESSAGES.CONFIG_SELECT_PROMPT,
|
|
4344
|
-
choices
|
|
4345
|
-
}).run();
|
|
4346
|
-
const currentValue = config2[selectedKey];
|
|
4347
|
-
const newValue = await promptConfigValue(selectedKey, currentValue);
|
|
4348
|
-
config2[selectedKey] = newValue;
|
|
4356
|
+
config2[key] = newValue;
|
|
4349
4357
|
saveConfig(config2);
|
|
4350
|
-
printSuccess(MESSAGES.CONFIG_SET_SUCCESS(
|
|
4358
|
+
printSuccess(MESSAGES.CONFIG_SET_SUCCESS(key, String(newValue)));
|
|
4351
4359
|
}
|
|
4352
4360
|
function handleConfigGet(key) {
|
|
4353
4361
|
if (!isValidConfigKey(key)) {
|
|
@@ -4391,7 +4399,7 @@ async function handleReset() {
|
|
|
4391
4399
|
}
|
|
4392
4400
|
|
|
4393
4401
|
// src/commands/status.ts
|
|
4394
|
-
import
|
|
4402
|
+
import chalk11 from "chalk";
|
|
4395
4403
|
function registerStatusCommand(program2) {
|
|
4396
4404
|
program2.command("status").description("\u663E\u793A\u9879\u76EE\u5168\u5C40\u72B6\u6001\u603B\u89C8\uFF08\u652F\u6301 --json \u683C\u5F0F\u8F93\u51FA\uFF09").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").option("-i, --interactive", "\u4EA4\u4E92\u5F0F\u9762\u677F\u6A21\u5F0F").action(async (options) => {
|
|
4397
4405
|
await handleStatus(options);
|
|
@@ -4509,7 +4517,7 @@ function printStatusAsJson(result) {
|
|
|
4509
4517
|
}
|
|
4510
4518
|
function printStatusAsText(result) {
|
|
4511
4519
|
printDoubleSeparator();
|
|
4512
|
-
printInfo(` ${
|
|
4520
|
+
printInfo(` ${chalk11.bold.cyan(MESSAGES.STATUS_TITLE(result.main.projectName))}`);
|
|
4513
4521
|
printDoubleSeparator();
|
|
4514
4522
|
printInfo("");
|
|
4515
4523
|
printMainSection(result.main);
|
|
@@ -4522,17 +4530,17 @@ function printStatusAsText(result) {
|
|
|
4522
4530
|
printDoubleSeparator();
|
|
4523
4531
|
}
|
|
4524
4532
|
function printMainSection(main2) {
|
|
4525
|
-
printInfo(` ${
|
|
4526
|
-
printInfo(` \u5206\u652F: ${
|
|
4533
|
+
printInfo(` ${chalk11.bold("\u25C6")} ${chalk11.bold(MESSAGES.STATUS_MAIN_SECTION)}`);
|
|
4534
|
+
printInfo(` \u5206\u652F: ${chalk11.bold(main2.branch)}`);
|
|
4527
4535
|
if (main2.isClean) {
|
|
4528
|
-
printInfo(` \u72B6\u6001: ${
|
|
4536
|
+
printInfo(` \u72B6\u6001: ${chalk11.green("\u2713 \u5E72\u51C0")}`);
|
|
4529
4537
|
} else {
|
|
4530
|
-
printInfo(` \u72B6\u6001: ${
|
|
4538
|
+
printInfo(` \u72B6\u6001: ${chalk11.yellow("\u2717 \u6709\u672A\u63D0\u4EA4\u4FEE\u6539")}`);
|
|
4531
4539
|
}
|
|
4532
4540
|
printInfo("");
|
|
4533
4541
|
}
|
|
4534
4542
|
function printWorktreesSection(worktrees, total) {
|
|
4535
|
-
printInfo(` ${
|
|
4543
|
+
printInfo(` ${chalk11.bold("\u25C6")} ${chalk11.bold(MESSAGES.STATUS_WORKTREES_SECTION)} (${total} \u4E2A)`);
|
|
4536
4544
|
printInfo("");
|
|
4537
4545
|
if (worktrees.length === 0) {
|
|
4538
4546
|
printInfo(` ${MESSAGES.STATUS_NO_WORKTREES}`);
|
|
@@ -4544,56 +4552,56 @@ function printWorktreesSection(worktrees, total) {
|
|
|
4544
4552
|
}
|
|
4545
4553
|
function printWorktreeItem(wt) {
|
|
4546
4554
|
const statusLabel = formatChangeStatusLabel2(wt.changeStatus);
|
|
4547
|
-
printInfo(` ${
|
|
4555
|
+
printInfo(` ${chalk11.bold("\u25CF")} ${chalk11.bold(wt.branch)} [${statusLabel}]`);
|
|
4548
4556
|
if (wt.insertions > 0 || wt.deletions > 0) {
|
|
4549
|
-
printInfo(` ${
|
|
4557
|
+
printInfo(` ${chalk11.green(`+${wt.insertions}`)} ${chalk11.red(`-${wt.deletions}`)}`);
|
|
4550
4558
|
}
|
|
4551
4559
|
if (wt.commitsAhead > 0) {
|
|
4552
|
-
printInfo(` ${
|
|
4560
|
+
printInfo(` ${chalk11.yellow(`${wt.commitsAhead} \u4E2A\u672C\u5730\u63D0\u4EA4`)}`);
|
|
4553
4561
|
}
|
|
4554
4562
|
if (wt.commitsBehind > 0) {
|
|
4555
|
-
printInfo(` ${
|
|
4563
|
+
printInfo(` ${chalk11.yellow(`\u843D\u540E\u4E3B\u5206\u652F ${wt.commitsBehind} \u4E2A\u63D0\u4EA4`)}`);
|
|
4556
4564
|
} else {
|
|
4557
|
-
printInfo(` ${
|
|
4565
|
+
printInfo(` ${chalk11.green("\u4E0E\u4E3B\u5206\u652F\u540C\u6B65")}`);
|
|
4558
4566
|
}
|
|
4559
4567
|
if (wt.createdAt) {
|
|
4560
4568
|
const relativeTime = formatRelativeTime(wt.createdAt);
|
|
4561
4569
|
if (relativeTime) {
|
|
4562
|
-
printInfo(` ${
|
|
4570
|
+
printInfo(` ${chalk11.gray(MESSAGES.STATUS_CREATED_AT(relativeTime))}`);
|
|
4563
4571
|
}
|
|
4564
4572
|
}
|
|
4565
4573
|
if (wt.snapshotTime) {
|
|
4566
4574
|
const relativeTime = formatRelativeTime(wt.snapshotTime);
|
|
4567
4575
|
if (relativeTime) {
|
|
4568
|
-
printInfo(` ${
|
|
4576
|
+
printInfo(` ${chalk11.green(MESSAGES.STATUS_LAST_VALIDATED(relativeTime))}`);
|
|
4569
4577
|
}
|
|
4570
4578
|
} else {
|
|
4571
|
-
printInfo(` ${
|
|
4579
|
+
printInfo(` ${chalk11.red(MESSAGES.STATUS_NOT_VALIDATED)}`);
|
|
4572
4580
|
}
|
|
4573
4581
|
printInfo("");
|
|
4574
4582
|
}
|
|
4575
4583
|
function formatChangeStatusLabel2(status) {
|
|
4576
4584
|
switch (status) {
|
|
4577
4585
|
case "committed":
|
|
4578
|
-
return
|
|
4586
|
+
return chalk11.green(MESSAGES.STATUS_CHANGE_COMMITTED);
|
|
4579
4587
|
case "uncommitted":
|
|
4580
|
-
return
|
|
4588
|
+
return chalk11.yellow(MESSAGES.STATUS_CHANGE_UNCOMMITTED);
|
|
4581
4589
|
case "conflict":
|
|
4582
|
-
return
|
|
4590
|
+
return chalk11.red(MESSAGES.STATUS_CHANGE_CONFLICT);
|
|
4583
4591
|
case "clean":
|
|
4584
|
-
return
|
|
4592
|
+
return chalk11.gray(MESSAGES.STATUS_CHANGE_CLEAN);
|
|
4585
4593
|
}
|
|
4586
4594
|
}
|
|
4587
4595
|
function printSnapshotsSection(snapshots) {
|
|
4588
|
-
printInfo(` ${
|
|
4596
|
+
printInfo(` ${chalk11.bold("\u25C6")} ${chalk11.bold(MESSAGES.STATUS_SNAPSHOTS_SECTION)} (${snapshots.total} \u4E2A)`);
|
|
4589
4597
|
if (snapshots.orphaned > 0) {
|
|
4590
|
-
printInfo(` ${
|
|
4598
|
+
printInfo(` ${chalk11.yellow(MESSAGES.STATUS_SNAPSHOT_ORPHANED(snapshots.orphaned))}`);
|
|
4591
4599
|
}
|
|
4592
4600
|
printInfo("");
|
|
4593
4601
|
}
|
|
4594
4602
|
|
|
4595
4603
|
// src/commands/alias.ts
|
|
4596
|
-
import
|
|
4604
|
+
import chalk12 from "chalk";
|
|
4597
4605
|
function getRegisteredCommandNames(program2) {
|
|
4598
4606
|
return program2.commands.map((cmd) => cmd.name());
|
|
4599
4607
|
}
|
|
@@ -4614,7 +4622,7 @@ ${MESSAGES.ALIAS_LIST_TITLE}
|
|
|
4614
4622
|
`);
|
|
4615
4623
|
printSeparator();
|
|
4616
4624
|
for (const [alias, command] of entries) {
|
|
4617
|
-
printInfo(` ${
|
|
4625
|
+
printInfo(` ${chalk12.bold(alias)} \u2192 ${chalk12.cyan(command)}`);
|
|
4618
4626
|
}
|
|
4619
4627
|
printInfo("");
|
|
4620
4628
|
printSeparator();
|
|
@@ -4663,7 +4671,7 @@ function registerAliasCommand(program2) {
|
|
|
4663
4671
|
// src/commands/projects.ts
|
|
4664
4672
|
import { existsSync as existsSync10, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
|
|
4665
4673
|
import { join as join8 } from "path";
|
|
4666
|
-
import
|
|
4674
|
+
import chalk13 from "chalk";
|
|
4667
4675
|
function registerProjectsCommand(program2) {
|
|
4668
4676
|
program2.command("projects [name]").description("\u5C55\u793A\u6240\u6709\u9879\u76EE\u7684 worktree \u6982\u89C8\uFF0C\u6216\u67E5\u770B\u6307\u5B9A\u9879\u76EE\u7684 worktree \u8BE6\u60C5").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((name, options) => {
|
|
4669
4677
|
handleProjects({ name, json: options.json });
|
|
@@ -4782,7 +4790,7 @@ function sortByLastActiveTimeDesc(projects) {
|
|
|
4782
4790
|
}
|
|
4783
4791
|
function printProjectsOverviewAsText(result) {
|
|
4784
4792
|
printDoubleSeparator();
|
|
4785
|
-
printInfo(` ${
|
|
4793
|
+
printInfo(` ${chalk13.bold.cyan(MESSAGES.PROJECTS_OVERVIEW_TITLE)}`);
|
|
4786
4794
|
printDoubleSeparator();
|
|
4787
4795
|
printInfo("");
|
|
4788
4796
|
if (result.projects.length === 0) {
|
|
@@ -4796,7 +4804,7 @@ function printProjectsOverviewAsText(result) {
|
|
|
4796
4804
|
}
|
|
4797
4805
|
printSeparator();
|
|
4798
4806
|
printInfo("");
|
|
4799
|
-
printInfo(` \u5171 ${
|
|
4807
|
+
printInfo(` \u5171 ${chalk13.bold(String(result.totalProjects))} \u4E2A\u9879\u76EE ${chalk13.gray(MESSAGES.PROJECTS_TOTAL_DISK_USAGE(formatDiskSize(result.totalDiskUsage)))}`);
|
|
4800
4808
|
printInfo("");
|
|
4801
4809
|
printDoubleSeparator();
|
|
4802
4810
|
}
|
|
@@ -4804,16 +4812,16 @@ function printProjectOverviewItem(project) {
|
|
|
4804
4812
|
const relativeTime = formatRelativeTime(project.lastActiveTime);
|
|
4805
4813
|
const activeLabel = relativeTime ? MESSAGES.PROJECTS_LAST_ACTIVE(relativeTime) : "";
|
|
4806
4814
|
const diskLabel = MESSAGES.PROJECTS_DISK_USAGE(formatDiskSize(project.diskUsage));
|
|
4807
|
-
printInfo(` ${
|
|
4808
|
-
printInfo(` ${MESSAGES.PROJECTS_WORKTREE_COUNT(project.worktreeCount)} ${
|
|
4815
|
+
printInfo(` ${chalk13.bold("\u25CF")} ${chalk13.bold(project.name)}`);
|
|
4816
|
+
printInfo(` ${MESSAGES.PROJECTS_WORKTREE_COUNT(project.worktreeCount)} ${chalk13.gray(activeLabel)} ${chalk13.gray(diskLabel)}`);
|
|
4809
4817
|
printInfo("");
|
|
4810
4818
|
}
|
|
4811
4819
|
function printProjectDetailAsText(result) {
|
|
4812
4820
|
printDoubleSeparator();
|
|
4813
|
-
printInfo(` ${
|
|
4821
|
+
printInfo(` ${chalk13.bold.cyan(MESSAGES.PROJECTS_DETAIL_TITLE(result.name))}`);
|
|
4814
4822
|
printDoubleSeparator();
|
|
4815
4823
|
printInfo("");
|
|
4816
|
-
printInfo(` ${
|
|
4824
|
+
printInfo(` ${chalk13.bold("\u25C6")} ${chalk13.bold(MESSAGES.PROJECTS_PATH(result.projectDir))}`);
|
|
4817
4825
|
printInfo(` ${MESSAGES.PROJECTS_TOTAL_DISK_USAGE(formatDiskSize(result.totalDiskUsage))}`);
|
|
4818
4826
|
printInfo("");
|
|
4819
4827
|
printSeparator();
|
|
@@ -4833,9 +4841,9 @@ function printWorktreeDetailItem(wt) {
|
|
|
4833
4841
|
const relativeTime = formatRelativeTime(wt.lastModifiedTime);
|
|
4834
4842
|
const modifiedLabel = relativeTime ? MESSAGES.PROJECTS_LAST_MODIFIED(relativeTime) : "";
|
|
4835
4843
|
const diskLabel = MESSAGES.PROJECTS_DISK_USAGE(formatDiskSize(wt.diskUsage));
|
|
4836
|
-
printInfo(` ${
|
|
4844
|
+
printInfo(` ${chalk13.bold("\u25CF")} ${chalk13.bold(wt.branch)}`);
|
|
4837
4845
|
printInfo(` ${wt.path}`);
|
|
4838
|
-
printInfo(` ${
|
|
4846
|
+
printInfo(` ${chalk13.gray(modifiedLabel)} ${chalk13.gray(diskLabel)}`);
|
|
4839
4847
|
printInfo("");
|
|
4840
4848
|
}
|
|
4841
4849
|
|
|
@@ -5058,16 +5066,23 @@ function registerInitCommand(program2) {
|
|
|
5058
5066
|
await handleInit(options);
|
|
5059
5067
|
});
|
|
5060
5068
|
initCmd.addCommand(
|
|
5061
|
-
new Cmd("show").description("\
|
|
5062
|
-
handleInitShow();
|
|
5069
|
+
new Cmd("show").description("\u4EA4\u4E92\u5F0F\u67E5\u770B\u548C\u4FEE\u6539\u9879\u76EE\u914D\u7F6E").action(async () => {
|
|
5070
|
+
await handleInitShow();
|
|
5063
5071
|
})
|
|
5064
5072
|
);
|
|
5065
5073
|
}
|
|
5066
|
-
function handleInitShow() {
|
|
5074
|
+
async function handleInitShow() {
|
|
5067
5075
|
validateMainWorktree();
|
|
5068
5076
|
const config2 = requireProjectConfig();
|
|
5069
|
-
|
|
5070
|
-
|
|
5077
|
+
logger.info("init show \u547D\u4EE4\u6267\u884C\uFF0C\u8FDB\u5165\u4EA4\u4E92\u5F0F\u9879\u76EE\u914D\u7F6E");
|
|
5078
|
+
const { key, newValue } = await interactiveConfigEditor(
|
|
5079
|
+
config2,
|
|
5080
|
+
PROJECT_CONFIG_DEFINITIONS,
|
|
5081
|
+
{ selectPrompt: MESSAGES.INIT_SELECT_PROMPT }
|
|
5082
|
+
);
|
|
5083
|
+
const updatedConfig = { ...config2, [key]: newValue };
|
|
5084
|
+
saveProjectConfig(updatedConfig);
|
|
5085
|
+
printSuccess(MESSAGES.INIT_SET_SUCCESS(key, String(newValue)));
|
|
5071
5086
|
}
|
|
5072
5087
|
async function handleInit(options) {
|
|
5073
5088
|
validateMainWorktree();
|
package/dist/postinstall.js
CHANGED
|
@@ -437,7 +437,11 @@ var INIT_MESSAGES = {
|
|
|
437
437
|
/** 项目未初始化(requireProjectConfig 使用) */
|
|
438
438
|
PROJECT_NOT_INITIALIZED: "\u9879\u76EE\u5C1A\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u6267\u884C clawt init \u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F",
|
|
439
439
|
/** 项目配置缺少 clawtMainWorkBranch 字段 */
|
|
440
|
-
PROJECT_CONFIG_MISSING_BRANCH: "\u9879\u76EE\u914D\u7F6E\u7F3A\u5C11\u4E3B\u5DE5\u4F5C\u5206\u652F\u4FE1\u606F\uFF0C\u8BF7\u91CD\u65B0\u6267\u884C clawt init \u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F"
|
|
440
|
+
PROJECT_CONFIG_MISSING_BRANCH: "\u9879\u76EE\u914D\u7F6E\u7F3A\u5C11\u4E3B\u5DE5\u4F5C\u5206\u652F\u4FE1\u606F\uFF0C\u8BF7\u91CD\u65B0\u6267\u884C clawt init \u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F",
|
|
441
|
+
/** init show 交互式面板选择配置项提示 */
|
|
442
|
+
INIT_SELECT_PROMPT: "\u9009\u62E9\u8981\u4FEE\u6539\u7684\u9879\u76EE\u914D\u7F6E\u9879",
|
|
443
|
+
/** init show 交互式面板配置项修改成功 */
|
|
444
|
+
INIT_SET_SUCCESS: (key, value) => `\u2713 \u9879\u76EE\u914D\u7F6E ${key} \u5DF2\u8BBE\u7F6E\u4E3A ${value}`
|
|
441
445
|
};
|
|
442
446
|
|
|
443
447
|
// src/constants/messages/interactive-panel.ts
|
|
@@ -553,6 +557,32 @@ function deriveConfigDescriptions(definitions) {
|
|
|
553
557
|
var DEFAULT_CONFIG = deriveDefaultConfig(CONFIG_DEFINITIONS);
|
|
554
558
|
var CONFIG_DESCRIPTIONS = deriveConfigDescriptions(CONFIG_DEFINITIONS);
|
|
555
559
|
|
|
560
|
+
// src/constants/project-config.ts
|
|
561
|
+
var PROJECT_CONFIG_DEFINITIONS = {
|
|
562
|
+
clawtMainWorkBranch: {
|
|
563
|
+
defaultValue: "",
|
|
564
|
+
description: "\u4E3B worktree \u7684\u5DE5\u4F5C\u5206\u652F\u540D"
|
|
565
|
+
},
|
|
566
|
+
validateRunCommand: {
|
|
567
|
+
defaultValue: void 0,
|
|
568
|
+
description: "validate \u6210\u529F\u540E\u81EA\u52A8\u6267\u884C\u7684\u547D\u4EE4\uFF08-r \u7684\u9ED8\u8BA4\u503C\uFF09"
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
function deriveDefaultConfig2(definitions) {
|
|
572
|
+
const entries = Object.entries(definitions).map(
|
|
573
|
+
([key, def]) => [key, def.defaultValue]
|
|
574
|
+
);
|
|
575
|
+
return Object.fromEntries(entries);
|
|
576
|
+
}
|
|
577
|
+
function deriveConfigDescriptions2(definitions) {
|
|
578
|
+
const entries = Object.entries(definitions).map(
|
|
579
|
+
([key, def]) => [key, def.description]
|
|
580
|
+
);
|
|
581
|
+
return Object.fromEntries(entries);
|
|
582
|
+
}
|
|
583
|
+
var PROJECT_DEFAULT_CONFIG = deriveDefaultConfig2(PROJECT_CONFIG_DEFINITIONS);
|
|
584
|
+
var PROJECT_CONFIG_DESCRIPTIONS = deriveConfigDescriptions2(PROJECT_CONFIG_DEFINITIONS);
|
|
585
|
+
|
|
556
586
|
// src/constants/update.ts
|
|
557
587
|
var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
558
588
|
|
package/docs/config.md
CHANGED
|
@@ -85,7 +85,8 @@ clawt config reset
|
|
|
85
85
|
|
|
86
86
|
- 配置项类型定义:`ConfigItemDefinition` 新增可选字段 `allowedValues`(`readonly string[]`),仅对 string 类型有效,用于枚举值校验和交互式 Select 提示
|
|
87
87
|
- 值解析与提示策略:`src/utils/config-strategy.ts` 中的 `parseConfigValue()`(CLI 字符串解析)和 `promptConfigValue()`(交互式提示),基于类型和 `allowedValues` 自动分发
|
|
88
|
+
- 交互式配置编辑:`handleInteractiveConfigSet` 调用通用的 `interactiveConfigEditor`(`src/utils/config-strategy.ts`),传入 `CONFIG_DEFINITIONS` 和 `disabledKeys`(对象类型配置项禁用映射),不再在 config 命令中直接构建选择列表和调用 `promptConfigValue`
|
|
88
89
|
- `saveConfig(config)`:`src/utils/config.ts` 中新增的通用配置写入函数,将完整配置对象持久化到文件
|
|
89
|
-
- `formatConfigValue(value)`:支持 boolean(绿色/黄色)和 string/number
|
|
90
|
+
- `formatConfigValue(value)`:支持 boolean(绿色/黄色)和 string/number(青色)的格式化显示。`undefined` / `null` 值显示为暗淡色的 `(未设置)`。对象类型配置项(如 `aliases`)在交互式列表中通过 `JSON.stringify` 以暗淡色显示
|
|
90
91
|
|
|
91
92
|
---
|
package/docs/init.md
CHANGED
|
@@ -18,11 +18,11 @@ clawt init show
|
|
|
18
18
|
| 参数/子命令 | 必填 | 说明 |
|
|
19
19
|
| --- | --- | --- |
|
|
20
20
|
| `-b` | 否 | 指定主工作分支名。不传则使用当前分支 |
|
|
21
|
-
| `show` | 否 |
|
|
21
|
+
| `show` | 否 | 交互式查看和修改项目配置 |
|
|
22
22
|
|
|
23
23
|
**功能说明:**
|
|
24
24
|
|
|
25
|
-
初始化项目级配置,将指定分支记录为该项目的主工作分支(`clawtMainWorkBranch`)。该配置用于 `create` / `run`
|
|
25
|
+
初始化项目级配置,将指定分支记录为该项目的主工作分支(`clawtMainWorkBranch`)。该配置用于 `create` / `run` 时检测当前分支是否为主工作分支,并在偏离时提醒用户。`init show` 子命令提供交互式面板,可查看和修改所有项目配置项(如 `validateRunCommand`)。项目级配置的完整说明见 [project-config.md](./project-config.md)。
|
|
26
26
|
|
|
27
27
|
**运行流程(设置模式):**
|
|
28
28
|
|
|
@@ -40,7 +40,12 @@ clawt init show
|
|
|
40
40
|
1. **主 worktree 校验** (2.1)
|
|
41
41
|
2. **读取项目级配置**:读取 `~/.clawt/projects/<projectName>/config.json`
|
|
42
42
|
- 配置不存在 → 抛出错误 `项目尚未初始化,请先执行 clawt init 设置主工作分支`
|
|
43
|
-
- 配置存在 →
|
|
43
|
+
- 配置存在 → 进入交互式面板
|
|
44
|
+
3. **交互式配置编辑**:调用 `interactiveConfigEditor`(`src/utils/config-strategy.ts`),基于 `PROJECT_CONFIG_DEFINITIONS` 构建配置项列表(详见 [project-config.md](./project-config.md))
|
|
45
|
+
- 列出所有项目配置项,显示名称、当前值和描述
|
|
46
|
+
- 用户选择配置项后,根据值类型自动选择输入方式(与全局配置的交互式编辑逻辑一致)
|
|
47
|
+
4. **持久化修改**:将修改后的值合并到当前配置并写入配置文件
|
|
48
|
+
5. **输出成功提示**:`✓ 项目配置 <key> 已设置为 <value>`
|
|
44
49
|
|
|
45
50
|
**输出格式:**
|
|
46
51
|
|
|
@@ -51,10 +56,8 @@ clawt init show
|
|
|
51
56
|
# 更新已有配置
|
|
52
57
|
✓ 已将主工作分支从 develop 更新为 main
|
|
53
58
|
|
|
54
|
-
# show
|
|
55
|
-
|
|
56
|
-
"clawtMainWorkBranch": "main"
|
|
57
|
-
}
|
|
59
|
+
# show 交互式修改成功
|
|
60
|
+
✓ 项目配置 validateRunCommand 已设置为 npm test
|
|
58
61
|
|
|
59
62
|
# show 未初始化(抛出错误)
|
|
60
63
|
项目尚未初始化,请先执行 clawt init 设置主工作分支
|
|
@@ -62,4 +65,10 @@ clawt init show
|
|
|
62
65
|
|
|
63
66
|
**重复执行:** 支持重复执行,后一次覆盖前一次的配置。
|
|
64
67
|
|
|
68
|
+
**实现要点:**
|
|
69
|
+
|
|
70
|
+
- `init show` 子命令从 JSON 展示改为交互式面板,调用 `interactiveConfigEditor`(`src/utils/config-strategy.ts`)实现通用交互式配置编辑
|
|
71
|
+
- 配置项定义来自 `PROJECT_CONFIG_DEFINITIONS`(`src/constants/project-config.ts`),详见 [项目级配置文档](./project-config.md)
|
|
72
|
+
- 消息常量:`MESSAGES.INIT_SELECT_PROMPT`(选择配置项提示语)、`MESSAGES.INIT_SET_SUCCESS`(修改成功提示),定义在 `src/constants/messages/init.ts`
|
|
73
|
+
|
|
65
74
|
---
|