infra-kit 0.1.97 → 0.1.99
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/.eslintcache +1 -1
- package/.turbo/turbo-eslint-check.log +1 -1
- package/.turbo/turbo-prettier-check.log +1 -1
- package/.turbo/turbo-test.log +7 -7
- package/.turbo/turbo-ts-check.log +1 -1
- package/dist/cli.js +73 -37
- package/dist/cli.js.map +4 -4
- package/dist/mcp.js +30 -26
- package/dist/mcp.js.map +4 -4
- package/package.json +1 -1
- package/src/commands/config/config.ts +125 -0
- package/src/commands/config/index.ts +1 -0
- package/src/commands/doctor/doctor.ts +27 -18
- package/src/commands/init/init.ts +65 -1
- package/src/commands/release-create/release-create.ts +226 -95
- package/src/commands/worktrees-add/worktrees-add.ts +16 -12
- package/src/entry/cli.ts +35 -27
- package/src/lib/__tests__/infra-kit-config.test.ts +53 -1
- package/src/lib/infra-kit-config/index.ts +2 -2
- package/src/lib/infra-kit-config/infra-kit-config.ts +190 -37
- package/src/lib/version-utils/__tests__/next-version.test.ts +217 -0
- package/src/lib/version-utils/index.ts +13 -0
- package/src/lib/version-utils/load-existing-versions.ts +67 -0
- package/src/lib/version-utils/next-version.ts +187 -0
- package/src/mcp/tools/index.ts +0 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/src/commands/release-create-batch/index.ts +0 -1
- package/src/commands/release-create-batch/release-create-batch.ts +0 -198
|
@@ -2,92 +2,145 @@ import confirm from '@inquirer/confirm'
|
|
|
2
2
|
import select from '@inquirer/select'
|
|
3
3
|
import process from 'node:process'
|
|
4
4
|
import { z } from 'zod/v4'
|
|
5
|
-
import {
|
|
5
|
+
import { question } from 'zx'
|
|
6
6
|
|
|
7
7
|
import { loadJiraConfig } from 'src/integrations/jira'
|
|
8
8
|
import { commandEcho } from 'src/lib/command-echo'
|
|
9
9
|
import { logger } from 'src/lib/logger'
|
|
10
10
|
import { createSingleRelease, prepareGitForRelease } from 'src/lib/release-utils'
|
|
11
|
-
import type { ReleaseType } from 'src/lib/release-utils'
|
|
11
|
+
import type { ReleaseCreationResult, ReleaseType } from 'src/lib/release-utils'
|
|
12
|
+
import {
|
|
13
|
+
NoPriorVersionsError,
|
|
14
|
+
computeNextVersion,
|
|
15
|
+
hasNextToken,
|
|
16
|
+
loadExistingVersions,
|
|
17
|
+
parseVersion,
|
|
18
|
+
resolveReleaseEntries,
|
|
19
|
+
} from 'src/lib/version-utils'
|
|
20
|
+
import type { ReleaseEntry, SemVer } from 'src/lib/version-utils'
|
|
12
21
|
import type { RequiredConfirmedOptionArg, ToolsExecutionResult } from 'src/types'
|
|
13
22
|
|
|
14
23
|
interface ReleaseCreateArgs extends RequiredConfirmedOptionArg {
|
|
15
|
-
|
|
16
|
-
description?: string
|
|
17
|
-
type?: ReleaseType
|
|
18
|
-
checkout?: boolean
|
|
24
|
+
releases?: ReleaseEntry[]
|
|
19
25
|
}
|
|
20
26
|
|
|
21
|
-
|
|
22
|
-
* Create a single release branch for the specified version
|
|
23
|
-
* Includes Jira version creation and GitHub release branch creation
|
|
24
|
-
*/
|
|
25
|
-
export const releaseCreate = async (args: ReleaseCreateArgs): Promise<ToolsExecutionResult> => {
|
|
26
|
-
const {
|
|
27
|
-
version: inputVersion,
|
|
28
|
-
description: inputDescription,
|
|
29
|
-
type: inputType,
|
|
30
|
-
confirmedCommand,
|
|
31
|
-
checkout: inputCheckout,
|
|
32
|
-
} = args
|
|
33
|
-
|
|
34
|
-
commandEcho.start('release-create')
|
|
27
|
+
const VERSION_PROMPT_HINT = '"1.2.5" or "next"'
|
|
35
28
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
29
|
+
const trySuggestNext = (known: SemVer[], type: ReleaseType): string | null => {
|
|
30
|
+
try {
|
|
31
|
+
return computeNextVersion(known, type)
|
|
32
|
+
} catch (err) {
|
|
33
|
+
if (err instanceof NoPriorVersionsError) return null
|
|
40
34
|
|
|
41
|
-
|
|
42
|
-
const jiraConfig = await loadJiraConfig()
|
|
43
|
-
|
|
44
|
-
if (!version) {
|
|
45
|
-
commandEcho.setInteractive()
|
|
46
|
-
version = await question('Enter version (e.g. 1.2.5): ')
|
|
35
|
+
throw err
|
|
47
36
|
}
|
|
37
|
+
}
|
|
48
38
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
39
|
+
const resolveOrExit = (entries: ReleaseEntry[], known: SemVer[]): ReleaseEntry[] => {
|
|
40
|
+
try {
|
|
41
|
+
return resolveReleaseEntries(entries, known)
|
|
42
|
+
} catch (err) {
|
|
43
|
+
if (err instanceof NoPriorVersionsError) {
|
|
44
|
+
logger.error(err.message)
|
|
45
|
+
process.exit(1)
|
|
46
|
+
}
|
|
54
47
|
|
|
55
|
-
|
|
48
|
+
throw err
|
|
49
|
+
}
|
|
50
|
+
}
|
|
56
51
|
|
|
57
|
-
|
|
52
|
+
const promptForReleasesInteractive = async (ensureKnown: () => Promise<SemVer[]>): Promise<ReleaseEntry[]> => {
|
|
53
|
+
commandEcho.setInteractive()
|
|
58
54
|
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
const baseKnown = await ensureKnown()
|
|
56
|
+
const running: SemVer[] = [...baseKnown]
|
|
57
|
+
const entries: ReleaseEntry[] = []
|
|
58
|
+
let addAnother = true
|
|
61
59
|
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
while (addAnother) {
|
|
61
|
+
const ordinal = entries.length + 1
|
|
62
|
+
const type = await select<ReleaseType>({
|
|
63
|
+
message: `Release #${ordinal} — select type:`,
|
|
64
64
|
choices: [
|
|
65
65
|
{ name: 'regular', value: 'regular' },
|
|
66
66
|
{ name: 'hotfix', value: 'hotfix' },
|
|
67
67
|
],
|
|
68
68
|
default: 'regular',
|
|
69
69
|
})
|
|
70
|
+
|
|
71
|
+
const suggestion = trySuggestNext(running, type)
|
|
72
|
+
const defaultHint = suggestion ? ` [${suggestion}]` : ''
|
|
73
|
+
const versionAnswer = (await question(` Version (e.g. ${VERSION_PROMPT_HINT})${defaultHint}: `)).trim()
|
|
74
|
+
const versionInput = versionAnswer === '' ? (suggestion ?? '') : versionAnswer
|
|
75
|
+
|
|
76
|
+
if (versionInput === '') {
|
|
77
|
+
logger.error('No version provided. Exiting...')
|
|
78
|
+
process.exit(1)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const resolved = resolveOrExit([{ version: versionInput, type }], running)[0] as ReleaseEntry
|
|
82
|
+
|
|
83
|
+
running.push(parseVersion(`v${resolved.version}`))
|
|
84
|
+
|
|
85
|
+
const description = (await question(' Description (optional, press Enter to skip): ')).trim()
|
|
86
|
+
|
|
87
|
+
entries.push({ ...resolved, ...(description !== '' ? { description } : {}) })
|
|
88
|
+
|
|
89
|
+
addAnother = await confirm({ message: 'Add another release?', default: false })
|
|
70
90
|
}
|
|
71
91
|
|
|
72
|
-
|
|
92
|
+
return entries
|
|
93
|
+
}
|
|
73
94
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
description = await question('Enter description (optional, press Enter to skip): ')
|
|
95
|
+
const formatReleaseSummary = (entry: ReleaseEntry): string => {
|
|
96
|
+
const parts = [`v${entry.version}`, entry.type]
|
|
77
97
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
98
|
+
if (entry.description) parts.push(entry.description)
|
|
99
|
+
|
|
100
|
+
return parts.join(' · ')
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const echoReleases = (entries: ReleaseEntry[]): void => {
|
|
104
|
+
for (const entry of entries) {
|
|
105
|
+
const spec = entry.description
|
|
106
|
+
? `${entry.version}:${entry.type}:${entry.description}`
|
|
107
|
+
: `${entry.version}:${entry.type}`
|
|
108
|
+
|
|
109
|
+
commandEcho.addOption('--release', spec)
|
|
81
110
|
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
interface FailedRelease {
|
|
114
|
+
version: string
|
|
115
|
+
error: string
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const collectEntries = async (
|
|
119
|
+
inputReleases: ReleaseEntry[] | undefined,
|
|
120
|
+
ensureKnown: () => Promise<SemVer[]>,
|
|
121
|
+
): Promise<ReleaseEntry[]> => {
|
|
122
|
+
if (inputReleases && inputReleases.length > 0) {
|
|
123
|
+
const known = hasNextToken(inputReleases) ? await ensureKnown() : []
|
|
124
|
+
const resolved = resolveOrExit(inputReleases, known)
|
|
82
125
|
|
|
83
|
-
|
|
84
|
-
|
|
126
|
+
echoReleases(resolved)
|
|
127
|
+
|
|
128
|
+
return resolved
|
|
85
129
|
}
|
|
86
130
|
|
|
131
|
+
const interactive = await promptForReleasesInteractive(ensureKnown)
|
|
132
|
+
|
|
133
|
+
echoReleases(interactive)
|
|
134
|
+
|
|
135
|
+
return interactive
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const confirmReleases = async (entries: ReleaseEntry[], confirmedCommand: boolean): Promise<void> => {
|
|
139
|
+
const summary = entries.map(formatReleaseSummary).join('\n - ')
|
|
87
140
|
const answer = confirmedCommand
|
|
88
141
|
? true
|
|
89
142
|
: await confirm({
|
|
90
|
-
message: `
|
|
143
|
+
message: `Create the following ${entries.length} release(s)?\n - ${summary}\n`,
|
|
91
144
|
})
|
|
92
145
|
|
|
93
146
|
if (!confirmedCommand) {
|
|
@@ -99,51 +152,105 @@ export const releaseCreate = async (args: ReleaseCreateArgs): Promise<ToolsExecu
|
|
|
99
152
|
process.exit(0)
|
|
100
153
|
}
|
|
101
154
|
|
|
102
|
-
// Track --yes flag if confirmation was interactive (user confirmed)
|
|
103
155
|
commandEcho.addOption('--yes', true)
|
|
156
|
+
}
|
|
104
157
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
158
|
+
interface ExecuteOneArgs {
|
|
159
|
+
entry: ReleaseEntry
|
|
160
|
+
jiraConfig: Awaited<ReturnType<typeof loadJiraConfig>>
|
|
161
|
+
}
|
|
109
162
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
163
|
+
const executeOne = async (
|
|
164
|
+
args: ExecuteOneArgs,
|
|
165
|
+
): Promise<{ result?: ReleaseCreationResult; failure?: FailedRelease }> => {
|
|
166
|
+
const { entry, jiraConfig } = args
|
|
113
167
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
commandEcho.setInteractive()
|
|
168
|
+
try {
|
|
169
|
+
await prepareGitForRelease(entry.type)
|
|
117
170
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
171
|
+
const result = await createSingleRelease({
|
|
172
|
+
version: entry.version,
|
|
173
|
+
jiraConfig,
|
|
174
|
+
description: entry.description,
|
|
175
|
+
type: entry.type,
|
|
121
176
|
})
|
|
177
|
+
|
|
178
|
+
logger.info(`✅ Successfully created release: v${entry.version} (${entry.type})`)
|
|
179
|
+
logger.info(`🔗 GitHub PR: ${result.prUrl}`)
|
|
180
|
+
logger.info(`🔗 Jira Version: ${result.jiraVersionUrl}\n`)
|
|
181
|
+
|
|
182
|
+
return { result }
|
|
183
|
+
} catch (error) {
|
|
184
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
185
|
+
|
|
186
|
+
logger.error(`❌ Failed to create release: v${entry.version}`)
|
|
187
|
+
logger.error(` Error: ${errorMessage}\n`)
|
|
188
|
+
|
|
189
|
+
return { failure: { version: entry.version, error: errorMessage } }
|
|
122
190
|
}
|
|
191
|
+
}
|
|
123
192
|
|
|
124
|
-
|
|
125
|
-
if (
|
|
126
|
-
|
|
193
|
+
const logFinalSummary = (total: number, successCount: number, failureCount: number): void => {
|
|
194
|
+
if (successCount === total) {
|
|
195
|
+
logger.info(`✅ All ${total} release branch(es) were created successfully.`)
|
|
196
|
+
} else if (successCount > 0) {
|
|
197
|
+
logger.warn(`⚠️ ${successCount} of ${total} release branches were created successfully.`)
|
|
198
|
+
logger.warn(`❌ ${failureCount} release(s) failed.`)
|
|
199
|
+
} else {
|
|
200
|
+
logger.error(`❌ All ${total} release branch(es) failed to create.`)
|
|
127
201
|
}
|
|
202
|
+
}
|
|
128
203
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
204
|
+
/**
|
|
205
|
+
* Create one or more release branches. Each release carries its own type
|
|
206
|
+
* (regular/hotfix) and optional Jira description, so a single invocation
|
|
207
|
+
* may mix regular and hotfix releases off their respective base branches.
|
|
208
|
+
*/
|
|
209
|
+
export const releaseCreate = async (args: ReleaseCreateArgs): Promise<ToolsExecutionResult> => {
|
|
210
|
+
const { releases: inputReleases, confirmedCommand } = args
|
|
211
|
+
|
|
212
|
+
commandEcho.start('release-create')
|
|
213
|
+
|
|
214
|
+
const jiraConfig = await loadJiraConfig()
|
|
215
|
+
|
|
216
|
+
let known: SemVer[] | null = null
|
|
217
|
+
const ensureKnown = async (): Promise<SemVer[]> => {
|
|
218
|
+
if (known === null) known = await loadExistingVersions()
|
|
219
|
+
|
|
220
|
+
return known
|
|
221
|
+
}
|
|
134
222
|
|
|
135
|
-
|
|
223
|
+
const entries = await collectEntries(inputReleases, ensureKnown)
|
|
224
|
+
|
|
225
|
+
if (entries.length === 0) {
|
|
226
|
+
logger.error('No releases provided. Exiting...')
|
|
227
|
+
process.exit(1)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
await confirmReleases(entries, Boolean(confirmedCommand))
|
|
231
|
+
|
|
232
|
+
const created: ReleaseCreationResult[] = []
|
|
233
|
+
const failed: FailedRelease[] = []
|
|
234
|
+
|
|
235
|
+
for (const entry of entries) {
|
|
236
|
+
const { result, failure } = await executeOne({ entry, jiraConfig })
|
|
237
|
+
|
|
238
|
+
if (result) created.push(result)
|
|
239
|
+
if (failure) failed.push(failure)
|
|
136
240
|
}
|
|
137
241
|
|
|
242
|
+
logFinalSummary(entries.length, created.length, failed.length)
|
|
243
|
+
|
|
138
244
|
commandEcho.print()
|
|
139
245
|
|
|
140
246
|
const structuredContent = {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
247
|
+
createdBranches: created.map((r) => {
|
|
248
|
+
return r.branchName
|
|
249
|
+
}),
|
|
250
|
+
successCount: created.length,
|
|
251
|
+
failureCount: failed.length,
|
|
252
|
+
releases: created,
|
|
253
|
+
failedReleases: failed,
|
|
147
254
|
}
|
|
148
255
|
|
|
149
256
|
return {
|
|
@@ -161,24 +268,48 @@ export const releaseCreate = async (args: ReleaseCreateArgs): Promise<ToolsExecu
|
|
|
161
268
|
export const releaseCreateMcpTool = {
|
|
162
269
|
name: 'release-create',
|
|
163
270
|
description:
|
|
164
|
-
'Create a
|
|
271
|
+
'Create one or more releases in a single call. Each entry in "releases" carries its own version, type (regular|hotfix, default regular), and optional description, so regular and hotfix releases can be mixed in the same invocation. For each release this tool switches to the appropriate base branch (dev for regular, main for hotfix), cuts the release branch, opens a GitHub release PR, and creates the matching Jira fix version. The literal token "next" auto-increments from the union of remote release branches and Jira fix versions (regular bumps minor + resets patch; hotfix bumps patch on the highest minor); multiple "next" tokens advance sequentially across mixed types. Confirmation is auto-skipped for MCP calls, so the caller is responsible for gating. Continues on per-release failure and reports successes/failures.',
|
|
165
272
|
inputSchema: {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
273
|
+
releases: z
|
|
274
|
+
.array(
|
|
275
|
+
z.object({
|
|
276
|
+
version: z
|
|
277
|
+
.string()
|
|
278
|
+
.describe('Version to create (e.g., "1.2.5") or the literal token "next" for auto-increment.'),
|
|
279
|
+
type: z
|
|
280
|
+
.enum(['regular', 'hotfix'])
|
|
281
|
+
.optional()
|
|
282
|
+
.default('regular')
|
|
283
|
+
.describe('Release type: "regular" (branches off dev) or "hotfix" (branches off main).'),
|
|
284
|
+
description: z.string().optional().describe('Optional description for the Jira version.'),
|
|
285
|
+
}),
|
|
286
|
+
)
|
|
287
|
+
.min(1)
|
|
288
|
+
.describe('One or more releases to create. Each entry has its own version, type, and optional description.'),
|
|
174
289
|
},
|
|
175
290
|
outputSchema: {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
291
|
+
createdBranches: z.array(z.string()).describe('List of created release branch names'),
|
|
292
|
+
successCount: z.number().describe('Number of releases created successfully'),
|
|
293
|
+
failureCount: z.number().describe('Number of releases that failed'),
|
|
294
|
+
releases: z
|
|
295
|
+
.array(
|
|
296
|
+
z.object({
|
|
297
|
+
version: z.string().describe('Version number'),
|
|
298
|
+
type: z.enum(['regular', 'hotfix']).describe('Release type'),
|
|
299
|
+
branchName: z.string().describe('Release branch name'),
|
|
300
|
+
prUrl: z.string().describe('GitHub PR URL'),
|
|
301
|
+
jiraVersionUrl: z.string().describe('Jira version URL'),
|
|
302
|
+
}),
|
|
303
|
+
)
|
|
304
|
+
.describe('Detailed information for each created release with URLs'),
|
|
305
|
+
failedReleases: z
|
|
306
|
+
.array(
|
|
307
|
+
z.object({
|
|
308
|
+
version: z.string().describe('Version number that failed'),
|
|
309
|
+
error: z.string().describe('Error message'),
|
|
310
|
+
}),
|
|
311
|
+
)
|
|
312
|
+
.describe('List of releases that failed with error messages'),
|
|
182
313
|
},
|
|
183
314
|
handler: releaseCreate,
|
|
184
315
|
}
|
|
@@ -130,8 +130,12 @@ export const worktreesAdd = async (options: WorktreeManagementArgs): Promise<Too
|
|
|
130
130
|
commandEcho.addOption('--yes', true)
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
const config = await getInfraKitConfig()
|
|
134
|
+
const cursorConfig = config.ide?.provider === 'cursor' ? config.ide.config : undefined
|
|
135
|
+
|
|
133
136
|
const cursorMode: CursorMode =
|
|
134
137
|
cursor ??
|
|
138
|
+
cursorConfig?.mode ??
|
|
135
139
|
(await select<CursorMode>({
|
|
136
140
|
message: 'Cursor mode for created worktrees?',
|
|
137
141
|
default: 'workspace',
|
|
@@ -150,16 +154,18 @@ export const worktreesAdd = async (options: WorktreeManagementArgs): Promise<Too
|
|
|
150
154
|
],
|
|
151
155
|
}))
|
|
152
156
|
|
|
153
|
-
if (typeof cursor === 'undefined') {
|
|
157
|
+
if (typeof cursor === 'undefined' && !cursorConfig?.mode) {
|
|
154
158
|
commandEcho.setInteractive()
|
|
155
159
|
}
|
|
156
160
|
|
|
157
161
|
commandEcho.addOption('--cursor', cursorMode)
|
|
158
162
|
|
|
159
163
|
const openInGithubDesktop =
|
|
160
|
-
githubDesktop ??
|
|
164
|
+
githubDesktop ??
|
|
165
|
+
config.worktrees?.openInGithubDesktop ??
|
|
166
|
+
(await confirm({ message: 'Open created worktrees in GitHub Desktop?' }))
|
|
161
167
|
|
|
162
|
-
if (typeof githubDesktop === 'undefined') {
|
|
168
|
+
if (typeof githubDesktop === 'undefined' && config.worktrees?.openInGithubDesktop === undefined) {
|
|
163
169
|
commandEcho.setInteractive()
|
|
164
170
|
}
|
|
165
171
|
|
|
@@ -169,9 +175,10 @@ export const worktreesAdd = async (options: WorktreeManagementArgs): Promise<Too
|
|
|
169
175
|
commandEcho.addOption('--no-github-desktop', true)
|
|
170
176
|
}
|
|
171
177
|
|
|
172
|
-
const openInCmux =
|
|
178
|
+
const openInCmux =
|
|
179
|
+
cmux ?? config.worktrees?.openInCmux ?? (await confirm({ message: 'Open created worktrees in cmux?' }))
|
|
173
180
|
|
|
174
|
-
if (typeof cmux === 'undefined') {
|
|
181
|
+
if (typeof cmux === 'undefined' && config.worktrees?.openInCmux === undefined) {
|
|
175
182
|
commandEcho.setInteractive()
|
|
176
183
|
}
|
|
177
184
|
|
|
@@ -191,11 +198,8 @@ export const worktreesAdd = async (options: WorktreeManagementArgs): Promise<Too
|
|
|
191
198
|
logResults(createdWorktrees)
|
|
192
199
|
|
|
193
200
|
if (cursorMode === 'workspace') {
|
|
194
|
-
const config = await getInfraKitConfig()
|
|
195
|
-
const cursorConfig = config.ide?.provider === 'cursor' ? config.ide.config : undefined
|
|
196
|
-
|
|
197
201
|
if (!cursorConfig?.workspaceConfigPath) {
|
|
198
|
-
logger.warn('⚠️ Skipping Cursor: ide.config.workspaceConfigPath is not set in infra-kit
|
|
202
|
+
logger.warn('⚠️ Skipping Cursor: ide.config.workspaceConfigPath is not set in infra-kit config')
|
|
199
203
|
} else {
|
|
200
204
|
const workspacePath = resolveCursorWorkspacePath(cursorConfig.workspaceConfigPath, projectRoot)
|
|
201
205
|
|
|
@@ -355,19 +359,19 @@ export const worktreesAddMcpTool = {
|
|
|
355
359
|
.enum(CURSOR_MODES)
|
|
356
360
|
.optional()
|
|
357
361
|
.describe(
|
|
358
|
-
'Cursor open mode for created worktrees. "workspace"
|
|
362
|
+
'Cursor open mode for created worktrees. "workspace" appends each worktree as a folder to "ide.config.workspaceConfigPath" in infra-kit config and opens the workspace. "windows" opens each worktree in its own Cursor window. "none" skips Cursor. Resolution order: this flag → "ide.config.mode" from infra-kit config → interactive prompt (CLI) / "none" (MCP, no TTY).',
|
|
359
363
|
),
|
|
360
364
|
githubDesktop: z
|
|
361
365
|
.boolean()
|
|
362
366
|
.optional()
|
|
363
367
|
.describe(
|
|
364
|
-
'Open each created worktree in GitHub Desktop.
|
|
368
|
+
'Open each created worktree in GitHub Desktop. Resolution order: this flag → "worktrees.openInGithubDesktop" from infra-kit config → interactive prompt (CLI) / false (MCP, no TTY).',
|
|
365
369
|
),
|
|
366
370
|
cmux: z
|
|
367
371
|
.boolean()
|
|
368
372
|
.optional()
|
|
369
373
|
.describe(
|
|
370
|
-
'Open each created worktree in a new cmux workspace with a 3-pane layout (left-top, left-bottom, full-height right), all rooted at the worktree directory.
|
|
374
|
+
'Open each created worktree in a new cmux workspace with a 3-pane layout (left-top, left-bottom, full-height right), all rooted at the worktree directory. Resolution order: this flag → "worktrees.openInCmux" from infra-kit config → interactive prompt (CLI) / false (MCP, no TTY).',
|
|
371
375
|
),
|
|
372
376
|
},
|
|
373
377
|
outputSchema: {
|
package/src/entry/cli.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import select, { Separator } from '@inquirer/select'
|
|
2
|
-
import { Command
|
|
2
|
+
import { Command } from 'commander'
|
|
3
3
|
import process from 'node:process'
|
|
4
4
|
|
|
5
|
+
import { configEdit, configPath } from 'src/commands/config'
|
|
5
6
|
import { doctor } from 'src/commands/doctor'
|
|
6
7
|
import { envClear } from 'src/commands/env-clear'
|
|
7
8
|
import { envList } from 'src/commands/env-list'
|
|
@@ -14,7 +15,6 @@ import { ghReleaseDeploySelected } from 'src/commands/gh-release-deploy-selected
|
|
|
14
15
|
import { ghReleaseList } from 'src/commands/gh-release-list'
|
|
15
16
|
import { init } from 'src/commands/init'
|
|
16
17
|
import { releaseCreate } from 'src/commands/release-create'
|
|
17
|
-
import { releaseCreateBatch } from 'src/commands/release-create-batch'
|
|
18
18
|
import { version } from 'src/commands/version'
|
|
19
19
|
import { CURSOR_MODES, worktreesAdd } from 'src/commands/worktrees-add'
|
|
20
20
|
import type { CursorMode } from 'src/commands/worktrees-add'
|
|
@@ -23,9 +23,14 @@ import { worktreesOpen } from 'src/commands/worktrees-open'
|
|
|
23
23
|
import { worktreesRemove } from 'src/commands/worktrees-remove'
|
|
24
24
|
import { worktreesSync } from 'src/commands/worktrees-sync'
|
|
25
25
|
import { logger } from 'src/lib/logger'
|
|
26
|
+
import { parseReleaseSpec } from 'src/lib/version-utils'
|
|
26
27
|
|
|
27
28
|
const program = new Command()
|
|
28
29
|
|
|
30
|
+
const collectReleaseSpec = (value: string, prev: string[]): string[] => {
|
|
31
|
+
return [...prev, value]
|
|
32
|
+
}
|
|
33
|
+
|
|
29
34
|
const normalizeCursorMode = (value: unknown): CursorMode | undefined => {
|
|
30
35
|
if (typeof value === 'undefined') {
|
|
31
36
|
return undefined
|
|
@@ -79,32 +84,20 @@ program
|
|
|
79
84
|
|
|
80
85
|
program
|
|
81
86
|
.command('release-create')
|
|
82
|
-
.description('Create
|
|
83
|
-
.option(
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
.description('Create one or more release branches (each entry can mix regular/hotfix and its own description)')
|
|
88
|
+
.option(
|
|
89
|
+
'-r, --release <spec>',
|
|
90
|
+
'Release spec "version[:type[:description]]" (repeatable). Examples: "1.2.5", "1.2.5:hotfix", "next:regular:Holiday backend"',
|
|
91
|
+
collectReleaseSpec,
|
|
92
|
+
[],
|
|
93
|
+
)
|
|
86
94
|
.option('-y, --yes', 'Skip confirmation prompt')
|
|
87
|
-
.option('--no-checkout', 'Do not checkout the created branch after creation (checkout is default)')
|
|
88
95
|
.action(async (options) => {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
description: options.description,
|
|
92
|
-
type: options.type,
|
|
93
|
-
confirmedCommand: options.yes,
|
|
94
|
-
checkout: options.checkout,
|
|
95
|
-
})
|
|
96
|
-
})
|
|
96
|
+
const specs = options.release as string[]
|
|
97
|
+
const releases = specs.length > 0 ? specs.map(parseReleaseSpec) : undefined
|
|
97
98
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
.description('Create multiple release branches (batch operation)')
|
|
101
|
-
.option('-v, --versions <versions>', 'Specify the versions to create by comma, e.g. 1.2.5, 1.2.6')
|
|
102
|
-
.addOption(new Option('-t, --type <type>', 'Release type (default: regular)').choices(['regular', 'hotfix']))
|
|
103
|
-
.option('-y, --yes', 'Skip confirmation prompt')
|
|
104
|
-
.action(async (options) => {
|
|
105
|
-
await releaseCreateBatch({
|
|
106
|
-
versions: options.versions,
|
|
107
|
-
type: options.type,
|
|
99
|
+
await releaseCreate({
|
|
100
|
+
releases,
|
|
108
101
|
confirmedCommand: options.yes,
|
|
109
102
|
})
|
|
110
103
|
})
|
|
@@ -199,6 +192,22 @@ program
|
|
|
199
192
|
await worktreesOpen()
|
|
200
193
|
})
|
|
201
194
|
|
|
195
|
+
const configCmd = program.command('config').description('Manage infra-kit configuration files')
|
|
196
|
+
|
|
197
|
+
configCmd
|
|
198
|
+
.command('path')
|
|
199
|
+
.description('Show the resolved config merge chain and file paths')
|
|
200
|
+
.action(async () => {
|
|
201
|
+
await configPath()
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
configCmd
|
|
205
|
+
.command('edit')
|
|
206
|
+
.description('Open the user-scope per-project override file in $EDITOR')
|
|
207
|
+
.action(async () => {
|
|
208
|
+
await configEdit()
|
|
209
|
+
})
|
|
210
|
+
|
|
202
211
|
program
|
|
203
212
|
.command('doctor')
|
|
204
213
|
.description('Check installation and authentication status of gh and doppler CLIs')
|
|
@@ -254,13 +263,12 @@ if (process.argv.length <= 2) {
|
|
|
254
263
|
'merge-dev',
|
|
255
264
|
'release-list',
|
|
256
265
|
'release-create',
|
|
257
|
-
'release-create-batch',
|
|
258
266
|
'release-deploy-all',
|
|
259
267
|
'release-deploy-selected',
|
|
260
268
|
'release-deliver',
|
|
261
269
|
]
|
|
262
270
|
const worktreeCommands = ['worktrees-add', 'worktrees-list', 'worktrees-open', 'worktrees-remove', 'worktrees-sync']
|
|
263
|
-
const envCommands = ['doctor', 'init', 'version', 'env-status', 'env-list', 'env-load', 'env-clear']
|
|
271
|
+
const envCommands = ['doctor', 'init', 'version', 'config', 'env-status', 'env-list', 'env-load', 'env-clear']
|
|
264
272
|
|
|
265
273
|
const commandMap = new Map(
|
|
266
274
|
program.commands.map((cmd) => {
|