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
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "infra-kit",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
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
|
|
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
|
-
`
|
|
52
|
+
` ${sessionConfig}: ${sessionLoadedCount} of ${sessionTotalCount} vars loaded (project: ${sessionProject}, loadedAt: ${loadedAtDisplay}, session: ${sessionId})\n`,
|
|
50
53
|
)
|
|
51
54
|
} else {
|
|
52
|
-
logger.info(
|
|
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 {
|
|
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
|
-
|
|
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:
|
|
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(
|
|
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 {
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
95
|
+
$.quiet = false
|
|
81
96
|
|
|
82
|
-
|
|
97
|
+
await $`gh workflow run deploy-all.yml --ref main -f environment=prod`
|
|
83
98
|
|
|
84
|
-
|
|
99
|
+
$.quiet = true
|
|
85
100
|
|
|
86
|
-
|
|
87
|
-
|
|
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 {
|
|
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
|
|
30
|
+
const releasePRsInfo = await getReleasePRsWithInfo()
|
|
29
31
|
|
|
30
|
-
const
|
|
32
|
+
const branches = releasePRsInfo.map((pr) => {
|
|
33
|
+
return pr.branch
|
|
34
|
+
})
|
|
31
35
|
|
|
32
|
-
|
|
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:
|
|
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 {
|
|
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
|
|
35
|
+
const releasePRsInfo = await getReleasePRsWithInfo()
|
|
34
36
|
|
|
35
|
-
const
|
|
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
|
|
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:
|
|
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 {
|
|
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
|
|
35
|
+
const releasePRsInfo = await getReleasePRsWithInfo()
|
|
34
36
|
|
|
35
|
-
const
|
|
37
|
+
const branches = releasePRsInfo.map((pr) => {
|
|
38
|
+
return pr.branch
|
|
39
|
+
})
|
|
36
40
|
|
|
37
|
-
|
|
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:
|
|
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 {
|
|
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
|
|
12
|
+
const releasePRs = await getReleasePRsWithInfo()
|
|
12
13
|
|
|
13
|
-
const
|
|
14
|
-
return
|
|
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${
|
|
41
|
+
logger.info(`\n${formattedLines.join('\n')}\n`)
|
|
19
42
|
|
|
20
43
|
const structuredContent = {
|
|
21
|
-
releases:
|
|
22
|
-
|
|
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
|
|
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 {
|
|
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'),
|