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.
@@ -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
- description?: string
16
+ type?: ReleaseType
16
17
  }
17
18
 
18
19
  /**
19
- * Create multiple release branches for the specified versions
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
- // eslint-disable-next-line sonarjs/cognitive-complexity
23
- export const releaseCreateBatch = async (args: ReleaseCreateBatchArgs): Promise<ToolsExecutionResult> => {
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
- // Inform user if they only provided one version
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
- if (description) {
74
- commandEcho.addOption('--description', description)
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, description)
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
- description: z.string().optional().describe('Optional description for the Jira versions'),
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 { getReleasePRs } from 'src/integrations/gh'
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 releasePRsList = await getReleasePRs()
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: releasePRsList.map((pr) => {
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('--branches', selectedReleaseBranches)
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 ? true : await confirm({ message: 'Open created worktrees in Cursor?' })
125
+ const openInCursor = cursor ?? (await confirm({ message: 'Open created worktrees in Cursor?' }))
107
126
 
108
- if (!openInCursor) {
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, projectRoot)
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, projectRoot: string): Promise<string[]> => {
186
- const created: string[] = []
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
- const rootEnvPath = `${projectRoot}/.env`
234
+ return branch
235
+ }),
236
+ )
195
237
 
196
- if (existsSync(rootEnvPath)) {
197
- copyFileSync(rootEnvPath, `${worktreePath}/.env`)
238
+ const created: string[] = []
198
239
 
199
- logger.info('📋 Copied .env to worktree')
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
- created.push(branch)
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('\n')
217
- logger.info('✅ Created worktrees:')
218
- logger.info(created.join('\n'))
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
- cursor: z.boolean().optional().describe('Open created worktrees in Cursor'),
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: currentWorktrees.map((pr) => {
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('--branches', selectedReleaseBranches)
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 removed: string[] = []
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
- removed.push(branch)
132
- } catch (error) {
133
- logger.error({ error, branch }, `❌ Failed to remove worktree for ${branch}`)
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
- logger.info(removed.join('\n'))
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
- logger.info(removed.join('\n'))
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
- .option('-d, --description <description>', 'Optional description for the Jira versions')
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
- description: options.description,
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({ confirmedCommand: options.yes, all: options.all, cursor: options.cursor })
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
  })