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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "infra-kit",
3
3
  "type": "module",
4
- "version": "0.1.78",
4
+ "version": "0.1.80",
5
5
  "description": "infra-kit",
6
6
  "main": "dist/cli.js",
7
7
  "module": "dist/cli.js",
@@ -35,7 +35,6 @@
35
35
  "@inquirer/select": "^5.1.2",
36
36
  "@modelcontextprotocol/sdk": "^1.27.1",
37
37
  "commander": "^14.0.3",
38
- "dotenv": "^17.3.1",
39
38
  "pino": "^10.3.1",
40
39
  "pino-pretty": "^13.1.3",
41
40
  "yaml": "^2.8.2",
@@ -21,7 +21,7 @@ import type { ToolsExecutionResult } from 'src/types'
21
21
  export const envStatus = async (): Promise<ToolsExecutionResult> => {
22
22
  await validateDopplerCliAndAuth()
23
23
 
24
- logger.info('Environment Session Status:\n')
24
+ logger.info('Environment session status:')
25
25
 
26
26
  // Check session-loaded vars — getSessionCacheDir() throws if INFRA_KIT_SESSION is unset
27
27
  const cacheDir = getSessionCacheDir()
@@ -31,6 +31,7 @@ export const envStatus = async (): Promise<ToolsExecutionResult> => {
31
31
 
32
32
  let sessionLoadedCount = 0
33
33
  let sessionTotalCount = 0
34
+
34
35
  const sessionConfig = process.env[INFRA_KIT_ENV_CONFIG_VAR] ?? null
35
36
  const sessionProject = process.env[INFRA_KIT_ENV_PROJECT_VAR] ?? null
36
37
  const sessionLoadedAt = process.env[INFRA_KIT_ENV_LOADED_AT_VAR] ?? null
@@ -45,13 +46,14 @@ export const envStatus = async (): Promise<ToolsExecutionResult> => {
45
46
  }).length
46
47
  }
47
48
 
49
+ const loadedAtDisplay = sessionLoadedAt?.replace(/\.\d{3}Z$/, '') ?? null
50
+
48
51
  logger.info(
49
- ` Session: ${sessionLoadedCount} of ${sessionTotalCount} vars loaded (project: ${sessionProject}, config: ${sessionConfig}, loaded at: ${sessionLoadedAt})`,
52
+ ` ${sessionConfig}: ${sessionLoadedCount} of ${sessionTotalCount} vars loaded (project: ${sessionProject}, loadedAt: ${loadedAtDisplay}, session: ${sessionId})\n`,
50
53
  )
51
54
  } else {
52
- logger.info(' Session: no env loaded')
55
+ logger.info(` Session ${sessionId}: no env loaded\n`)
53
56
  }
54
- logger.info(` Session ID: ${sessionId}`)
55
57
 
