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.
@@ -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
+ }
@@ -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,