infra-kit 0.1.78 â 0.1.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +33 -34
- package/dist/cli.js.map +4 -4
- package/dist/mcp.js +27 -28
- package/dist/mcp.js.map +4 -4
- package/package.json +1 -2
- package/src/commands/env-status/env-status.ts +7 -5
- package/src/commands/gh-merge-dev/gh-merge-dev.ts +20 -9
- package/src/commands/gh-release-deliver/gh-release-deliver.ts +49 -27
- package/src/commands/gh-release-deploy-all/gh-release-deploy-all.ts +17 -10
- package/src/commands/gh-release-deploy-selected/gh-release-deploy-selected.ts +17 -10
- package/src/commands/gh-release-deploy-service/gh-release-deploy-service.ts +17 -10
- package/src/commands/gh-release-list/gh-release-list.ts +45 -8
- package/src/commands/release-create/release-create.ts +35 -3
- package/src/commands/release-create-batch/release-create-batch.ts +43 -21
- package/src/commands/worktrees-add/worktrees-add.ts +81 -36
- package/src/commands/worktrees-list/worktrees-list.ts +1 -1
- package/src/commands/worktrees-remove/worktrees-remove.ts +51 -20
- package/src/commands/worktrees-sync/worktrees-sync.ts +3 -1
- package/src/entry/cli.ts +20 -12
- package/src/integrations/gh/gh-release-prs/gh-release-prs.ts +85 -16
- package/src/integrations/gh/gh-release-prs/index.ts +2 -1
- package/src/integrations/gh/index.ts +2 -1
- package/src/integrations/jira/api.ts +1 -1
- package/src/integrations/jira/index.ts +7 -1
- package/src/lib/command-echo/command-echo.ts +2 -3
- package/src/lib/release-utils/index.ts +11 -1
- package/src/lib/release-utils/release-utils.ts +110 -15
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import confirm from '@inquirer/confirm'
|
|
2
|
+
import select from '@inquirer/select'
|
|
2
3
|
import process from 'node:process'
|
|
3
4
|
import { z } from 'zod'
|
|
4
5
|
import { question } from 'zx'
|
|
@@ -7,28 +8,22 @@ import { loadJiraConfig } from 'src/integrations/jira'
|
|
|
7
8
|
import { commandEcho } from 'src/lib/command-echo'
|
|
8
9
|
import { logger } from 'src/lib/logger'
|
|
9
10
|
import { createSingleRelease, prepareGitForRelease } from 'src/lib/release-utils'
|
|
10
|
-
import type { ReleaseCreationResult } from 'src/lib/release-utils'
|
|
11
|
+
import type { ReleaseCreationResult, ReleaseType } from 'src/lib/release-utils'
|
|
11
12
|
import type { RequiredConfirmedOptionArg, ToolsExecutionResult } from 'src/types'
|
|
12
13
|
|
|
13
14
|
interface ReleaseCreateBatchArgs extends RequiredConfirmedOptionArg {
|
|
14
15
|
versions: string
|
|
15
|
-
|
|
16
|
+
type?: ReleaseType
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
|
-
*
|
|
20
|
-
* Includes Jira version creation and GitHub release branch creation for each version
|
|
20
|
+
* Gather and validate batch release inputs interactively if needed
|
|
21
21
|
*/
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const { versions: inputVersions, description, confirmedCommand } = args
|
|
25
|
-
|
|
26
|
-
commandEcho.start('release-create-batch')
|
|
22
|
+
const resolveInputs = async (args: ReleaseCreateBatchArgs): Promise<{ versionsList: string[]; type: ReleaseType }> => {
|
|
23
|
+
const { versions: inputVersions, type: inputType, confirmedCommand } = args
|
|
27
24
|
|
|
28
25
|
let versionInput = inputVersions
|
|
29
|
-
|
|
30
|
-
// Load Jira config - it is now mandatory
|
|
31
|
-
const jiraConfig = await loadJiraConfig()
|
|
26
|
+
let type: ReleaseType = inputType || 'regular'
|
|
32
27
|
|
|
33
28
|
if (!versionInput) {
|
|
34
29
|
commandEcho.setInteractive()
|
|
@@ -41,13 +36,26 @@ export const releaseCreateBatch = async (args: ReleaseCreateBatchArgs): Promise<
|
|
|
41
36
|
|
|
42
37
|
commandEcho.addOption('--versions', versionsList.join(', '))
|
|
43
38
|
|
|
44
|
-
// Validate input
|
|
45
39
|
if (versionsList.length === 0) {
|
|
46
40
|
logger.error('No versions provided. Exiting...')
|
|
47
41
|
process.exit(1)
|
|
48
42
|
}
|
|
49
43
|
|
|
50
|
-
|
|
44
|
+
if (!inputType) {
|
|
45
|
+
commandEcho.setInteractive()
|
|
46
|
+
|
|
47
|
+
type = await select<ReleaseType>({
|
|
48
|
+
message: 'Select release type:',
|
|
49
|
+
choices: [
|
|
50
|
+
{ name: 'regular', value: 'regular' },
|
|
51
|
+
{ name: 'hotfix', value: 'hotfix' },
|
|
52
|
+
],
|
|
53
|
+
default: 'regular',
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
commandEcho.addOption('--type', type)
|
|
58
|
+
|
|
51
59
|
if (versionsList.length === 1) {
|
|
52
60
|
logger.warn('đĄ You are creating only one release. Consider using "create-release" command for single releases.')
|
|
53
61
|
}
|
|
@@ -67,14 +75,23 @@ export const releaseCreateBatch = async (args: ReleaseCreateBatchArgs): Promise<
|
|
|
67
75
|
process.exit(0)
|
|
68
76
|
}
|
|
69
77
|
|
|
70
|
-
// Track --yes flag if confirmation was interactive (user confirmed)
|
|
71
78
|
commandEcho.addOption('--yes', true)
|
|
72
79
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
80
|
+
return { versionsList, type }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Create multiple release branches for the specified versions
|
|
85
|
+
* Includes Jira version creation and GitHub release branch creation for each version
|
|
86
|
+
*/
|
|
87
|
+
export const releaseCreateBatch = async (args: ReleaseCreateBatchArgs): Promise<ToolsExecutionResult> => {
|
|
88
|
+
commandEcho.start('release-create-batch')
|
|
89
|
+
|
|
90
|
+
const jiraConfig = await loadJiraConfig()
|
|
91
|
+
|
|
92
|
+
const { versionsList, type } = await resolveInputs(args)
|
|
76
93
|
|
|
77
|
-
await prepareGitForRelease()
|
|
94
|
+
await prepareGitForRelease(type)
|
|
78
95
|
|
|
79
96
|
const releases: ReleaseCreationResult[] = []
|
|
80
97
|
const failedReleases: Array<{ version: string; error: string }> = []
|
|
@@ -82,7 +99,7 @@ export const releaseCreateBatch = async (args: ReleaseCreateBatchArgs): Promise<
|
|
|
82
99
|
for (const version of versionsList) {
|
|
83
100
|
try {
|
|
84
101
|
// Create each release
|
|
85
|
-
const release = await createSingleRelease(version, jiraConfig,
|
|
102
|
+
const release = await createSingleRelease({ version, jiraConfig, type })
|
|
86
103
|
|
|
87
104
|
releases.push(release)
|
|
88
105
|
|
|
@@ -141,7 +158,11 @@ export const releaseCreateBatchMcpTool = {
|
|
|
141
158
|
description: 'Create multiple release branches for specified versions with Jira version creation (batch operation)',
|
|
142
159
|
inputSchema: {
|
|
143
160
|
versions: z.string().describe('Comma-separated list of versions to create (e.g., "1.2.5, 1.2.6")'),
|
|
144
|
-
|
|
161
|
+
type: z
|
|
162
|
+
.enum(['regular', 'hotfix'])
|
|
163
|
+
.optional()
|
|
164
|
+
.default('regular')
|
|
165
|
+
.describe('Release type: "regular" or "hotfix" (default: "regular")'),
|
|
145
166
|
},
|
|
146
167
|
outputSchema: {
|
|
147
168
|
createdBranches: z.array(z.string()).describe('List of created release branches'),
|
|
@@ -151,6 +172,7 @@ export const releaseCreateBatchMcpTool = {
|
|
|
151
172
|
.array(
|
|
152
173
|
z.object({
|
|
153
174
|
version: z.string().describe('Version number'),
|
|
175
|
+
type: z.enum(['regular', 'hotfix']).describe('Release type'),
|
|
154
176
|
branchName: z.string().describe('Release branch name'),
|
|
155
177
|
prUrl: z.string().describe('GitHub PR URL'),
|
|
156
178
|
jiraVersionUrl: z.string().describe('Jira version URL'),
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
/* eslint-disable sonarjs/cognitive-complexity */
|
|
2
2
|
import checkbox from '@inquirer/checkbox'
|
|
3
3
|
import confirm from '@inquirer/confirm'
|
|
4
|
-
import { copyFileSync, existsSync } from 'node:fs'
|
|
5
4
|
import process from 'node:process'
|
|
6
5
|
import { z } from 'zod'
|
|
7
6
|
import { $ } from 'zx'
|
|
8
7
|
|
|
9
|
-
import {
|
|
8
|
+
import { getReleasePRsWithInfo } from 'src/integrations/gh'
|
|
10
9
|
import { commandEcho } from 'src/lib/command-echo'
|
|
11
10
|
import { WORKTREES_DIR_SUFFIX } from 'src/lib/constants'
|
|
12
11
|
import { getCurrentWorktrees, getProjectRoot } from 'src/lib/git-utils'
|
|
13
12
|
import { logger } from 'src/lib/logger'
|
|
13
|
+
import { detectReleaseType, formatBranchChoices, getJiraDescriptions } from 'src/lib/release-utils'
|
|
14
|
+
import type { ReleaseType } from 'src/lib/release-utils'
|
|
14
15
|
import type { RequiredConfirmedOptionArg, ToolsExecutionResult } from 'src/types'
|
|
15
16
|
|
|
16
17
|
// Constants
|
|
@@ -20,7 +21,9 @@ const RELEASE_BRANCH_PREFIX = 'release/v'
|
|
|
20
21
|
|
|
21
22
|
interface WorktreeManagementArgs extends RequiredConfirmedOptionArg {
|
|
22
23
|
all: boolean
|
|
24
|
+
versions?: string
|
|
23
25
|
cursor?: boolean
|
|
26
|
+
githubDesktop?: boolean
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
/**
|
|
@@ -28,7 +31,7 @@ interface WorktreeManagementArgs extends RequiredConfirmedOptionArg {
|
|
|
28
31
|
* Creates worktrees for active release branches and removes unused ones
|
|
29
32
|
*/
|
|
30
33
|
export const worktreesAdd = async (options: WorktreeManagementArgs): Promise<ToolsExecutionResult> => {
|
|
31
|
-
const { confirmedCommand, all, cursor } = options
|
|
34
|
+
const { confirmedCommand, all, versions, cursor, githubDesktop } = options
|
|
32
35
|
|
|
33
36
|
commandEcho.start('worktrees-add')
|
|
34
37
|
|
|
@@ -41,7 +44,17 @@ export const worktreesAdd = async (options: WorktreeManagementArgs): Promise<Too
|
|
|
41
44
|
await ensureWorktreeDirectory(`${worktreeDir}/${RELEASE_DIR}`)
|
|
42
45
|
await ensureWorktreeDirectory(`${worktreeDir}/${FEATURE_DIR}`)
|
|
43
46
|
|
|
44
|
-
const
|
|
47
|
+
const releasePRsInfo = await getReleasePRsWithInfo()
|
|
48
|
+
|
|
49
|
+
const releasePRsList = releasePRsInfo.map((pr) => {
|
|
50
|
+
return pr.branch
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const releaseTypes = new Map<string, ReleaseType>(
|
|
54
|
+
releasePRsInfo.map((pr) => {
|
|
55
|
+
return [pr.branch, detectReleaseType(pr.title)]
|
|
56
|
+
}),
|
|
57
|
+
)
|
|
45
58
|
|
|
46
59
|
if (releasePRsList.length === 0) {
|
|
47
60
|
logger.info('âšī¸ No open release branches found')
|
|
@@ -58,18 +71,19 @@ export const worktreesAdd = async (options: WorktreeManagementArgs): Promise<Too
|
|
|
58
71
|
|
|
59
72
|
if (all) {
|
|
60
73
|
selectedReleaseBranches = releasePRsList
|
|
74
|
+
} else if (versions) {
|
|
75
|
+
selectedReleaseBranches = versions.split(',').map((v) => {
|
|
76
|
+
return `release/v${v.trim()}`
|
|
77
|
+
})
|
|
61
78
|
} else {
|
|
62
79
|
commandEcho.setInteractive()
|
|
63
80
|
|
|
81
|
+
const descriptions = await getJiraDescriptions()
|
|
82
|
+
|
|
64
83
|
selectedReleaseBranches = await checkbox({
|
|
65
84
|
required: true,
|
|
66
85
|
message: 'đŋ Select release branches',
|
|
67
|
-
choices:
|
|
68
|
-
return {
|
|
69
|
-
name: pr.replace('release/v', ''),
|
|
70
|
-
value: pr,
|
|
71
|
-
}
|
|
72
|
-
}),
|
|
86
|
+
choices: formatBranchChoices({ branches: releasePRsList, descriptions, types: releaseTypes }),
|
|
73
87
|
})
|
|
74
88
|
}
|
|
75
89
|
|
|
@@ -79,7 +93,12 @@ export const worktreesAdd = async (options: WorktreeManagementArgs): Promise<Too
|
|
|
79
93
|
if (allSelected) {
|
|
80
94
|
commandEcho.addOption('--all', true)
|
|
81
95
|
} else {
|
|
82
|
-
commandEcho.addOption(
|
|
96
|
+
commandEcho.addOption(
|
|
97
|
+
'--versions',
|
|
98
|
+
selectedReleaseBranches.map((branch) => {
|
|
99
|
+
return branch.replace('release/v', '')
|
|
100
|
+
}),
|
|
101
|
+
)
|
|
83
102
|
}
|
|
84
103
|
|
|
85
104
|
// Ask for confirmation
|
|
@@ -103,14 +122,29 @@ export const worktreesAdd = async (options: WorktreeManagementArgs): Promise<Too
|
|
|
103
122
|
commandEcho.addOption('--yes', true)
|
|
104
123
|
}
|
|
105
124
|
|
|
106
|
-
const openInCursor = cursor
|
|
125
|
+
const openInCursor = cursor ?? (await confirm({ message: 'Open created worktrees in Cursor?' }))
|
|
107
126
|
|
|
108
|
-
if (
|
|
127
|
+
if (typeof cursor === 'undefined') {
|
|
109
128
|
commandEcho.setInteractive()
|
|
110
129
|
}
|
|
111
130
|
|
|
112
131
|
if (openInCursor) {
|
|
113
132
|
commandEcho.addOption('--cursor', true)
|
|
133
|
+
} else {
|
|
134
|
+
commandEcho.addOption('--no-cursor', true)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const openInGithubDesktop =
|
|
138
|
+
githubDesktop ?? (await confirm({ message: 'Open created worktrees in GitHub Desktop?' }))
|
|
139
|
+
|
|
140
|
+
if (typeof githubDesktop === 'undefined') {
|
|
141
|
+
commandEcho.setInteractive()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (openInGithubDesktop) {
|
|
145
|
+
commandEcho.addOption('--github-desktop', true)
|
|
146
|
+
} else {
|
|
147
|
+
commandEcho.addOption('--no-github-desktop', true)
|
|
114
148
|
}
|
|
115
149
|
|
|
116
150
|
const { branchesToCreate } = categorizeWorktrees({
|
|
@@ -118,7 +152,7 @@ export const worktreesAdd = async (options: WorktreeManagementArgs): Promise<Too
|
|
|
118
152
|
currentWorktrees,
|
|
119
153
|
})
|
|
120
154
|
|
|
121
|
-
const createdWorktrees = await createWorktrees(branchesToCreate, worktreeDir
|
|
155
|
+
const createdWorktrees = await createWorktrees(branchesToCreate, worktreeDir)
|
|
122
156
|
|
|
123
157
|
logResults(createdWorktrees)
|
|
124
158
|
|
|
@@ -128,6 +162,13 @@ export const worktreesAdd = async (options: WorktreeManagementArgs): Promise<Too
|
|
|
128
162
|
}
|
|
129
163
|
}
|
|
130
164
|
|
|
165
|
+
if (openInGithubDesktop) {
|
|
166
|
+
for (const branch of createdWorktrees) {
|
|
167
|
+
await $`github ${worktreeDir}/${branch}`
|
|
168
|
+
await $`sleep 5`
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
131
172
|
commandEcho.print()
|
|
132
173
|
|
|
133
174
|
const structuredContent = {
|
|
@@ -182,26 +223,27 @@ const categorizeWorktrees = (args: CategorizeWorktreesArgs): { branchesToCreate:
|
|
|
182
223
|
/**
|
|
183
224
|
* Create worktrees for the specified branches
|
|
184
225
|
*/
|
|
185
|
-
const createWorktrees = async (branches: string[], worktreeDir: string
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
for (const branch of branches) {
|
|
189
|
-
try {
|
|
226
|
+
const createWorktrees = async (branches: string[], worktreeDir: string): Promise<string[]> => {
|
|
227
|
+
const results = await Promise.allSettled(
|
|
228
|
+
branches.map(async (branch) => {
|
|
190
229
|
const worktreePath = `${worktreeDir}/${branch}`
|
|
191
230
|
|
|
192
231
|
await $`git worktree add ${worktreePath} ${branch}`
|
|
232
|
+
await $({ cwd: worktreePath })`pnpm install`
|
|
193
233
|
|
|
194
|
-
|
|
234
|
+
return branch
|
|
235
|
+
}),
|
|
236
|
+
)
|
|
195
237
|
|
|
196
|
-
|
|
197
|
-
copyFileSync(rootEnvPath, `${worktreePath}/.env`)
|
|
238
|
+
const created: string[] = []
|
|
198
239
|
|
|
199
|
-
|
|
200
|
-
|
|
240
|
+
for (const [index, result] of results.entries()) {
|
|
241
|
+
if (result.status === 'fulfilled') {
|
|
242
|
+
created.push(result.value)
|
|
243
|
+
} else {
|
|
244
|
+
const branch = branches[index]
|
|
201
245
|
|
|
202
|
-
|
|
203
|
-
} catch (error) {
|
|
204
|
-
logger.error({ error, branch }, `â Failed to create worktree for ${branch}`)
|
|
246
|
+
logger.error({ error: result.reason }, `â Failed to create worktree for ${branch}`)
|
|
205
247
|
}
|
|
206
248
|
}
|
|
207
249
|
|
|
@@ -213,26 +255,29 @@ const createWorktrees = async (branches: string[], worktreeDir: string, projectR
|
|
|
213
255
|
*/
|
|
214
256
|
const logResults = (created: string[]): void => {
|
|
215
257
|
if (created.length > 0) {
|
|
216
|
-
logger.info('
|
|
217
|
-
|
|
218
|
-
|
|
258
|
+
logger.info('â
Created git worktrees:')
|
|
259
|
+
for (const branch of created) {
|
|
260
|
+
logger.info(branch)
|
|
261
|
+
}
|
|
219
262
|
logger.info('')
|
|
220
263
|
} else {
|
|
221
|
-
logger.info('âšī¸ No new worktrees to create')
|
|
264
|
+
logger.info('âšī¸ No new git worktrees to create')
|
|
222
265
|
}
|
|
223
266
|
}
|
|
224
267
|
|
|
225
268
|
// MCP Tool Registration
|
|
226
269
|
export const worktreesAddMcpTool = {
|
|
227
270
|
name: 'worktrees-add',
|
|
228
|
-
description: 'Create worktrees for selected release branches',
|
|
271
|
+
description: 'Create git worktrees for selected release branches',
|
|
229
272
|
inputSchema: {
|
|
230
|
-
all: z.boolean().describe('Add worktrees for all release branches without prompting'),
|
|
231
|
-
|
|
273
|
+
all: z.boolean().describe('Add git worktrees for all release branches without prompting'),
|
|
274
|
+
versions: z.string().optional().describe('Specify versions by comma, e.g. 1.2.5, 1.2.6'),
|
|
275
|
+
cursor: z.boolean().optional().describe('Open created git worktrees in Cursor'),
|
|
276
|
+
githubDesktop: z.boolean().optional().describe('Open created git worktrees in GitHub Desktop'),
|
|
232
277
|
},
|
|
233
278
|
outputSchema: {
|
|
234
|
-
createdWorktrees: z.array(z.string()).describe('List of created worktree branches'),
|
|
235
|
-
count: z.number().describe('Number of worktrees created'),
|
|
279
|
+
createdWorktrees: z.array(z.string()).describe('List of created git worktree branches'),
|
|
280
|
+
count: z.number().describe('Number of git worktrees created'),
|
|
236
281
|
},
|
|
237
282
|
handler: worktreesAdd,
|
|
238
283
|
}
|
|
@@ -225,7 +225,7 @@ const logResults = (worktrees: WorktreeInfo[]): void => {
|
|
|
225
225
|
|
|
226
226
|
logger.info(`\n${'â'.repeat(100)}`)
|
|
227
227
|
logger.info(
|
|
228
|
-
`đ Summary: ${worktrees.length} total worktrees (${releases.length} releases, ${features.length} features)`,
|
|
228
|
+
`đ Summary: ${worktrees.length} total git worktrees (${releases.length} releases, ${features.length} features)`,
|
|
229
229
|
)
|
|
230
230
|
|
|
231
231
|
if (current) {
|
|
@@ -4,15 +4,19 @@ import process from 'node:process'
|
|
|
4
4
|
import { z } from 'zod'
|
|
5
5
|
import { $ } from 'zx'
|
|
6
6
|
|
|
7
|
+
import { getReleasePRsWithInfo } from 'src/integrations/gh'
|
|
7
8
|
import { commandEcho } from 'src/lib/command-echo'
|
|
8
9
|
import { WORKTREES_DIR_SUFFIX } from 'src/lib/constants'
|
|
9
10
|
import { getCurrentWorktrees, getProjectRoot } from 'src/lib/git-utils'
|
|
10
11
|
import { logger } from 'src/lib/logger'
|
|
12
|
+
import { detectReleaseType, formatBranchChoices, getJiraDescriptions } from 'src/lib/release-utils'
|
|
13
|
+
import type { ReleaseType } from 'src/lib/release-utils'
|
|
11
14
|
import type { RequiredConfirmedOptionArg, ToolsExecutionResult } from 'src/types'
|
|
12
15
|
|
|
13
16
|
// Constants
|
|
14
17
|
interface WorktreeManagementArgs extends RequiredConfirmedOptionArg {
|
|
15
18
|
all: boolean
|
|
19
|
+
versions?: string
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
/**
|
|
@@ -20,7 +24,7 @@ interface WorktreeManagementArgs extends RequiredConfirmedOptionArg {
|
|
|
20
24
|
* Creates worktrees for active release branches and removes unused ones
|
|
21
25
|
*/
|
|
22
26
|
export const worktreesRemove = async (options: WorktreeManagementArgs): Promise<ToolsExecutionResult> => {
|
|
23
|
-
const { confirmedCommand, all } = options
|
|
27
|
+
const { confirmedCommand, all, versions } = options
|
|
24
28
|
|
|
25
29
|
commandEcho.start('worktrees-remove')
|
|
26
30
|
|
|
@@ -46,18 +50,25 @@ export const worktreesRemove = async (options: WorktreeManagementArgs): Promise<
|
|
|
46
50
|
|
|
47
51
|
if (all) {
|
|
48
52
|
selectedReleaseBranches = currentWorktrees
|
|
53
|
+
} else if (versions) {
|
|
54
|
+
selectedReleaseBranches = versions.split(',').map((v) => {
|
|
55
|
+
return `release/v${v.trim()}`
|
|
56
|
+
})
|
|
49
57
|
} else {
|
|
50
58
|
commandEcho.setInteractive()
|
|
51
59
|
|
|
60
|
+
const [descriptions, prInfo] = await Promise.all([getJiraDescriptions(), getReleasePRsWithInfo()])
|
|
61
|
+
|
|
62
|
+
const releaseTypes = new Map<string, ReleaseType>(
|
|
63
|
+
prInfo.map((pr) => {
|
|
64
|
+
return [pr.branch, detectReleaseType(pr.title)]
|
|
65
|
+
}),
|
|
66
|
+
)
|
|
67
|
+
|
|
52
68
|
selectedReleaseBranches = await checkbox({
|
|
53
69
|
required: true,
|
|
54
70
|
message: 'đŋ Select release branches',
|
|
55
|
-
choices:
|
|
56
|
-
return {
|
|
57
|
-
name: pr.replace('release/v', ''),
|
|
58
|
-
value: pr,
|
|
59
|
-
}
|
|
60
|
-
}),
|
|
71
|
+
choices: formatBranchChoices({ branches: currentWorktrees, descriptions, types: releaseTypes }),
|
|
61
72
|
})
|
|
62
73
|
}
|
|
63
74
|
|
|
@@ -67,7 +78,7 @@ export const worktreesRemove = async (options: WorktreeManagementArgs): Promise<
|
|
|
67
78
|
if (allSelected) {
|
|
68
79
|
commandEcho.addOption('--all', true)
|
|
69
80
|
} else {
|
|
70
|
-
commandEcho.addOption('--
|
|
81
|
+
commandEcho.addOption('--versions', selectedReleaseBranches)
|
|
71
82
|
}
|
|
72
83
|
|
|
73
84
|
// Ask for confirmation
|
|
@@ -118,22 +129,39 @@ export const worktreesRemove = async (options: WorktreeManagementArgs): Promise<
|
|
|
118
129
|
}
|
|
119
130
|
|
|
120
131
|
/**
|
|
121
|
-
* Remove worktrees for the specified branches
|
|
132
|
+
* Remove worktrees for the specified branches and whole folder
|
|
122
133
|
*/
|
|
123
134
|
const removeWorktrees = async (branches: string[], worktreeDir: string): Promise<string[]> => {
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
for (const branch of branches) {
|
|
127
|
-
try {
|
|
135
|
+
const results = await Promise.allSettled(
|
|
136
|
+
branches.map(async (branch) => {
|
|
128
137
|
const worktreePath = `${worktreeDir}/${branch}`
|
|
129
138
|
|
|
130
139
|
await $`git worktree remove ${worktreePath}`
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
140
|
+
|
|
141
|
+
return branch
|
|
142
|
+
}),
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
const removed: string[] = []
|
|
146
|
+
|
|
147
|
+
for (const [index, result] of results.entries()) {
|
|
148
|
+
if (result.status === 'fulfilled') {
|
|
149
|
+
removed.push(result.value)
|
|
150
|
+
} else {
|
|
151
|
+
const branch = branches[index]
|
|
152
|
+
|
|
153
|
+
logger.error({ error: result.reason }, `â Failed to remove worktree for ${branch}`)
|
|
134
154
|
}
|
|
135
155
|
}
|
|
136
156
|
|
|
157
|
+
if (removed.length === branches.length) {
|
|
158
|
+
await $`git worktree prune`
|
|
159
|
+
await $`rm -rf ${worktreeDir}`
|
|
160
|
+
|
|
161
|
+
logger.info(`đī¸ Removed worktree folder: ${worktreeDir}`)
|
|
162
|
+
logger.info('')
|
|
163
|
+
}
|
|
164
|
+
|
|
137
165
|
return removed
|
|
138
166
|
}
|
|
139
167
|
|
|
@@ -143,7 +171,9 @@ const removeWorktrees = async (branches: string[], worktreeDir: string): Promise
|
|
|
143
171
|
const logResults = (removed: string[]): void => {
|
|
144
172
|
if (removed.length > 0) {
|
|
145
173
|
logger.info('â Removed worktrees:')
|
|
146
|
-
|
|
174
|
+
for (const branch of removed) {
|
|
175
|
+
logger.info(branch)
|
|
176
|
+
}
|
|
147
177
|
logger.info('')
|
|
148
178
|
} else {
|
|
149
179
|
logger.info('âšī¸ No unused worktrees to remove')
|
|
@@ -155,11 +185,12 @@ export const worktreesRemoveMcpTool = {
|
|
|
155
185
|
name: 'worktrees-remove',
|
|
156
186
|
description: 'Remove selected worktrees',
|
|
157
187
|
inputSchema: {
|
|
158
|
-
all: z.boolean().describe('Remove all worktrees without prompting'),
|
|
188
|
+
all: z.boolean().describe('Remove all git worktrees without prompting'),
|
|
189
|
+
versions: z.string().optional().describe('Specify versions by comma, e.g. 1.2.5, 1.2.6'),
|
|
159
190
|
},
|
|
160
191
|
outputSchema: {
|
|
161
|
-
removedWorktrees: z.array(z.string()).describe('List of removed worktree branches'),
|
|
162
|
-
count: z.number().describe('Number of worktrees removed'),
|
|
192
|
+
removedWorktrees: z.array(z.string()).describe('List of removed git worktree branches'),
|
|
193
|
+
count: z.number().describe('Number of git worktrees removed'),
|
|
163
194
|
},
|
|
164
195
|
handler: worktreesRemove,
|
|
165
196
|
}
|
|
@@ -133,7 +133,9 @@ const removeWorktrees = async (branches: string[], worktreeDir: string): Promise
|
|
|
133
133
|
const logResults = (removed: string[]): void => {
|
|
134
134
|
if (removed.length > 0) {
|
|
135
135
|
logger.info('â Removed worktrees:')
|
|
136
|
-
|
|
136
|
+
for (const branch of removed) {
|
|
137
|
+
logger.info(branch)
|
|
138
|
+
}
|
|
137
139
|
logger.info('')
|
|
138
140
|
} else {
|
|
139
141
|
logger.info('âšī¸ No unused worktrees to remove')
|
package/src/entry/cli.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Command } from 'commander'
|
|
1
|
+
import { Command, Option } from 'commander'
|
|
2
2
|
|
|
3
3
|
import { doctor } from 'src/commands/doctor'
|
|
4
4
|
import { envClear } from 'src/commands/env-clear'
|
|
@@ -6,7 +6,6 @@ import { envInit } from 'src/commands/env-init'
|
|
|
6
6
|
import { envList } from 'src/commands/env-list'
|
|
7
7
|
import { envLoad } from 'src/commands/env-load'
|
|
8
8
|
import { envStatus } from 'src/commands/env-status'
|
|
9
|
-
// Commands
|
|
10
9
|
import { ghMergeDev } from 'src/commands/gh-merge-dev'
|
|
11
10
|
import { ghReleaseDeliver } from 'src/commands/gh-release-deliver'
|
|
12
11
|
import { ghReleaseDeployAll } from 'src/commands/gh-release-deploy-all'
|
|
@@ -43,12 +42,14 @@ program
|
|
|
43
42
|
.description('Create a single release branch')
|
|
44
43
|
.option('-v, --version <version>', 'Specify the version to create, e.g. 1.2.5')
|
|
45
44
|
.option('-d, --description <description>', 'Optional description for the Jira version')
|
|
45
|
+
.addOption(new Option('-t, --type <type>', 'Release type (default: regular)').choices(['regular', 'hotfix']))
|
|
46
46
|
.option('-y, --yes', 'Skip confirmation prompt')
|
|
47
47
|
.option('--no-checkout', 'Do not checkout the created branch after creation (checkout is default)')
|
|
48
48
|
.action(async (options) => {
|
|
49
49
|
await releaseCreate({
|
|
50
50
|
version: options.version,
|
|
51
51
|
description: options.description,
|
|
52
|
+
type: options.type,
|
|
52
53
|
confirmedCommand: options.yes,
|
|
53
54
|
checkout: options.checkout,
|
|
54
55
|
})
|
|
@@ -58,12 +59,12 @@ program
|
|
|
58
59
|
.command('release-create-batch')
|
|
59
60
|
.description('Create multiple release branches (batch operation)')
|
|
60
61
|
.option('-v, --versions <versions>', 'Specify the versions to create by comma, e.g. 1.2.5, 1.2.6')
|
|
61
|
-
.
|
|
62
|
+
.addOption(new Option('-t, --type <type>', 'Release type (default: regular)').choices(['regular', 'hotfix']))
|
|
62
63
|
.option('-y, --yes', 'Skip confirmation prompt')
|
|
63
64
|
.action(async (options) => {
|
|
64
65
|
await releaseCreateBatch({
|
|
65
66
|
versions: options.versions,
|
|
66
|
-
|
|
67
|
+
type: options.type,
|
|
67
68
|
confirmedCommand: options.yes,
|
|
68
69
|
})
|
|
69
70
|
})
|
|
@@ -132,9 +133,19 @@ program
|
|
|
132
133
|
.description('Add git worktrees for release branches')
|
|
133
134
|
.option('-y, --yes', 'Skip confirmation prompt')
|
|
134
135
|
.option('-a, --all', 'Select all active release branches')
|
|
136
|
+
.option('-v, --versions <versions>', 'Specify versions by comma, e.g. 1.2.5, 1.2.6')
|
|
135
137
|
.option('-c, --cursor', 'Open created worktrees in Cursor')
|
|
138
|
+
.option('--no-cursor', 'Skip Cursor prompt')
|
|
139
|
+
.option('-g, --github-desktop', 'Open created worktrees in GitHub Desktop')
|
|
140
|
+
.option('--no-github-desktop', 'Skip GitHub Desktop prompt')
|
|
136
141
|
.action(async (options) => {
|
|
137
|
-
await worktreesAdd({
|
|
142
|
+
await worktreesAdd({
|
|
143
|
+
confirmedCommand: options.yes,
|
|
144
|
+
all: options.all,
|
|
145
|
+
versions: options.versions,
|
|
146
|
+
cursor: options.cursor,
|
|
147
|
+
githubDesktop: options.githubDesktop,
|
|
148
|
+
})
|
|
138
149
|
})
|
|
139
150
|
|
|
140
151
|
program
|
|
@@ -149,8 +160,9 @@ program
|
|
|
149
160
|
.description('Remove git worktrees for release branches')
|
|
150
161
|
.option('-y, --yes', 'Skip confirmation prompt')
|
|
151
162
|
.option('-a, --all', 'Select all active release branches')
|
|
163
|
+
.option('-v, --versions <versions>', 'Specify versions by comma, e.g. 1.2.5, 1.2.6')
|
|
152
164
|
.action(async (options) => {
|
|
153
|
-
await worktreesRemove({ confirmedCommand: options.yes, all: options.all })
|
|
165
|
+
await worktreesRemove({ confirmedCommand: options.yes, all: options.all, versions: options.versions })
|
|
154
166
|
})
|
|
155
167
|
|
|
156
168
|
program
|
|
@@ -183,9 +195,7 @@ program
|
|
|
183
195
|
|
|
184
196
|
program
|
|
185
197
|
.command('env-load')
|
|
186
|
-
.description(
|
|
187
|
-
'Load Doppler env vars for a config. Prints a file path to stdout â auto-sourced by the env-load shell alias, or run source <path> manually',
|
|
188
|
-
)
|
|
198
|
+
.description('Load Doppler env vars for a config. Source the returned file path to apply.')
|
|
189
199
|
.option('-c, --config <config>', 'Environment config name to load (e.g. dev, arthur)')
|
|
190
200
|
.action(async (options) => {
|
|
191
201
|
await envLoad({ config: options.config })
|
|
@@ -193,9 +203,7 @@ program
|
|
|
193
203
|
|
|
194
204
|
program
|
|
195
205
|
.command('env-clear')
|
|
196
|
-
.description(
|
|
197
|
-
'Clear loaded env vars. Prints a file path to stdout â auto-sourced by the env-clear shell alias, or run source <path> manually',
|
|
198
|
-
)
|
|
206
|
+
.description('Clear loaded env vars. Source the returned file path to apply.')
|
|
199
207
|
.action(async () => {
|
|
200
208
|
await envClear()
|
|
201
209
|
})
|