fireflyy 4.0.0-dev.644fea9 → 4.0.0-dev.988c93c
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/assets/firefly.schema.json +1 -3
- package/dist/commit-analysis.service-Ba6hLgsr.js +503 -0
- package/dist/config.d.ts +68 -0
- package/dist/{index.js → config.js} +0 -11
- package/dist/{dry-run-BfYCtldz.js → dry-run-Cdg-c0EP.js} +2 -2
- package/dist/{filesystem.service-9VHML130.js → filesystem.service-B8qg87n-.js} +4 -4
- package/dist/{git.service-CACrfCW8.js → git.service-BBqDH7qx.js} +3 -3
- package/dist/logging-BuIkRrn1.js +20 -0
- package/dist/main.js +4 -26
- package/dist/{package-json.service-DACeZzRg.js → package-json.service-CHmtIoQQ.js} +3 -3
- package/dist/{program-CHc5t2Xm.js → program-Dqo9sQjU.js} +221 -71
- package/dist/{result.constructors-C9M1MP3_.js → result.constructors-BMtOWD2-.js} +1 -1
- package/dist/{result.utilities-DC5shlhT.js → result.utilities-BTVU-GsT.js} +2 -12
- package/dist/{schema.utilities-BGd9t1wm.js → schema.utilities-BxiRR-GI.js} +1 -1
- package/dist/version-bumper.service-DMYR0npB.js +318 -0
- package/dist/version-strategy.service-CmfeZLYC.js +258 -0
- package/package.json +2 -2
- package/dist/index.d.ts +0 -79
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"description": "Firefly CLI configuration.",
|
|
3
4
|
"type": "object",
|
|
4
5
|
"properties": {
|
|
5
6
|
"cwd": {
|
|
@@ -144,17 +145,14 @@
|
|
|
144
145
|
},
|
|
145
146
|
"releaseLatest": {
|
|
146
147
|
"description": "Mark as latest release.",
|
|
147
|
-
"default": true,
|
|
148
148
|
"type": "boolean"
|
|
149
149
|
},
|
|
150
150
|
"releasePreRelease": {
|
|
151
151
|
"description": "Mark as pre-release.",
|
|
152
|
-
"default": false,
|
|
153
152
|
"type": "boolean"
|
|
154
153
|
},
|
|
155
154
|
"releaseDraft": {
|
|
156
155
|
"description": "Release as draft version.",
|
|
157
|
-
"default": false,
|
|
158
156
|
"type": "boolean"
|
|
159
157
|
}
|
|
160
158
|
}
|
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
import { h as invalidError, i as FireflyOkAsync, r as FireflyOk, t as FireflyErr } from "./result.constructors-BMtOWD2-.js";
|
|
2
|
+
import { t as logger } from "./logging-BuIkRrn1.js";
|
|
3
|
+
import { TRANSITION_KEYWORDS } from "./version-strategy.service-CmfeZLYC.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 };
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
|
|
3
|
+
//#region src/cli/config/config.schema.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Complete Firefly configuration schema.
|
|
7
|
+
* Combines global options with command-specific configuration sections.
|
|
8
|
+
*/
|
|
9
|
+
declare const FireflyConfigSchema: z.ZodObject<{
|
|
10
|
+
release: z.ZodOptional<z.ZodObject<{
|
|
11
|
+
name: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
12
|
+
scope: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
13
|
+
base: z.ZodOptional<z.ZodDefault<z.ZodString>>;
|
|
14
|
+
branch: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
15
|
+
changelogPath: z.ZodOptional<z.ZodDefault<z.ZodString>>;
|
|
16
|
+
bumpStrategy: z.ZodOptional<z.ZodDefault<z.ZodUnion<[z.ZodEnum<{
|
|
17
|
+
auto: "auto";
|
|
18
|
+
manual: "manual";
|
|
19
|
+
}>, z.ZodLiteral<"">]>>>;
|
|
20
|
+
releaseType: z.ZodOptional<z.ZodOptional<z.ZodEnum<{
|
|
21
|
+
major: "major";
|
|
22
|
+
minor: "minor";
|
|
23
|
+
patch: "patch";
|
|
24
|
+
prerelease: "prerelease";
|
|
25
|
+
premajor: "premajor";
|
|
26
|
+
preminor: "preminor";
|
|
27
|
+
prepatch: "prepatch";
|
|
28
|
+
graduate: "graduate";
|
|
29
|
+
}>>>;
|
|
30
|
+
preReleaseId: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
31
|
+
preReleaseBase: z.ZodOptional<z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodLiteral<"0">, z.ZodLiteral<"1">]>>>;
|
|
32
|
+
releaseNotes: z.ZodOptional<z.ZodDefault<z.ZodString>>;
|
|
33
|
+
commitMessage: z.ZodOptional<z.ZodDefault<z.ZodString>>;
|
|
34
|
+
tagName: z.ZodOptional<z.ZodDefault<z.ZodString>>;
|
|
35
|
+
skipBump: z.ZodOptional<z.ZodDefault<z.ZodCoercedBoolean<unknown>>>;
|
|
36
|
+
skipChangelog: z.ZodOptional<z.ZodDefault<z.ZodCoercedBoolean<unknown>>>;
|
|
37
|
+
skipPush: z.ZodOptional<z.ZodDefault<z.ZodCoercedBoolean<unknown>>>;
|
|
38
|
+
skipGitHubRelease: z.ZodOptional<z.ZodDefault<z.ZodCoercedBoolean<unknown>>>;
|
|
39
|
+
skipGit: z.ZodOptional<z.ZodDefault<z.ZodCoercedBoolean<unknown>>>;
|
|
40
|
+
skipPreflightCheck: z.ZodOptional<z.ZodDefault<z.ZodCoercedBoolean<unknown>>>;
|
|
41
|
+
releaseTitle: z.ZodOptional<z.ZodDefault<z.ZodString>>;
|
|
42
|
+
releaseLatest: z.ZodOptional<z.ZodOptional<z.ZodCoercedBoolean<unknown>>>;
|
|
43
|
+
releasePreRelease: z.ZodOptional<z.ZodOptional<z.ZodCoercedBoolean<unknown>>>;
|
|
44
|
+
releaseDraft: z.ZodOptional<z.ZodOptional<z.ZodCoercedBoolean<unknown>>>;
|
|
45
|
+
}, z.core.$strip>>;
|
|
46
|
+
cwd: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
47
|
+
dryRun: z.ZodOptional<z.ZodOptional<z.ZodBoolean>>;
|
|
48
|
+
verbose: z.ZodOptional<z.ZodOptional<z.ZodBoolean>>;
|
|
49
|
+
enableRollback: z.ZodOptional<z.ZodOptional<z.ZodBoolean>>;
|
|
50
|
+
}, z.core.$strip>;
|
|
51
|
+
/**
|
|
52
|
+
* TypeScript type for Firefly configuration.
|
|
53
|
+
* Use this type when you need to reference the configuration shape without runtime validation.
|
|
54
|
+
*/
|
|
55
|
+
type FireflyConfig = z.infer<typeof FireflyConfigSchema>;
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/config/index.d.ts
|
|
58
|
+
/**
|
|
59
|
+
* Helper function to define a type-safe Firefly configuration.
|
|
60
|
+
*
|
|
61
|
+
* Provides IntelliSense autocompletion and type checking for config files.
|
|
62
|
+
*
|
|
63
|
+
* @param options - The configuration options
|
|
64
|
+
* @returns The same options (identity function for type inference)
|
|
65
|
+
*/
|
|
66
|
+
declare function defineConfig<T extends FireflyConfig>(options: T): T;
|
|
67
|
+
//#endregion
|
|
68
|
+
export { defineConfig };
|
|
@@ -6,17 +6,6 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @param options - The configuration options
|
|
8
8
|
* @returns The same options (identity function for type inference)
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* ```ts
|
|
12
|
-
* export default defineConfig({
|
|
13
|
-
* verbose: true,
|
|
14
|
-
* release: {
|
|
15
|
-
* bumpStrategy: "conventional",
|
|
16
|
-
* releaseType: "github",
|
|
17
|
-
* },
|
|
18
|
-
* });
|
|
19
|
-
* ```
|
|
20
9
|
*/
|
|
21
10
|
function defineConfig(options) {
|
|
22
11
|
return options;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { i as FireflyOkAsync } from "./result.constructors-BMtOWD2-.js";
|
|
2
|
+
import { t as logger } from "./logging-BuIkRrn1.js";
|
|
3
3
|
|
|
4
4
|
//#region src/infrastructure/dry-run/index.ts
|
|
5
5
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { t as withDryRun } from "./dry-run-
|
|
1
|
+
import { c as notFoundErrAsync } from "./result.constructors-BMtOWD2-.js";
|
|
2
|
+
import { n as wrapPromise } from "./result.utilities-BTVU-GsT.js";
|
|
3
|
+
import { t as logger } from "./logging-BuIkRrn1.js";
|
|
4
|
+
import { t as withDryRun } from "./dry-run-Cdg-c0EP.js";
|
|
5
5
|
|
|
6
6
|
//#region src/services/implementations/filesystem.service.ts
|
|
7
7
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { t as withDryRun } from "./dry-run-
|
|
1
|
+
import { i as FireflyOkAsync, m as failedError, o as failedErrAsync } from "./result.constructors-BMtOWD2-.js";
|
|
2
|
+
import { t as logger } from "./logging-BuIkRrn1.js";
|
|
3
|
+
import { t as withDryRun } from "./dry-run-Cdg-c0EP.js";
|
|
4
4
|
import { ResultAsync } from "neverthrow";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createConsola } from "consola";
|
|
2
|
+
import { colors } from "consola/utils";
|
|
3
|
+
|
|
4
|
+
//#region src/infrastructure/logging/index.ts
|
|
5
|
+
const opts = {
|
|
6
|
+
date: false,
|
|
7
|
+
compact: true,
|
|
8
|
+
columns: 0
|
|
9
|
+
};
|
|
10
|
+
const _logger = createConsola({ formatOptions: opts });
|
|
11
|
+
const logger = createConsola({
|
|
12
|
+
formatOptions: opts,
|
|
13
|
+
reporters: [{ log(logObj) {
|
|
14
|
+
if (logObj.type === "verbose") console.log(colors.gray(logObj.args.join(" ")));
|
|
15
|
+
else _logger.log(logObj);
|
|
16
|
+
} }]
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
export { logger as t };
|