infra-kit 0.1.97 → 0.1.98

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,148 @@
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
+ /**
101
+ * Resolve a list of input tokens (mix of "next" and explicit semver strings)
102
+ * into concrete version strings. Each "next" advances based on the running
103
+ * max so "next,next" produces sequential versions.
104
+ */
105
+ export const resolveVersionTokens = (tokens: string[], type: ReleaseType, known: SemVer[]): string[] => {
106
+ const running: SemVer[] = [...known]
107
+ const resolved: string[] = []
108
+
109
+ for (const token of tokens) {
110
+ const trimmed = token.trim()
111
+
112
+ if (trimmed === '') continue
113
+
114
+ if (isNextToken(trimmed)) {
115
+ const next = computeNextVersion(running, type)
116
+
117
+ resolved.push(next)
118
+ running.push(parseVersion(`v${next}`))
119
+ continue
120
+ }
121
+
122
+ const parsed = tryParse(trimmed)
123
+
124
+ if (!parsed) {
125
+ throw new Error(`Invalid version "${trimmed}". Expected semver like "1.2.5" or the token "next".`)
126
+ }
127
+
128
+ const explicit = `${parsed[0]}.${parsed[1]}.${parsed[2]}`
129
+
130
+ resolved.push(explicit)
131
+ running.push(parsed)
132
+ }
133
+
134
+ return resolved
135
+ }
136
+
137
+ /**
138
+ * Split a raw user input into tokens, trimming and removing empties.
139
+ * Accepts both whitespace-separated and comma-separated lists.
140
+ */
141
+ export const splitVersionInput = (input: string): string[] => {
142
+ return input
143
+ .split(',')
144
+ .map((t) => {
145
+ return t.trim()
146
+ })
147
+ .filter(Boolean)
148
+ }