clawt 2.12.1 → 2.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/dist/index.js +314 -58
- package/dist/postinstall.js +49 -3
- package/docs/spec.md +111 -1
- package/package.json +1 -1
- package/src/commands/alias.ts +132 -0
- package/src/commands/config.ts +116 -40
- package/src/constants/config.ts +6 -0
- package/src/constants/index.ts +1 -1
- package/src/constants/messages/alias.ts +22 -0
- package/src/constants/messages/config.ts +22 -0
- package/src/constants/messages/index.ts +2 -0
- package/src/index.ts +7 -1
- package/src/types/config.ts +4 -0
- package/src/utils/alias.ts +20 -0
- package/src/utils/config-strategy.ts +196 -0
- package/src/utils/config.ts +17 -1
- package/src/utils/index.ts +3 -1
- package/tests/unit/commands/alias.test.ts +184 -0
- package/tests/unit/commands/config.test.ts +324 -24
- package/tests/unit/constants/config.test.ts +25 -1
- package/tests/unit/constants/messages.test.ts +28 -0
- package/tests/unit/utils/alias.test.ts +51 -0
- package/tests/unit/utils/config-strategy.test.ts +217 -0
- package/tests/unit/utils/config.test.ts +25 -1
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import Enquirer from 'enquirer';
|
|
3
|
+
import { DEFAULT_CONFIG, CONFIG_DEFINITIONS, MESSAGES } from '../constants/index.js';
|
|
4
|
+
import type { ClawtConfig } from '../types/index.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 校验 key 是否为有效的配置项名称
|
|
8
|
+
* @param {string} key - 待校验的配置项名称
|
|
9
|
+
* @returns {boolean} 是否有效
|
|
10
|
+
*/
|
|
11
|
+
export function isValidConfigKey(key: string): key is keyof ClawtConfig {
|
|
12
|
+
return key in DEFAULT_CONFIG;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 获取所有有效配置项名称列表
|
|
17
|
+
* @returns {string[]} 配置项名称数组
|
|
18
|
+
*/
|
|
19
|
+
export function getValidConfigKeys(): string[] {
|
|
20
|
+
return Object.keys(DEFAULT_CONFIG);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 将字符串值解析并校验为目标配置项的正确类型
|
|
25
|
+
*
|
|
26
|
+
* 策略分发规则:
|
|
27
|
+
* - boolean → 解析 'true'/'false'
|
|
28
|
+
* - number → Number() 解析
|
|
29
|
+
* - string + 有 allowedValues → 枚举校验
|
|
30
|
+
* - string + 无 allowedValues → 无额外校验
|
|
31
|
+
*
|
|
32
|
+
* @param {keyof ClawtConfig} key - 配置项名称
|
|
33
|
+
* @param {string} rawValue - 原始字符串值
|
|
34
|
+
* @returns {{ success: true; value: ClawtConfig[keyof ClawtConfig] } | { success: false; error: string }} 解析结果
|
|
35
|
+
*/
|
|
36
|
+
export function parseConfigValue(
|
|
37
|
+
key: keyof ClawtConfig,
|
|
38
|
+
rawValue: string,
|
|
39
|
+
): { success: true; value: ClawtConfig[keyof ClawtConfig] } | { success: false; error: string } {
|
|
40
|
+
const expectedType = typeof DEFAULT_CONFIG[key];
|
|
41
|
+
|
|
42
|
+
// 布尔类型策略
|
|
43
|
+
if (expectedType === 'boolean') {
|
|
44
|
+
if (rawValue === 'true') return { success: true, value: true };
|
|
45
|
+
if (rawValue === 'false') return { success: true, value: false };
|
|
46
|
+
return { success: false, error: MESSAGES.CONFIG_INVALID_BOOLEAN(key) };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 数字类型策略
|
|
50
|
+
if (expectedType === 'number') {
|
|
51
|
+
const num = Number(rawValue);
|
|
52
|
+
if (Number.isNaN(num)) {
|
|
53
|
+
return { success: false, error: MESSAGES.CONFIG_INVALID_NUMBER(key) };
|
|
54
|
+
}
|
|
55
|
+
return { success: true, value: num };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 字符串类型:根据 allowedValues 自动选择校验策略
|
|
59
|
+
const definition = CONFIG_DEFINITIONS[key];
|
|
60
|
+
if (definition.allowedValues && !definition.allowedValues.includes(rawValue)) {
|
|
61
|
+
return { success: false, error: MESSAGES.CONFIG_INVALID_ENUM(key, definition.allowedValues) };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { success: true, value: rawValue };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 交互式提示用户输入配置值
|
|
69
|
+
*
|
|
70
|
+
* 策略分发规则:
|
|
71
|
+
* - boolean → Select(true, false)
|
|
72
|
+
* - number → Input(带数字校验)
|
|
73
|
+
* - string + 有 allowedValues → Select(枚举列表)
|
|
74
|
+
* - string + 无 allowedValues → Input(自由输入)
|
|
75
|
+
*
|
|
76
|
+
* @param {keyof ClawtConfig} key - 配置项名称
|
|
77
|
+
* @param {ClawtConfig[keyof ClawtConfig]} currentValue - 当前值
|
|
78
|
+
* @returns {Promise<ClawtConfig[keyof ClawtConfig]>} 用户输入/选择的新值
|
|
79
|
+
*/
|
|
80
|
+
export async function promptConfigValue(
|
|
81
|
+
key: keyof ClawtConfig,
|
|
82
|
+
currentValue: ClawtConfig[keyof ClawtConfig],
|
|
83
|
+
): Promise<ClawtConfig[keyof ClawtConfig]> {
|
|
84
|
+
const expectedType = typeof currentValue;
|
|
85
|
+
|
|
86
|
+
// 布尔类型策略
|
|
87
|
+
if (expectedType === 'boolean') {
|
|
88
|
+
return promptBooleanValue(key, currentValue as boolean);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 数字类型策略
|
|
92
|
+
if (expectedType === 'number') {
|
|
93
|
+
return promptNumberValue(key, currentValue as number);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 字符串类型:根据 allowedValues 自动选择提示策略
|
|
97
|
+
const definition = CONFIG_DEFINITIONS[key];
|
|
98
|
+
if (definition.allowedValues) {
|
|
99
|
+
return promptEnumValue(key, currentValue as string, definition.allowedValues);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return promptStringValue(key, currentValue as string);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 格式化配置值的显示样式
|
|
107
|
+
* @param {ClawtConfig[keyof ClawtConfig]} value - 配置值
|
|
108
|
+
* @returns {string} 格式化后的字符串
|
|
109
|
+
*/
|
|
110
|
+
export function formatConfigValue(value: ClawtConfig[keyof ClawtConfig]): string {
|
|
111
|
+
if (typeof value === 'boolean') {
|
|
112
|
+
return value ? chalk.green('true') : chalk.yellow('false');
|
|
113
|
+
}
|
|
114
|
+
return chalk.cyan(String(value));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 交互式布尔值选择(内部辅助函数)
|
|
119
|
+
* @param {keyof ClawtConfig} key - 配置项名称
|
|
120
|
+
* @param {boolean} currentValue - 当前值
|
|
121
|
+
* @returns {Promise<boolean>} 用户选择的布尔值
|
|
122
|
+
*/
|
|
123
|
+
async function promptBooleanValue(key: keyof ClawtConfig, currentValue: boolean): Promise<boolean> {
|
|
124
|
+
const choices = [
|
|
125
|
+
{ name: 'true', message: 'true' },
|
|
126
|
+
{ name: 'false', message: 'false' },
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
// @ts-expect-error enquirer 类型声明未导出 Select 类,但运行时存在
|
|
130
|
+
const selected: string = await new Enquirer.Select({
|
|
131
|
+
message: MESSAGES.CONFIG_INPUT_PROMPT(key),
|
|
132
|
+
choices,
|
|
133
|
+
initial: currentValue ? 0 : 1,
|
|
134
|
+
}).run();
|
|
135
|
+
|
|
136
|
+
return selected === 'true';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 交互式数字输入(内部辅助函数)
|
|
141
|
+
* @param {keyof ClawtConfig} key - 配置项名称
|
|
142
|
+
* @param {number} currentValue - 当前值
|
|
143
|
+
* @returns {Promise<number>} 用户输入的数字值
|
|
144
|
+
*/
|
|
145
|
+
async function promptNumberValue(key: keyof ClawtConfig, currentValue: number): Promise<number> {
|
|
146
|
+
// @ts-expect-error enquirer 类型声明未导出 Input 类,但运行时存在
|
|
147
|
+
const input: string = await new Enquirer.Input({
|
|
148
|
+
message: MESSAGES.CONFIG_INPUT_PROMPT(key),
|
|
149
|
+
initial: String(currentValue),
|
|
150
|
+
validate: (val: string) => {
|
|
151
|
+
if (Number.isNaN(Number(val))) return '请输入有效的数字';
|
|
152
|
+
return true;
|
|
153
|
+
},
|
|
154
|
+
}).run();
|
|
155
|
+
|
|
156
|
+
return Number(input);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* 交互式枚举值选择(内部辅助函数,用于有 allowedValues 的 string 配置项)
|
|
161
|
+
* @param {keyof ClawtConfig} key - 配置项名称
|
|
162
|
+
* @param {string} currentValue - 当前值
|
|
163
|
+
* @param {readonly string[]} allowedValues - 允许的枚举值列表
|
|
164
|
+
* @returns {Promise<string>} 用户选择的枚举值
|
|
165
|
+
*/
|
|
166
|
+
async function promptEnumValue(
|
|
167
|
+
key: keyof ClawtConfig,
|
|
168
|
+
currentValue: string,
|
|
169
|
+
allowedValues: readonly string[],
|
|
170
|
+
): Promise<string> {
|
|
171
|
+
const choices = allowedValues.map((v) => ({
|
|
172
|
+
name: v,
|
|
173
|
+
message: v,
|
|
174
|
+
}));
|
|
175
|
+
|
|
176
|
+
// @ts-expect-error enquirer 类型声明未导出 Select 类,但运行时存在
|
|
177
|
+
return await new Enquirer.Select({
|
|
178
|
+
message: MESSAGES.CONFIG_INPUT_PROMPT(key),
|
|
179
|
+
choices,
|
|
180
|
+
initial: allowedValues.indexOf(currentValue),
|
|
181
|
+
}).run();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 交互式字符串自由输入(内部辅助函数,用于无 allowedValues 的 string 配置项)
|
|
186
|
+
* @param {keyof ClawtConfig} key - 配置项名称
|
|
187
|
+
* @param {string} currentValue - 当前值
|
|
188
|
+
* @returns {Promise<string>} 用户输入的字符串值
|
|
189
|
+
*/
|
|
190
|
+
async function promptStringValue(key: keyof ClawtConfig, currentValue: string): Promise<string> {
|
|
191
|
+
// @ts-expect-error enquirer 类型声明未导出 Input 类,但运行时存在
|
|
192
|
+
return await new Enquirer.Input({
|
|
193
|
+
message: MESSAGES.CONFIG_INPUT_PROMPT(key),
|
|
194
|
+
initial: currentValue,
|
|
195
|
+
}).run();
|
|
196
|
+
}
|
package/src/utils/config.ts
CHANGED
|
@@ -24,11 +24,27 @@ export function loadConfig(): ClawtConfig {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* 将指定配置对象写入配置文件
|
|
29
|
+
* @param {ClawtConfig} config - 要写入的完整配置对象
|
|
30
|
+
*/
|
|
31
|
+
export function writeConfig(config: ClawtConfig): void {
|
|
32
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');
|
|
33
|
+
}
|
|
34
|
+
|
|
27
35
|
/**
|
|
28
36
|
* 将默认配置写入配置文件
|
|
29
37
|
*/
|
|
30
38
|
export function writeDefaultConfig(): void {
|
|
31
|
-
|
|
39
|
+
writeConfig(DEFAULT_CONFIG);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 将配置对象完整写入配置文件
|
|
44
|
+
* @param {ClawtConfig} config - 要持久化的配置对象
|
|
45
|
+
*/
|
|
46
|
+
export function saveConfig(config: ClawtConfig): void {
|
|
47
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');
|
|
32
48
|
}
|
|
33
49
|
|
|
34
50
|
/**
|
package/src/utils/index.ts
CHANGED
|
@@ -48,7 +48,7 @@ export {
|
|
|
48
48
|
export { sanitizeBranchName, generateBranchNames, validateBranchesNotExist } from './branch.js';
|
|
49
49
|
export { validateMainWorktree, validateGitInstalled, validateClaudeCodeInstalled } from './validation.js';
|
|
50
50
|
export { createWorktrees, getProjectWorktrees, getProjectWorktreeDir, cleanupWorktrees, getWorktreeStatus, createWorktreesByBranches } from './worktree.js';
|
|
51
|
-
export { loadConfig, writeDefaultConfig, getConfigValue, ensureClawtDirs } from './config.js';
|
|
51
|
+
export { loadConfig, writeDefaultConfig, writeConfig, saveConfig, getConfigValue, ensureClawtDirs } from './config.js';
|
|
52
52
|
export { printSuccess, printError, printWarning, printInfo, printSeparator, printDoubleSeparator, confirmAction, confirmDestructiveAction, formatWorktreeStatus, isWorktreeIdle, formatDuration } from './formatter.js';
|
|
53
53
|
export { ensureDir, removeEmptyDir } from './fs.js';
|
|
54
54
|
export { multilineInput } from './prompt.js';
|
|
@@ -60,4 +60,6 @@ export { ProgressRenderer } from './progress.js';
|
|
|
60
60
|
export { parseTaskFile, loadTaskFile } from './task-file.js';
|
|
61
61
|
export { executeBatchTasks } from './task-executor.js';
|
|
62
62
|
export { detectTerminalApp, openCommandInNewTerminalTab } from './terminal.js';
|
|
63
|
+
export { applyAliases } from './alias.js';
|
|
64
|
+
export { isValidConfigKey, getValidConfigKeys, parseConfigValue, promptConfigValue, formatConfigValue } from './config-strategy.js';
|
|
63
65
|
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
|
|
4
|
+
// mock 依赖模块
|
|
5
|
+
vi.mock('../../../src/logger/index.js', () => ({
|
|
6
|
+
logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
vi.mock('../../../src/utils/index.js', () => ({
|
|
10
|
+
loadConfig: vi.fn(),
|
|
11
|
+
writeConfig: vi.fn(),
|
|
12
|
+
printInfo: vi.fn(),
|
|
13
|
+
printSuccess: vi.fn(),
|
|
14
|
+
printError: vi.fn(),
|
|
15
|
+
printSeparator: vi.fn(),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
vi.mock('../../../src/constants/index.js', () => ({
|
|
19
|
+
MESSAGES: {
|
|
20
|
+
ALIAS_LIST_EMPTY: '(无别名)',
|
|
21
|
+
ALIAS_LIST_TITLE: '当前别名列表:',
|
|
22
|
+
ALIAS_SET_SUCCESS: (a: string, c: string) => `✓ 已设置别名: ${a} → ${c}`,
|
|
23
|
+
ALIAS_REMOVE_SUCCESS: (a: string) => `✓ 已移除别名: ${a}`,
|
|
24
|
+
ALIAS_NOT_FOUND: (a: string) => `别名 "${a}" 不存在`,
|
|
25
|
+
ALIAS_CONFLICTS_BUILTIN: (a: string) => `别名 "${a}" 与内置命令冲突,不允许覆盖内置命令`,
|
|
26
|
+
ALIAS_TARGET_NOT_FOUND: (c: string) => `目标命令 "${c}" 不存在,请指定已注册的内置命令名`,
|
|
27
|
+
},
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
import { registerAliasCommand } from '../../../src/commands/alias.js';
|
|
31
|
+
import { loadConfig, writeConfig, printInfo, printSuccess, printError } from '../../../src/utils/index.js';
|
|
32
|
+
|
|
33
|
+
const mockedLoadConfig = vi.mocked(loadConfig);
|
|
34
|
+
const mockedWriteConfig = vi.mocked(writeConfig);
|
|
35
|
+
const mockedPrintInfo = vi.mocked(printInfo);
|
|
36
|
+
const mockedPrintSuccess = vi.mocked(printSuccess);
|
|
37
|
+
const mockedPrintError = vi.mocked(printError);
|
|
38
|
+
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
vi.clearAllMocks();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/** 构造默认配置 mock 数据 */
|
|
44
|
+
function mockDefaultConfig(aliases: Record<string, string> = {}) {
|
|
45
|
+
return {
|
|
46
|
+
autoDeleteBranch: false,
|
|
47
|
+
claudeCodeCommand: 'claude',
|
|
48
|
+
autoPullPush: false,
|
|
49
|
+
confirmDestructiveOps: true,
|
|
50
|
+
maxConcurrency: 0,
|
|
51
|
+
terminalApp: 'auto',
|
|
52
|
+
aliases,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
describe('registerAliasCommand', () => {
|
|
57
|
+
it('注册 alias 命令及 list/set/remove 子命令', () => {
|
|
58
|
+
const program = new Command();
|
|
59
|
+
registerAliasCommand(program);
|
|
60
|
+
const aliasCmd = program.commands.find((c) => c.name() === 'alias');
|
|
61
|
+
expect(aliasCmd).toBeDefined();
|
|
62
|
+
expect(aliasCmd!.commands.find((c) => c.name() === 'list')).toBeDefined();
|
|
63
|
+
expect(aliasCmd!.commands.find((c) => c.name() === 'set')).toBeDefined();
|
|
64
|
+
expect(aliasCmd!.commands.find((c) => c.name() === 'remove')).toBeDefined();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('alias list(通过 action 间接测试)', () => {
|
|
69
|
+
it('无别名时展示空提示', () => {
|
|
70
|
+
mockedLoadConfig.mockReturnValue(mockDefaultConfig());
|
|
71
|
+
|
|
72
|
+
const program = new Command();
|
|
73
|
+
program.exitOverride();
|
|
74
|
+
registerAliasCommand(program);
|
|
75
|
+
program.parse(['alias'], { from: 'user' });
|
|
76
|
+
|
|
77
|
+
expect(mockedPrintInfo).toHaveBeenCalledWith('(无别名)');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('通过 alias list 子命令展示别名列表', () => {
|
|
81
|
+
mockedLoadConfig.mockReturnValue(mockDefaultConfig({ ls: 'list', rm: 'remove' }));
|
|
82
|
+
|
|
83
|
+
const program = new Command();
|
|
84
|
+
program.exitOverride();
|
|
85
|
+
registerAliasCommand(program);
|
|
86
|
+
program.parse(['alias', 'list'], { from: 'user' });
|
|
87
|
+
|
|
88
|
+
// 应展示列表标题
|
|
89
|
+
expect(mockedPrintInfo).toHaveBeenCalled();
|
|
90
|
+
const calls = mockedPrintInfo.mock.calls.map((c) => c[0]);
|
|
91
|
+
// 至少有一个调用包含别名信息
|
|
92
|
+
expect(calls.some((c) => typeof c === 'string' && c.includes('ls'))).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('alias set(通过 action 间接测试)', () => {
|
|
97
|
+
it('别名与内置命令冲突时报错', () => {
|
|
98
|
+
const program = new Command();
|
|
99
|
+
program.exitOverride();
|
|
100
|
+
program.command('list').action(() => {});
|
|
101
|
+
registerAliasCommand(program);
|
|
102
|
+
program.parse(['alias', 'set', 'list', 'create'], { from: 'user' });
|
|
103
|
+
|
|
104
|
+
expect(mockedPrintError).toHaveBeenCalled();
|
|
105
|
+
expect(mockedWriteConfig).not.toHaveBeenCalled();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('目标命令不存在时报错', () => {
|
|
109
|
+
mockedLoadConfig.mockReturnValue(mockDefaultConfig());
|
|
110
|
+
|
|
111
|
+
const program = new Command();
|
|
112
|
+
program.exitOverride();
|
|
113
|
+
registerAliasCommand(program);
|
|
114
|
+
program.parse(['alias', 'set', 'ls', 'nonexistent'], { from: 'user' });
|
|
115
|
+
|
|
116
|
+
expect(mockedPrintError).toHaveBeenCalled();
|
|
117
|
+
expect(mockedWriteConfig).not.toHaveBeenCalled();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('正常设置别名', () => {
|
|
121
|
+
mockedLoadConfig.mockReturnValue(mockDefaultConfig());
|
|
122
|
+
|
|
123
|
+
const program = new Command();
|
|
124
|
+
program.exitOverride();
|
|
125
|
+
program.command('list').action(() => {});
|
|
126
|
+
registerAliasCommand(program);
|
|
127
|
+
program.parse(['alias', 'set', 'ls', 'list'], { from: 'user' });
|
|
128
|
+
|
|
129
|
+
expect(mockedWriteConfig).toHaveBeenCalledWith(
|
|
130
|
+
expect.objectContaining({
|
|
131
|
+
aliases: { ls: 'list' },
|
|
132
|
+
}),
|
|
133
|
+
);
|
|
134
|
+
expect(mockedPrintSuccess).toHaveBeenCalled();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('覆盖已有别名', () => {
|
|
138
|
+
mockedLoadConfig.mockReturnValue(mockDefaultConfig({ ls: 'list' }));
|
|
139
|
+
|
|
140
|
+
const program = new Command();
|
|
141
|
+
program.exitOverride();
|
|
142
|
+
program.command('list').action(() => {});
|
|
143
|
+
program.command('status').action(() => {});
|
|
144
|
+
registerAliasCommand(program);
|
|
145
|
+
program.parse(['alias', 'set', 'ls', 'status'], { from: 'user' });
|
|
146
|
+
|
|
147
|
+
expect(mockedWriteConfig).toHaveBeenCalledWith(
|
|
148
|
+
expect.objectContaining({
|
|
149
|
+
aliases: { ls: 'status' },
|
|
150
|
+
}),
|
|
151
|
+
);
|
|
152
|
+
expect(mockedPrintSuccess).toHaveBeenCalled();
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe('alias remove(通过 action 间接测试)', () => {
|
|
157
|
+
it('别名不存在时报错', () => {
|
|
158
|
+
mockedLoadConfig.mockReturnValue(mockDefaultConfig());
|
|
159
|
+
|
|
160
|
+
const program = new Command();
|
|
161
|
+
program.exitOverride();
|
|
162
|
+
registerAliasCommand(program);
|
|
163
|
+
program.parse(['alias', 'remove', 'nonexistent'], { from: 'user' });
|
|
164
|
+
|
|
165
|
+
expect(mockedPrintError).toHaveBeenCalled();
|
|
166
|
+
expect(mockedWriteConfig).not.toHaveBeenCalled();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('正常移除别名', () => {
|
|
170
|
+
mockedLoadConfig.mockReturnValue(mockDefaultConfig({ ls: 'list' }));
|
|
171
|
+
|
|
172
|
+
const program = new Command();
|
|
173
|
+
program.exitOverride();
|
|
174
|
+
registerAliasCommand(program);
|
|
175
|
+
program.parse(['alias', 'remove', 'ls'], { from: 'user' });
|
|
176
|
+
|
|
177
|
+
expect(mockedWriteConfig).toHaveBeenCalledWith(
|
|
178
|
+
expect.objectContaining({
|
|
179
|
+
aliases: {},
|
|
180
|
+
}),
|
|
181
|
+
);
|
|
182
|
+
expect(mockedPrintSuccess).toHaveBeenCalled();
|
|
183
|
+
});
|
|
184
|
+
});
|