infra-kit 0.1.98 → 0.1.100
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintcache +1 -1
- package/.omc/state/agent-replay-d367c3be-9c2a-48e7-bcea-b45861af568c.jsonl +2 -0
- package/.omc/state/agent-replay-f2846d8f-974c-486c-b16f-4bdaa28ca45f.jsonl +1 -0
- package/.omc/state/last-tool-error.json +7 -0
- package/.omc/state/subagent-tracking.json +7 -0
- package/.turbo/turbo-eslint-check.log +2 -4
- package/.turbo/turbo-eslint-fix.log +1 -0
- package/.turbo/turbo-prettier-check.log +2 -4
- package/.turbo/turbo-prettier-fix.log +1 -10
- package/.turbo/turbo-test.log +12 -191
- package/.turbo/turbo-ts-check.log +2 -9
- package/dist/cli.js +69 -44
- package/dist/cli.js.map +4 -4
- package/dist/mcp.js +45 -34
- package/dist/mcp.js.map +4 -4
- package/package.json +11 -11
- package/src/commands/config/config.ts +1 -1
- package/src/commands/doctor/doctor.ts +62 -12
- package/src/commands/env-clear/env-clear.ts +5 -10
- package/src/commands/env-list/env-list.ts +5 -10
- package/src/commands/env-load/env-load.ts +5 -10
- package/src/commands/env-status/env-status.ts +5 -10
- package/src/commands/gh-merge-dev/gh-merge-dev.ts +17 -18
- package/src/commands/gh-release-deliver/gh-release-deliver.ts +290 -89
- package/src/commands/gh-release-deploy-all/gh-release-deploy-all.ts +15 -14
- package/src/commands/gh-release-deploy-selected/gh-release-deploy-selected.ts +30 -23
- package/src/commands/gh-release-list/gh-release-list.ts +5 -10
- package/src/commands/init/init.ts +17 -6
- package/src/commands/release-create/release-create.ts +223 -139
- package/src/commands/release-desc-edit/index.ts +1 -0
- package/src/commands/release-desc-edit/release-desc-edit.ts +207 -0
- package/src/commands/version/version.ts +5 -10
- package/src/commands/worktrees-add/worktrees-add.ts +34 -26
- package/src/commands/worktrees-list/worktrees-list.ts +6 -11
- package/src/commands/worktrees-open/worktrees-open.ts +10 -6
- package/src/commands/worktrees-remove/worktrees-remove.ts +18 -14
- package/src/commands/worktrees-sync/worktrees-sync.ts +17 -12
- package/src/entry/cli.ts +24 -21
- package/src/integrations/gh/gh-release-prs/gh-release-prs.ts +21 -0
- package/src/integrations/gh/gh-release-prs/index.ts +1 -1
- package/src/integrations/gh/index.ts +1 -1
- package/src/integrations/jira/api.ts +8 -17
- package/src/integrations/jira/index.ts +2 -0
- package/src/lib/__tests__/infra-kit-config.test.ts +50 -0
- package/src/lib/errors/__tests__/operation-error.test.ts +62 -0
- package/src/lib/errors/format-zx-error.ts +54 -0
- package/src/lib/errors/operation-error.ts +80 -0
- package/src/lib/infra-kit-config/infra-kit-config.ts +7 -0
- package/src/lib/version-utils/__tests__/next-version.test.ts +128 -23
- package/src/lib/version-utils/index.ts +4 -2
- package/src/lib/version-utils/next-version.ts +64 -25
- package/src/mcp/tools/index.ts +2 -2
- package/src/types.ts +56 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/src/commands/release-create-batch/index.ts +0 -1
- package/src/commands/release-create-batch/release-create-batch.ts +0 -222
|
@@ -3,12 +3,12 @@ import { z } from 'zod/v4'
|
|
|
3
3
|
import { getReleasePRsWithInfo } from 'src/integrations/gh'
|
|
4
4
|
import { logger } from 'src/lib/logger'
|
|
5
5
|
import { detectReleaseType, formatVersionLabel, getJiraDescriptions } from 'src/lib/release-utils'
|
|
6
|
-
import
|
|
6
|
+
import { defineMcpTool, textContent } from 'src/types'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* List all open release branches
|
|
10
10
|
*/
|
|
11
|
-
export const ghReleaseList = async ()
|
|
11
|
+
export const ghReleaseList = async () => {
|
|
12
12
|
const releasePRs = await getReleasePRsWithInfo()
|
|
13
13
|
|
|
14
14
|
const releases = releasePRs.map((pr) => {
|
|
@@ -52,18 +52,13 @@ export const ghReleaseList = async (): Promise<ToolsExecutionResult> => {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
return {
|
|
55
|
-
content:
|
|
56
|
-
{
|
|
57
|
-
type: 'text',
|
|
58
|
-
text: JSON.stringify(structuredContent, null, 2),
|
|
59
|
-
},
|
|
60
|
-
],
|
|
55
|
+
content: textContent(JSON.stringify(structuredContent, null, 2)),
|
|
61
56
|
structuredContent,
|
|
62
57
|
}
|
|
63
58
|
}
|
|
64
59
|
|
|
65
60
|
// MCP Tool Registration
|
|
66
|
-
export const ghReleaseListMcpTool = {
|
|
61
|
+
export const ghReleaseListMcpTool = defineMcpTool({
|
|
67
62
|
name: 'gh-release-list',
|
|
68
63
|
description:
|
|
69
64
|
'List every open release PR with its version, type (regular / hotfix), and associated Jira fix-version description. Read-only; sourced from GitHub and Jira.',
|
|
@@ -81,4 +76,4 @@ export const ghReleaseListMcpTool = {
|
|
|
81
76
|
count: z.number().describe('Number of release branches'),
|
|
82
77
|
},
|
|
83
78
|
handler: ghReleaseList,
|
|
84
|
-
}
|
|
79
|
+
})
|
|
@@ -10,20 +10,31 @@ export const MARKER_END = '# -- infra-kit:end --'
|
|
|
10
10
|
const LEGACY_PAIRED: [start: string, end: string][] = [['# region infra-kit', '# endregion infra-kit']]
|
|
11
11
|
const LEGACY_SINGLE = '# infra-kit shell functions'
|
|
12
12
|
|
|
13
|
-
const USER_GLOBAL_CONFIG_STUB = `# infra-kit user-global config
|
|
13
|
+
const USER_GLOBAL_CONFIG_STUB = `# infra-kit user-global config — ~/.infra-kit/config.yml
|
|
14
14
|
#
|
|
15
|
-
#
|
|
16
|
-
# infra-kit.yml
|
|
15
|
+
# Merge chain (later layers override earlier ones at top-level keys):
|
|
16
|
+
# 1. <repo>/infra-kit.yml — committed project config (required)
|
|
17
|
+
# 2. ~/.infra-kit/config.yml — this file (user-global)
|
|
18
|
+
# 3. ~/.infra-kit/projects/<repo-name>/infra-kit.yml — user-scope per-project override
|
|
17
19
|
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
+
# Merge is shallow: setting a top-level key here replaces that whole section
|
|
21
|
+
# from layer 1. Arrays do not concatenate. Top-level keys recognized:
|
|
22
|
+
# environments, envManagement, ide, taskManager, worktrees.
|
|
23
|
+
#
|
|
24
|
+
# Uncomment the blocks you want to apply globally across every project on this
|
|
25
|
+
# machine. Per-project tweaks belong in layer 3 — run \`infra-kit config edit\`.
|
|
20
26
|
|
|
21
27
|
# Per-developer IDE config
|
|
22
28
|
# ide:
|
|
23
29
|
# provider: cursor
|
|
24
30
|
# config:
|
|
25
31
|
# mode: workspace
|
|
26
|
-
# workspaceConfigPath:
|
|
32
|
+
# workspaceConfigPath: /path/to/your.code-workspace
|
|
33
|
+
|
|
34
|
+
# Worktree prompt defaults — silences the follow-up prompts in \`worktrees-add\`
|
|
35
|
+
# worktrees:
|
|
36
|
+
# openInGithubDesktop: false
|
|
37
|
+
# openInCmux: true
|
|
27
38
|
`
|
|
28
39
|
|
|
29
40
|
/**
|
|
@@ -6,42 +6,27 @@ import { question } from 'zx'
|
|
|
6
6
|
|
|
7
7
|
import { loadJiraConfig } from 'src/integrations/jira'
|
|
8
8
|
import { commandEcho } from 'src/lib/command-echo'
|
|
9
|
+
import { OperationError } from 'src/lib/errors/operation-error'
|
|
9
10
|
import { logger } from 'src/lib/logger'
|
|
10
11
|
import { createSingleRelease, prepareGitForRelease } from 'src/lib/release-utils'
|
|
11
|
-
import type { ReleaseType } from 'src/lib/release-utils'
|
|
12
|
+
import type { ReleaseCreationResult, ReleaseType } from 'src/lib/release-utils'
|
|
12
13
|
import {
|
|
13
|
-
NEXT_TOKEN,
|
|
14
14
|
NoPriorVersionsError,
|
|
15
15
|
computeNextVersion,
|
|
16
|
+
hasNextToken,
|
|
16
17
|
loadExistingVersions,
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
parseVersion,
|
|
19
|
+
resolveReleaseEntries,
|
|
19
20
|
} from 'src/lib/version-utils'
|
|
20
|
-
import type { SemVer } from 'src/lib/version-utils'
|
|
21
|
-
import
|
|
22
|
-
|
|
23
|
-
import { releaseCreateBatch } from '../release-create-batch/release-create-batch'
|
|
21
|
+
import type { ReleaseEntry, SemVer } from 'src/lib/version-utils'
|
|
22
|
+
import { defineMcpTool, textContent } from 'src/types'
|
|
23
|
+
import type { RequiredConfirmedOptionArg } from 'src/types'
|
|
24
24
|
|
|
25
25
|
interface ReleaseCreateArgs extends RequiredConfirmedOptionArg {
|
|
26
|
-
|
|
27
|
-
description?: string
|
|
28
|
-
type?: ReleaseType
|
|
26
|
+
releases?: ReleaseEntry[]
|
|
29
27
|
}
|
|
30
28
|
|
|
31
|
-
const VERSION_PROMPT_HINT = '"1.2.5"
|
|
32
|
-
|
|
33
|
-
const promptForType = async (): Promise<ReleaseType> => {
|
|
34
|
-
commandEcho.setInteractive()
|
|
35
|
-
|
|
36
|
-
return select<ReleaseType>({
|
|
37
|
-
message: 'Select release type:',
|
|
38
|
-
choices: [
|
|
39
|
-
{ name: 'regular', value: 'regular' },
|
|
40
|
-
{ name: 'hotfix', value: 'hotfix' },
|
|
41
|
-
],
|
|
42
|
-
default: 'regular',
|
|
43
|
-
})
|
|
44
|
-
}
|
|
29
|
+
const VERSION_PROMPT_HINT = '"1.2.5" or "next"'
|
|
45
30
|
|
|
46
31
|
const trySuggestNext = (known: SemVer[], type: ReleaseType): string | null => {
|
|
47
32
|
try {
|
|
@@ -53,116 +38,113 @@ const trySuggestNext = (known: SemVer[], type: ReleaseType): string | null => {
|
|
|
53
38
|
}
|
|
54
39
|
}
|
|
55
40
|
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
41
|
+
const resolveOrExit = (entries: ReleaseEntry[], known: SemVer[]): ReleaseEntry[] => {
|
|
42
|
+
try {
|
|
43
|
+
return resolveReleaseEntries(entries, known)
|
|
44
|
+
} catch (err) {
|
|
45
|
+
if (err instanceof NoPriorVersionsError) {
|
|
46
|
+
throw new OperationError(err, {
|
|
47
|
+
operation: 'resolve release version',
|
|
48
|
+
remediation: 'pass an explicit version (e.g. "1.2.5") instead of "next" when there are no prior versions',
|
|
49
|
+
})
|
|
50
|
+
}
|
|
61
51
|
|
|
62
|
-
|
|
52
|
+
throw err
|
|
53
|
+
}
|
|
63
54
|
}
|
|
64
55
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
type: ReleaseType
|
|
68
|
-
ensureKnown: () => Promise<SemVer[]>
|
|
69
|
-
}
|
|
56
|
+
const promptForReleasesInteractive = async (ensureKnown: () => Promise<SemVer[]>): Promise<ReleaseEntry[]> => {
|
|
57
|
+
commandEcho.setInteractive()
|
|
70
58
|
|
|
71
|
-
const
|
|
72
|
-
const
|
|
59
|
+
const baseKnown = await ensureKnown()
|
|
60
|
+
const running: SemVer[] = [...baseKnown]
|
|
61
|
+
const entries: ReleaseEntry[] = []
|
|
62
|
+
let addAnother = true
|
|
63
|
+
|
|
64
|
+
while (addAnother) {
|
|
65
|
+
const ordinal = entries.length + 1
|
|
66
|
+
const type = await select<ReleaseType>({
|
|
67
|
+
message: `Release #${ordinal} — select type:`,
|
|
68
|
+
choices: [
|
|
69
|
+
{ name: 'regular', value: 'regular' },
|
|
70
|
+
{ name: 'hotfix', value: 'hotfix' },
|
|
71
|
+
],
|
|
72
|
+
default: 'regular',
|
|
73
|
+
})
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
75
|
+
const suggestion = trySuggestNext(running, type)
|
|
76
|
+
const defaultHint = suggestion ? ` [${suggestion}]` : ''
|
|
77
|
+
const versionAnswer = (await question(` Version (e.g. ${VERSION_PROMPT_HINT})${defaultHint}: `)).trim()
|
|
78
|
+
const versionInput = versionAnswer === '' ? (suggestion ?? '') : versionAnswer
|
|
77
79
|
|
|
78
|
-
|
|
80
|
+
if (versionInput === '') {
|
|
81
|
+
logger.error('No version provided. Exiting...')
|
|
82
|
+
process.exit(1)
|
|
83
|
+
}
|
|
79
84
|
|
|
80
|
-
|
|
81
|
-
const defaultHint = suggestion ? ` [${suggestion}]` : ''
|
|
82
|
-
const answer = (await question(`Enter version(s) (e.g. ${VERSION_PROMPT_HINT})${defaultHint}: `)).trim()
|
|
85
|
+
const resolved = resolveOrExit([{ version: versionInput, type }], running)[0] as ReleaseEntry
|
|
83
86
|
|
|
84
|
-
|
|
87
|
+
running.push(parseVersion(`v${resolved.version}`))
|
|
85
88
|
|
|
86
|
-
|
|
87
|
-
}
|
|
89
|
+
const description = (await question(' Description (optional, press Enter to skip): ')).trim()
|
|
88
90
|
|
|
89
|
-
|
|
90
|
-
const rawTokens = await resolveRawTokens(args)
|
|
91
|
+
entries.push({ ...resolved, ...(description !== '' ? { description } : {}) })
|
|
91
92
|
|
|
92
|
-
|
|
93
|
-
logger.error('No version provided. Exiting...')
|
|
94
|
-
process.exit(1)
|
|
93
|
+
addAnother = await confirm({ message: 'Add another release?', default: false })
|
|
95
94
|
}
|
|
96
95
|
|
|
97
|
-
|
|
98
|
-
return t.toLowerCase() === NEXT_TOKEN
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
try {
|
|
102
|
-
return resolveVersionTokens(rawTokens, args.type, needsKnown ? await args.ensureKnown() : [])
|
|
103
|
-
} catch (err) {
|
|
104
|
-
return exitOnNoPrior(err)
|
|
105
|
-
}
|
|
96
|
+
return entries
|
|
106
97
|
}
|
|
107
98
|
|
|
108
|
-
const
|
|
109
|
-
|
|
99
|
+
const formatReleaseSummary = (entry: ReleaseEntry): string => {
|
|
100
|
+
const parts = [`v${entry.version}`, entry.type]
|
|
110
101
|
|
|
111
|
-
|
|
112
|
-
const answer = await question('Enter description (optional, press Enter to skip): ')
|
|
102
|
+
if (entry.description) parts.push(entry.description)
|
|
113
103
|
|
|
114
|
-
return
|
|
104
|
+
return parts.join(' · ')
|
|
115
105
|
}
|
|
116
106
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const { version: inputVersion, description: inputDescription, type: inputType, confirmedCommand } = args
|
|
123
|
-
|
|
124
|
-
commandEcho.start('release-create')
|
|
125
|
-
|
|
126
|
-
// Load Jira config - it is now mandatory
|
|
127
|
-
const jiraConfig = await loadJiraConfig()
|
|
128
|
-
|
|
129
|
-
const type: ReleaseType = inputType ?? (await promptForType())
|
|
130
|
-
|
|
131
|
-
commandEcho.addOption('--type', type)
|
|
132
|
-
|
|
133
|
-
let known: SemVer[] | null = null
|
|
134
|
-
const ensureKnown = async (): Promise<SemVer[]> => {
|
|
135
|
-
if (known === null) known = await loadExistingVersions()
|
|
107
|
+
const echoReleases = (entries: ReleaseEntry[]): void => {
|
|
108
|
+
for (const entry of entries) {
|
|
109
|
+
const spec = entry.description
|
|
110
|
+
? `${entry.version}:${entry.type}:${entry.description}`
|
|
111
|
+
: `${entry.version}:${entry.type}`
|
|
136
112
|
|
|
137
|
-
|
|
113
|
+
commandEcho.addOption('--release', spec)
|
|
138
114
|
}
|
|
115
|
+
}
|
|
139
116
|
|
|
140
|
-
|
|
117
|
+
interface FailedRelease {
|
|
118
|
+
version: string
|
|
119
|
+
error: string
|
|
120
|
+
}
|
|
141
121
|
|
|
142
|
-
|
|
143
|
-
|
|
122
|
+
const collectEntries = async (
|
|
123
|
+
inputReleases: ReleaseEntry[] | undefined,
|
|
124
|
+
ensureKnown: () => Promise<SemVer[]>,
|
|
125
|
+
): Promise<ReleaseEntry[]> => {
|
|
126
|
+
if (inputReleases && inputReleases.length > 0) {
|
|
127
|
+
const known = hasNextToken(inputReleases) ? await ensureKnown() : []
|
|
128
|
+
const resolved = resolveOrExit(inputReleases, known)
|
|
144
129
|
|
|
145
|
-
|
|
146
|
-
versions: resolvedVersions.join(','),
|
|
147
|
-
type,
|
|
148
|
-
confirmedCommand,
|
|
149
|
-
})
|
|
150
|
-
}
|
|
130
|
+
echoReleases(resolved)
|
|
151
131
|
|
|
152
|
-
|
|
132
|
+
return resolved
|
|
133
|
+
}
|
|
153
134
|
|
|
154
|
-
|
|
135
|
+
const interactive = await promptForReleasesInteractive(ensureKnown)
|
|
155
136
|
|
|
156
|
-
|
|
137
|
+
echoReleases(interactive)
|
|
157
138
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
139
|
+
return interactive
|
|
140
|
+
}
|
|
161
141
|
|
|
142
|
+
const confirmReleases = async (entries: ReleaseEntry[], confirmedCommand: boolean): Promise<void> => {
|
|
143
|
+
const summary = entries.map(formatReleaseSummary).join('\n - ')
|
|
162
144
|
const answer = confirmedCommand
|
|
163
145
|
? true
|
|
164
146
|
: await confirm({
|
|
165
|
-
message: `
|
|
147
|
+
message: `Create the following ${entries.length} release(s)?\n - ${summary}\n`,
|
|
166
148
|
})
|
|
167
149
|
|
|
168
150
|
if (!confirmedCommand) {
|
|
@@ -174,62 +156,164 @@ export const releaseCreate = async (args: ReleaseCreateArgs): Promise<ToolsExecu
|
|
|
174
156
|
process.exit(0)
|
|
175
157
|
}
|
|
176
158
|
|
|
177
|
-
// Track --yes flag if confirmation was interactive (user confirmed)
|
|
178
159
|
commandEcho.addOption('--yes', true)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
interface ExecuteOneArgs {
|
|
163
|
+
entry: ReleaseEntry
|
|
164
|
+
jiraConfig: Awaited<ReturnType<typeof loadJiraConfig>>
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const executeOne = async (
|
|
168
|
+
args: ExecuteOneArgs,
|
|
169
|
+
): Promise<{ result?: ReleaseCreationResult; failure?: FailedRelease }> => {
|
|
170
|
+
const { entry, jiraConfig } = args
|
|
179
171
|
|
|
180
|
-
|
|
172
|
+
try {
|
|
173
|
+
await prepareGitForRelease(entry.type)
|
|
174
|
+
|
|
175
|
+
const result = await createSingleRelease({
|
|
176
|
+
version: entry.version,
|
|
177
|
+
jiraConfig,
|
|
178
|
+
description: entry.description,
|
|
179
|
+
type: entry.type,
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
logger.info(`✅ Successfully created release: v${entry.version} (${entry.type})`)
|
|
183
|
+
logger.info(`🔗 GitHub PR: ${result.prUrl}`)
|
|
184
|
+
logger.info(`🔗 Jira Version: ${result.jiraVersionUrl}\n`)
|
|
185
|
+
|
|
186
|
+
return { result }
|
|
187
|
+
} catch (error) {
|
|
188
|
+
const err = new OperationError(error, {
|
|
189
|
+
operation: `create release v${entry.version} (${entry.type})`,
|
|
190
|
+
remediation: 'verify the version is unique and the base branch is clean',
|
|
191
|
+
})
|
|
181
192
|
|
|
182
|
-
|
|
193
|
+
logger.error(`❌ ${err.message}\n`)
|
|
183
194
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
195
|
+
return { failure: { version: entry.version, error: err.message } }
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const logFinalSummary = (total: number, successCount: number, failureCount: number): void => {
|
|
200
|
+
if (successCount === total) {
|
|
201
|
+
logger.info(`✅ All ${total} release branch(es) were created successfully.`)
|
|
202
|
+
} else if (successCount > 0) {
|
|
203
|
+
logger.warn(`⚠️ ${successCount} of ${total} release branches were created successfully.`)
|
|
204
|
+
logger.warn(`❌ ${failureCount} release(s) failed.`)
|
|
205
|
+
} else {
|
|
206
|
+
logger.error(`❌ All ${total} release branch(es) failed to create.`)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Create one or more release branches. Each release carries its own type
|
|
212
|
+
* (regular/hotfix) and optional Jira description, so a single invocation
|
|
213
|
+
* may mix regular and hotfix releases off their respective base branches.
|
|
214
|
+
*/
|
|
215
|
+
export const releaseCreate = async (args: ReleaseCreateArgs) => {
|
|
216
|
+
const { releases: inputReleases, confirmedCommand } = args
|
|
217
|
+
|
|
218
|
+
commandEcho.start('release-create')
|
|
219
|
+
|
|
220
|
+
const jiraConfig = await loadJiraConfig()
|
|
221
|
+
|
|
222
|
+
let known: SemVer[] | null = null
|
|
223
|
+
const ensureKnown = async (): Promise<SemVer[]> => {
|
|
224
|
+
if (known === null) known = await loadExistingVersions()
|
|
225
|
+
|
|
226
|
+
return known
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const entries = await collectEntries(inputReleases, ensureKnown)
|
|
230
|
+
|
|
231
|
+
if (entries.length === 0) {
|
|
232
|
+
throw new OperationError(undefined, {
|
|
233
|
+
operation: 'create release',
|
|
234
|
+
remediation: 'pass at least one entry in "releases" (e.g. [{ version: "1.2.5", type: "regular" }])',
|
|
235
|
+
stderrExcerpt: 'no releases provided',
|
|
236
|
+
})
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
await confirmReleases(entries, Boolean(confirmedCommand))
|
|
240
|
+
|
|
241
|
+
const created: ReleaseCreationResult[] = []
|
|
242
|
+
const failed: FailedRelease[] = []
|
|
243
|
+
|
|
244
|
+
for (const entry of entries) {
|
|
245
|
+
const { result, failure } = await executeOne({ entry, jiraConfig })
|
|
246
|
+
|
|
247
|
+
if (result) created.push(result)
|
|
248
|
+
if (failure) failed.push(failure)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
logFinalSummary(entries.length, created.length, failed.length)
|
|
187
252
|
|
|
188
253
|
commandEcho.print()
|
|
189
254
|
|
|
190
255
|
const structuredContent = {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
256
|
+
createdBranches: created.map((r) => {
|
|
257
|
+
return r.branchName
|
|
258
|
+
}),
|
|
259
|
+
successCount: created.length,
|
|
260
|
+
failureCount: failed.length,
|
|
261
|
+
releases: created,
|
|
262
|
+
failedReleases: failed,
|
|
196
263
|
}
|
|
197
264
|
|
|
198
265
|
return {
|
|
199
|
-
content:
|
|
200
|
-
{
|
|
201
|
-
type: 'text',
|
|
202
|
-
text: JSON.stringify(structuredContent, null, 2),
|
|
203
|
-
},
|
|
204
|
-
],
|
|
266
|
+
content: textContent(JSON.stringify(structuredContent, null, 2)),
|
|
205
267
|
structuredContent,
|
|
206
268
|
}
|
|
207
269
|
}
|
|
208
270
|
|
|
209
271
|
// MCP Tool Registration
|
|
210
|
-
export const releaseCreateMcpTool = {
|
|
272
|
+
export const releaseCreateMcpTool = defineMcpTool({
|
|
211
273
|
name: 'release-create',
|
|
212
274
|
description:
|
|
213
|
-
'Create
|
|
275
|
+
'Create one or more releases in a single call. Each entry in "releases" carries its own version, type (regular|hotfix, default regular), and optional description, so regular and hotfix releases can be mixed in the same invocation. For each release this tool switches to the appropriate base branch (dev for regular, main for hotfix), cuts the release branch, opens a GitHub release PR, and creates the matching Jira fix version. The literal token "next" auto-increments from the union of remote release branches and Jira fix versions (regular bumps minor + resets patch; hotfix bumps patch on the highest minor); multiple "next" tokens advance sequentially across mixed types. Confirmation is auto-skipped for MCP calls, so the caller is responsible for gating. Continues on per-release failure and reports successes/failures.',
|
|
214
276
|
inputSchema: {
|
|
215
|
-
|
|
216
|
-
.
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
277
|
+
releases: z
|
|
278
|
+
.array(
|
|
279
|
+
z.object({
|
|
280
|
+
version: z
|
|
281
|
+
.string()
|
|
282
|
+
.describe('Version to create (e.g., "1.2.5") or the literal token "next" for auto-increment.'),
|
|
283
|
+
type: z
|
|
284
|
+
.enum(['regular', 'hotfix'])
|
|
285
|
+
.optional()
|
|
286
|
+
.default('regular')
|
|
287
|
+
.describe('Release type: "regular" (branches off dev) or "hotfix" (branches off main).'),
|
|
288
|
+
description: z.string().optional().describe('Optional description for the Jira version.'),
|
|
289
|
+
}),
|
|
290
|
+
)
|
|
291
|
+
.min(1)
|
|
292
|
+
.describe('One or more releases to create. Each entry has its own version, type, and optional description.'),
|
|
226
293
|
},
|
|
227
294
|
outputSchema: {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
295
|
+
createdBranches: z.array(z.string()).describe('List of created release branch names'),
|
|
296
|
+
successCount: z.number().describe('Number of releases created successfully'),
|
|
297
|
+
failureCount: z.number().describe('Number of releases that failed'),
|
|
298
|
+
releases: z
|
|
299
|
+
.array(
|
|
300
|
+
z.object({
|
|
301
|
+
version: z.string().describe('Version number'),
|
|
302
|
+
type: z.enum(['regular', 'hotfix']).describe('Release type'),
|
|
303
|
+
branchName: z.string().describe('Release branch name'),
|
|
304
|
+
prUrl: z.string().describe('GitHub PR URL'),
|
|
305
|
+
jiraVersionUrl: z.string().describe('Jira version URL'),
|
|
306
|
+
}),
|
|
307
|
+
)
|
|
308
|
+
.describe('Detailed information for each created release with URLs'),
|
|
309
|
+
failedReleases: z
|
|
310
|
+
.array(
|
|
311
|
+
z.object({
|
|
312
|
+
version: z.string().describe('Version number that failed'),
|
|
313
|
+
error: z.string().describe('Error message'),
|
|
314
|
+
}),
|
|
315
|
+
)
|
|
316
|
+
.describe('List of releases that failed with error messages'),
|
|
233
317
|
},
|
|
234
318
|
handler: releaseCreate,
|
|
235
|
-
}
|
|
319
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { releaseDescEdit, releaseDescEditMcpTool } from './release-desc-edit'
|