infra-kit 0.1.97 → 0.1.99
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/.turbo/turbo-eslint-check.log +1 -1
- package/.turbo/turbo-prettier-check.log +1 -1
- package/.turbo/turbo-test.log +7 -7
- package/.turbo/turbo-ts-check.log +1 -1
- package/dist/cli.js +73 -37
- package/dist/cli.js.map +4 -4
- package/dist/mcp.js +30 -26
- package/dist/mcp.js.map +4 -4
- package/package.json +1 -1
- package/src/commands/config/config.ts +125 -0
- package/src/commands/config/index.ts +1 -0
- package/src/commands/doctor/doctor.ts +27 -18
- package/src/commands/init/init.ts +65 -1
- package/src/commands/release-create/release-create.ts +226 -95
- package/src/commands/worktrees-add/worktrees-add.ts +16 -12
- package/src/entry/cli.ts +35 -27
- package/src/lib/__tests__/infra-kit-config.test.ts +53 -1
- package/src/lib/infra-kit-config/index.ts +2 -2
- package/src/lib/infra-kit-config/infra-kit-config.ts +190 -37
- package/src/lib/version-utils/__tests__/next-version.test.ts +217 -0
- package/src/lib/version-utils/index.ts +13 -0
- package/src/lib/version-utils/load-existing-versions.ts +67 -0
- package/src/lib/version-utils/next-version.ts +187 -0
- package/src/mcp/tools/index.ts +0 -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 -198
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import type { ReleaseType } from 'src/lib/release-utils'
|
|
2
|
+
|
|
3
|
+
import { parseVersion, sortVersions } from './version-utils'
|
|
4
|
+
|
|
5
|
+
export const NEXT_TOKEN = 'next'
|
|
6
|
+
|
|
7
|
+
export type SemVer = readonly [number, number, number]
|
|
8
|
+
|
|
9
|
+
export interface ExistingVersionsSources {
|
|
10
|
+
remoteBranches?: string[]
|
|
11
|
+
jiraVersions?: string[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const VERSION_RE = /^v?(\d+)\.(\d+)\.(\d+)$/
|
|
15
|
+
|
|
16
|
+
const stripBranchPrefix = (raw: string): string => {
|
|
17
|
+
return raw.replace(/^.*release\//, '')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const tryParse = (raw: string): SemVer | null => {
|
|
21
|
+
const cleaned = stripBranchPrefix(raw.trim())
|
|
22
|
+
const match = VERSION_RE.exec(cleaned)
|
|
23
|
+
|
|
24
|
+
if (!match) return null
|
|
25
|
+
|
|
26
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const semverKey = (v: SemVer): string => {
|
|
30
|
+
return `${v[0]}.${v[1]}.${v[2]}`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const collectKnownVersions = (sources: ExistingVersionsSources): SemVer[] => {
|
|
34
|
+
const all = [...(sources.remoteBranches ?? []), ...(sources.jiraVersions ?? [])]
|
|
35
|
+
const parsed: SemVer[] = []
|
|
36
|
+
const seen = new Set<string>()
|
|
37
|
+
|
|
38
|
+
for (const raw of all) {
|
|
39
|
+
const v = tryParse(raw)
|
|
40
|
+
|
|
41
|
+
if (!v) continue
|
|
42
|
+
|
|
43
|
+
const key = semverKey(v)
|
|
44
|
+
|
|
45
|
+
if (seen.has(key)) continue
|
|
46
|
+
|
|
47
|
+
seen.add(key)
|
|
48
|
+
parsed.push(v)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return sortVersions(
|
|
52
|
+
parsed.map((v) => {
|
|
53
|
+
return semverKey(v)
|
|
54
|
+
}),
|
|
55
|
+
).map((s) => {
|
|
56
|
+
return parseVersion(`v${s}`)
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export class NoPriorVersionsError extends Error {
|
|
61
|
+
constructor() {
|
|
62
|
+
super('No prior release versions found from git or Jira. Specify the version explicitly.')
|
|
63
|
+
this.name = 'NoPriorVersionsError'
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Compute the next semantic version based on release type.
|
|
69
|
+
* - regular: bump minor, reset patch to 0
|
|
70
|
+
* - hotfix: bump patch on the highest minor (any patch)
|
|
71
|
+
*
|
|
72
|
+
* Returns the version without the leading "v" (e.g. "1.64.0").
|
|
73
|
+
*/
|
|
74
|
+
export const computeNextVersion = (known: SemVer[], type: ReleaseType): string => {
|
|
75
|
+
if (known.length === 0) throw new NoPriorVersionsError()
|
|
76
|
+
|
|
77
|
+
const max = known[known.length - 1] as SemVer
|
|
78
|
+
|
|
79
|
+
if (type === 'hotfix') {
|
|
80
|
+
const [major, minor] = max
|
|
81
|
+
|
|
82
|
+
const highestPatchOnMinor = known.reduce((acc, v) => {
|
|
83
|
+
if (v[0] === major && v[1] === minor) return Math.max(acc, v[2])
|
|
84
|
+
|
|
85
|
+
return acc
|
|
86
|
+
}, 0)
|
|
87
|
+
|
|
88
|
+
return `${major}.${minor}.${highestPatchOnMinor + 1}`
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const [major, minor] = max
|
|
92
|
+
|
|
93
|
+
return `${major}.${minor + 1}.0`
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const isNextToken = (token: string): boolean => {
|
|
97
|
+
return token.trim().toLowerCase() === NEXT_TOKEN
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface ReleaseEntry {
|
|
101
|
+
version: string
|
|
102
|
+
type: ReleaseType
|
|
103
|
+
description?: string
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const isReleaseType = (value: string): value is ReleaseType => {
|
|
107
|
+
return value === 'regular' || value === 'hotfix'
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Parse a CLI release spec of the form `version[:type[:description]]`.
|
|
112
|
+
* Type defaults to "regular". Description is everything after the second
|
|
113
|
+
* colon, so colons inside descriptions are preserved.
|
|
114
|
+
*/
|
|
115
|
+
export const parseReleaseSpec = (raw: string): ReleaseEntry => {
|
|
116
|
+
const spec = raw.trim()
|
|
117
|
+
|
|
118
|
+
if (spec === '') throw new Error('Release spec is empty')
|
|
119
|
+
|
|
120
|
+
const firstColon = spec.indexOf(':')
|
|
121
|
+
|
|
122
|
+
if (firstColon === -1) {
|
|
123
|
+
return { version: spec, type: 'regular' }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const version = spec.slice(0, firstColon).trim()
|
|
127
|
+
const rest = spec.slice(firstColon + 1)
|
|
128
|
+
const secondColon = rest.indexOf(':')
|
|
129
|
+
|
|
130
|
+
const typeRaw = secondColon === -1 ? rest.trim() : rest.slice(0, secondColon).trim()
|
|
131
|
+
const description = secondColon === -1 ? '' : rest.slice(secondColon + 1).trim()
|
|
132
|
+
const typeLower = typeRaw.toLowerCase()
|
|
133
|
+
|
|
134
|
+
if (!isReleaseType(typeLower)) {
|
|
135
|
+
throw new Error(`Invalid release type "${typeRaw}". Expected "regular" or "hotfix".`)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const entry: ReleaseEntry = { version, type: typeLower }
|
|
139
|
+
|
|
140
|
+
if (description !== '') entry.description = description
|
|
141
|
+
|
|
142
|
+
return entry
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Resolve a list of release entries (each with its own type and optional
|
|
147
|
+
* "next" version token) into entries with concrete versions. Each "next"
|
|
148
|
+
* advances based on the running max so successive "next" tokens produce
|
|
149
|
+
* sequential versions, even across mixed types.
|
|
150
|
+
*/
|
|
151
|
+
export const resolveReleaseEntries = (entries: ReleaseEntry[], known: SemVer[]): ReleaseEntry[] => {
|
|
152
|
+
const running: SemVer[] = [...known]
|
|
153
|
+
|
|
154
|
+
return entries.map((entry) => {
|
|
155
|
+
const trimmed = entry.version.trim()
|
|
156
|
+
|
|
157
|
+
if (trimmed === '') {
|
|
158
|
+
throw new Error('Release entry has an empty version')
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (isNextToken(trimmed)) {
|
|
162
|
+
const next = computeNextVersion(running, entry.type)
|
|
163
|
+
|
|
164
|
+
running.push(parseVersion(`v${next}`))
|
|
165
|
+
|
|
166
|
+
return { ...entry, version: next }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const parsed = tryParse(trimmed)
|
|
170
|
+
|
|
171
|
+
if (!parsed) {
|
|
172
|
+
throw new Error(`Invalid version "${trimmed}". Expected semver like "1.2.5" or the token "next".`)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const explicit = `${parsed[0]}.${parsed[1]}.${parsed[2]}`
|
|
176
|
+
|
|
177
|
+
running.push(parsed)
|
|
178
|
+
|
|
179
|
+
return { ...entry, version: explicit }
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export const hasNextToken = (entries: ReleaseEntry[]): boolean => {
|
|
184
|
+
return entries.some((e) => {
|
|
185
|
+
return isNextToken(e.version)
|
|
186
|
+
})
|
|
187
|
+
}
|
package/src/mcp/tools/index.ts
CHANGED
|
@@ -10,7 +10,6 @@ import { ghReleaseDeployAllMcpTool } from 'src/commands/gh-release-deploy-all'
|
|
|
10
10
|
import { ghReleaseDeploySelectedMcpTool } from 'src/commands/gh-release-deploy-selected'
|
|
11
11
|
import { ghReleaseListMcpTool } from 'src/commands/gh-release-list'
|
|
12
12
|
import { releaseCreateMcpTool } from 'src/commands/release-create'
|
|
13
|
-
import { releaseCreateBatchMcpTool } from 'src/commands/release-create-batch'
|
|
14
13
|
import { versionMcpTool } from 'src/commands/version'
|
|
15
14
|
import { worktreesAddMcpTool } from 'src/commands/worktrees-add'
|
|
16
15
|
import { worktreesListMcpTool } from 'src/commands/worktrees-list'
|
|
@@ -26,7 +25,6 @@ const tools = [
|
|
|
26
25
|
envClearMcpTool,
|
|
27
26
|
ghMergeDevMcpTool,
|
|
28
27
|
releaseCreateMcpTool,
|
|
29
|
-
releaseCreateBatchMcpTool,
|
|
30
28
|
ghReleaseDeliverMcpTool,
|
|
31
29
|
ghReleaseDeployAllMcpTool,
|
|
32
30
|
ghReleaseDeploySelectedMcpTool,
|