fireflyy 4.0.0-dev.c94480d → 4.0.0-dev.df58d2b
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/commit-analysis.service-CtIh7eez.js +503 -0
- package/dist/{dry-run-BfYCtldz.js → dry-run-korHDlWr.js} +1 -1
- package/dist/{filesystem.service-9VHML130.js → filesystem.service-BcQtLzfl.js} +3 -3
- package/dist/{git.service-CACrfCW8.js → git.service-D1Mf6LZ0.js} +2 -2
- package/dist/main.js +2 -2
- package/dist/{package-json.service-DACeZzRg.js → package-json.service-BKbXaHXQ.js} +2 -2
- package/dist/{program-DcCz3-Sc.js → program-CW3hIbzh.js} +87 -17
- package/dist/{result.constructors-C9M1MP3_.js → result.constructors-D9jmQ0uj.js} +5 -1
- package/dist/{result.utilities-DC5shlhT.js → result.utilities-CdBL5noX.js} +1 -1
- package/dist/{schema.utilities-BGd9t1wm.js → schema.utilities-BOJqWEey.js} +1 -1
- package/dist/version-bumper.service-CvPx7EZx.js +318 -0
- package/dist/version-strategy.service-BO_7MY5O.js +4 -0
- package/dist/version-strategy.service-vh5KeNCA.js +258 -0
- package/package.json +1 -1
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
import { t as logger } from "./main.js";
|
|
2
|
+
import { g as invalidError, i as FireflyOkAsync, r as FireflyOk, t as FireflyErr } from "./result.constructors-D9jmQ0uj.js";
|
|
3
|
+
import { n as TRANSITION_KEYWORDS } from "./version-strategy.service-vh5KeNCA.js";
|
|
4
|
+
|
|
5
|
+
//#region src/domain/commits/commit-types.ts
|
|
6
|
+
/**
|
|
7
|
+
* Default configuration for conventional commit type analysis.
|
|
8
|
+
*/
|
|
9
|
+
const DEFAULT_COMMIT_TYPE_CONFIG = {
|
|
10
|
+
major: ["revert"],
|
|
11
|
+
minor: ["feat", "feature"],
|
|
12
|
+
patch: [
|
|
13
|
+
"fix",
|
|
14
|
+
"perf",
|
|
15
|
+
"refactor",
|
|
16
|
+
"style",
|
|
17
|
+
"test",
|
|
18
|
+
"build",
|
|
19
|
+
"ci",
|
|
20
|
+
"chore",
|
|
21
|
+
"docs",
|
|
22
|
+
"security"
|
|
23
|
+
],
|
|
24
|
+
scopeRules: {
|
|
25
|
+
deps: "patch",
|
|
26
|
+
dependencies: "patch",
|
|
27
|
+
security: "patch",
|
|
28
|
+
api: "minor",
|
|
29
|
+
breaking: "major",
|
|
30
|
+
"breaking-change": "major"
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/services/implementations/commit-analysis.service.ts
|
|
36
|
+
const COMMIT_MESSAGE_PATTERNS = {
|
|
37
|
+
BREAKING: /^(\w*)(?:\((.*)\))?!: (.*)$/,
|
|
38
|
+
CONVENTIONAL: /^(\w*)(?:\((.*)\))?: (.*)$/,
|
|
39
|
+
REVERT: /^Revert "(.+)"\s*\[([a-f0-9]+)\]$/
|
|
40
|
+
};
|
|
41
|
+
const EXTRACTION_PATTERNS = {
|
|
42
|
+
BREAKING_CHANGE: /BREAKING CHANGE:\s*(.+?)(?=\n\n|\n[A-Z]|$)/gs,
|
|
43
|
+
MENTION: /@([a-zA-Z0-9_-]+)/g,
|
|
44
|
+
REFERENCE: /(fixes?|closes?|resolves?)\s+#(\d+)|#(\d+)/gi
|
|
45
|
+
};
|
|
46
|
+
const ANALYSIS_PATTERNS = {
|
|
47
|
+
BREAKING_HEADER: /^[a-zA-Z]+(?:\([^)]*\))?!:/,
|
|
48
|
+
SCOPE_HEADER: /^[a-zA-Z]+\(([^)]+)\)[:!]/
|
|
49
|
+
};
|
|
50
|
+
const VERSION_LEVELS = {
|
|
51
|
+
MAJOR: 0,
|
|
52
|
+
MINOR: 1,
|
|
53
|
+
PATCH: 2
|
|
54
|
+
};
|
|
55
|
+
const LEVEL_TO_RELEASE_TYPE = {
|
|
56
|
+
0: "major",
|
|
57
|
+
1: "minor",
|
|
58
|
+
2: "patch"
|
|
59
|
+
};
|
|
60
|
+
var DefaultCommitAnalysisService = class {
|
|
61
|
+
git;
|
|
62
|
+
constructor(git) {
|
|
63
|
+
this.git = git;
|
|
64
|
+
}
|
|
65
|
+
analyzeForVersion(options) {
|
|
66
|
+
logger.verbose("CommitAnalysisService: Starting commit analysis for version recommendation...");
|
|
67
|
+
const startTime = Date.now();
|
|
68
|
+
return this.getCommitsSinceLastTag().map((commits) => {
|
|
69
|
+
if (commits.length === 0) {
|
|
70
|
+
logger.verbose("CommitAnalysisService: No commits provided, returning patch recommendation.");
|
|
71
|
+
return this.createDefaultPatchRecommendation();
|
|
72
|
+
}
|
|
73
|
+
const config = this.mergeConfiguration(options?.config ?? {});
|
|
74
|
+
const analysis = this.performDetailedAnalysis(commits, config);
|
|
75
|
+
const versionLevel = this.determineVersionLevel(analysis);
|
|
76
|
+
const recommendation = this.buildVersionRecommendation(versionLevel, analysis);
|
|
77
|
+
const duration = Date.now() - startTime;
|
|
78
|
+
logger.verbose(`CommitAnalysisService: Analysis completed in ${duration}ms. Recommendation: ${recommendation.releaseType}`);
|
|
79
|
+
return recommendation;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
createDefaultRecommendation() {
|
|
83
|
+
return this.createDefaultPatchRecommendation();
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Retrieves commits since the last tag without analysis.
|
|
87
|
+
*
|
|
88
|
+
* @returns Array of parsed commits
|
|
89
|
+
*/
|
|
90
|
+
getCommitsSinceLastTag() {
|
|
91
|
+
logger.verbose("CommitAnalysisService: Retrieving commits since last tag");
|
|
92
|
+
return this.git.getLatestTag().andThen((lastTag) => {
|
|
93
|
+
logger.verbose(`CommitAnalysisService: Last tag is: ${lastTag ?? "none"}`);
|
|
94
|
+
return this.git.getCommitHashesSince(lastTag).andThen((hashes) => {
|
|
95
|
+
if (hashes.length === 0) {
|
|
96
|
+
logger.verbose("CommitAnalysisService: No new commits found since last tag");
|
|
97
|
+
return FireflyOkAsync([]);
|
|
98
|
+
}
|
|
99
|
+
return this.parseCommitHashes(hashes);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Extracts commit fields from raw commit details.
|
|
105
|
+
*
|
|
106
|
+
* @param rawDetails - The raw commit details string
|
|
107
|
+
* @returns An object containing extracted commit fields
|
|
108
|
+
*/
|
|
109
|
+
extractCommitFields(rawDetails) {
|
|
110
|
+
const lines = rawDetails.trim().split("\n");
|
|
111
|
+
const commitData = {};
|
|
112
|
+
for (const line of lines) {
|
|
113
|
+
const colonIndex = line.indexOf(":");
|
|
114
|
+
if (colonIndex === -1) continue;
|
|
115
|
+
const key = line.substring(0, colonIndex);
|
|
116
|
+
commitData[key] = line.substring(colonIndex + 1);
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
subject: commitData.subject ?? "",
|
|
120
|
+
body: commitData.body ?? "",
|
|
121
|
+
author: commitData.author ?? "",
|
|
122
|
+
date: commitData.date ?? "",
|
|
123
|
+
notes: commitData.notes ?? ""
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Analyzes the commit message to extract type, scope, and subject.
|
|
128
|
+
*
|
|
129
|
+
* @param subject - The commit subject line
|
|
130
|
+
* @returns An object with type, scope, and subject
|
|
131
|
+
*/
|
|
132
|
+
analyzeCommitMessage(subject) {
|
|
133
|
+
const breakingMatch = subject.match(COMMIT_MESSAGE_PATTERNS.BREAKING);
|
|
134
|
+
if (breakingMatch) return {
|
|
135
|
+
type: breakingMatch[1] ?? null,
|
|
136
|
+
scope: breakingMatch[2] ?? null,
|
|
137
|
+
subject: breakingMatch[3] ?? null
|
|
138
|
+
};
|
|
139
|
+
const conventionalMatch = subject.match(COMMIT_MESSAGE_PATTERNS.CONVENTIONAL);
|
|
140
|
+
if (conventionalMatch) return {
|
|
141
|
+
type: conventionalMatch[1] ?? null,
|
|
142
|
+
scope: conventionalMatch[2] ?? null,
|
|
143
|
+
subject: conventionalMatch[3] ?? null
|
|
144
|
+
};
|
|
145
|
+
return {
|
|
146
|
+
type: null,
|
|
147
|
+
scope: null,
|
|
148
|
+
subject: subject || null
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Extracts revert information from commit subject.
|
|
153
|
+
*
|
|
154
|
+
* @param subject - The commit subject line
|
|
155
|
+
* @returns An object with revert header and hash, or null if not a revert
|
|
156
|
+
*/
|
|
157
|
+
extractRevertInfo(subject) {
|
|
158
|
+
const revertMatch = subject.match(COMMIT_MESSAGE_PATTERNS.REVERT);
|
|
159
|
+
if (revertMatch) return {
|
|
160
|
+
header: revertMatch[1] ?? null,
|
|
161
|
+
hash: revertMatch[2] ?? null
|
|
162
|
+
};
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Extracts BREAKING CHANGE notes from commit body and notes.
|
|
167
|
+
*
|
|
168
|
+
* @param body - The commit body text
|
|
169
|
+
* @param notes - The commit notes text
|
|
170
|
+
* @returns An array of CommitNote objects
|
|
171
|
+
*/
|
|
172
|
+
extractBreakingChangeNotes(body, notes) {
|
|
173
|
+
const breakingNotes = [];
|
|
174
|
+
const fullText = `${body}\n${notes}`;
|
|
175
|
+
EXTRACTION_PATTERNS.BREAKING_CHANGE.lastIndex = 0;
|
|
176
|
+
let match = EXTRACTION_PATTERNS.BREAKING_CHANGE.exec(fullText);
|
|
177
|
+
while (match !== null) {
|
|
178
|
+
const text = match[1];
|
|
179
|
+
if (text) breakingNotes.push({
|
|
180
|
+
title: "BREAKING CHANGE",
|
|
181
|
+
text: text.trim()
|
|
182
|
+
});
|
|
183
|
+
match = EXTRACTION_PATTERNS.BREAKING_CHANGE.exec(fullText);
|
|
184
|
+
}
|
|
185
|
+
return breakingNotes;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Extracts @mentions from commit text.
|
|
189
|
+
*
|
|
190
|
+
* @param text - The commit text to analyze
|
|
191
|
+
* @returns An array of mention strings
|
|
192
|
+
*/
|
|
193
|
+
extractMentions(text) {
|
|
194
|
+
const mentions = [];
|
|
195
|
+
EXTRACTION_PATTERNS.MENTION.lastIndex = 0;
|
|
196
|
+
let match = EXTRACTION_PATTERNS.MENTION.exec(text);
|
|
197
|
+
while (match !== null) {
|
|
198
|
+
const mention = match[1];
|
|
199
|
+
if (mention) mentions.push(mention);
|
|
200
|
+
match = EXTRACTION_PATTERNS.MENTION.exec(text);
|
|
201
|
+
}
|
|
202
|
+
return mentions;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Extracts issue/PR references from commit text.
|
|
206
|
+
*
|
|
207
|
+
* @param text - The commit text to analyze
|
|
208
|
+
* @returns An array of CommitReference objects
|
|
209
|
+
*/
|
|
210
|
+
extractReferences(text) {
|
|
211
|
+
const references = [];
|
|
212
|
+
EXTRACTION_PATTERNS.REFERENCE.lastIndex = 0;
|
|
213
|
+
let match = EXTRACTION_PATTERNS.REFERENCE.exec(text);
|
|
214
|
+
while (match !== null) {
|
|
215
|
+
const action = match[1] ?? null;
|
|
216
|
+
const issue = match[2] ?? match[3];
|
|
217
|
+
if (issue) references.push({
|
|
218
|
+
raw: match[0],
|
|
219
|
+
action: action?.toLowerCase() ?? null,
|
|
220
|
+
owner: null,
|
|
221
|
+
repository: null,
|
|
222
|
+
issue,
|
|
223
|
+
prefix: "#"
|
|
224
|
+
});
|
|
225
|
+
match = EXTRACTION_PATTERNS.REFERENCE.exec(text);
|
|
226
|
+
}
|
|
227
|
+
return references;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Parses raw commit details into a Commit object.
|
|
231
|
+
*
|
|
232
|
+
* @param rawDetails - The raw commit details string
|
|
233
|
+
* @param hash - The commit hash
|
|
234
|
+
* @returns A FireflyResult containing the parsed Commit object
|
|
235
|
+
*/
|
|
236
|
+
parseCommitFromRaw(rawDetails, hash) {
|
|
237
|
+
if (!rawDetails || typeof rawDetails !== "string") return FireflyErr(invalidError({ message: `Invalid commit details for ${hash}` }));
|
|
238
|
+
const details = this.extractCommitFields(rawDetails);
|
|
239
|
+
const messageAnalysis = this.analyzeCommitMessage(details.subject);
|
|
240
|
+
const revertInfo = this.extractRevertInfo(details.subject);
|
|
241
|
+
const breakingNotes = this.extractBreakingChangeNotes(details.body, details.notes);
|
|
242
|
+
const mentions = this.extractMentions(details.body);
|
|
243
|
+
const references = this.extractReferences(details.body);
|
|
244
|
+
return FireflyOk({
|
|
245
|
+
hash,
|
|
246
|
+
date: details.date || null,
|
|
247
|
+
author: details.author || null,
|
|
248
|
+
header: details.subject || null,
|
|
249
|
+
body: details.body || null,
|
|
250
|
+
footer: null,
|
|
251
|
+
type: messageAnalysis.type,
|
|
252
|
+
scope: messageAnalysis.scope,
|
|
253
|
+
subject: messageAnalysis.subject,
|
|
254
|
+
merge: null,
|
|
255
|
+
revert: revertInfo,
|
|
256
|
+
notes: breakingNotes,
|
|
257
|
+
mentions,
|
|
258
|
+
references
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Parses a list of commit hashes into Commit objects.
|
|
263
|
+
*
|
|
264
|
+
* @param hashes - The list of commit hashes to parse
|
|
265
|
+
* @returns A FireflyAsyncResult containing the list of parsed Commit objects
|
|
266
|
+
*/
|
|
267
|
+
parseCommitHashes(hashes) {
|
|
268
|
+
return hashes.reduce((acc, hash) => acc.andThen((commits) => this.git.getCommitDetails(hash).andThen((rawDetails) => {
|
|
269
|
+
const parsed = this.parseCommitFromRaw(rawDetails, hash);
|
|
270
|
+
if (parsed.isErr()) {
|
|
271
|
+
logger.verbose(`CommitAnalysisService: Failed to parse commit ${hash}: ${parsed.error.message}`);
|
|
272
|
+
return FireflyOkAsync(commits);
|
|
273
|
+
}
|
|
274
|
+
return FireflyOkAsync([...commits, parsed.value]);
|
|
275
|
+
})), FireflyOkAsync([]));
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Merges user-provided commit type configuration with defaults.
|
|
279
|
+
*
|
|
280
|
+
* @param partial - Partial commit type configuration from user
|
|
281
|
+
* @returns Complete commit type configuration
|
|
282
|
+
*/
|
|
283
|
+
mergeConfiguration(partial) {
|
|
284
|
+
return {
|
|
285
|
+
major: [...DEFAULT_COMMIT_TYPE_CONFIG.major, ...partial.major ?? []],
|
|
286
|
+
minor: [...DEFAULT_COMMIT_TYPE_CONFIG.minor, ...partial.minor ?? []],
|
|
287
|
+
patch: [...DEFAULT_COMMIT_TYPE_CONFIG.patch, ...partial.patch ?? []],
|
|
288
|
+
scopeRules: {
|
|
289
|
+
...DEFAULT_COMMIT_TYPE_CONFIG.scopeRules,
|
|
290
|
+
...partial.scopeRules
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Determines if the commit header indicates a breaking change.
|
|
296
|
+
*
|
|
297
|
+
* @param commit - The commit to analyze
|
|
298
|
+
* @returns True if the header indicates a breaking change, false otherwise
|
|
299
|
+
*/
|
|
300
|
+
hasBreakingHeader(commit) {
|
|
301
|
+
if (!commit.header) return false;
|
|
302
|
+
return ANALYSIS_PATTERNS.BREAKING_HEADER.test(commit.header);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Counts the number of breaking changes in a commit.
|
|
306
|
+
*
|
|
307
|
+
* @param commit - The commit to analyze
|
|
308
|
+
* @returns The number of breaking changes found
|
|
309
|
+
*/
|
|
310
|
+
countBreakingChanges(commit) {
|
|
311
|
+
let breakingCount = 0;
|
|
312
|
+
breakingCount += commit.notes?.length ?? 0;
|
|
313
|
+
if (this.hasBreakingHeader(commit)) breakingCount += 1;
|
|
314
|
+
return breakingCount;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Determines if the commit is a feature commit.
|
|
318
|
+
*
|
|
319
|
+
* @param commit - The commit to analyze
|
|
320
|
+
* @param config - The commit type configuration
|
|
321
|
+
* @returns True if the commit is a feature commit, false otherwise
|
|
322
|
+
*/
|
|
323
|
+
isFeatureCommit(commit, config) {
|
|
324
|
+
const type = commit.type?.toLowerCase() ?? "";
|
|
325
|
+
return config.minor.includes(type);
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Determines if the commit is a patch-level commit.
|
|
329
|
+
*
|
|
330
|
+
* @param commit - The commit to analyze
|
|
331
|
+
* @param config - The commit type configuration
|
|
332
|
+
* @returns True if the commit is a patch-level commit, false otherwise
|
|
333
|
+
*/
|
|
334
|
+
isPatchCommit(commit, config) {
|
|
335
|
+
const type = commit.type?.toLowerCase() ?? "";
|
|
336
|
+
return config.patch.includes(type);
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Extracts the scope from a commit.
|
|
340
|
+
*
|
|
341
|
+
* @param commit - The commit to extract the scope from
|
|
342
|
+
* @returns The extracted scope or null if not found
|
|
343
|
+
*/
|
|
344
|
+
extractCommitScope(commit) {
|
|
345
|
+
if (commit.scope) return commit.scope;
|
|
346
|
+
if (commit.header) return commit.header.match(ANALYSIS_PATTERNS.SCOPE_HEADER)?.[1] ?? null;
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Analyzes the commit scope for breaking changes based on configuration.
|
|
351
|
+
*
|
|
352
|
+
* @param commit - The commit to analyze
|
|
353
|
+
* @param config - The commit type configuration
|
|
354
|
+
* @returns An array of scopes that indicate breaking changes
|
|
355
|
+
*/
|
|
356
|
+
analyzeScopeBreaking(commit, config) {
|
|
357
|
+
const scope = this.extractCommitScope(commit);
|
|
358
|
+
if (!scope) return [];
|
|
359
|
+
if (config.scopeRules[scope.toLowerCase()] === "major") return [scope];
|
|
360
|
+
return [];
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Detects if the commit indicates a pre-release to stable transition.
|
|
364
|
+
*
|
|
365
|
+
* @param commit - The commit to analyze
|
|
366
|
+
* @returns True if a transition is detected, false otherwise
|
|
367
|
+
*/
|
|
368
|
+
detectPreReleaseTransition(commit) {
|
|
369
|
+
const message = commit.header?.toLowerCase() ?? "";
|
|
370
|
+
const body = commit.body?.toLowerCase() ?? "";
|
|
371
|
+
return TRANSITION_KEYWORDS.some((keyword) => message.includes(keyword) || body.includes(keyword));
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Performs a detailed analysis of the commits to determine their impact on versioning.
|
|
375
|
+
*
|
|
376
|
+
* @param commits - The list of commits to analyze
|
|
377
|
+
* @param config - The commit type configuration
|
|
378
|
+
* @returns The detailed commit analysis
|
|
379
|
+
*/
|
|
380
|
+
performDetailedAnalysis(commits, config) {
|
|
381
|
+
logger.verbose("CommitAnalysisService: Performing detailed commit analysis...");
|
|
382
|
+
const analysis = commits.reduce((acc, commit) => {
|
|
383
|
+
const type = commit.type?.toLowerCase() ?? "unknown";
|
|
384
|
+
const updatedCommitsByType = { ...acc.commitsByType };
|
|
385
|
+
if (!updatedCommitsByType[type]) updatedCommitsByType[type] = [];
|
|
386
|
+
updatedCommitsByType[type] = [...updatedCommitsByType[type], commit];
|
|
387
|
+
return {
|
|
388
|
+
breakingChanges: acc.breakingChanges + this.countBreakingChanges(commit),
|
|
389
|
+
features: acc.features + (this.isFeatureCommit(commit, config) ? 1 : 0),
|
|
390
|
+
patches: acc.patches + (this.isPatchCommit(commit, config) ? 1 : 0),
|
|
391
|
+
scopedBreaking: [...acc.scopedBreaking, ...this.analyzeScopeBreaking(commit, config)],
|
|
392
|
+
hasPreReleaseTransition: acc.hasPreReleaseTransition || this.detectPreReleaseTransition(commit),
|
|
393
|
+
commitsByType: updatedCommitsByType
|
|
394
|
+
};
|
|
395
|
+
}, {
|
|
396
|
+
breakingChanges: 0,
|
|
397
|
+
features: 0,
|
|
398
|
+
patches: 0,
|
|
399
|
+
scopedBreaking: [],
|
|
400
|
+
hasPreReleaseTransition: false,
|
|
401
|
+
commitsByType: {}
|
|
402
|
+
});
|
|
403
|
+
logger.verbose(`CommitAnalysisService: Analysis results - Breaking: ${analysis.breakingChanges}, Features: ${analysis.features}, Patches: ${analysis.patches}`);
|
|
404
|
+
return analysis;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Determines the version level based on the commit analysis.
|
|
408
|
+
*
|
|
409
|
+
* @param analysis - The detailed commit analysis
|
|
410
|
+
* @returns The recommended version level
|
|
411
|
+
*/
|
|
412
|
+
determineVersionLevel(analysis) {
|
|
413
|
+
logger.verbose("CommitAnalysisService: Determining version level from analysis...");
|
|
414
|
+
if (analysis.breakingChanges > 0 || analysis.scopedBreaking.length > 0) {
|
|
415
|
+
logger.verbose("CommitAnalysisService: Breaking changes detected, recommending MAJOR version bump.");
|
|
416
|
+
return VERSION_LEVELS.MAJOR;
|
|
417
|
+
}
|
|
418
|
+
if (analysis.features > 0) {
|
|
419
|
+
logger.verbose("CommitAnalysisService: New features detected, recommending MINOR version bump.");
|
|
420
|
+
return VERSION_LEVELS.MINOR;
|
|
421
|
+
}
|
|
422
|
+
if (analysis.patches > 0) {
|
|
423
|
+
logger.verbose("CommitAnalysisService: Patch-level changes detected, recommending PATCH version bump.");
|
|
424
|
+
return VERSION_LEVELS.PATCH;
|
|
425
|
+
}
|
|
426
|
+
logger.verbose("CommitAnalysisService: No significant changes detected, defaulting to PATCH version bump.");
|
|
427
|
+
return VERSION_LEVELS.PATCH;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Generates a human-readable reason for the version recommendation.
|
|
431
|
+
*
|
|
432
|
+
* @param analysis - The detailed commit analysis
|
|
433
|
+
* @returns A string explaining the recommendation
|
|
434
|
+
*/
|
|
435
|
+
generateRecommendationReason(analysis) {
|
|
436
|
+
const reasonParts = [];
|
|
437
|
+
if (analysis.breakingChanges > 0) {
|
|
438
|
+
const plural = analysis.breakingChanges === 1 ? "change" : "changes";
|
|
439
|
+
reasonParts.push(`${analysis.breakingChanges} breaking ${plural}`);
|
|
440
|
+
}
|
|
441
|
+
if (analysis.scopedBreaking.length > 0) {
|
|
442
|
+
const scopes = analysis.scopedBreaking.join(", ");
|
|
443
|
+
reasonParts.push(`breaking scope(s): ${scopes}`);
|
|
444
|
+
}
|
|
445
|
+
if (analysis.features > 0) {
|
|
446
|
+
const plural = analysis.features === 1 ? "feature" : "features";
|
|
447
|
+
reasonParts.push(`${analysis.features} new ${plural}`);
|
|
448
|
+
}
|
|
449
|
+
if (analysis.patches > 0) {
|
|
450
|
+
const plural = analysis.patches === 1 ? "fix" : "fixes";
|
|
451
|
+
reasonParts.push(`${analysis.patches} ${plural}`);
|
|
452
|
+
}
|
|
453
|
+
if (analysis.hasPreReleaseTransition) reasonParts.push("pre-release transition detected");
|
|
454
|
+
if (reasonParts.length === 0) return "No significant changes detected, defaulting to patch increment";
|
|
455
|
+
return `Analysis found: ${reasonParts.join(", ")}`;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Creates a default patch recommendation when no commits are provided.
|
|
459
|
+
*
|
|
460
|
+
* @returns The default version recommendation
|
|
461
|
+
*/
|
|
462
|
+
createDefaultPatchRecommendation() {
|
|
463
|
+
return {
|
|
464
|
+
level: VERSION_LEVELS.PATCH,
|
|
465
|
+
releaseType: "patch",
|
|
466
|
+
reason: "No commits provided, defaulting to patch increment for safety",
|
|
467
|
+
analysis: {
|
|
468
|
+
breakingChanges: 0,
|
|
469
|
+
features: 0,
|
|
470
|
+
patches: 0,
|
|
471
|
+
scopedBreaking: [],
|
|
472
|
+
hasPreReleaseTransition: false,
|
|
473
|
+
commitsByType: {}
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Builds the final version recommendation object.
|
|
479
|
+
*
|
|
480
|
+
* @param level - The determined version level
|
|
481
|
+
* @param analysis - The detailed commit analysis
|
|
482
|
+
* @returns The version recommendation
|
|
483
|
+
*/
|
|
484
|
+
buildVersionRecommendation(level, analysis) {
|
|
485
|
+
logger.verbose("CommitAnalysisService: Building version recommendation...");
|
|
486
|
+
return {
|
|
487
|
+
level,
|
|
488
|
+
releaseType: LEVEL_TO_RELEASE_TYPE[level],
|
|
489
|
+
reason: this.generateRecommendationReason(analysis),
|
|
490
|
+
analysis
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
/**
|
|
495
|
+
* Creates a commit analysis service instance.
|
|
496
|
+
* @param git - The Git service to use
|
|
497
|
+
*/
|
|
498
|
+
function createCommitAnalysisService(git) {
|
|
499
|
+
return new DefaultCommitAnalysisService(git);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
//#endregion
|
|
503
|
+
export { createCommitAnalysisService };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as logger } from "./main.js";
|
|
2
|
-
import {
|
|
3
|
-
import { n as wrapPromise } from "./result.utilities-
|
|
4
|
-
import { t as withDryRun } from "./dry-run-
|
|
2
|
+
import { l as notFoundErrAsync } from "./result.constructors-D9jmQ0uj.js";
|
|
3
|
+
import { n as wrapPromise } from "./result.utilities-CdBL5noX.js";
|
|
4
|
+
import { t as withDryRun } from "./dry-run-korHDlWr.js";
|
|
5
5
|
|
|
6
6
|
//#region src/services/implementations/filesystem.service.ts
|
|
7
7
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as logger } from "./main.js";
|
|
2
|
-
import {
|
|
3
|
-
import { t as withDryRun } from "./dry-run-
|
|
2
|
+
import { h as failedError, i as FireflyOkAsync, o as failedErrAsync } from "./result.constructors-D9jmQ0uj.js";
|
|
3
|
+
import { t as withDryRun } from "./dry-run-korHDlWr.js";
|
|
4
4
|
import { ResultAsync } from "neverthrow";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
|
package/dist/main.js
CHANGED
|
@@ -71,7 +71,7 @@ const logger = createConsola({
|
|
|
71
71
|
|
|
72
72
|
//#endregion
|
|
73
73
|
//#region package.json
|
|
74
|
-
var version = "4.0.0-dev.
|
|
74
|
+
var version = "4.0.0-dev.df58d2b";
|
|
75
75
|
var description = " CLI orchestrator for automatic semantic versioning, changelog generation, and creating releases. Built for my own use cases.";
|
|
76
76
|
var dependencies = {
|
|
77
77
|
"c12": "^3.3.2",
|
|
@@ -99,7 +99,7 @@ async function main() {
|
|
|
99
99
|
description,
|
|
100
100
|
gitCliffVersion: dependencies["git-cliff"]?.replace("^", "") || "unknown"
|
|
101
101
|
});
|
|
102
|
-
const { createFireflyCLI } = await import("./program-
|
|
102
|
+
const { createFireflyCLI } = await import("./program-CW3hIbzh.js");
|
|
103
103
|
createFireflyCLI().parseAsync(process.argv).catch((error) => {
|
|
104
104
|
logger.error("Fatal error:", error);
|
|
105
105
|
process.exit(1);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as logger } from "./main.js";
|
|
2
|
-
import { d as
|
|
3
|
-
import { n as parseSchema } from "./schema.utilities-
|
|
2
|
+
import { d as validationErr, f as validationErrAsync, i as FireflyOkAsync, v as toFireflyError } from "./result.constructors-D9jmQ0uj.js";
|
|
3
|
+
import { n as parseSchema } from "./schema.utilities-BOJqWEey.js";
|
|
4
4
|
import { Result } from "neverthrow";
|
|
5
5
|
import z$1 from "zod";
|
|
6
6
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { n as RuntimeEnv, t as logger } from "./main.js";
|
|
2
|
-
import { _ as
|
|
3
|
-
import { n as wrapPromise, r as zip3Async, t as ensureNotAsync } from "./result.utilities-
|
|
4
|
-
import { n as parseSchema, t as formatZodErrors } from "./schema.utilities-
|
|
2
|
+
import { _ as notFoundError, a as conflictErrAsync, b as wrapErrorMessage, c as invalidErrAsync, d as validationErr, f as validationErrAsync, h as failedError, i as FireflyOkAsync, l as notFoundErrAsync, n as FireflyErrAsync, p as conflictError, r as FireflyOk, s as invalidErr, t as FireflyErr, u as timeoutErrAsync, y as validationError } from "./result.constructors-D9jmQ0uj.js";
|
|
3
|
+
import { n as wrapPromise, r as zip3Async, t as ensureNotAsync } from "./result.utilities-CdBL5noX.js";
|
|
4
|
+
import { n as parseSchema, t as formatZodErrors } from "./schema.utilities-BOJqWEey.js";
|
|
5
5
|
import { LogLevels } from "consola";
|
|
6
6
|
import { colors } from "consola/utils";
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -1003,9 +1003,10 @@ function createBumpExecutionGroup() {
|
|
|
1003
1003
|
*/
|
|
1004
1004
|
function shouldSkipBumpStrategy(ctx) {
|
|
1005
1005
|
const { skipBump, releaseType, bumpStrategy } = ctx.config;
|
|
1006
|
+
const { selectedReleaseType, selectedBumpStrategy } = ctx.data;
|
|
1006
1007
|
if (skipBump) return true;
|
|
1007
|
-
if (releaseType) return true;
|
|
1008
|
-
if (!bumpStrategy) return true;
|
|
1008
|
+
if (releaseType || selectedReleaseType) return true;
|
|
1009
|
+
if (!Boolean(bumpStrategy || selectedBumpStrategy)) return true;
|
|
1009
1010
|
return false;
|
|
1010
1011
|
}
|
|
1011
1012
|
/**
|
|
@@ -1013,9 +1014,11 @@ function shouldSkipBumpStrategy(ctx) {
|
|
|
1013
1014
|
*/
|
|
1014
1015
|
function getSkipReason(ctx) {
|
|
1015
1016
|
const { skipBump, releaseType, bumpStrategy } = ctx.config;
|
|
1017
|
+
const { selectedReleaseType, selectedBumpStrategy } = ctx.data;
|
|
1016
1018
|
if (skipBump) return "skipBump is enabled";
|
|
1017
|
-
if (releaseType) return `releaseType already set to '${releaseType}'`;
|
|
1018
|
-
if (
|
|
1019
|
+
if (releaseType) return `releaseType already set to '${releaseType}' (config)`;
|
|
1020
|
+
if (selectedReleaseType) return `releaseType already set to '${selectedReleaseType}' (data)`;
|
|
1021
|
+
if (!Boolean(bumpStrategy || selectedBumpStrategy)) return "no bumpStrategy configured";
|
|
1019
1022
|
return "unknown reason";
|
|
1020
1023
|
}
|
|
1021
1024
|
function createDelegateBumpStrategyTask() {
|
|
@@ -1034,7 +1037,10 @@ function createDelegateBumpStrategyTask() {
|
|
|
1034
1037
|
//#endregion
|
|
1035
1038
|
//#region src/commands/release/tasks/determine-automatic-bump.task.ts
|
|
1036
1039
|
function createDetermineAutomaticBump() {
|
|
1037
|
-
return TaskBuilder.create("determine-automatic-bump").description("Automatically determines the version bump from commit messages").dependsOn("delegate-bump-strategy").skipWhenWithReason((ctx) =>
|
|
1040
|
+
return TaskBuilder.create("determine-automatic-bump").description("Automatically determines the version bump from commit messages").dependsOn("delegate-bump-strategy").skipWhenWithReason((ctx) => {
|
|
1041
|
+
const bumpStrategy = ctx.data.selectedBumpStrategy ?? ctx.config.bumpStrategy;
|
|
1042
|
+
return ctx.config.skipBump || bumpStrategy !== BUMP_STRATEGY_AUTO;
|
|
1043
|
+
}, "Skipped: skipBump enabled or bumpStrategy is not 'auto'").execute((ctx) => {
|
|
1038
1044
|
logger.info("determine-automatic-bump");
|
|
1039
1045
|
return FireflyOkAsync(ctx);
|
|
1040
1046
|
}).build();
|
|
@@ -1042,17 +1048,61 @@ function createDetermineAutomaticBump() {
|
|
|
1042
1048
|
|
|
1043
1049
|
//#endregion
|
|
1044
1050
|
//#region src/commands/release/tasks/prompt-bump-strategy.task.ts
|
|
1051
|
+
const BUMP_STRATEGIES = [{
|
|
1052
|
+
label: "Automatic Bump",
|
|
1053
|
+
value: BUMP_STRATEGY_AUTO,
|
|
1054
|
+
hint: "Determines the next version based on conventional commits history"
|
|
1055
|
+
}, {
|
|
1056
|
+
label: "Manual Bump",
|
|
1057
|
+
value: BUMP_STRATEGY_MANUAL,
|
|
1058
|
+
hint: "Manually specify the next version"
|
|
1059
|
+
}];
|
|
1060
|
+
const VALID_STRATEGY_VALUES = BUMP_STRATEGIES.map((s) => s.value);
|
|
1061
|
+
/**
|
|
1062
|
+
* Validates that the selected strategy is one of the allowed values.
|
|
1063
|
+
*
|
|
1064
|
+
* @param strategy The selected version bump strategy.
|
|
1065
|
+
* @returns A FireflyResult indicating success or failure of validation.
|
|
1066
|
+
*/
|
|
1067
|
+
function validateStrategy(strategy) {
|
|
1068
|
+
if (!VALID_STRATEGY_VALUES.includes(strategy)) return invalidErr({ message: `Invalid version bump strategy: ${strategy}` });
|
|
1069
|
+
return FireflyOk(strategy);
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Prompts the user to select a bump strategy using the logger's prompt API.
|
|
1073
|
+
*/
|
|
1074
|
+
function promptBumpStrategy() {
|
|
1075
|
+
const defaultStrategy = BUMP_STRATEGIES[0];
|
|
1076
|
+
if (!defaultStrategy) return notFoundErrAsync({ message: "No default version bump strategy found" });
|
|
1077
|
+
logger.verbose("PromptBumpStrategyTask: Prompting user for version bump strategy.");
|
|
1078
|
+
const prompt = logger.prompt("Select version bump strategy", {
|
|
1079
|
+
type: "select",
|
|
1080
|
+
options: BUMP_STRATEGIES,
|
|
1081
|
+
initial: defaultStrategy.value,
|
|
1082
|
+
cancel: "reject"
|
|
1083
|
+
});
|
|
1084
|
+
if (logger.level !== LogLevels.verbose) logger.log("");
|
|
1085
|
+
return wrapPromise(prompt).andTee(() => {
|
|
1086
|
+
if (logger.level === LogLevels.verbose) logger.log("");
|
|
1087
|
+
}).andThen((selected) => {
|
|
1088
|
+
if (!selected || selected === "") return invalidErrAsync({ message: "No version bump strategy selected" });
|
|
1089
|
+
const validationResult = validateStrategy(selected);
|
|
1090
|
+
if (validationResult.isErr()) return FireflyErrAsync(validationResult.error);
|
|
1091
|
+
logger.verbose(`PromptBumpStrategyTask: Selected version bump strategy: '${selected}'`);
|
|
1092
|
+
return FireflyOkAsync(selected);
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1045
1095
|
function createPromptBumpStrategyTask() {
|
|
1046
|
-
return TaskBuilder.create("prompt-bump-strategy").description("Prompts the user for a version bump strategy").dependsOn("initialize-release-version").skipWhenWithReason((ctx) => ctx.config.skipBump || Boolean(ctx.config.bumpStrategy) || Boolean(ctx.config.releaseType), "Skipped: skipBump enabled, or bumpStrategy/releaseType already specified").execute((ctx) =>
|
|
1047
|
-
logger.info("prompt-bump-strategy");
|
|
1048
|
-
return FireflyOkAsync(ctx);
|
|
1049
|
-
}).build();
|
|
1096
|
+
return TaskBuilder.create("prompt-bump-strategy").description("Prompts the user for a version bump strategy").dependsOn("initialize-release-version").skipWhenWithReason((ctx) => ctx.config.skipBump || Boolean(ctx.config.bumpStrategy) || Boolean(ctx.config.releaseType), "Skipped: skipBump enabled, or bumpStrategy/releaseType already specified").execute((ctx) => promptBumpStrategy().andThen((strategy) => FireflyOkAsync(ctx.fork("selectedBumpStrategy", strategy)))).build();
|
|
1050
1097
|
}
|
|
1051
1098
|
|
|
1052
1099
|
//#endregion
|
|
1053
1100
|
//#region src/commands/release/tasks/prompt-manual-bump.task.ts
|
|
1054
1101
|
function createPromptManualVersionTask() {
|
|
1055
|
-
return TaskBuilder.create("prompt-manual-version").description("Prompts the user for a manual version bump selections").dependsOn("delegate-bump-strategy").skipWhenWithReason((ctx) =>
|
|
1102
|
+
return TaskBuilder.create("prompt-manual-version").description("Prompts the user for a manual version bump selections").dependsOn("delegate-bump-strategy").skipWhenWithReason((ctx) => {
|
|
1103
|
+
const bumpStrategy = ctx.data.selectedBumpStrategy ?? ctx.config.bumpStrategy;
|
|
1104
|
+
return ctx.config.skipBump || bumpStrategy !== BUMP_STRATEGY_MANUAL;
|
|
1105
|
+
}, "Skipped: skipBump enabled or bumpStrategy is not 'manual'").execute((ctx) => {
|
|
1056
1106
|
logger.info("prompt-manual-version");
|
|
1057
1107
|
return FireflyOkAsync(ctx);
|
|
1058
1108
|
}).build();
|
|
@@ -1611,21 +1661,41 @@ function defineService(definition) {
|
|
|
1611
1661
|
*/
|
|
1612
1662
|
const SERVICE_DEFINITIONS = {
|
|
1613
1663
|
fs: defineService({ factory: async ({ basePath }) => {
|
|
1614
|
-
const { createFileSystemService } = await import("./filesystem.service-
|
|
1664
|
+
const { createFileSystemService } = await import("./filesystem.service-BcQtLzfl.js");
|
|
1615
1665
|
return createFileSystemService(basePath);
|
|
1616
1666
|
} }),
|
|
1617
1667
|
packageJson: defineService({
|
|
1618
1668
|
dependencies: ["fs"],
|
|
1619
1669
|
factory: async ({ getService }) => {
|
|
1620
1670
|
const fs = await getService("fs");
|
|
1621
|
-
const { createPackageJsonService } = await import("./package-json.service-
|
|
1671
|
+
const { createPackageJsonService } = await import("./package-json.service-BKbXaHXQ.js");
|
|
1622
1672
|
return createPackageJsonService(fs);
|
|
1623
1673
|
}
|
|
1624
1674
|
}),
|
|
1625
1675
|
git: defineService({ factory: async ({ basePath }) => {
|
|
1626
|
-
const { createGitService } = await import("./git.service-
|
|
1676
|
+
const { createGitService } = await import("./git.service-D1Mf6LZ0.js");
|
|
1627
1677
|
return createGitService(basePath);
|
|
1628
|
-
} })
|
|
1678
|
+
} }),
|
|
1679
|
+
versionBumper: defineService({ factory: async () => {
|
|
1680
|
+
const { createVersionBumperService } = await import("./version-bumper.service-CvPx7EZx.js");
|
|
1681
|
+
return createVersionBumperService();
|
|
1682
|
+
} }),
|
|
1683
|
+
versionStrategy: defineService({
|
|
1684
|
+
dependencies: ["versionBumper"],
|
|
1685
|
+
factory: async ({ getService }) => {
|
|
1686
|
+
const versionBumper = await getService("versionBumper");
|
|
1687
|
+
const { createVersionStrategyService } = await import("./version-strategy.service-BO_7MY5O.js");
|
|
1688
|
+
return createVersionStrategyService(versionBumper);
|
|
1689
|
+
}
|
|
1690
|
+
}),
|
|
1691
|
+
commitAnalysis: defineService({
|
|
1692
|
+
dependencies: ["git"],
|
|
1693
|
+
factory: async ({ getService }) => {
|
|
1694
|
+
const git = await getService("git");
|
|
1695
|
+
const { createCommitAnalysisService } = await import("./commit-analysis.service-CtIh7eez.js");
|
|
1696
|
+
return createCommitAnalysisService(git);
|
|
1697
|
+
}
|
|
1698
|
+
})
|
|
1629
1699
|
};
|
|
1630
1700
|
/**
|
|
1631
1701
|
* Array of all service keys for iteration
|
|
@@ -253,9 +253,13 @@ const failedErrAsync = (opts) => errAsync(failedError(opts));
|
|
|
253
253
|
*/
|
|
254
254
|
const invalidErr = (opts) => err(invalidError(opts));
|
|
255
255
|
/**
|
|
256
|
+
* Creates an async invalid error result.
|
|
257
|
+
*/
|
|
258
|
+
const invalidErrAsync = (opts) => errAsync(invalidError(opts));
|
|
259
|
+
/**
|
|
256
260
|
* Creates an async timeout error result.
|
|
257
261
|
*/
|
|
258
262
|
const timeoutErrAsync = (opts) => errAsync(timeoutError(opts));
|
|
259
263
|
|
|
260
264
|
//#endregion
|
|
261
|
-
export {
|
|
265
|
+
export { notFoundError as _, conflictErrAsync as a, wrapErrorMessage as b, invalidErrAsync as c, validationErr as d, validationErrAsync as f, invalidError as g, failedError as h, FireflyOkAsync as i, notFoundErrAsync as l, createFireflyError as m, FireflyErrAsync as n, failedErrAsync as o, conflictError as p, FireflyOk as r, invalidErr as s, FireflyErr as t, timeoutErrAsync as u, toFireflyError as v, validationError as y };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { f as validationErrAsync, i as FireflyOkAsync, m as createFireflyError, v as toFireflyError } from "./result.constructors-D9jmQ0uj.js";
|
|
2
2
|
import { ResultAsync } from "neverthrow";
|
|
3
3
|
|
|
4
4
|
//#region src/core/result/result.utilities.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { m as createFireflyError, r as FireflyOk, t as FireflyErr, v as toFireflyError } from "./result.constructors-D9jmQ0uj.js";
|
|
2
2
|
import { ResultAsync } from "neverthrow";
|
|
3
3
|
import z$1 from "zod";
|
|
4
4
|
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { r as FireflyOk, s as invalidErr, t as FireflyErr } from "./result.constructors-D9jmQ0uj.js";
|
|
2
|
+
import semver from "semver";
|
|
3
|
+
|
|
4
|
+
//#region src/domain/semver/version.ts
|
|
5
|
+
/**
|
|
6
|
+
* Represents a parsed semantic version with immutable access.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* const result = Version.from("1.2.3-alpha.1");
|
|
11
|
+
* if (result.isOk()) {
|
|
12
|
+
* console.log(result.value.major); // 1
|
|
13
|
+
* console.log(result.value.isPrerelease); // true
|
|
14
|
+
* }
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
var Version = class Version {
|
|
18
|
+
_raw;
|
|
19
|
+
_parsed;
|
|
20
|
+
constructor(version, parsed) {
|
|
21
|
+
this._raw = version;
|
|
22
|
+
this._parsed = parsed;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Creates a Version from any valid semver string.
|
|
26
|
+
* Handles cleaning (removing 'v' prefix, etc.).
|
|
27
|
+
*/
|
|
28
|
+
static from(input) {
|
|
29
|
+
const cleaned = semver.clean(input);
|
|
30
|
+
if (!cleaned) return invalidErr({ message: `"${input}" is not a valid semantic version.` });
|
|
31
|
+
const parsed = semver.parse(cleaned);
|
|
32
|
+
if (!parsed) return invalidErr({ message: `Failed to parse semantic version "${cleaned}".` });
|
|
33
|
+
return FireflyOk(new Version(cleaned, parsed));
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Creates a Version from an already-clean semver string.
|
|
37
|
+
* Use when you know the input is already normalized.
|
|
38
|
+
*/
|
|
39
|
+
static fromClean(cleanVersion) {
|
|
40
|
+
const parsed = semver.parse(cleanVersion);
|
|
41
|
+
if (!parsed) return invalidErr({ message: `Expected clean version but got invalid: ${cleanVersion}` });
|
|
42
|
+
return FireflyOk(new Version(cleanVersion, parsed));
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The raw version string.
|
|
46
|
+
*/
|
|
47
|
+
get raw() {
|
|
48
|
+
return this._raw;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Major version number.
|
|
52
|
+
*/
|
|
53
|
+
get major() {
|
|
54
|
+
return this._parsed.major;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Minor version number.
|
|
58
|
+
*/
|
|
59
|
+
get minor() {
|
|
60
|
+
return this._parsed.minor;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Patch version number.
|
|
64
|
+
*/
|
|
65
|
+
get patch() {
|
|
66
|
+
return this._parsed.patch;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Whether this version has prerelease identifiers.
|
|
70
|
+
*/
|
|
71
|
+
get isPrerelease() {
|
|
72
|
+
return this._parsed.prerelease.length > 0;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Prerelease identifiers (e.g., ["alpha", 1] for "1.0.0-alpha.1").
|
|
76
|
+
*/
|
|
77
|
+
get prerelease() {
|
|
78
|
+
return this._parsed.prerelease;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The prerelease identifier (e.g., "alpha" from "1.0.0-alpha.1").
|
|
82
|
+
*/
|
|
83
|
+
get prereleaseIdentifier() {
|
|
84
|
+
if (!this.isPrerelease) return null;
|
|
85
|
+
const first = this._parsed.prerelease[0];
|
|
86
|
+
return typeof first === "string" ? first : null;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The prerelease number (e.g., 1 from "1.0.0-alpha.1").
|
|
90
|
+
*/
|
|
91
|
+
get prereleaseNumber() {
|
|
92
|
+
if (!this.isPrerelease) return null;
|
|
93
|
+
const last = this._parsed.prerelease.at(-1);
|
|
94
|
+
return typeof last === "number" ? last : null;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Build metadata identifiers.
|
|
98
|
+
*/
|
|
99
|
+
get build() {
|
|
100
|
+
return this._parsed.build;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Returns the raw version string.
|
|
104
|
+
*/
|
|
105
|
+
toString() {
|
|
106
|
+
return this._raw;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Checks equality with another Version.
|
|
110
|
+
*
|
|
111
|
+
* @param other - The other Version to compare with
|
|
112
|
+
* @returns True if equal, false otherwise
|
|
113
|
+
*/
|
|
114
|
+
equals(other) {
|
|
115
|
+
return semver.eq(this._raw, other._raw);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Compares with another Version: -1 (less), 0 (equal), 1 (greater).
|
|
119
|
+
*
|
|
120
|
+
* @param other - The other Version to compare with
|
|
121
|
+
* @returns -1, 0, or 1 based on comparison
|
|
122
|
+
*/
|
|
123
|
+
compare(other) {
|
|
124
|
+
return semver.compare(this._raw, other._raw);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Checks if this version is greater than another.
|
|
128
|
+
*
|
|
129
|
+
* @param other - The other Version to compare with
|
|
130
|
+
* @returns True if this version is greater, false otherwise
|
|
131
|
+
*/
|
|
132
|
+
isGreaterThan(other) {
|
|
133
|
+
return semver.gt(this._raw, other._raw);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Checks if this version is less than another.
|
|
137
|
+
*
|
|
138
|
+
* @param other - The other Version to compare with
|
|
139
|
+
* @returns True if this version is less, false otherwise
|
|
140
|
+
*/
|
|
141
|
+
isLessThan(other) {
|
|
142
|
+
return semver.lt(this._raw, other._raw);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Checks if this version satisfies a semver range.
|
|
146
|
+
*
|
|
147
|
+
* @param range - The semver range to check against
|
|
148
|
+
* @returns True if it satisfies the range, false otherwise
|
|
149
|
+
*/
|
|
150
|
+
satisfies(range) {
|
|
151
|
+
return semver.satisfies(this._raw, range);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Converts a prerelease version to its stable form.
|
|
155
|
+
* "1.2.3-alpha.1" → "1.2.3"
|
|
156
|
+
*/
|
|
157
|
+
toStable() {
|
|
158
|
+
const stableVersion = `${this.major}.${this.minor}.${this.patch}`;
|
|
159
|
+
return Version.fromClean(stableVersion);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
//#endregion
|
|
164
|
+
//#region src/services/implementations/version-bumper.service.ts
|
|
165
|
+
/**
|
|
166
|
+
* Default implementation of the version bumper service.
|
|
167
|
+
*/
|
|
168
|
+
var DefaultVersionBumperService = class {
|
|
169
|
+
bump(options) {
|
|
170
|
+
const { currentVersion, releaseType, prereleaseIdentifier, prereleaseBase } = options;
|
|
171
|
+
if (releaseType === "major" || releaseType === "minor" || releaseType === "patch") return this.bumpStandard(currentVersion, releaseType);
|
|
172
|
+
if (releaseType === "premajor" || releaseType === "preminor" || releaseType === "prepatch") return this.bumpPreStandard(currentVersion, releaseType, prereleaseIdentifier, prereleaseBase);
|
|
173
|
+
if (releaseType === "prerelease") return this.bumpPrerelease(currentVersion, prereleaseIdentifier, prereleaseBase);
|
|
174
|
+
if (releaseType === "graduate") return this.graduatePrerelease(currentVersion);
|
|
175
|
+
return invalidErr({
|
|
176
|
+
message: `Unsupported release type: ${releaseType}`,
|
|
177
|
+
source: "services/version-bumper"
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Normalizes the prerelease base to a string "0" or "1".
|
|
182
|
+
*
|
|
183
|
+
* @param base - The prerelease base to normalize
|
|
184
|
+
* @returns The normalized base or an error if invalid
|
|
185
|
+
*/
|
|
186
|
+
normalizeBase(base) {
|
|
187
|
+
if (base === void 0 || base === null) return FireflyOk(void 0);
|
|
188
|
+
if (base === "0" || base === "1") return FireflyOk(base);
|
|
189
|
+
if (typeof base === "number" && (base === 0 || base === 1)) return FireflyOk(base.toString());
|
|
190
|
+
return invalidErr({ message: `Invalid prerelease base '${base}'. Must be "0", "1", 0, or 1.` });
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Determines if the prerelease identifier is complex (contains dots).
|
|
194
|
+
*
|
|
195
|
+
* @param identifier - The prerelease identifier to check
|
|
196
|
+
* @returns True if the identifier is complex, false otherwise
|
|
197
|
+
*/
|
|
198
|
+
isComplexIdentifier(identifier) {
|
|
199
|
+
return typeof identifier === "string" && identifier.includes(".");
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Bumps a version with a complex prerelease identifier (e.g., "canary.abc123").
|
|
203
|
+
*
|
|
204
|
+
* @param currentVersion - The current version to bump
|
|
205
|
+
* @param identifier - The complex prerelease identifier
|
|
206
|
+
* @returns The new version with the complex identifier
|
|
207
|
+
*/
|
|
208
|
+
bumpWithComplexIdentifier(currentVersion, identifier) {
|
|
209
|
+
if (!identifier) return semver.inc(currentVersion.raw, "prerelease", void 0, "0");
|
|
210
|
+
return semver.inc(currentVersion.raw, "prerelease", identifier, false);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Bumps an existing prerelease version.
|
|
214
|
+
*
|
|
215
|
+
* @param currentVersion - The current version to bump
|
|
216
|
+
* @param identifier - Optional prerelease identifier
|
|
217
|
+
* @returns The new prerelease version
|
|
218
|
+
*/
|
|
219
|
+
bumpExistingPrerelease(currentVersion, identifier) {
|
|
220
|
+
return identifier ? semver.inc(currentVersion.raw, "prerelease", identifier) : semver.inc(currentVersion.raw, "prerelease");
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Bumps a standard version (major, minor, patch) according to the provided release type.
|
|
224
|
+
*
|
|
225
|
+
* @param currentVersion - The current version to bump
|
|
226
|
+
* @param releaseType - The type of standard release
|
|
227
|
+
* @returns The new standard version
|
|
228
|
+
*/
|
|
229
|
+
bumpStandard(currentVersion, releaseType) {
|
|
230
|
+
let baseVersionString = currentVersion.raw;
|
|
231
|
+
if (currentVersion.isPrerelease) {
|
|
232
|
+
const stableResult = currentVersion.toStable();
|
|
233
|
+
if (stableResult.isErr()) return FireflyErr(stableResult.error);
|
|
234
|
+
baseVersionString = stableResult.value.raw;
|
|
235
|
+
}
|
|
236
|
+
const newVersionString = semver.inc(baseVersionString, releaseType);
|
|
237
|
+
if (!newVersionString) return invalidErr({
|
|
238
|
+
message: `Failed to bump ${releaseType} version from '${baseVersionString}'.`,
|
|
239
|
+
source: "services/version-bumper"
|
|
240
|
+
});
|
|
241
|
+
return Version.fromClean(newVersionString);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Bumps a pre-standard version (premajor, preminor, prepatch) according to the provided options.
|
|
245
|
+
*
|
|
246
|
+
* @param currentVersion - The current version to bump
|
|
247
|
+
* @param releaseType - The type of pre-standard release
|
|
248
|
+
* @param identifier - Optional prerelease identifier (e.g., "alpha", "beta")
|
|
249
|
+
* @param base - Optional base number for the prerelease
|
|
250
|
+
* @returns The new pre-standard version
|
|
251
|
+
*/
|
|
252
|
+
bumpPreStandard(currentVersion, releaseType, identifier, base) {
|
|
253
|
+
const normalizedBaseResult = this.normalizeBase(base);
|
|
254
|
+
if (normalizedBaseResult.isErr()) return FireflyErr(normalizedBaseResult.error);
|
|
255
|
+
const normalizedBase = normalizedBaseResult.value;
|
|
256
|
+
let newVersionString = null;
|
|
257
|
+
if (normalizedBase !== void 0) newVersionString = identifier ? semver.inc(currentVersion.raw, releaseType, identifier, normalizedBase) : semver.inc(currentVersion.raw, releaseType, void 0, normalizedBase);
|
|
258
|
+
else newVersionString = identifier ? semver.inc(currentVersion.raw, releaseType, identifier) : semver.inc(currentVersion.raw, releaseType);
|
|
259
|
+
if (!newVersionString) return invalidErr({
|
|
260
|
+
message: `Failed to bump ${releaseType} version from '${currentVersion.raw}' with identifier '${identifier}' and base '${base}'.`,
|
|
261
|
+
source: "services/version-bumper"
|
|
262
|
+
});
|
|
263
|
+
return Version.fromClean(newVersionString);
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Bumps a prerelease version according to the provided options.
|
|
267
|
+
* Handles complex identifiers, explicit bases, continuing existing
|
|
268
|
+
* prereleases, and starting new prereleases from stable versions.
|
|
269
|
+
*
|
|
270
|
+
* @param currentVersion - The current version to bump
|
|
271
|
+
* @param identifier - Optional prerelease identifier (e.g., "alpha", "beta")
|
|
272
|
+
* @param base - Optional base number for the prerelease
|
|
273
|
+
* @returns The new prerelease version
|
|
274
|
+
*/
|
|
275
|
+
bumpPrerelease(currentVersion, identifier, base) {
|
|
276
|
+
let newVersionString = null;
|
|
277
|
+
if (this.isComplexIdentifier(identifier)) newVersionString = this.bumpWithComplexIdentifier(currentVersion, identifier);
|
|
278
|
+
else if (base !== void 0 && base !== null) {
|
|
279
|
+
const normalizedBaseResult = this.normalizeBase(base);
|
|
280
|
+
if (normalizedBaseResult.isErr()) return FireflyErr(normalizedBaseResult.error);
|
|
281
|
+
const normalizedBase = normalizedBaseResult.value;
|
|
282
|
+
newVersionString = identifier ? semver.inc(currentVersion.raw, "prerelease", identifier, normalizedBase) : semver.inc(currentVersion.raw, "prerelease", void 0, normalizedBase);
|
|
283
|
+
} else if (currentVersion.isPrerelease) newVersionString = this.bumpExistingPrerelease(currentVersion, identifier);
|
|
284
|
+
else {
|
|
285
|
+
const defaultIdentifier = identifier || "alpha";
|
|
286
|
+
newVersionString = semver.inc(currentVersion.raw, "prerelease", defaultIdentifier);
|
|
287
|
+
}
|
|
288
|
+
if (!newVersionString) return invalidErr({
|
|
289
|
+
message: `Failed to bump prerelease version from '${currentVersion.raw}' with identifier '${identifier}' and base '${base}'.`,
|
|
290
|
+
source: "services/version-bumper"
|
|
291
|
+
});
|
|
292
|
+
return Version.fromClean(newVersionString);
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Graduates a prerelease version to its stable counterpart.
|
|
296
|
+
*
|
|
297
|
+
* @param currentVersion - The current prerelease version
|
|
298
|
+
* @returns The stable version
|
|
299
|
+
*/
|
|
300
|
+
graduatePrerelease(currentVersion) {
|
|
301
|
+
if (!currentVersion.isPrerelease) return invalidErr({
|
|
302
|
+
message: `Cannot graduate non-prerelease version '${currentVersion.raw}'. Only prerelease versions can be graduated.`,
|
|
303
|
+
source: "services/version-bumper"
|
|
304
|
+
});
|
|
305
|
+
const stableVersionResult = currentVersion.toStable();
|
|
306
|
+
if (stableVersionResult.isErr()) return FireflyErr(stableVersionResult.error);
|
|
307
|
+
return FireflyOk(stableVersionResult.value);
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
/**
|
|
311
|
+
* Creates a version bumper service instance.
|
|
312
|
+
*/
|
|
313
|
+
function createVersionBumperService() {
|
|
314
|
+
return new DefaultVersionBumperService();
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
//#endregion
|
|
318
|
+
export { createVersionBumperService };
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { t as logger } from "./main.js";
|
|
2
|
+
import { g as invalidError, r as FireflyOk, t as FireflyErr } from "./result.constructors-D9jmQ0uj.js";
|
|
3
|
+
import { Result } from "neverthrow";
|
|
4
|
+
|
|
5
|
+
//#region src/services/implementations/version-strategy.service.ts
|
|
6
|
+
const TRANSITION_KEYWORDS = [
|
|
7
|
+
"general availability",
|
|
8
|
+
"promote to stable",
|
|
9
|
+
"move to stable"
|
|
10
|
+
];
|
|
11
|
+
const LEVEL_TO_RELEASE_TYPE = {
|
|
12
|
+
0: "major",
|
|
13
|
+
1: "minor",
|
|
14
|
+
2: "patch"
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Version type categories for organizing choices.
|
|
18
|
+
*/
|
|
19
|
+
const VERSION_TYPES = {
|
|
20
|
+
RELEASE: [
|
|
21
|
+
"patch",
|
|
22
|
+
"minor",
|
|
23
|
+
"major"
|
|
24
|
+
],
|
|
25
|
+
PRERELEASE: [
|
|
26
|
+
"prepatch",
|
|
27
|
+
"preminor",
|
|
28
|
+
"premajor"
|
|
29
|
+
],
|
|
30
|
+
CONTINUATION: ["prerelease"],
|
|
31
|
+
GRADUATION: ["graduate"]
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Pre-configured choice sets for different scenarios.
|
|
35
|
+
*/
|
|
36
|
+
const VERSION_CHOICE_SETS = {
|
|
37
|
+
latestIsPreRelease: [
|
|
38
|
+
...VERSION_TYPES.CONTINUATION,
|
|
39
|
+
...VERSION_TYPES.GRADUATION,
|
|
40
|
+
...VERSION_TYPES.RELEASE
|
|
41
|
+
],
|
|
42
|
+
preRelease: VERSION_TYPES.PRERELEASE,
|
|
43
|
+
default: [...VERSION_TYPES.RELEASE, ...VERSION_TYPES.PRERELEASE]
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Human-readable descriptions for each release type.
|
|
47
|
+
*/
|
|
48
|
+
const VERSION_DESCRIPTIONS = {
|
|
49
|
+
patch: "Fixes and minor enhancements without breaking compatibility. Suitable for bug fixes and small improvements.",
|
|
50
|
+
minor: "New, backward-compatible functionality. Adds features that do not break existing APIs.",
|
|
51
|
+
major: "Incompatible API changes. Introduces breaking changes or removes deprecated features.",
|
|
52
|
+
prepatch: "Unstable patch release candidate. Used for testing patch changes before a stable release.",
|
|
53
|
+
preminor: "Unstable minor release candidate. Used for previewing new features before a minor release.",
|
|
54
|
+
premajor: "Unstable major release candidate. Used for testing breaking changes before a major release.",
|
|
55
|
+
prerelease: "Unstable pre-release continuation. Increments the pre-release number or changes identifier.",
|
|
56
|
+
graduate: "Promote pre-release to stable. Removes pre-release identifiers to create a stable version."
|
|
57
|
+
};
|
|
58
|
+
var DefaultVersionStrategyService = class {
|
|
59
|
+
bumper;
|
|
60
|
+
constructor(bumper) {
|
|
61
|
+
this.bumper = bumper;
|
|
62
|
+
}
|
|
63
|
+
resolveVersion(options, recommendation) {
|
|
64
|
+
logger.verbose("DefaultVersionStrategyService: Deciding next version...");
|
|
65
|
+
const preReleaseContext = this.analyzePreReleaseContext(options.currentVersion, recommendation);
|
|
66
|
+
if (options.releaseType === "prerelease") {
|
|
67
|
+
logger.verbose("DefaultVersionStrategyService: Handling prerelease request...");
|
|
68
|
+
return this.handlePreReleaseRequest(options, preReleaseContext);
|
|
69
|
+
}
|
|
70
|
+
if (preReleaseContext.isCurrentPreRelease && preReleaseContext.hasStableTransition) {
|
|
71
|
+
logger.verbose("DefaultVersionStrategyService: Handling pre-release to stable transition...");
|
|
72
|
+
return this.handlePreReleaseToStableTransition(options, recommendation);
|
|
73
|
+
}
|
|
74
|
+
if (recommendation) {
|
|
75
|
+
logger.verbose("DefaultVersionStrategyService: Handling recommendation-based versioning...");
|
|
76
|
+
return this.createRecommendationBasedVersion(options, recommendation, preReleaseContext);
|
|
77
|
+
}
|
|
78
|
+
if (options.releaseType) {
|
|
79
|
+
logger.verbose("DefaultVersionStrategyService: Handling explicit release type...");
|
|
80
|
+
return this.bumper.bump({
|
|
81
|
+
currentVersion: options.currentVersion,
|
|
82
|
+
releaseType: options.releaseType,
|
|
83
|
+
prereleaseIdentifier: options.prereleaseIdentifier,
|
|
84
|
+
prereleaseBase: options.prereleaseBase
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return FireflyErr(invalidError({ message: "Cannot determine next version: no release type or recommendation provided" }));
|
|
88
|
+
}
|
|
89
|
+
generateChoices(options) {
|
|
90
|
+
logger.verbose(`DefaultVersionStrategyService: Creating version choices for '${options.currentVersion.raw}'...`);
|
|
91
|
+
const choicesResults = this.determineAvailableVersionTypes(options.currentVersion, options.releaseType).map((releaseType) => this.createSingleChoice(options.currentVersion, releaseType, options.prereleaseIdentifier, options.prereleaseBase));
|
|
92
|
+
const combinedResult = Result.combine(choicesResults);
|
|
93
|
+
if (combinedResult.isErr()) return FireflyErr(combinedResult.error);
|
|
94
|
+
logger.verbose(`DefaultVersionStrategyService: Created ${combinedResult.value.length} version choices.`);
|
|
95
|
+
return FireflyOk(combinedResult.value);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Analyzes the current version and recommendation to determine pre-release context.
|
|
99
|
+
*
|
|
100
|
+
* @param currentVersion - The current version
|
|
101
|
+
* @param recommendation - The version recommendation
|
|
102
|
+
* @returns The pre-release context
|
|
103
|
+
*/
|
|
104
|
+
analyzePreReleaseContext(currentVersion, recommendation) {
|
|
105
|
+
return {
|
|
106
|
+
isCurrentPreRelease: currentVersion.isPrerelease,
|
|
107
|
+
prereleaseIdentifier: currentVersion.prereleaseIdentifier,
|
|
108
|
+
hasStableTransition: this.detectStableTransition(recommendation)
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Detects if the recommendation indicates a transition to a stable version.
|
|
113
|
+
*
|
|
114
|
+
* @param recommendation - The version recommendation
|
|
115
|
+
* @returns True if a stable transition is detected, false otherwise
|
|
116
|
+
*/
|
|
117
|
+
detectStableTransition(recommendation) {
|
|
118
|
+
if (!recommendation) return false;
|
|
119
|
+
const reason = recommendation.reason.toLowerCase();
|
|
120
|
+
return TRANSITION_KEYWORDS.some((keyword) => reason.includes(keyword));
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Handles explicit requests for pre-release versions.
|
|
124
|
+
*
|
|
125
|
+
* @param options - The version resolution options
|
|
126
|
+
* @param context - The pre-release context
|
|
127
|
+
* @returns A result containing the new pre-release version or an error
|
|
128
|
+
*/
|
|
129
|
+
handlePreReleaseRequest(options, context) {
|
|
130
|
+
logger.verbose("DefaultVersionStrategyService: Bumping to prerelease version...");
|
|
131
|
+
return this.bumper.bump({
|
|
132
|
+
currentVersion: options.currentVersion,
|
|
133
|
+
releaseType: "prerelease",
|
|
134
|
+
prereleaseIdentifier: options.prereleaseIdentifier ?? context.prereleaseIdentifier ?? "alpha",
|
|
135
|
+
prereleaseBase: options.prereleaseBase
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Handles the transition from a pre-release version to a stable version.
|
|
140
|
+
*
|
|
141
|
+
* @param options - The version resolution options
|
|
142
|
+
* @param recommendation - The version recommendation
|
|
143
|
+
* @returns A result containing the new stable version or an error
|
|
144
|
+
*/
|
|
145
|
+
handlePreReleaseToStableTransition(options, recommendation) {
|
|
146
|
+
if (!recommendation) return FireflyErr(invalidError({
|
|
147
|
+
message: "Cannot transition to stable version without recommendation",
|
|
148
|
+
source: "services/version-strategy"
|
|
149
|
+
}));
|
|
150
|
+
const graduateResult = this.bumper.bump({
|
|
151
|
+
currentVersion: options.currentVersion,
|
|
152
|
+
releaseType: "graduate"
|
|
153
|
+
});
|
|
154
|
+
if (graduateResult.isErr()) return FireflyErr(graduateResult.error);
|
|
155
|
+
const stableVersion = graduateResult.value;
|
|
156
|
+
if (recommendation.level < 2) {
|
|
157
|
+
logger.verbose("DefaultVersionStrategyService: Further bumping after graduation...");
|
|
158
|
+
const releaseType = LEVEL_TO_RELEASE_TYPE[recommendation.level];
|
|
159
|
+
return this.bumper.bump({
|
|
160
|
+
currentVersion: stableVersion,
|
|
161
|
+
releaseType
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
logger.verbose("DefaultVersionStrategyService: Graduated to stable version:", stableVersion.raw);
|
|
165
|
+
return FireflyOk(stableVersion);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Creates a new version based on the provided recommendation and pre-release context.
|
|
169
|
+
*
|
|
170
|
+
* @param options - The version resolution options
|
|
171
|
+
* @param recommendation - The version recommendation
|
|
172
|
+
* @param context - The pre-release context
|
|
173
|
+
* @returns A result containing the new version or an error
|
|
174
|
+
*/
|
|
175
|
+
createRecommendationBasedVersion(options, recommendation, context) {
|
|
176
|
+
if (context.isCurrentPreRelease && !context.hasStableTransition) {
|
|
177
|
+
logger.verbose("DefaultVersionStrategyService: Continuing prerelease versioning...");
|
|
178
|
+
return this.bumper.bump({
|
|
179
|
+
currentVersion: options.currentVersion,
|
|
180
|
+
releaseType: "prerelease",
|
|
181
|
+
prereleaseIdentifier: options.prereleaseIdentifier ?? context.prereleaseIdentifier ?? "alpha",
|
|
182
|
+
prereleaseBase: options.prereleaseBase
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
const releaseType = LEVEL_TO_RELEASE_TYPE[recommendation.level];
|
|
186
|
+
logger.verbose("DefaultVersionStrategyService: Bumping version based on recommendation...");
|
|
187
|
+
return this.bumper.bump({
|
|
188
|
+
currentVersion: options.currentVersion,
|
|
189
|
+
releaseType,
|
|
190
|
+
prereleaseIdentifier: options.prereleaseIdentifier,
|
|
191
|
+
prereleaseBase: options.prereleaseBase
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Determines the available version types based on current version and optional release type.
|
|
196
|
+
*
|
|
197
|
+
* @param currentVersion - The current version
|
|
198
|
+
* @param releaseType - Optional specific release type
|
|
199
|
+
* @returns An array of available release types
|
|
200
|
+
*/
|
|
201
|
+
determineAvailableVersionTypes(currentVersion, releaseType) {
|
|
202
|
+
if (releaseType !== void 0) return this.getVersionTypesForReleaseType(releaseType);
|
|
203
|
+
return currentVersion.isPrerelease ? VERSION_CHOICE_SETS.latestIsPreRelease : VERSION_CHOICE_SETS.default;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Retrieves the version types applicable for a given release type.
|
|
207
|
+
*
|
|
208
|
+
* @param releaseType - The type of release
|
|
209
|
+
* @returns An array of applicable release types
|
|
210
|
+
*/
|
|
211
|
+
getVersionTypesForReleaseType(releaseType) {
|
|
212
|
+
return releaseType === "prerelease" ? VERSION_CHOICE_SETS.preRelease : VERSION_CHOICE_SETS.default;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Retrieves the description for a given release type.
|
|
216
|
+
*
|
|
217
|
+
* @param releaseType - The type of release
|
|
218
|
+
* @returns The description string for the release type
|
|
219
|
+
*/
|
|
220
|
+
getVersionDescription(releaseType) {
|
|
221
|
+
return VERSION_DESCRIPTIONS[releaseType] ?? "";
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Creates a single version choice based on the provided parameters.
|
|
225
|
+
*
|
|
226
|
+
* @param currentVersion - The current version
|
|
227
|
+
* @param releaseType - The type of release to create
|
|
228
|
+
* @param prereleaseIdentifier - Optional pre-release identifier
|
|
229
|
+
* @param prereleaseBase - Optional base for pre-release
|
|
230
|
+
* @returns A result containing the version choice or an error
|
|
231
|
+
*/
|
|
232
|
+
createSingleChoice(currentVersion, releaseType, prereleaseIdentifier, prereleaseBase) {
|
|
233
|
+
const bumpOptions = {
|
|
234
|
+
currentVersion,
|
|
235
|
+
releaseType,
|
|
236
|
+
prereleaseIdentifier,
|
|
237
|
+
prereleaseBase
|
|
238
|
+
};
|
|
239
|
+
const newVersionResult = this.bumper.bump(bumpOptions);
|
|
240
|
+
if (newVersionResult.isErr()) return FireflyErr(newVersionResult.error);
|
|
241
|
+
const newVersion = newVersionResult.value;
|
|
242
|
+
return FireflyOk({
|
|
243
|
+
label: `${releaseType} (${newVersion.raw})`,
|
|
244
|
+
hint: this.getVersionDescription(releaseType),
|
|
245
|
+
value: newVersion.raw
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
/**
|
|
250
|
+
* Creates a version strategy service instance.
|
|
251
|
+
* @param bumper - The version bumper service to use
|
|
252
|
+
*/
|
|
253
|
+
function createVersionStrategyService(bumper) {
|
|
254
|
+
return new DefaultVersionStrategyService(bumper);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
//#endregion
|
|
258
|
+
export { TRANSITION_KEYWORDS as n, createVersionStrategyService as r, DefaultVersionStrategyService as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fireflyy",
|
|
3
|
-
"version": "4.0.0-dev.
|
|
3
|
+
"version": "4.0.0-dev.df58d2b",
|
|
4
4
|
"description": " CLI orchestrator for automatic semantic versioning, changelog generation, and creating releases. Built for my own use cases.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|