@icode-js/icode 3.0.4 → 3.2.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/LICENSE +21 -0
- package/README.md +142 -5
- package/bin/icode.js +4 -4
- package/package.json +1 -1
- package/src/cli.js +60 -23
- package/src/commands/ai.js +4 -4
- package/src/commands/checkout.js +2 -2
- package/src/commands/clean.js +5 -2
- package/src/commands/codereview.js +5 -2
- package/src/commands/completion.js +93 -0
- package/src/commands/config.js +123 -10
- package/src/commands/doctor.js +80 -0
- package/src/commands/explain.js +6 -3
- package/src/commands/help.js +17 -0
- package/src/commands/info.js +5 -2
- package/src/commands/migrate.js +40 -3
- package/src/commands/push.js +72 -24
- package/src/commands/ready.js +110 -0
- package/src/commands/save.js +98 -0
- package/src/commands/sync.js +2 -2
- package/src/commands/tag.js +2 -2
- package/src/commands/undo.js +73 -10
- package/src/core/{ai-client.js → ai/client.js} +3 -3
- package/src/core/{ai-config.js → ai/config.js} +2 -2
- package/src/core/ai/diff-prompt.js +204 -0
- package/src/core/{ai-diff-range.js → ai/diff-range.js} +2 -2
- package/src/core/{args.js → cli/args.js} +23 -2
- package/src/core/cli/completion.js +720 -0
- package/src/core/cli/update-notifier.js +138 -0
- package/src/core/{shell.js → commands/shell.js} +13 -1
- package/src/core/{config-store.js → config/store.js} +53 -3
- package/src/core/{git-context.js → git/context.js} +24 -10
- package/src/core/git/preconditions.js +88 -0
- package/src/core/{git-service.js → git/service.js} +195 -5
- package/src/core/remote-merge/client.js +42 -0
- package/src/core/remote-merge/providers/gitlab.js +596 -0
- package/src/core/remote-merge/shared.js +49 -0
- package/src/core/tools/interactive.js +368 -0
- package/src/core/{loading.js → tools/loading.js} +5 -1
- package/src/core/{logger.js → tools/logger.js} +5 -0
- package/src/workflows/ai-codereview-workflow.js +18 -6
- package/src/workflows/ai-commit-workflow.js +20 -12
- package/src/workflows/ai-conflict-workflow.js +4 -4
- package/src/workflows/ai-explain-workflow.js +4 -4
- package/src/workflows/ai-risk-review-workflow.js +1 -1
- package/src/workflows/checkout-workflow.js +17 -5
- package/src/workflows/clean-workflow.js +92 -75
- package/src/workflows/doctor-workflow.js +131 -0
- package/src/workflows/info-workflow.js +4 -4
- package/src/workflows/migrate-workflow.js +269 -62
- package/src/workflows/push-workflow.js +475 -58
- package/src/workflows/ready-workflow.js +278 -0
- package/src/workflows/rollback-workflow.js +24 -9
- package/src/workflows/save-workflow.js +152 -0
- package/src/workflows/sync-workflow.js +22 -6
- package/src/workflows/tag-workflow.js +4 -4
- package/src/workflows/undo-workflow.js +37 -12
- package/src/core/prompts.js +0 -152
- /package/src/core/{ai-commit-summary.js → ai/commit-summary.js} +0 -0
- /package/src/core/{commit-conventions.js → git/commit-conventions.js} +0 -0
- /package/src/core/{hook-diagnostics.js → git/hook-diagnostics.js} +0 -0
package/src/commands/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'node:path'
|
|
2
2
|
import { parseArgs } from 'node:util'
|
|
3
|
-
import { askAi } from '../core/ai
|
|
3
|
+
import { askAi } from '../core/ai/client.js'
|
|
4
4
|
import {
|
|
5
5
|
getAiCommandOptions,
|
|
6
6
|
getAiProfileForDisplay,
|
|
@@ -11,28 +11,31 @@ import {
|
|
|
11
11
|
upsertAiCommandOptions,
|
|
12
12
|
upsertAiProfile,
|
|
13
13
|
useAiProfile
|
|
14
|
-
} from '../core/ai
|
|
14
|
+
} from '../core/ai/config.js'
|
|
15
15
|
import {
|
|
16
16
|
deleteValue,
|
|
17
17
|
getConfigFilePath,
|
|
18
|
+
getPlatformConfig,
|
|
18
19
|
getRepoPolicy,
|
|
19
20
|
getValue,
|
|
20
21
|
readConfig,
|
|
22
|
+
setPlatformConfig,
|
|
21
23
|
setRepoPolicy,
|
|
22
24
|
setValue
|
|
23
|
-
} from '../core/config
|
|
24
|
-
import { parseConfigValue } from '../core/args.js'
|
|
25
|
+
} from '../core/config/store.js'
|
|
26
|
+
import { normalizeLegacyArgs, parseConfigValue } from '../core/cli/args.js'
|
|
25
27
|
import { IcodeError } from '../core/errors.js'
|
|
26
|
-
import { resolveGitContext } from '../core/git
|
|
27
|
-
import { logger } from '../core/logger.js'
|
|
28
|
+
import { resolveGitContext } from '../core/git/context.js'
|
|
29
|
+
import { logger } from '../core/tools/logger.js'
|
|
28
30
|
|
|
31
|
+
// 输出 config 命令帮助,并展示当前支持的远程合并配置方式。
|
|
29
32
|
function printHelp() {
|
|
30
33
|
process.stdout.write(`
|
|
31
34
|
Usage:
|
|
32
35
|
icode config <command> [...args]
|
|
33
36
|
|
|
34
37
|
Arguments:
|
|
35
|
-
<command> 子命令(list/get/set/delete/ai/protect)
|
|
38
|
+
<command> 子命令(list/get/set/delete/ai/protect/platform)
|
|
36
39
|
|
|
37
40
|
Commands:
|
|
38
41
|
list 查看全部配置
|
|
@@ -43,6 +46,7 @@ Commands:
|
|
|
43
46
|
protect list 查看受保护分支
|
|
44
47
|
protect add <branch...> 添加受保护分支
|
|
45
48
|
protect remove <branch...> 移除受保护分支
|
|
49
|
+
platform <subcommand> 平台级配置(如远程合并密钥)
|
|
46
50
|
|
|
47
51
|
Options:
|
|
48
52
|
--repo-mode <mode> 仓库模式: auto(自动继承父仓库) | strict(禁止继承),仅影响 protect
|
|
@@ -52,11 +56,112 @@ Examples:
|
|
|
52
56
|
icode config set defaults.repoMode strict
|
|
53
57
|
icode config get defaults.repoMode
|
|
54
58
|
icode config protect add main release
|
|
59
|
+
icode config platform remote-merge set --provider gitlab --api-key rm_xxx
|
|
55
60
|
icode config ai set openai --format openai --base-url https://api.openai.com/v1 --api-key sk-xxx --model gpt-4o-mini --activate
|
|
56
61
|
icode config ai set ollama --format ollama --base-url http://127.0.0.1:11434 --model qwen2.5:7b --activate
|
|
57
62
|
`)
|
|
58
63
|
}
|
|
59
64
|
|
|
65
|
+
function maskSecret(secret) {
|
|
66
|
+
const raw = String(secret || '').trim()
|
|
67
|
+
if (!raw) {
|
|
68
|
+
return ''
|
|
69
|
+
}
|
|
70
|
+
if (raw.length <= 8) {
|
|
71
|
+
return `${raw.slice(0, 2)}****${raw.slice(-1)}`
|
|
72
|
+
}
|
|
73
|
+
return `${raw.slice(0, 4)}****${raw.slice(-4)}`
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 输出 platform 子命令帮助,明确 remote-merge 仅需平台名与密钥。
|
|
77
|
+
function printPlatformHelp() {
|
|
78
|
+
process.stdout.write(`
|
|
79
|
+
Usage:
|
|
80
|
+
icode config platform <subcommand> [options]
|
|
81
|
+
|
|
82
|
+
Subcommands:
|
|
83
|
+
remote-merge show
|
|
84
|
+
remote-merge set --provider gitlab --api-key <key>
|
|
85
|
+
|
|
86
|
+
Examples:
|
|
87
|
+
icode config platform remote-merge show
|
|
88
|
+
icode config platform remote-merge set --provider gitlab --api-key rm_xxx
|
|
89
|
+
`)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 处理平台级远程合并配置,当前只接受 GitLab 平台密钥写入。
|
|
93
|
+
async function runPlatformConfigCommand(args) {
|
|
94
|
+
if (!args.length || args[0] === '-h' || args[0] === '--help') {
|
|
95
|
+
printPlatformHelp()
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const [domain, action, ...rest] = args
|
|
100
|
+
if (domain !== 'remote-merge') {
|
|
101
|
+
throw new IcodeError(`未知 platform 子命令: ${domain}`, {
|
|
102
|
+
code: 'CONFIG_PLATFORM_UNKNOWN_SUBCOMMAND',
|
|
103
|
+
exitCode: 2
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (action === 'show') {
|
|
108
|
+
const config = getPlatformConfig('remoteMerge')
|
|
109
|
+
process.stdout.write(`${JSON.stringify({
|
|
110
|
+
provider: config.provider || '',
|
|
111
|
+
hasApiKey: Boolean(config.apiKey),
|
|
112
|
+
apiKeyMasked: maskSecret(config.apiKey)
|
|
113
|
+
}, null, 2)}\n`)
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (action === 'set') {
|
|
118
|
+
const parsed = parseArgs({
|
|
119
|
+
args: rest,
|
|
120
|
+
allowPositionals: true,
|
|
121
|
+
options: {
|
|
122
|
+
provider: { type: 'string' },
|
|
123
|
+
'api-key': { type: 'string' }
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
const provider = String(parsed.values.provider || '').trim()
|
|
128
|
+
const apiKey = String(parsed.values['api-key'] || '').trim()
|
|
129
|
+
|
|
130
|
+
if (!provider) {
|
|
131
|
+
throw new IcodeError('缺少 --provider,示例: icode config platform remote-merge set --provider gitlab --api-key xxx', {
|
|
132
|
+
code: 'CONFIG_PLATFORM_PROVIDER_REQUIRED',
|
|
133
|
+
exitCode: 2
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!apiKey) {
|
|
138
|
+
throw new IcodeError('缺少 --api-key,示例: icode config platform remote-merge set --provider gitlab --api-key xxx', {
|
|
139
|
+
code: 'CONFIG_PLATFORM_API_KEY_REQUIRED',
|
|
140
|
+
exitCode: 2
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (provider !== 'gitlab') {
|
|
145
|
+
throw new IcodeError(`platform remote-merge 当前仅支持 gitlab,收到 provider=${provider}`, {
|
|
146
|
+
code: 'CONFIG_PLATFORM_PROVIDER_UNSUPPORTED',
|
|
147
|
+
exitCode: 2
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
setPlatformConfig('remoteMerge', {
|
|
152
|
+
provider,
|
|
153
|
+
apiKey
|
|
154
|
+
})
|
|
155
|
+
logger.success(`平台远程合并密钥已写入: ${provider}`)
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
throw new IcodeError('platform remote-merge 仅支持: show | set', {
|
|
160
|
+
code: 'CONFIG_PLATFORM_INVALID_ACTION',
|
|
161
|
+
exitCode: 2
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
60
165
|
function printAiHelp() {
|
|
61
166
|
process.stdout.write(`
|
|
62
167
|
Usage:
|
|
@@ -380,14 +485,22 @@ async function resolvePolicyRoot(repoMode) {
|
|
|
380
485
|
}
|
|
381
486
|
}
|
|
382
487
|
|
|
488
|
+
// 执行 config 子命令,输入原始参数,输出配置读写结果;参数会先归一化。
|
|
383
489
|
export async function runConfigCommand(rawArgs) {
|
|
384
|
-
|
|
385
|
-
|
|
490
|
+
const args = normalizeLegacyArgs(rawArgs)
|
|
491
|
+
|
|
492
|
+
if (args[0] === 'ai') {
|
|
493
|
+
await runAiConfigCommand(args.slice(1))
|
|
494
|
+
return
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (args[0] === 'platform') {
|
|
498
|
+
await runPlatformConfigCommand(args.slice(1))
|
|
386
499
|
return
|
|
387
500
|
}
|
|
388
501
|
|
|
389
502
|
const parsed = parseArgs({
|
|
390
|
-
args
|
|
503
|
+
args,
|
|
391
504
|
allowPositionals: true,
|
|
392
505
|
options: {
|
|
393
506
|
help: { type: 'boolean', short: 'h', default: false },
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { parseArgs } from 'node:util'
|
|
2
|
+
import { normalizeLegacyArgs } from '../core/cli/args.js'
|
|
3
|
+
import { logger } from '../core/tools/logger.js'
|
|
4
|
+
import { runDoctorWorkflow } from '../workflows/doctor-workflow.js'
|
|
5
|
+
|
|
6
|
+
// 输出 doctor 子命令帮助,输入为空,输出标准帮助文本。
|
|
7
|
+
function printHelp() {
|
|
8
|
+
process.stdout.write(`
|
|
9
|
+
Usage:
|
|
10
|
+
icode doctor [options]
|
|
11
|
+
|
|
12
|
+
Options:
|
|
13
|
+
--repo-mode <mode> 仓库模式: auto(自动继承父仓库) | strict(禁止继承)
|
|
14
|
+
--json 输出机器可读 JSON
|
|
15
|
+
-h, --help 查看帮助
|
|
16
|
+
|
|
17
|
+
Notes:
|
|
18
|
+
检查 Git、origin、hooks、AI profile 与远程合并配置,并给出修复建议。
|
|
19
|
+
`)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 根据检查状态选择输出函数,输入状态,输出 logger 方法。
|
|
23
|
+
function resolveLogMethod(status) {
|
|
24
|
+
if (status === 'fail') {
|
|
25
|
+
return logger.error
|
|
26
|
+
}
|
|
27
|
+
if (status === 'warn') {
|
|
28
|
+
return logger.warn
|
|
29
|
+
}
|
|
30
|
+
return logger.success
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 输出单条 doctor 检查结果,输入检查项,输出彩色日志。
|
|
34
|
+
function printCheck(check) {
|
|
35
|
+
const log = resolveLogMethod(check.status).bind(logger)
|
|
36
|
+
const statusLabel = check.status.toUpperCase()
|
|
37
|
+
log(`[${statusLabel}] ${check.title}: ${check.detail}`)
|
|
38
|
+
if (check.suggestion) {
|
|
39
|
+
logger.info(`建议: ${check.suggestion}`)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 执行 doctor 子命令,输入原始参数,输出健康检查结果。
|
|
44
|
+
// 执行 doctor 子命令,输入原始参数,输出健康检查结果;参数会先归一化。
|
|
45
|
+
export async function runDoctorCommand(rawArgs) {
|
|
46
|
+
const args = normalizeLegacyArgs(rawArgs)
|
|
47
|
+
const parsed = parseArgs({
|
|
48
|
+
args,
|
|
49
|
+
allowPositionals: true,
|
|
50
|
+
options: {
|
|
51
|
+
'repo-mode': { type: 'string', default: 'auto' },
|
|
52
|
+
json: { type: 'boolean', default: false },
|
|
53
|
+
help: { type: 'boolean', short: 'h', default: false }
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
if (parsed.values.help) {
|
|
58
|
+
printHelp()
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const result = await runDoctorWorkflow({
|
|
63
|
+
repoMode: parsed.values['repo-mode']
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
if (parsed.values.json) {
|
|
67
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`)
|
|
68
|
+
return result
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
logger.info(`仓库根目录: ${result.context.topLevelPath}`)
|
|
72
|
+
logger.info(`配置文件: ${result.configPath}`)
|
|
73
|
+
result.checks.forEach((check) => {
|
|
74
|
+
printCheck(check)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const summary = result.summary
|
|
78
|
+
logger.info(`doctor 完成: pass=${summary.pass}, warn=${summary.warn}, fail=${summary.fail}`)
|
|
79
|
+
return result
|
|
80
|
+
}
|
package/src/commands/explain.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseArgs } from 'node:util'
|
|
2
|
-
import { getAiCommandOptions } from '../core/ai
|
|
3
|
-
import {
|
|
2
|
+
import { getAiCommandOptions } from '../core/ai/config.js'
|
|
3
|
+
import { normalizeLegacyArgs } from '../core/cli/args.js'
|
|
4
|
+
import { logger } from '../core/tools/logger.js'
|
|
4
5
|
import { runAiExplainWorkflow } from '../workflows/ai-explain-workflow.js'
|
|
5
6
|
|
|
6
7
|
function printHelp() {
|
|
@@ -47,10 +48,12 @@ function resolveStringOption(cliValue, configValue, fallback = '') {
|
|
|
47
48
|
return fallback
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
// 执行 explain 子命令,输入原始参数,输出 AI 对 diff 的解释;参数会先归一化。
|
|
50
52
|
export async function runExplainCommand(rawArgs) {
|
|
51
53
|
const scopedOptions = getAiCommandOptions('explain')
|
|
54
|
+
const args = normalizeLegacyArgs(rawArgs)
|
|
52
55
|
const parsed = parseArgs({
|
|
53
|
-
args
|
|
56
|
+
args,
|
|
54
57
|
allowPositionals: true,
|
|
55
58
|
options: {
|
|
56
59
|
base: { type: 'string' },
|
package/src/commands/help.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 输出主帮助文本,集中展示命令总览与常用示例。
|
|
3
|
+
* 输入为空,输出为标准输出文本。
|
|
4
|
+
*/
|
|
1
5
|
export function printMainHelp() {
|
|
2
6
|
process.stdout.write(`
|
|
3
7
|
icode v3 - Git workflow CLI
|
|
@@ -9,6 +13,8 @@ Commands:
|
|
|
9
13
|
ai AI 助手能力(提交信息/冲突建议/代码评审)
|
|
10
14
|
codereview AI 代码评审(不带 ai 前缀)
|
|
11
15
|
checkout 切换/创建分支(本地/远程自动识别)
|
|
16
|
+
ready 检查仓库是否适合继续 save/push(适合 AI agent 读取)
|
|
17
|
+
save 安全快速提交本地改动(检查 + add + commit)
|
|
12
18
|
push 提交并推送,可合并到多个目标分支
|
|
13
19
|
sync 批量同步分支(fetch + pull)
|
|
14
20
|
clean 清理已合并分支(可删远程)
|
|
@@ -16,8 +22,10 @@ Commands:
|
|
|
16
22
|
migrate 迁移分支提交(cherry-pick)
|
|
17
23
|
tag 创建并推送 tag(支持自动命名)
|
|
18
24
|
config 查看和修改本地配置(含 AI profile)
|
|
25
|
+
doctor 检查 Git/AI/远程合并配置健康状态
|
|
19
26
|
explain AI 解释 Git diff(自然语言)
|
|
20
27
|
info 查看当前 git 与配置环境
|
|
28
|
+
completion 生成 bash/zsh tab 补全脚本
|
|
21
29
|
help 查看帮助(命令总览)
|
|
22
30
|
|
|
23
31
|
Global options:
|
|
@@ -29,6 +37,11 @@ Tips:
|
|
|
29
37
|
|
|
30
38
|
Examples:
|
|
31
39
|
icode checkout feature/login main --push-origin
|
|
40
|
+
icode ready
|
|
41
|
+
icode ready --json
|
|
42
|
+
icode save -m "feat: add login form"
|
|
43
|
+
icode save --ai-commit -y
|
|
44
|
+
icode save -m "fix: adjust copy" --dry-run
|
|
32
45
|
icode ai commit --apply -y
|
|
33
46
|
icode ai codereview
|
|
34
47
|
icode codereview --base origin/main --head HEAD
|
|
@@ -36,14 +49,18 @@ Examples:
|
|
|
36
49
|
icode push release test -m "feat: batch publish" -y
|
|
37
50
|
icode push release test --ai-commit -y
|
|
38
51
|
icode push release test -m "feat: keep merge commit" --local-merge -y
|
|
52
|
+
icode push release test --dry-run
|
|
39
53
|
icode sync --all-local --merge-main
|
|
40
54
|
icode clean --remote --force -y
|
|
55
|
+
icode undo a1b2c3d --mode revert -y
|
|
41
56
|
icode undo --mode revert --ref HEAD~1 -y
|
|
42
57
|
icode undo --recover abort
|
|
43
58
|
icode migrate feature/login release --push -y
|
|
44
59
|
icode migrate --interactive
|
|
45
60
|
icode push --no-verify -m "chore: bypass hooks"
|
|
46
61
|
icode config protect add main release
|
|
62
|
+
icode doctor
|
|
63
|
+
source <(icode completion zsh)
|
|
47
64
|
icode info
|
|
48
65
|
`)
|
|
49
66
|
}
|
package/src/commands/info.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseArgs } from 'node:util'
|
|
2
|
-
import {
|
|
2
|
+
import { normalizeLegacyArgs } from '../core/cli/args.js'
|
|
3
|
+
import { logger } from '../core/tools/logger.js'
|
|
3
4
|
import { runInfoWorkflow } from '../workflows/info-workflow.js'
|
|
4
5
|
|
|
5
6
|
function printHelp() {
|
|
@@ -16,9 +17,11 @@ Notes:
|
|
|
16
17
|
`)
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
// 执行 info 子命令,输入原始参数,输出仓库与配置概览;参数会先归一化。
|
|
19
21
|
export async function runInfoCommand(rawArgs) {
|
|
22
|
+
const args = normalizeLegacyArgs(rawArgs)
|
|
20
23
|
const parsed = parseArgs({
|
|
21
|
-
args
|
|
24
|
+
args,
|
|
22
25
|
allowPositionals: true,
|
|
23
26
|
options: {
|
|
24
27
|
'repo-mode': { type: 'string', default: 'auto' },
|
package/src/commands/migrate.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseArgs } from 'node:util'
|
|
2
|
-
import { normalizeLegacyArgs } from '../core/args.js'
|
|
2
|
+
import { normalizeLegacyArgs } from '../core/cli/args.js'
|
|
3
3
|
import { IcodeError } from '../core/errors.js'
|
|
4
|
-
import { logger } from '../core/logger.js'
|
|
4
|
+
import { logger } from '../core/tools/logger.js'
|
|
5
5
|
import { runMigrateWorkflow } from '../workflows/migrate-workflow.js'
|
|
6
6
|
|
|
7
7
|
function printHelp() {
|
|
@@ -17,6 +17,8 @@ Arguments:
|
|
|
17
17
|
Options:
|
|
18
18
|
-i, --interactive 交互模式(单选/多选 source/target 与迁移范围)
|
|
19
19
|
--range <spec> 指定提交范围,例如 main..feature-x(交互模式下若提供则直接使用)
|
|
20
|
+
--dry-run 只展示迁移计划,不 checkout/pull/cherry-pick/push
|
|
21
|
+
--json 输出机器可读 JSON,适合 AI agent 或脚本消费
|
|
20
22
|
--push 迁移后自动推送 target 分支
|
|
21
23
|
-y, --yes 自动确认(跳过确认提示)
|
|
22
24
|
--repo-mode <mode> 仓库模式: auto(自动继承父仓库) | strict(禁止继承)
|
|
@@ -29,11 +31,32 @@ Notes:
|
|
|
29
31
|
Examples:
|
|
30
32
|
icode migrate feature/login release
|
|
31
33
|
icode migrate --interactive
|
|
34
|
+
icode migrate feature/login release --dry-run --json
|
|
32
35
|
icode migrate feature/login release --range HEAD~3..feature/login
|
|
33
36
|
icode migrate feature/login release --push -y
|
|
34
37
|
`)
|
|
35
38
|
}
|
|
36
39
|
|
|
40
|
+
// 输出 migrate dry-run 计划,输入 workflow 结果,输出人类可读步骤。
|
|
41
|
+
function printDryRunPlan(result) {
|
|
42
|
+
const previewLimit = 10
|
|
43
|
+
const previewCommits = result.commits.slice(0, previewLimit)
|
|
44
|
+
|
|
45
|
+
logger.info(`[dry-run] source: ${result.sourceBranch} (${result.sourceRef})`)
|
|
46
|
+
logger.info(`[dry-run] target: ${result.targetBranch} (${result.targetRef})`)
|
|
47
|
+
logger.info(`[dry-run] range: ${result.rangeSpec}`)
|
|
48
|
+
logger.info(`[dry-run] commits: ${result.migratedCommits}`)
|
|
49
|
+
previewCommits.forEach((commit, index) => {
|
|
50
|
+
logger.info(`[dry-run] commit ${index + 1}: ${commit.summary || commit.hash}`)
|
|
51
|
+
})
|
|
52
|
+
if (result.commits.length > previewLimit) {
|
|
53
|
+
logger.info(`[dry-run] ... 还有 ${result.commits.length - previewLimit} 个提交未显示,可用 --json 查看完整计划`)
|
|
54
|
+
}
|
|
55
|
+
result.steps.forEach((step, index) => {
|
|
56
|
+
logger.info(`[dry-run] ${index + 1}. ${step.action}: ${step.detail}`)
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
37
60
|
export async function runMigrateCommand(rawArgs) {
|
|
38
61
|
const args = normalizeLegacyArgs(rawArgs)
|
|
39
62
|
const parsed = parseArgs({
|
|
@@ -42,6 +65,8 @@ export async function runMigrateCommand(rawArgs) {
|
|
|
42
65
|
options: {
|
|
43
66
|
interactive: { type: 'boolean', short: 'i', default: false },
|
|
44
67
|
range: { type: 'string' },
|
|
68
|
+
'dry-run': { type: 'boolean', default: false },
|
|
69
|
+
json: { type: 'boolean', default: false },
|
|
45
70
|
push: { type: 'boolean', default: false },
|
|
46
71
|
yes: { type: 'boolean', short: 'y', default: false },
|
|
47
72
|
'repo-mode': { type: 'string', default: 'auto' },
|
|
@@ -70,16 +95,28 @@ export async function runMigrateCommand(rawArgs) {
|
|
|
70
95
|
targetBranch,
|
|
71
96
|
interactive,
|
|
72
97
|
range: parsed.values.range,
|
|
98
|
+
dryRun: parsed.values['dry-run'],
|
|
73
99
|
push: parsed.values.push,
|
|
74
100
|
yes: parsed.values.yes,
|
|
75
101
|
repoMode: parsed.values['repo-mode'],
|
|
76
|
-
noVerify: parsed.values['no-verify']
|
|
102
|
+
noVerify: parsed.values['no-verify'],
|
|
103
|
+
silentLog: parsed.values.json
|
|
77
104
|
})
|
|
78
105
|
|
|
106
|
+
if (parsed.values.json) {
|
|
107
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`)
|
|
108
|
+
return result
|
|
109
|
+
}
|
|
110
|
+
|
|
79
111
|
if (result.canceled) {
|
|
80
112
|
return
|
|
81
113
|
}
|
|
82
114
|
|
|
115
|
+
if (result.dryRun) {
|
|
116
|
+
printDryRunPlan(result)
|
|
117
|
+
return result
|
|
118
|
+
}
|
|
119
|
+
|
|
83
120
|
logger.success(
|
|
84
121
|
`migrate 完成: ${result.sourceBranch} -> ${result.targetBranch}, commits=${result.migratedCommits}, mode=${result.rangeMode || 'range'}`
|
|
85
122
|
)
|
package/src/commands/push.js
CHANGED
|
@@ -1,15 +1,33 @@
|
|
|
1
1
|
import { parseArgs } from 'node:util'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { logger } from '../core/logger.js'
|
|
2
|
+
import { getAiCommandOptions } from '../core/ai/config.js'
|
|
3
|
+
import { normalizeLegacyArgs } from '../core/cli/args.js'
|
|
4
|
+
import { logger } from '../core/tools/logger.js'
|
|
5
5
|
import { runPushWorkflow } from '../workflows/push-workflow.js'
|
|
6
6
|
|
|
7
|
+
const PUSH_PARSE_OPTIONS = {
|
|
8
|
+
message: { type: 'string', short: 'm' },
|
|
9
|
+
yes: { type: 'boolean', short: 'y' },
|
|
10
|
+
'local-merge': { type: 'boolean' },
|
|
11
|
+
'remote-merge': { type: 'boolean', short: 'r' },
|
|
12
|
+
'dry-run': { type: 'boolean' },
|
|
13
|
+
'ai-commit': { type: 'boolean' },
|
|
14
|
+
'ai-profile': { type: 'string' },
|
|
15
|
+
help: { type: 'boolean', short: 'h' },
|
|
16
|
+
'pull-main': { type: 'boolean' },
|
|
17
|
+
'not-push-current': { type: 'boolean' },
|
|
18
|
+
'force-protected': { type: 'boolean' },
|
|
19
|
+
'repo-mode': { type: 'string' },
|
|
20
|
+
'no-verify': { type: 'boolean' }
|
|
21
|
+
}
|
|
22
|
+
|
|
7
23
|
function formatBranchStatus(status) {
|
|
8
24
|
const map = {
|
|
9
25
|
pushed: '已推送',
|
|
10
26
|
'merged-and-pushed': '已合并并推送',
|
|
27
|
+
'remote-merged-and-pushed': '已远程合并并推送',
|
|
11
28
|
'skipped-protected': '已跳过(受保护)',
|
|
12
|
-
'skipped-missing-remote': '已跳过(远程分支不存在)'
|
|
29
|
+
'skipped-missing-remote': '已跳过(远程分支不存在)',
|
|
30
|
+
paused: '已暂停'
|
|
13
31
|
}
|
|
14
32
|
|
|
15
33
|
return map[status] || status
|
|
@@ -27,6 +45,8 @@ Options:
|
|
|
27
45
|
-m, --message <msg> 提交信息(未填会提示输入)
|
|
28
46
|
-y, --yes 自动确认(跳过确认提示)
|
|
29
47
|
--local-merge 使用本地 merge 模式(默认,会切换分支并生成 merge commit)
|
|
48
|
+
-r, --remote-merge 使用远程 PR/MR 合并模式(默认启用项目远程合并)
|
|
49
|
+
--dry-run 只展示计划,不执行 commit/fetch/pull/push/merge/MR
|
|
30
50
|
--ai-commit push 前自动执行 AI commit(会参考本地 hook/commitlint 规范)
|
|
31
51
|
--ai-profile <name> 指定 AI profile(用于 --ai-commit)
|
|
32
52
|
--pull-main 提交前将主分支同步到当前分支
|
|
@@ -39,6 +59,8 @@ Options:
|
|
|
39
59
|
Notes:
|
|
40
60
|
默认使用本地 merge 模式。
|
|
41
61
|
未指定 target 时默认处理当前分支。
|
|
62
|
+
远程 merge 通过 PR/MR API 发起,不会本地切换到目标分支。
|
|
63
|
+
远程 merge 若出现冲突会暂停流程,并保留明确失败原因。
|
|
42
64
|
布尔开关仅在命令行显式传入时生效(如 --ai-commit / --pull-main / --no-verify / -y)。
|
|
43
65
|
`)
|
|
44
66
|
}
|
|
@@ -69,12 +91,33 @@ function resolveStringOption(cliValue, configValue, fallback = '') {
|
|
|
69
91
|
return fallback
|
|
70
92
|
}
|
|
71
93
|
|
|
94
|
+
// 统一输出 push 结果摘要,失败分支会追加具体原因,方便定位问题目标分支。
|
|
95
|
+
function printPushSummary(summary = []) {
|
|
96
|
+
summary.forEach((item) => {
|
|
97
|
+
const reason = typeof item.reason === 'string' && item.reason.trim() ? ` - ${item.reason.trim()}` : ''
|
|
98
|
+
logger.info(`[结果] ${item.branch}: ${formatBranchStatus(item.status)}${reason}`)
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 输出 dry-run 计划摘要,输入 workflow 返回的计划,输出对用户可读的阶段列表。
|
|
103
|
+
function printDryRunPlan(plan) {
|
|
104
|
+
logger.info(`[dry-run] 当前分支: ${plan.currentBranch}`)
|
|
105
|
+
logger.info(`[dry-run] 目标分支: ${plan.branchTargets.join(', ')}`)
|
|
106
|
+
logger.info(`[dry-run] 合并模式: ${plan.remoteMerge ? 'remote-merge' : 'local-merge'}`)
|
|
107
|
+
plan.steps.forEach((step, index) => {
|
|
108
|
+
const reason = step.reason ? ` - ${step.reason}` : ''
|
|
109
|
+
logger.info(`[dry-run] ${index + 1}. ${step.action}: ${step.branch}${reason}`)
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
72
113
|
export function resolvePushWorkflowOptions(parsedValues, parsedPositionals, scopedOptions = {}) {
|
|
73
114
|
return {
|
|
74
115
|
targetBranches: parsedPositionals,
|
|
75
116
|
message: parsedValues.message,
|
|
76
117
|
// 显式传入开关才生效,避免配置项隐式开启 push 行为。
|
|
77
118
|
yes: resolveBooleanOption(parsedValues.yes, false),
|
|
119
|
+
remoteMerge: resolveBooleanOption(parsedValues['remote-merge'], false),
|
|
120
|
+
dryRun: resolveBooleanOption(parsedValues['dry-run'], false),
|
|
78
121
|
aiCommit: resolveBooleanOption(parsedValues['ai-commit'], false),
|
|
79
122
|
aiCommitLang: resolveStringOption(undefined, scopedOptions.aiCommitLang, 'zh'),
|
|
80
123
|
aiProfile: resolveStringOption(parsedValues['ai-profile'], scopedOptions.aiProfile, ''),
|
|
@@ -86,40 +129,45 @@ export function resolvePushWorkflowOptions(parsedValues, parsedPositionals, scop
|
|
|
86
129
|
}
|
|
87
130
|
}
|
|
88
131
|
|
|
89
|
-
|
|
132
|
+
// 解析 push 子命令参数;rawArgs 是不含命令名的参数数组,返回 node:util parseArgs 的结构化结果。
|
|
133
|
+
export function parsePushCommandArgs(rawArgs) {
|
|
90
134
|
const args = normalizeLegacyArgs(rawArgs)
|
|
91
|
-
|
|
92
|
-
const parsed = parseArgs({
|
|
135
|
+
return parseArgs({
|
|
93
136
|
args,
|
|
94
137
|
allowPositionals: true,
|
|
95
|
-
options:
|
|
96
|
-
message: { type: 'string', short: 'm' },
|
|
97
|
-
yes: { type: 'boolean', short: 'y' },
|
|
98
|
-
'local-merge': { type: 'boolean' },
|
|
99
|
-
'ai-commit': { type: 'boolean' },
|
|
100
|
-
'ai-profile': { type: 'string' },
|
|
101
|
-
help: { type: 'boolean', short: 'h' },
|
|
102
|
-
'pull-main': { type: 'boolean' },
|
|
103
|
-
'not-push-current': { type: 'boolean' },
|
|
104
|
-
'force-protected': { type: 'boolean' },
|
|
105
|
-
'repo-mode': { type: 'string' },
|
|
106
|
-
'no-verify': { type: 'boolean' }
|
|
107
|
-
}
|
|
138
|
+
options: PUSH_PARSE_OPTIONS
|
|
108
139
|
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 执行 push 子命令;rawArgs 是不含命令名的参数数组,输出结果摘要并保留 workflow 抛出的失败信息。
|
|
143
|
+
export async function runPushCommand(rawArgs) {
|
|
144
|
+
const scopedOptions = getAiCommandOptions('push')
|
|
145
|
+
const parsed = parsePushCommandArgs(rawArgs)
|
|
109
146
|
|
|
110
147
|
if (parsed.values.help) {
|
|
111
148
|
printHelp()
|
|
112
149
|
return
|
|
113
150
|
}
|
|
114
151
|
|
|
115
|
-
|
|
152
|
+
let result
|
|
153
|
+
try {
|
|
154
|
+
result = await runPushWorkflow(resolvePushWorkflowOptions(parsed.values, parsed.positionals, scopedOptions))
|
|
155
|
+
} catch (error) {
|
|
156
|
+
if (Array.isArray(error?.meta?.summary) && error.meta.summary.length) {
|
|
157
|
+
printPushSummary(error.meta.summary)
|
|
158
|
+
}
|
|
159
|
+
throw error
|
|
160
|
+
}
|
|
116
161
|
|
|
117
162
|
if (result.canceled) {
|
|
118
163
|
return
|
|
119
164
|
}
|
|
120
165
|
|
|
121
|
-
result.
|
|
122
|
-
|
|
123
|
-
|
|
166
|
+
if (result.dryRun) {
|
|
167
|
+
printDryRunPlan(result.plan)
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
printPushSummary(result.summary)
|
|
124
172
|
logger.success(`push 完成,共处理 ${result.summary.length} 个分支。`)
|
|
125
173
|
}
|