56
58
  const structuredContent = {
57
59
  sessionId,
@@ -82,7 +84,7 @@ export const envStatusMcpTool = {
82
84
  sessionId: z.string().describe('Current terminal session ID'),
83
85
  sessionLoadedCount: z.number().describe('Number of cached vars active in the current session'),
84
86
  sessionTotalCount: z.number().describe('Total number of cached var names'),
85
- sessionConfig: z.string().nullable().describe('Doppler config name of the loaded session'),
87
+ sessionConfig: z.string().nullable().describe('Doppler config name of the loaded session (environment name)'),
86
88
  sessionProject: z.string().nullable().describe('Doppler project name of the loaded session'),
87
89
  sessionLoadedAt: z.string().nullable().describe('ISO 8601 timestamp of when the env was loaded'),
88
90
  },
@@ -5,9 +5,10 @@ import process from 'node:process'
5
5
  import { z } from 'zod'
6
6
  import { $ } from 'zx'
7
7
 
8
- import { getReleasePRs } from 'src/integrations/gh'
8
+ import { getReleasePRsWithInfo } from 'src/integrations/gh'
9
9
  import { commandEcho } from 'src/lib/command-echo'
10
10
  import { logger } from 'src/lib/logger'
11
+ import { detectReleaseType, formatBranchChoices, getJiraDescriptions } from 'src/lib/release-utils'
11
12
  import type { RequiredConfirmedOptionArg, ToolsExecutionResult } from 'src/types'
12
13
 
13
14
  interface GhMergeDevArgs extends RequiredConfirmedOptionArg {
@@ -22,7 +23,15 @@ export const ghMergeDev = async (args: GhMergeDevArgs): Promise<ToolsExecutionRe
22
23
 
23
24
  commandEcho.start('merge-dev')
24
25
 
25
- const releasePRsList = await getReleasePRs()
26
+ // Only merge dev into regular releases (not hotfixes, which target main)
27
+ const allPRs = await getReleasePRsWithInfo()
28
+ const releasePRsList = allPRs
29
+ .filter((pr) => {
30
+ return detectReleaseType(pr.title) === 'regular'
31
+ })
32
+ .map((pr) => {
33
+ return pr.branch
34
+ })
26
35
 
27
36
  if (releasePRsList.length === 0) {
28
37
  logger.info('ℹ️ No open release branches found')
@@ -47,15 +56,12 @@ export const ghMergeDev = async (args: GhMergeDevArgs): Promise<ToolsExecutionRe
47
56
  } else {
48
57
  commandEcho.setInteractive()
49
58
 
59
+ const descriptions = await getJiraDescriptions()
60
+
50
61
  selectedReleaseBranches = await checkbox({
51
62
  required: true,
52
63
  message: '🌿 Select release branches',
53
- choices: releasePRsList.map((pr) => {
54
- return {
55
- name: pr.replace('release/v', ''),
56
- value: pr,
57
- }
58
- }),
64
+ choices: formatBranchChoices({ branches: releasePRsList, descriptions }),
59
65
  })
60
66
  }
61
67
 
@@ -65,7 +71,12 @@ export const ghMergeDev = async (args: GhMergeDevArgs): Promise<ToolsExecutionRe
65
71
  if (allSelected) {
66
72
  commandEcho.addOption('--all', true)
67
73
  } else {
68
- commandEcho.addOption('--branches', selectedReleaseBranches)
74
+ commandEcho.addOption(
75
+ '--versions',
76
+ selectedReleaseBranches.map((branch) => {
77
+ return branch.replace('release/v', '')
78
+ }),
79
+ )
69
80
  }
70
81
 
71
82
  // Validate input
@@ -4,10 +4,12 @@ import process from 'node:process'
4
4
  import { z } from 'zod'
5
5
  import { $ } from 'zx'
6
6
 
7
- import { getReleasePRs } from 'src/integrations/gh'
7
+ import { getReleasePRsWithInfo } from 'src/integrations/gh'
8
8
  import { deliverJiraRelease, loadJiraConfigOptional } from 'src/integrations/jira'
9
9
  import { commandEcho } from 'src/lib/command-echo'
10
10
  import { logger } from 'src/lib/logger'
11
+ import { detectReleaseType, formatBranchChoices, getJiraDescriptions } from 'src/lib/release-utils'
12
+ import type { ReleaseType } from 'src/lib/release-utils'
11
13
  import type { RequiredConfirmedOptionArg, ToolsExecutionResult } from 'src/types'
12
14
 
13
15
  interface GhReleaseDeliverArgs extends RequiredConfirmedOptionArg {
@@ -22,7 +24,17 @@ export const ghReleaseDeliver = async (args: GhReleaseDeliverArgs): Promise<Tool
22
24
 
23
25
  commandEcho.start('release-deliver')
24
26
 
25
- const releasePRsList = await getReleasePRs()
27
+ const releasePRsInfo = await getReleasePRsWithInfo()
28
+
29
+ const branches = releasePRsInfo.map((pr) => {
30
+ return pr.branch
31
+ })
32
+
33
+ const releaseTypes = new Map<string, ReleaseType>(
34
+ releasePRsInfo.map((pr) => {
35
+ return [pr.branch, detectReleaseType(pr.title)]
36
+ }),
37
+ )
26
38
 
27
39
  let selectedReleaseBranch = '' // "release/v1.8.0"
28
40
 
@@ -31,14 +43,11 @@ export const ghReleaseDeliver = async (args: GhReleaseDeliverArgs): Promise<Tool
31
43
  } else {
32
44
  commandEcho.setInteractive()
33
45
 
46
+ const descriptions = await getJiraDescriptions()
47
+
34
48
  selectedReleaseBranch = await select({
35
49
  message: '🌿 Select release branch',
36
- choices: releasePRsList.map((pr) => {
37
- return {
38
- name: pr.replace('release/v', ''),
39
- value: pr,
40
- }
41
- }),
50
+ choices: formatBranchChoices({ branches, descriptions, types: releaseTypes }),
42
51
  })
43
52
  }
44
53
 
@@ -47,11 +56,17 @@ export const ghReleaseDeliver = async (args: GhReleaseDeliverArgs): Promise<Tool
47
56
  commandEcho.addOption('--version', selectedVersion)
48
57
 
49
58
  // Check if release branch exists in the list
50
- if (!releasePRsList.includes(selectedReleaseBranch)) {
59
+ const prInfo = releasePRsInfo.find((pr) => {
60
+ return pr.branch === selectedReleaseBranch
61
+ })
62
+
63
+ if (!prInfo) {
51
64
  logger.error(`❌ Release branch ${selectedReleaseBranch} not found in open PRs. Exiting...`)
52
65
  process.exit(1)
53
66
  }
54
67
 
68
+ const releaseType: ReleaseType = detectReleaseType(prInfo.title)
69
+
55
70
  const answer = confirmedCommand
56
71
  ? true
57
72
  : await confirm({
@@ -73,18 +88,33 @@ export const ghReleaseDeliver = async (args: GhReleaseDeliverArgs): Promise<Tool
73
88
  try {
74
89
  $.quiet = true
75
90
 
76
- await $`gh pr merge ${selectedReleaseBranch} --squash --admin --delete-branch` // TODO: add --body (AI for generate message)
77
- await $`gh pr create --base main --head dev --title "Release v${selectedReleaseBranch.replace('release/v', '')} (RC)" --body ""`
78
- await $`gh pr merge dev --squash --admin`
91
+ if (releaseType === 'hotfix') {
92
+ // Hotfix: merge directly into main, deploy, sync back to dev
93
+ await $`gh pr merge ${selectedReleaseBranch} --squash --admin --delete-branch`
79
94
 
80
- $.quiet = false
95
+ $.quiet = false
81
96
 
82
- await $`gh workflow run deploy-all.yml --ref main -f environment=prod`
97
+ await $`gh workflow run deploy-all.yml --ref main -f environment=prod`
83
98
 
84
- $.quiet = true
99
+ $.quiet = true
85
100
 
86
- // Merge main into dev
87
- await $`git switch main && git pull && git switch dev && git pull && git merge main --no-edit && git push`
101
+ // Sync main into dev
102
+ await $`git switch main && git pull && git switch dev && git pull && git merge main --no-edit && git push`
103
+ } else {
104
+ // Regular: merge into dev, create RC PR to main, merge to main, deploy, sync
105
+ await $`gh pr merge ${selectedReleaseBranch} --squash --admin --delete-branch`
106
+ await $`gh pr create --base main --head dev --title "Release v${selectedVersion} (RC)" --body ""`
107
+ await $`gh pr merge dev --squash --admin`
108
+
109
+ $.quiet = false
110
+
111
+ await $`gh workflow run deploy-all.yml --ref main -f environment=prod`
112
+
113
+ $.quiet = true
114
+
115
+ // Sync main into dev
116
+ await $`git switch main && git pull && git switch dev && git pull && git merge main --no-edit && git push`
117
+ }
88
118
 
89
119
  $.quiet = false
90
120
 
@@ -96,16 +126,6 @@ export const ghReleaseDeliver = async (args: GhReleaseDeliverArgs): Promise<Tool
96
126
  const versionName = selectedReleaseBranch.replace('release/', '')
97
127
 
98
128
  await deliverJiraRelease({ versionName }, jiraConfig)
99
- // const result = await deliverJiraRelease({ versionName }, jiraConfig)
100
-
101
- // logger.info(
102
- // {
103
- // // versionId: result.version.id,
104
- // versionName: result.version.name,
105
- // // releaseDate: result.version.releaseDate,
106
- // },
107
- // 'Successfully delivered Jira release',
108
- // )
109
129
  } catch (error) {
110
130
  logger.error({ error }, 'Failed to deliver Jira release (non-blocking)')
111
131
  }
@@ -120,6 +140,7 @@ export const ghReleaseDeliver = async (args: GhReleaseDeliverArgs): Promise<Tool
120
140
  const structuredContent = {
121
141
  releaseBranch: selectedReleaseBranch,
122
142
  version: selectedReleaseBranch.replace('release/v', ''),
143
+ type: releaseType,
123
144
  success: true,
124
145
  }
125
146
 
@@ -148,6 +169,7 @@ export const ghReleaseDeliverMcpTool = {
148
169
  outputSchema: {
149
170
  releaseBranch: z.string().describe('The release branch that was delivered'),
150
171
  version: z.string().describe('The version that was delivered'),
172
+ type: z.enum(['regular', 'hotfix']).describe('Release type'),
151
173
  success: z.boolean().describe('Whether the delivery was successful'),
152
174
  },
153
175
  handler: ghReleaseDeliver,
@@ -3,10 +3,12 @@ import process from 'node:process'
3
3
  import { z } from 'zod'
4
4
  import { $ } from 'zx'
5
5
 
6
- import { getReleasePRs } from 'src/integrations/gh'
6
+ import { getReleasePRsWithInfo } from 'src/integrations/gh'
7
7
  import { commandEcho } from 'src/lib/command-echo'
8
8
  import { ENVs } from 'src/lib/constants'
9
9
  import { logger } from 'src/lib/logger'
10
+ import { detectReleaseType, formatBranchChoices, getJiraDescriptions } from 'src/lib/release-utils'
11
+ import type { ReleaseType } from 'src/lib/release-utils'
10
12
  import type { ToolsExecutionResult } from 'src/types'
11
13
 
12
14
  interface GhReleaseDeployAllArgs {
@@ -25,11 +27,19 @@ export const ghReleaseDeployAll = async (args: GhReleaseDeployAllArgs): Promise<
25
27
 
26
28
  // TODO: add validation for semver version for version variable
27
29
 
28
- const releasePRsList: string[] = ['dev'] // ["release/v1.8.0", "release/v1.9.0"]
30
+ const releasePRsInfo = await getReleasePRsWithInfo()
29
31
 
30
- const releasePRs = await getReleasePRs()
32
+ const branches = releasePRsInfo.map((pr) => {
33
+ return pr.branch
34
+ })
31
35
 
32
- releasePRsList.push(...releasePRs)
36
+ const releaseTypes = new Map<string, ReleaseType>(
37
+ releasePRsInfo.map((pr) => {
38
+ return [pr.branch, detectReleaseType(pr.title)]
39
+ }),
40
+ )
41
+
42
+ const releasePRsList: string[] = ['dev', ...branches]
33
43
 
34
44
  let selectedReleaseBranch = '' // "release/v1.8.0"
35
45
 
@@ -38,14 +48,11 @@ export const ghReleaseDeployAll = async (args: GhReleaseDeployAllArgs): Promise<
38
48
  } else {
39
49
  commandEcho.setInteractive()
40
50
 
51
+ const descriptions = await getJiraDescriptions()
52
+
41
53
  selectedReleaseBranch = await select({
42
54
  message: '🌿 Select release branch',
43
- choices: releasePRsList.map((pr) => {
44
- return {
45
- name: pr.replace('release/v', ''),
46
- value: pr,
47
- }
48
- }),
55
+ choices: [{ name: 'dev', value: 'dev' }, ...formatBranchChoices({ branches, descriptions, types: releaseTypes })],
49
56
  })
50
57
  }
51
58
 
@@ -7,11 +7,13 @@ import yaml from 'yaml'
7
7
  import { z } from 'zod'
8
8
  import { $ } from 'zx'
9
9
 
10
- import { getReleasePRs } from 'src/integrations/gh'
10
+ import { getReleasePRsWithInfo } from 'src/integrations/gh'
11
11
  import { commandEcho } from 'src/lib/command-echo'
12
12
  import { ENVs } from 'src/lib/constants'
13
13
  import { getProjectRoot } from 'src/lib/git-utils'
14
14
  import { logger } from 'src/lib/logger'
15
+ import { detectReleaseType, formatBranchChoices, getJiraDescriptions } from 'src/lib/release-utils'
16
+ import type { ReleaseType } from 'src/lib/release-utils'
15
17
  import type { ToolsExecutionResult } from 'src/types'
16
18
 
17
19
  interface GhReleaseDeploySelectedArgs {
@@ -30,11 +32,19 @@ export const ghReleaseDeploySelected = async (args: GhReleaseDeploySelectedArgs)
30
32
 
31
33
  commandEcho.start('release-deploy-selected')
32
34
 
33
- const releasePRsList: string[] = ['dev']
35
+ const releasePRsInfo = await getReleasePRsWithInfo()
34
36
 
35
- const releasePRs = await getReleasePRs()
37
+ const branches = releasePRsInfo.map((pr) => {
38
+ return pr.branch
39
+ })
40
+
41
+ const releaseTypes = new Map<string, ReleaseType>(
42
+ releasePRsInfo.map((pr) => {
43
+ return [pr.branch, detectReleaseType(pr.title)]
44
+ }),
45
+ )
36
46
 
37
- releasePRsList.push(...releasePRs)
47
+ const releasePRsList: string[] = ['dev', ...branches]
38
48
 
39
49
  let selectedReleaseBranch = ''
40
50
 
@@ -43,14 +53,11 @@ export const ghReleaseDeploySelected = async (args: GhReleaseDeploySelectedArgs)
43
53
  } else {
44
54
  commandEcho.setInteractive()
45
55
 
56
+ const descriptions = await getJiraDescriptions()
57
+
46
58
  selectedReleaseBranch = await select({
47
59
  message: '🌿 Select release branch',
48
- choices: releasePRsList.map((pr) => {
49
- return {
50
- name: pr.replace('release/v', ''),
51
- value: pr,
52
- }
53
- }),
60
+ choices: [{ name: 'dev', value: 'dev' }, ...formatBranchChoices({ branches, descriptions, types: releaseTypes })],
54
61
  })
55
62
  }
56
63
 
@@ -6,11 +6,13 @@ import yaml from 'yaml'
6
6
  import { z } from 'zod'
7
7
  import { $ } from 'zx'
8
8
 
9
- import { getReleasePRs } from 'src/integrations/gh'
9
+ import { getReleasePRsWithInfo } from 'src/integrations/gh'
10
10
  import { commandEcho } from 'src/lib/command-echo'
11
11
  import { ENVs } from 'src/lib/constants'
12
12
  import { getProjectRoot } from 'src/lib/git-utils'
13
13
  import { logger } from 'src/lib/logger'
14
+ import { detectReleaseType, formatBranchChoices, getJiraDescriptions } from 'src/lib/release-utils'
15
+ import type { ReleaseType } from 'src/lib/release-utils'
14
16
  import type { ToolsExecutionResult } from 'src/types'
15
17
 
16
18
  interface GhReleaseDeployServiceArgs {
@@ -30,11 +32,19 @@ export const ghReleaseDeployService = async (args: GhReleaseDeployServiceArgs):
30
32
 
31
33
  // TODO: add validation for semver version for version variable
32
34
 
33
- const releasePRsList: string[] = ['dev'] // ["release/v1.8.0", "release/v1.9.0"]
35
+ const releasePRsInfo = await getReleasePRsWithInfo()
34
36
 
35
- const releasePRs = await getReleasePRs()
37
+ const branches = releasePRsInfo.map((pr) => {
38
+ return pr.branch
39
+ })
36
40
 
37
- releasePRsList.push(...releasePRs)
41
+ const releaseTypes = new Map<string, ReleaseType>(
42
+ releasePRsInfo.map((pr) => {
43
+ return [pr.branch, detectReleaseType(pr.title)]
44
+ }),
45
+ )
46
+
47
+ const releasePRsList: string[] = ['dev', ...branches]
38
48
 
39
49
  let selectedReleaseBranch = '' // "release/v1.8.0"
40
50
 
@@ -43,14 +53,11 @@ export const ghReleaseDeployService = async (args: GhReleaseDeployServiceArgs):
43
53
  } else {
44
54
  commandEcho.setInteractive()
45
55
 
56
+ const descriptions = await getJiraDescriptions()
57
+
46
58
  selectedReleaseBranch = await select({
47
59
  message: '🌿 Select release branch',
48
- choices: releasePRsList.map((pr) => {
49
- return {
50
- name: pr.replace('release/v', ''),
51
- value: pr,
52
- }
53
- }),
60
+ choices: [{ name: 'dev', value: 'dev' }, ...formatBranchChoices({ branches, descriptions, types: releaseTypes })],
54
61
  })
55
62
  }
56
63
 
@@ -1,25 +1,54 @@
1
1
  import { z } from 'zod'
2
2
 
3
- import { getReleasePRs } from 'src/integrations/gh'
3
+ import { getReleasePRsWithInfo } from 'src/integrations/gh'
4
4
  import { logger } from 'src/lib/logger'
5
+ import { detectReleaseType, formatVersionLabel, getJiraDescriptions } from 'src/lib/release-utils'
5
6
  import type { ToolsExecutionResult } from 'src/types'
6
7
 
7
8
  /**
8
9
  * List all open release branches
9
10
  */
10
11
  export const ghReleaseList = async (): Promise<ToolsExecutionResult> => {
11
- const releasePRs = await getReleasePRs()
12
+ const releasePRs = await getReleasePRsWithInfo()
12
13
 
13
- const releasePRsList = releasePRs.map((pr) => {
14
- return pr.replace('release/', '')
14
+ const releases = releasePRs.map((pr) => {
15
+ return {
16
+ version: pr.branch.replace('release/', ''),
17
+ type: detectReleaseType(pr.title),
18
+ }
19
+ })
20
+
21
+ const jiraDescriptions = await getJiraDescriptions()
22
+
23
+ const maxVersionLength = Math.max(
24
+ ...releases.map((r) => {
25
+ return r.version.length
26
+ }),
27
+ )
28
+
29
+ const formattedLines = releases.map((release) => {
30
+ const label = formatVersionLabel(release.version, release.type, maxVersionLength)
31
+ const description = jiraDescriptions.get(release.version)
32
+
33
+ if (description) {
34
+ return `${label} ${description}`
35
+ }
36
+
37
+ return label
15
38
  })
16
39
 
17
40
  logger.info('All release branches: \n')
18
- logger.info(`\n${releasePRsList.join('\n')}`)
41
+ logger.info(`\n${formattedLines.join('\n')}\n`)
19
42
 
20
43
  const structuredContent = {
21
- releases: releasePRsList,
22
- count: releasePRsList.length,
44
+ releases: releases.map((release) => {
45
+ return {
46
+ version: release.version,
47
+ type: release.type,
48
+ description: jiraDescriptions.get(release.version) || null,
49
+ }
50
+ }),
51
+ count: releases.length,
23
52
  }
24
53
 
25
54
  return {
@@ -39,7 +68,15 @@ export const ghReleaseListMcpTool = {
39
68
  description: 'List all open release branches',
40
69
  inputSchema: {},
41
70
  outputSchema: {
42
- releases: z.array(z.string()).describe('List of all release branches'),
71
+ releases: z
72
+ .array(
73
+ z.object({
74
+ version: z.string().describe('Release version'),
75
+ type: z.enum(['regular', 'hotfix']).describe('Release type'),
76
+ description: z.string().nullable().describe('Jira version description'),
77
+ }),
78
+ )
79
+ .describe('List of all release branches'),
43
80
  count: z.number().describe('Number of release branches'),
44
81
  },
45
82
  handler: ghReleaseList,
@@ -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,11 +8,13 @@ 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'
11
+ import type { ReleaseType } from 'src/lib/release-utils'
10
12
  import type { RequiredConfirmedOptionArg, ToolsExecutionResult } from 'src/types'
11
13
 
12
14
  interface ReleaseCreateArgs extends RequiredConfirmedOptionArg {
13
15
  version?: string
14
16
  description?: string
17
+ type?: ReleaseType
15
18
  checkout?: boolean
16
19
  }
17
20
 
@@ -20,12 +23,19 @@ interface ReleaseCreateArgs extends RequiredConfirmedOptionArg {
20
23
  * Includes Jira version creation and GitHub release branch creation
21
24
  */
22
25
  export const releaseCreate = async (args: ReleaseCreateArgs): Promise<ToolsExecutionResult> => {
23
- const { version: inputVersion, description: inputDescription, confirmedCommand, checkout: inputCheckout } = args
26
+ const {
27
+ version: inputVersion,
28
+ description: inputDescription,
29
+ type: inputType,
30
+ confirmedCommand,
31
+ checkout: inputCheckout,
32
+ } = args
24
33
 
25
34
  commandEcho.start('release-create')
26
35
 
27
36
  let version = inputVersion
28
37
  let description = inputDescription
38
+ let type: ReleaseType = inputType || 'regular'
29
39
  let checkout = inputCheckout
30
40
 
31
41
  // Load Jira config - it is now mandatory
@@ -46,6 +56,21 @@ export const releaseCreate = async (args: ReleaseCreateArgs): Promise<ToolsExecu
46
56
 
47
57
  commandEcho.addOption('--version', trimmedVersion)
48
58
 
59
+ if (!inputType) {
60
+ commandEcho.setInteractive()
61
+
62
+ type = await select<ReleaseType>({
63
+ message: 'Select release type:',
64
+ choices: [
65
+ { name: 'regular', value: 'regular' },
66
+ { name: 'hotfix', value: 'hotfix' },
67
+ ],
68
+ default: 'regular',
69
+ })
70
+ }
71
+
72
+ commandEcho.addOption('--type', type)
73
+
49
74
  if (description === undefined) {
50
75
  commandEcho.setInteractive()
51
76
  description = await question('Enter description (optional, press Enter to skip): ')
@@ -77,10 +102,10 @@ export const releaseCreate = async (args: ReleaseCreateArgs): Promise<ToolsExecu
77
102
  // Track --yes flag if confirmation was interactive (user confirmed)
78
103
  commandEcho.addOption('--yes', true)
79
104
 
80
- await prepareGitForRelease()
105
+ await prepareGitForRelease(type)
81
106
 
82
107
  // Create the release
83
- const release = await createSingleRelease(trimmedVersion, jiraConfig, description)
108
+ const release = await createSingleRelease({ version: trimmedVersion, jiraConfig, description, type })
84
109
 
85
110
  logger.info(`✅ Successfully created release: v${trimmedVersion}`)
86
111
  logger.info(`🔗 GitHub PR: ${release.prUrl}`)
@@ -114,6 +139,7 @@ export const releaseCreate = async (args: ReleaseCreateArgs): Promise<ToolsExecu
114
139
 
115
140
  const structuredContent = {
116
141
  version: trimmedVersion,
142
+ type,
117
143
  branchName: release.branchName,
118
144
  prUrl: release.prUrl,
119
145
  jiraVersionUrl: release.jiraVersionUrl,
@@ -138,10 +164,16 @@ export const releaseCreateMcpTool = {
138
164
  inputSchema: {
139
165
  version: z.string().describe('Version to create (e.g., "1.2.5")'),
140
166
  description: z.string().optional().describe('Optional description for the Jira version'),
167
+ type: z
168
+ .enum(['regular', 'hotfix'])
169
+ .optional()
170
+ .default('regular')
171
+ .describe('Release type: "regular" or "hotfix" (default: "regular")'),
141
172
  checkout: z.boolean().optional().default(true).describe('Checkout to the created branch (default: true)'),
142
173
  },
143
174
  outputSchema: {
144
175
  version: z.string().describe('Version number'),
176
+ type: z.enum(['regular', 'hotfix']).describe('Release type'),
145
177
  branchName: z.string().describe('Release branch name'),
146
178
  prUrl: z.string().describe('GitHub PR URL'),
147
179
  jiraVersionUrl: z.string().describe('Jira version URL'),