@hyperfrontend/versioning 0.1.0 → 0.2.0
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/ARCHITECTURE.md +50 -1
- package/CHANGELOG.md +23 -23
- package/README.md +12 -9
- package/changelog/index.cjs.js +23 -2
- package/changelog/index.cjs.js.map +1 -1
- package/changelog/index.esm.js +23 -2
- package/changelog/index.esm.js.map +1 -1
- package/changelog/models/entry.d.ts +5 -0
- package/changelog/models/entry.d.ts.map +1 -1
- package/changelog/models/index.cjs.js +2 -0
- package/changelog/models/index.cjs.js.map +1 -1
- package/changelog/models/index.esm.js +2 -0
- package/changelog/models/index.esm.js.map +1 -1
- package/changelog/operations/index.cjs.js.map +1 -1
- package/changelog/operations/index.esm.js.map +1 -1
- package/changelog/parse/index.cjs.js +23 -2
- package/changelog/parse/index.cjs.js.map +1 -1
- package/changelog/parse/index.esm.js +23 -2
- package/changelog/parse/index.esm.js.map +1 -1
- package/changelog/parse/line.d.ts.map +1 -1
- package/commits/classify/classifier.d.ts +73 -0
- package/commits/classify/classifier.d.ts.map +1 -0
- package/commits/classify/index.cjs.js +705 -0
- package/commits/classify/index.cjs.js.map +1 -0
- package/commits/classify/index.d.ts +8 -0
- package/commits/classify/index.d.ts.map +1 -0
- package/commits/classify/index.esm.js +678 -0
- package/commits/classify/index.esm.js.map +1 -0
- package/commits/classify/infrastructure.d.ts +205 -0
- package/commits/classify/infrastructure.d.ts.map +1 -0
- package/commits/classify/models.d.ts +108 -0
- package/commits/classify/models.d.ts.map +1 -0
- package/commits/classify/project-scopes.d.ts +59 -0
- package/commits/classify/project-scopes.d.ts.map +1 -0
- package/commits/index.cjs.js +702 -0
- package/commits/index.cjs.js.map +1 -1
- package/commits/index.d.ts +1 -0
- package/commits/index.d.ts.map +1 -1
- package/commits/index.esm.js +677 -1
- package/commits/index.esm.js.map +1 -1
- package/flow/executor/execute.d.ts +6 -0
- package/flow/executor/execute.d.ts.map +1 -1
- package/flow/executor/index.cjs.js +1604 -42
- package/flow/executor/index.cjs.js.map +1 -1
- package/flow/executor/index.esm.js +1610 -48
- package/flow/executor/index.esm.js.map +1 -1
- package/flow/index.cjs.js +6651 -2893
- package/flow/index.cjs.js.map +1 -1
- package/flow/index.esm.js +6655 -2899
- package/flow/index.esm.js.map +1 -1
- package/flow/models/index.cjs.js +125 -0
- package/flow/models/index.cjs.js.map +1 -1
- package/flow/models/index.esm.js +125 -0
- package/flow/models/index.esm.js.map +1 -1
- package/flow/models/types.d.ts +148 -3
- package/flow/models/types.d.ts.map +1 -1
- package/flow/presets/conventional.d.ts +9 -8
- package/flow/presets/conventional.d.ts.map +1 -1
- package/flow/presets/independent.d.ts.map +1 -1
- package/flow/presets/index.cjs.js +3588 -298
- package/flow/presets/index.cjs.js.map +1 -1
- package/flow/presets/index.esm.js +3588 -298
- package/flow/presets/index.esm.js.map +1 -1
- package/flow/presets/synced.d.ts.map +1 -1
- package/flow/steps/analyze-commits.d.ts +9 -6
- package/flow/steps/analyze-commits.d.ts.map +1 -1
- package/flow/steps/calculate-bump.d.ts.map +1 -1
- package/flow/steps/fetch-registry.d.ts.map +1 -1
- package/flow/steps/generate-changelog.d.ts.map +1 -1
- package/flow/steps/index.cjs.js +3604 -318
- package/flow/steps/index.cjs.js.map +1 -1
- package/flow/steps/index.d.ts +1 -0
- package/flow/steps/index.d.ts.map +1 -1
- package/flow/steps/index.esm.js +3603 -319
- package/flow/steps/index.esm.js.map +1 -1
- package/flow/steps/resolve-repository.d.ts +36 -0
- package/flow/steps/resolve-repository.d.ts.map +1 -0
- package/flow/steps/update-packages.d.ts.map +1 -1
- package/git/factory.d.ts +14 -0
- package/git/factory.d.ts.map +1 -1
- package/git/index.cjs.js +65 -0
- package/git/index.cjs.js.map +1 -1
- package/git/index.esm.js +66 -2
- package/git/index.esm.js.map +1 -1
- package/git/operations/index.cjs.js +40 -0
- package/git/operations/index.cjs.js.map +1 -1
- package/git/operations/index.d.ts +1 -1
- package/git/operations/index.d.ts.map +1 -1
- package/git/operations/index.esm.js +41 -2
- package/git/operations/index.esm.js.map +1 -1
- package/git/operations/log.d.ts +23 -0
- package/git/operations/log.d.ts.map +1 -1
- package/index.cjs.js +6962 -4413
- package/index.cjs.js.map +1 -1
- package/index.esm.js +6964 -4415
- package/index.esm.js.map +1 -1
- package/package.json +26 -1
- package/registry/index.cjs.js +3 -3
- package/registry/index.cjs.js.map +1 -1
- package/registry/index.esm.js +3 -3
- package/registry/index.esm.js.map +1 -1
- package/registry/models/index.cjs.js +2 -0
- package/registry/models/index.cjs.js.map +1 -1
- package/registry/models/index.esm.js +2 -0
- package/registry/models/index.esm.js.map +1 -1
- package/registry/models/version-info.d.ts +10 -0
- package/registry/models/version-info.d.ts.map +1 -1
- package/registry/npm/client.d.ts.map +1 -1
- package/registry/npm/index.cjs.js +1 -3
- package/registry/npm/index.cjs.js.map +1 -1
- package/registry/npm/index.esm.js +1 -3
- package/registry/npm/index.esm.js.map +1 -1
- package/repository/index.cjs.js +998 -0
- package/repository/index.cjs.js.map +1 -0
- package/repository/index.d.ts +4 -0
- package/repository/index.d.ts.map +1 -0
- package/repository/index.esm.js +981 -0
- package/repository/index.esm.js.map +1 -0
- package/repository/models/index.cjs.js +301 -0
- package/repository/models/index.cjs.js.map +1 -0
- package/repository/models/index.d.ts +7 -0
- package/repository/models/index.d.ts.map +1 -0
- package/repository/models/index.esm.js +290 -0
- package/repository/models/index.esm.js.map +1 -0
- package/repository/models/platform.d.ts +58 -0
- package/repository/models/platform.d.ts.map +1 -0
- package/repository/models/repository-config.d.ts +132 -0
- package/repository/models/repository-config.d.ts.map +1 -0
- package/repository/models/resolution.d.ts +121 -0
- package/repository/models/resolution.d.ts.map +1 -0
- package/repository/parse/index.cjs.js +755 -0
- package/repository/parse/index.cjs.js.map +1 -0
- package/repository/parse/index.d.ts +5 -0
- package/repository/parse/index.d.ts.map +1 -0
- package/repository/parse/index.esm.js +749 -0
- package/repository/parse/index.esm.js.map +1 -0
- package/repository/parse/package-json.d.ts +100 -0
- package/repository/parse/package-json.d.ts.map +1 -0
- package/repository/parse/url.d.ts +81 -0
- package/repository/parse/url.d.ts.map +1 -0
- package/repository/url/compare.d.ts +84 -0
- package/repository/url/compare.d.ts.map +1 -0
- package/repository/url/index.cjs.js +178 -0
- package/repository/url/index.cjs.js.map +1 -0
- package/repository/url/index.d.ts +3 -0
- package/repository/url/index.d.ts.map +1 -0
- package/repository/url/index.esm.js +176 -0
- package/repository/url/index.esm.js.map +1 -0
- package/workspace/discovery/index.cjs.js +324 -330
- package/workspace/discovery/index.cjs.js.map +1 -1
- package/workspace/discovery/index.esm.js +324 -330
- package/workspace/discovery/index.esm.js.map +1 -1
- package/workspace/discovery/packages.d.ts +0 -6
- package/workspace/discovery/packages.d.ts.map +1 -1
- package/workspace/index.cjs.js +0 -6
- package/workspace/index.cjs.js.map +1 -1
- package/workspace/index.esm.js +0 -6
- package/workspace/index.esm.js.map +1 -1
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates an empty classification summary.
|
|
5
|
+
*
|
|
6
|
+
* @returns A new ClassificationSummary with all counts at zero
|
|
7
|
+
*/
|
|
8
|
+
function createEmptyClassificationSummary() {
|
|
9
|
+
return {
|
|
10
|
+
total: 0,
|
|
11
|
+
included: 0,
|
|
12
|
+
excluded: 0,
|
|
13
|
+
bySource: {
|
|
14
|
+
'direct-scope': 0,
|
|
15
|
+
'direct-file': 0,
|
|
16
|
+
'unscoped-file': 0,
|
|
17
|
+
'indirect-dependency': 0,
|
|
18
|
+
'indirect-infra': 0,
|
|
19
|
+
'unscoped-global': 0,
|
|
20
|
+
excluded: 0,
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Creates a classified commit.
|
|
26
|
+
*
|
|
27
|
+
* @param commit - The parsed conventional commit
|
|
28
|
+
* @param raw - The raw git commit
|
|
29
|
+
* @param source - How the commit relates to the project
|
|
30
|
+
* @param options - Additional classification options
|
|
31
|
+
* @param options.touchedFiles - Files in the project modified by this commit
|
|
32
|
+
* @param options.dependencyPath - Chain of dependencies leading to indirect inclusion
|
|
33
|
+
* @returns A new ClassifiedCommit object
|
|
34
|
+
*/
|
|
35
|
+
function createClassifiedCommit(commit, raw, source, options) {
|
|
36
|
+
const include = isIncludedSource(source);
|
|
37
|
+
const preserveScope = shouldPreserveScope(source);
|
|
38
|
+
return {
|
|
39
|
+
commit,
|
|
40
|
+
raw,
|
|
41
|
+
source,
|
|
42
|
+
include,
|
|
43
|
+
preserveScope,
|
|
44
|
+
touchedFiles: options?.touchedFiles,
|
|
45
|
+
dependencyPath: options?.dependencyPath,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Determines if a source type should be included in changelog.
|
|
50
|
+
*
|
|
51
|
+
* @param source - The commit source type
|
|
52
|
+
* @returns True if commits with this source should be included
|
|
53
|
+
*/
|
|
54
|
+
function isIncludedSource(source) {
|
|
55
|
+
switch (source) {
|
|
56
|
+
case 'direct-scope':
|
|
57
|
+
case 'direct-file':
|
|
58
|
+
case 'unscoped-file':
|
|
59
|
+
case 'indirect-dependency':
|
|
60
|
+
case 'indirect-infra':
|
|
61
|
+
return true;
|
|
62
|
+
case 'unscoped-global':
|
|
63
|
+
case 'excluded':
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Determines if scope should be preserved for a source type.
|
|
69
|
+
*
|
|
70
|
+
* Direct commits omit scope (redundant in project changelog).
|
|
71
|
+
* Indirect commits preserve scope for context.
|
|
72
|
+
*
|
|
73
|
+
* @param source - The commit source type
|
|
74
|
+
* @returns True if scope should be preserved in changelog
|
|
75
|
+
*/
|
|
76
|
+
function shouldPreserveScope(source) {
|
|
77
|
+
switch (source) {
|
|
78
|
+
case 'direct-scope':
|
|
79
|
+
case 'unscoped-file':
|
|
80
|
+
return false; // Scope would be redundant
|
|
81
|
+
case 'direct-file':
|
|
82
|
+
case 'indirect-dependency':
|
|
83
|
+
case 'indirect-infra':
|
|
84
|
+
return true; // Scope provides context
|
|
85
|
+
case 'unscoped-global':
|
|
86
|
+
case 'excluded':
|
|
87
|
+
return false; // Won't be shown
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Safe copies of Set built-in via factory function.
|
|
93
|
+
*
|
|
94
|
+
* Since constructors cannot be safely captured via Object.assign, this module
|
|
95
|
+
* provides a factory function that uses Reflect.construct internally.
|
|
96
|
+
*
|
|
97
|
+
* These references are captured at module initialization time to protect against
|
|
98
|
+
* prototype pollution attacks. Import only what you need for tree-shaking.
|
|
99
|
+
*
|
|
100
|
+
* @module @hyperfrontend/immutable-api-utils/built-in-copy/set
|
|
101
|
+
*/
|
|
102
|
+
// Capture references at module initialization time
|
|
103
|
+
const _Set = globalThis.Set;
|
|
104
|
+
const _Reflect = globalThis.Reflect;
|
|
105
|
+
/**
|
|
106
|
+
* (Safe copy) Creates a new Set using the captured Set constructor.
|
|
107
|
+
* Use this instead of `new Set()`.
|
|
108
|
+
*
|
|
109
|
+
* @param iterable - Optional iterable of values.
|
|
110
|
+
* @returns A new Set instance.
|
|
111
|
+
*/
|
|
112
|
+
const createSet = (iterable) => _Reflect.construct(_Set, iterable ? [iterable] : []);
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Derives all scope variations that should match a project.
|
|
116
|
+
*
|
|
117
|
+
* Given a project named 'lib-versioning' with package '@hyperfrontend/versioning',
|
|
118
|
+
* this generates variations like:
|
|
119
|
+
* - 'lib-versioning' (full project name)
|
|
120
|
+
* - 'versioning' (without lib- prefix)
|
|
121
|
+
*
|
|
122
|
+
* @param options - Project identification options
|
|
123
|
+
* @returns Array of scope strings that match this project
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* deriveProjectScopes({ projectName: 'lib-versioning', packageName: '@hyperfrontend/versioning' })
|
|
127
|
+
* // Returns: ['lib-versioning', 'versioning']
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* deriveProjectScopes({ projectName: 'app-demo', packageName: 'demo-app' })
|
|
131
|
+
* // Returns: ['app-demo', 'demo']
|
|
132
|
+
*/
|
|
133
|
+
function deriveProjectScopes(options) {
|
|
134
|
+
const { projectName, packageName, additionalScopes = [] } = options;
|
|
135
|
+
const scopes = createSet();
|
|
136
|
+
// Always include the full project name
|
|
137
|
+
scopes.add(projectName);
|
|
138
|
+
// Add variations based on common prefixes
|
|
139
|
+
const prefixVariations = extractPrefixVariations(projectName);
|
|
140
|
+
for (const variation of prefixVariations) {
|
|
141
|
+
scopes.add(variation);
|
|
142
|
+
}
|
|
143
|
+
// Add package name variations if provided
|
|
144
|
+
if (packageName) {
|
|
145
|
+
const packageVariations = extractPackageNameVariations(packageName);
|
|
146
|
+
for (const variation of packageVariations) {
|
|
147
|
+
scopes.add(variation);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Add any additional scopes
|
|
151
|
+
for (const scope of additionalScopes) {
|
|
152
|
+
if (scope) {
|
|
153
|
+
scopes.add(scope);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return [...scopes];
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Recognized project name prefixes that can be stripped for scope matching.
|
|
160
|
+
*/
|
|
161
|
+
const PROJECT_PREFIXES = ['lib-', 'app-', 'e2e-', 'tool-', 'plugin-', 'feature-', 'package-'];
|
|
162
|
+
/**
|
|
163
|
+
* Generates scope variations by stripping recognized project prefixes.
|
|
164
|
+
*
|
|
165
|
+
* @param projectName - The project name to extract variations from
|
|
166
|
+
* @returns Array of scope name variations
|
|
167
|
+
*/
|
|
168
|
+
function extractPrefixVariations(projectName) {
|
|
169
|
+
const variations = [];
|
|
170
|
+
for (const prefix of PROJECT_PREFIXES) {
|
|
171
|
+
if (projectName.startsWith(prefix)) {
|
|
172
|
+
const withoutPrefix = projectName.slice(prefix.length);
|
|
173
|
+
if (withoutPrefix) {
|
|
174
|
+
variations.push(withoutPrefix);
|
|
175
|
+
}
|
|
176
|
+
break; // Only remove one prefix
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return variations;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Extracts scope variations from an npm package name.
|
|
183
|
+
*
|
|
184
|
+
* @param packageName - The npm package name (e.g., '@scope/name')
|
|
185
|
+
* @returns Array of name variations
|
|
186
|
+
*/
|
|
187
|
+
function extractPackageNameVariations(packageName) {
|
|
188
|
+
const variations = [];
|
|
189
|
+
// Handle scoped packages: @scope/name -> name
|
|
190
|
+
if (packageName.startsWith('@')) {
|
|
191
|
+
const slashIndex = packageName.indexOf('/');
|
|
192
|
+
if (slashIndex !== -1) {
|
|
193
|
+
const unscoped = packageName.slice(slashIndex + 1);
|
|
194
|
+
if (unscoped) {
|
|
195
|
+
variations.push(unscoped);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
// Non-scoped package: just use the name
|
|
201
|
+
variations.push(packageName);
|
|
202
|
+
}
|
|
203
|
+
return variations;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Checks if a commit scope matches any of the project scopes.
|
|
207
|
+
*
|
|
208
|
+
* @param commitScope - The scope from a conventional commit
|
|
209
|
+
* @param projectScopes - Array of scopes that match the project
|
|
210
|
+
* @returns True if the commit scope matches the project
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* scopeMatchesProject('versioning', ['lib-versioning', 'versioning']) // true
|
|
214
|
+
* scopeMatchesProject('logging', ['lib-versioning', 'versioning']) // false
|
|
215
|
+
*/
|
|
216
|
+
function scopeMatchesProject(commitScope, projectScopes) {
|
|
217
|
+
if (!commitScope) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
// Case-insensitive comparison
|
|
221
|
+
const normalizedScope = commitScope.toLowerCase();
|
|
222
|
+
return projectScopes.some((scope) => scope.toLowerCase() === normalizedScope);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Checks if a commit scope should be explicitly excluded.
|
|
226
|
+
*
|
|
227
|
+
* @param commitScope - The scope from a conventional commit
|
|
228
|
+
* @param excludeScopes - Array of scopes to exclude
|
|
229
|
+
* @returns True if the scope should be excluded
|
|
230
|
+
*/
|
|
231
|
+
function scopeIsExcluded(commitScope, excludeScopes) {
|
|
232
|
+
if (!commitScope) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
const normalizedScope = commitScope.toLowerCase();
|
|
236
|
+
return excludeScopes.some((scope) => scope.toLowerCase() === normalizedScope);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Default scopes to exclude from changelogs.
|
|
240
|
+
*
|
|
241
|
+
* These represent repository-level or infrastructure changes
|
|
242
|
+
* that typically don't belong in individual project changelogs.
|
|
243
|
+
*/
|
|
244
|
+
const DEFAULT_EXCLUDE_SCOPES = ['release', 'deps', 'workspace', 'root', 'repo', 'ci', 'build'];
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Classifies a single commit against a project.
|
|
248
|
+
*
|
|
249
|
+
* Implements the hybrid classification strategy:
|
|
250
|
+
* 1. Check scope match (fast path)
|
|
251
|
+
* 2. Check file touch (validation/catch-all)
|
|
252
|
+
* 3. Check dependency touch (indirect)
|
|
253
|
+
* 4. Fallback to excluded
|
|
254
|
+
*
|
|
255
|
+
* @param input - The commit to classify
|
|
256
|
+
* @param context - Classification context with project info
|
|
257
|
+
* @returns Classified commit with source attribution
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* const classified = classifyCommit(
|
|
261
|
+
* { commit: parsedCommit, raw: gitCommit },
|
|
262
|
+
* { projectScopes: ['versioning'], fileCommitHashes: new Set(['abc123']) }
|
|
263
|
+
* )
|
|
264
|
+
*/
|
|
265
|
+
function classifyCommit(input, context) {
|
|
266
|
+
const { commit, raw } = input;
|
|
267
|
+
const { projectScopes, fileCommitHashes, dependencyCommitMap, infrastructureCommitHashes, excludeScopes = DEFAULT_EXCLUDE_SCOPES, includeScopes = [], } = context;
|
|
268
|
+
const scope = commit.scope;
|
|
269
|
+
const hasScope = !!scope;
|
|
270
|
+
const allProjectScopes = [...projectScopes, ...includeScopes];
|
|
271
|
+
// First check: Is this scope explicitly excluded?
|
|
272
|
+
if (hasScope && scopeIsExcluded(scope, excludeScopes)) {
|
|
273
|
+
return createClassifiedCommit(commit, raw, 'excluded');
|
|
274
|
+
}
|
|
275
|
+
// Priority 1: Scope-based direct match (fast path)
|
|
276
|
+
if (hasScope && scopeMatchesProject(scope, allProjectScopes)) {
|
|
277
|
+
return createClassifiedCommit(commit, raw, 'direct-scope');
|
|
278
|
+
}
|
|
279
|
+
// Priority 2: File-based direct match (validation/catch-all)
|
|
280
|
+
if (fileCommitHashes.has(raw.hash)) {
|
|
281
|
+
// Commit touched project files
|
|
282
|
+
if (hasScope) {
|
|
283
|
+
// Has a scope but it's different - likely a typo or cross-cutting change
|
|
284
|
+
return createClassifiedCommit(commit, raw, 'direct-file');
|
|
285
|
+
}
|
|
286
|
+
// No scope but touched project files
|
|
287
|
+
return createClassifiedCommit(commit, raw, 'unscoped-file');
|
|
288
|
+
}
|
|
289
|
+
// Priority 3: Indirect dependency match
|
|
290
|
+
if (hasScope && dependencyCommitMap) {
|
|
291
|
+
const dependencyPath = findDependencyPath(scope, raw.hash, dependencyCommitMap);
|
|
292
|
+
if (dependencyPath) {
|
|
293
|
+
return createClassifiedCommit(commit, raw, 'indirect-dependency', { dependencyPath });
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
// File-based infrastructure match
|
|
297
|
+
if (infrastructureCommitHashes?.has(raw.hash)) {
|
|
298
|
+
return createClassifiedCommit(commit, raw, 'indirect-infra');
|
|
299
|
+
}
|
|
300
|
+
// Fallback: No match found
|
|
301
|
+
if (!hasScope) {
|
|
302
|
+
// Unscoped commit that didn't touch any project files
|
|
303
|
+
return createClassifiedCommit(commit, raw, 'unscoped-global');
|
|
304
|
+
}
|
|
305
|
+
// Scoped commit that doesn't match anything
|
|
306
|
+
return createClassifiedCommit(commit, raw, 'excluded');
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Classifies multiple commits against a project.
|
|
310
|
+
*
|
|
311
|
+
* @param commits - Array of commits to classify
|
|
312
|
+
* @param context - Classification context with project info
|
|
313
|
+
* @returns Classification result with all commits and summary
|
|
314
|
+
*/
|
|
315
|
+
function classifyCommits(commits, context) {
|
|
316
|
+
const classified = [];
|
|
317
|
+
const included = [];
|
|
318
|
+
const excluded = [];
|
|
319
|
+
const summary = createEmptyClassificationSummary();
|
|
320
|
+
const bySource = { ...summary.bySource };
|
|
321
|
+
for (const input of commits) {
|
|
322
|
+
const result = classifyCommit(input, context);
|
|
323
|
+
classified.push(result);
|
|
324
|
+
// Update summary
|
|
325
|
+
bySource[result.source]++;
|
|
326
|
+
if (result.include) {
|
|
327
|
+
included.push(result);
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
excluded.push(result);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return {
|
|
334
|
+
commits: classified,
|
|
335
|
+
included,
|
|
336
|
+
excluded,
|
|
337
|
+
summary: {
|
|
338
|
+
total: classified.length,
|
|
339
|
+
included: included.length,
|
|
340
|
+
excluded: excluded.length,
|
|
341
|
+
bySource,
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Finds a dependency path for a given scope and commit hash.
|
|
347
|
+
*
|
|
348
|
+
* Verifies both:
|
|
349
|
+
* 1. The scope matches a dependency name (or variation)
|
|
350
|
+
* 2. The commit hash is in that dependency's commit set
|
|
351
|
+
*
|
|
352
|
+
* This prevents false positives from mislabeled commits.
|
|
353
|
+
*
|
|
354
|
+
* @param scope - The commit scope
|
|
355
|
+
* @param hash - The commit hash to verify
|
|
356
|
+
* @param dependencyCommitMap - Map of dependencies to their commit hashes
|
|
357
|
+
* @returns Dependency path if found and hash verified, undefined otherwise
|
|
358
|
+
*/
|
|
359
|
+
function findDependencyPath(scope, hash, dependencyCommitMap) {
|
|
360
|
+
const normalizedScope = scope.toLowerCase();
|
|
361
|
+
for (const [depName, depHashes] of dependencyCommitMap) {
|
|
362
|
+
// Check if scope matches dependency name or variations
|
|
363
|
+
const depVariations = getDependencyVariations(depName);
|
|
364
|
+
if (depVariations.some((v) => v.toLowerCase() === normalizedScope)) {
|
|
365
|
+
// CRITICAL: Verify the commit actually touched this dependency's files
|
|
366
|
+
// This prevents false positives from mislabeled commits
|
|
367
|
+
if (depHashes.has(hash)) {
|
|
368
|
+
return [depName];
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return undefined;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Generates name variations for a dependency to enable flexible scope matching.
|
|
376
|
+
*
|
|
377
|
+
* @param depName - The dependency project or package name
|
|
378
|
+
* @returns Array of name variations including stripped prefixes
|
|
379
|
+
*/
|
|
380
|
+
function getDependencyVariations(depName) {
|
|
381
|
+
const variations = [depName];
|
|
382
|
+
// Handle lib- prefix
|
|
383
|
+
if (depName.startsWith('lib-')) {
|
|
384
|
+
variations.push(depName.slice(4));
|
|
385
|
+
}
|
|
386
|
+
// Handle @scope/name
|
|
387
|
+
if (depName.startsWith('@')) {
|
|
388
|
+
const slashIndex = depName.indexOf('/');
|
|
389
|
+
if (slashIndex !== -1) {
|
|
390
|
+
variations.push(depName.slice(slashIndex + 1));
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return variations;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Creates a classification context from common inputs.
|
|
397
|
+
*
|
|
398
|
+
* @param projectScopes - Scopes that match the project
|
|
399
|
+
* @param fileCommitHashes - Set of commit hashes that touched project files
|
|
400
|
+
* @param options - Additional context options
|
|
401
|
+
* @param options.dependencyCommitMap - Map of dependency names to commit hashes touching them
|
|
402
|
+
* @param options.infrastructureCommitHashes - Set of commit hashes touching infrastructure paths
|
|
403
|
+
* @param options.excludeScopes - Scopes to explicitly exclude from classification
|
|
404
|
+
* @param options.includeScopes - Additional scopes to include as direct matches
|
|
405
|
+
* @returns A ClassificationContext object
|
|
406
|
+
*/
|
|
407
|
+
function createClassificationContext(projectScopes, fileCommitHashes, options) {
|
|
408
|
+
return {
|
|
409
|
+
projectScopes,
|
|
410
|
+
fileCommitHashes,
|
|
411
|
+
dependencyCommitMap: options?.dependencyCommitMap,
|
|
412
|
+
infrastructureCommitHashes: options?.infrastructureCommitHashes,
|
|
413
|
+
excludeScopes: options?.excludeScopes ?? DEFAULT_EXCLUDE_SCOPES,
|
|
414
|
+
includeScopes: options?.includeScopes,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Filters an array of classified commits to only included ones.
|
|
419
|
+
*
|
|
420
|
+
* @param commits - Array of classified commits
|
|
421
|
+
* @returns Only commits marked for inclusion
|
|
422
|
+
*/
|
|
423
|
+
function filterIncluded(commits) {
|
|
424
|
+
return commits.filter((c) => c.include);
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Extracts conventional commits from classified commits for changelog generation.
|
|
428
|
+
*
|
|
429
|
+
* @param commits - Array of classified commits
|
|
430
|
+
* @returns Array of conventional commits
|
|
431
|
+
*/
|
|
432
|
+
function extractConventionalCommits(commits) {
|
|
433
|
+
return commits.map((c) => c.commit);
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Creates a modified conventional commit with scope handling based on classification.
|
|
437
|
+
*
|
|
438
|
+
* For direct commits, the scope is removed (redundant in project changelog).
|
|
439
|
+
* For indirect commits, the scope is preserved (provides context).
|
|
440
|
+
*
|
|
441
|
+
* @param classified - Commit with classification metadata determining scope display
|
|
442
|
+
* @returns A conventional commit with appropriate scope handling
|
|
443
|
+
*/
|
|
444
|
+
function toChangelogCommit(classified) {
|
|
445
|
+
const { commit, preserveScope } = classified;
|
|
446
|
+
if (!preserveScope && commit.scope) {
|
|
447
|
+
// Remove the scope for direct commits
|
|
448
|
+
return {
|
|
449
|
+
...commit,
|
|
450
|
+
scope: undefined,
|
|
451
|
+
// Rebuild raw to reflect removed scope
|
|
452
|
+
raw: rebuildRawWithoutScope(commit),
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
return commit;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Reconstructs a conventional commit message string without the scope portion.
|
|
459
|
+
*
|
|
460
|
+
* @param commit - The conventional commit to rebuild
|
|
461
|
+
* @returns Reconstructed raw message with scope removed
|
|
462
|
+
*/
|
|
463
|
+
function rebuildRawWithoutScope(commit) {
|
|
464
|
+
const breaking = commit.breaking && !commit.breakingDescription ? '!' : '';
|
|
465
|
+
const header = `${commit.type}${breaking}: ${commit.subject}`;
|
|
466
|
+
if (!commit.body && commit.footers.length === 0) {
|
|
467
|
+
return header;
|
|
468
|
+
}
|
|
469
|
+
let raw = header;
|
|
470
|
+
if (commit.body) {
|
|
471
|
+
raw += `\n\n${commit.body}`;
|
|
472
|
+
}
|
|
473
|
+
for (const footer of commit.footers) {
|
|
474
|
+
raw += `\n${footer.key}${footer.separator}${footer.value}`;
|
|
475
|
+
}
|
|
476
|
+
return raw;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Creates a matcher that checks if commit scope matches any of the given scopes.
|
|
481
|
+
*
|
|
482
|
+
* @param scopes - Scopes to match against (case-insensitive)
|
|
483
|
+
* @returns Matcher that returns true if scope matches
|
|
484
|
+
*
|
|
485
|
+
* @example
|
|
486
|
+
* const matcher = scopeMatcher(['ci', 'build', 'tooling'])
|
|
487
|
+
* matcher({ scope: 'CI', ... }) // true
|
|
488
|
+
* matcher({ scope: 'feat', ... }) // false
|
|
489
|
+
*/
|
|
490
|
+
function scopeMatcher(scopes) {
|
|
491
|
+
const normalizedScopes = createSet(scopes.map((s) => s.toLowerCase()));
|
|
492
|
+
return (ctx) => {
|
|
493
|
+
if (!ctx.scope)
|
|
494
|
+
return false;
|
|
495
|
+
return normalizedScopes.has(ctx.scope.toLowerCase());
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Creates a matcher that checks if commit scope starts with any of the given prefixes.
|
|
500
|
+
*
|
|
501
|
+
* @param prefixes - Scope prefixes to match (case-insensitive)
|
|
502
|
+
* @returns Matcher that returns true if scope starts with any prefix
|
|
503
|
+
*
|
|
504
|
+
* @example
|
|
505
|
+
* const matcher = scopePrefixMatcher(['tool-', 'infra-'])
|
|
506
|
+
* matcher({ scope: 'tool-package', ... }) // true
|
|
507
|
+
* matcher({ scope: 'lib-utils', ... }) // false
|
|
508
|
+
*/
|
|
509
|
+
function scopePrefixMatcher(prefixes) {
|
|
510
|
+
const normalizedPrefixes = prefixes.map((p) => p.toLowerCase());
|
|
511
|
+
return (ctx) => {
|
|
512
|
+
if (!ctx.scope)
|
|
513
|
+
return false;
|
|
514
|
+
const normalizedScope = ctx.scope.toLowerCase();
|
|
515
|
+
return normalizedPrefixes.some((prefix) => normalizedScope.startsWith(prefix));
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Creates a matcher that checks if commit message contains any of the given patterns.
|
|
520
|
+
*
|
|
521
|
+
* @param patterns - Patterns to search for in commit message (case-insensitive)
|
|
522
|
+
* @returns Matcher that returns true if message contains any pattern
|
|
523
|
+
*
|
|
524
|
+
* @example
|
|
525
|
+
* const matcher = messageMatcher(['[infra]', '[ci skip]'])
|
|
526
|
+
*/
|
|
527
|
+
function messageMatcher(patterns) {
|
|
528
|
+
const normalizedPatterns = patterns.map((p) => p.toLowerCase());
|
|
529
|
+
return (ctx) => {
|
|
530
|
+
const normalizedMessage = ctx.message.toLowerCase();
|
|
531
|
+
return normalizedPatterns.some((pattern) => normalizedMessage.includes(pattern));
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Creates a matcher from a regex pattern tested against the scope.
|
|
536
|
+
*
|
|
537
|
+
* @param pattern - Regex pattern to test against scope
|
|
538
|
+
* @returns Matcher that returns true if scope matches regex
|
|
539
|
+
*
|
|
540
|
+
* @example
|
|
541
|
+
* const matcher = scopeRegexMatcher(/^(ci|build|tool)-.+/)
|
|
542
|
+
*/
|
|
543
|
+
function scopeRegexMatcher(pattern) {
|
|
544
|
+
return (ctx) => {
|
|
545
|
+
if (!ctx.scope)
|
|
546
|
+
return false;
|
|
547
|
+
return pattern.test(ctx.scope);
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Combines matchers with OR logic - returns true if ANY matcher matches.
|
|
552
|
+
*
|
|
553
|
+
* @param matchers - Matchers to combine
|
|
554
|
+
* @returns Combined matcher
|
|
555
|
+
*
|
|
556
|
+
* @example
|
|
557
|
+
* const combined = anyOf(
|
|
558
|
+
* scopeMatcher(['ci', 'build']),
|
|
559
|
+
* messageMatcher(['[infra]']),
|
|
560
|
+
* custom((ctx) => ctx.scope?.startsWith('tool-'))
|
|
561
|
+
* )
|
|
562
|
+
*/
|
|
563
|
+
function anyOf(...matchers) {
|
|
564
|
+
return (ctx) => matchers.some((matcher) => matcher(ctx));
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Combines matchers with AND logic - returns true if ALL matchers match.
|
|
568
|
+
*
|
|
569
|
+
* @param matchers - Matchers to combine
|
|
570
|
+
* @returns Combined matcher
|
|
571
|
+
*
|
|
572
|
+
* @example
|
|
573
|
+
* const combined = allOf(
|
|
574
|
+
* scopeMatcher(['deps']),
|
|
575
|
+
* messageMatcher(['security'])
|
|
576
|
+
* )
|
|
577
|
+
*/
|
|
578
|
+
function allOf(...matchers) {
|
|
579
|
+
return (ctx) => matchers.every((matcher) => matcher(ctx));
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Negates a matcher - returns true if matcher returns false.
|
|
583
|
+
*
|
|
584
|
+
* @param matcher - Matcher to negate
|
|
585
|
+
* @returns Negated matcher
|
|
586
|
+
*
|
|
587
|
+
* @example
|
|
588
|
+
* const notRelease = not(scopeMatcher(['release']))
|
|
589
|
+
*/
|
|
590
|
+
function not(matcher) {
|
|
591
|
+
return (ctx) => !matcher(ctx);
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Matches common CI/CD scopes.
|
|
595
|
+
*
|
|
596
|
+
* Matches: ci, cd, build, pipeline, workflow, actions
|
|
597
|
+
*/
|
|
598
|
+
const CI_SCOPE_MATCHER = scopeMatcher(['ci', 'cd', 'build', 'pipeline', 'workflow', 'actions']);
|
|
599
|
+
/**
|
|
600
|
+
* Matches common tooling/workspace scopes.
|
|
601
|
+
*
|
|
602
|
+
* Matches: tooling, workspace, monorepo, nx, root
|
|
603
|
+
*/
|
|
604
|
+
const TOOLING_SCOPE_MATCHER = scopeMatcher(['tooling', 'workspace', 'monorepo', 'nx', 'root']);
|
|
605
|
+
/**
|
|
606
|
+
* Matches tool-prefixed scopes (e.g., tool-package, tool-scripts).
|
|
607
|
+
*/
|
|
608
|
+
const TOOL_PREFIX_MATCHER = scopePrefixMatcher(['tool-']);
|
|
609
|
+
/**
|
|
610
|
+
* Combined matcher for common infrastructure patterns.
|
|
611
|
+
*
|
|
612
|
+
* Combines CI, tooling, and tool-prefix matchers.
|
|
613
|
+
*/
|
|
614
|
+
const DEFAULT_INFRA_SCOPE_MATCHER = anyOf(CI_SCOPE_MATCHER, TOOLING_SCOPE_MATCHER, TOOL_PREFIX_MATCHER);
|
|
615
|
+
/**
|
|
616
|
+
* Builds a combined matcher from infrastructure configuration.
|
|
617
|
+
*
|
|
618
|
+
* Combines scope-based matching with any custom matcher using OR logic.
|
|
619
|
+
* Path-based matching is handled separately via git queries.
|
|
620
|
+
*
|
|
621
|
+
* @param config - Infrastructure configuration
|
|
622
|
+
* @returns Combined matcher, or null if no matchers configured
|
|
623
|
+
*
|
|
624
|
+
* @example
|
|
625
|
+
* const matcher = buildInfrastructureMatcher({
|
|
626
|
+
* scopes: ['ci', 'build'],
|
|
627
|
+
* matcher: (ctx) => ctx.scope?.startsWith('tool-')
|
|
628
|
+
* })
|
|
629
|
+
*/
|
|
630
|
+
function buildInfrastructureMatcher(config) {
|
|
631
|
+
const matchers = [];
|
|
632
|
+
// Add scope matcher if scopes configured
|
|
633
|
+
if (config.scopes && config.scopes.length > 0) {
|
|
634
|
+
matchers.push(scopeMatcher(config.scopes));
|
|
635
|
+
}
|
|
636
|
+
// Add custom matcher if provided
|
|
637
|
+
if (config.matcher) {
|
|
638
|
+
matchers.push(config.matcher);
|
|
639
|
+
}
|
|
640
|
+
// Return combined or null
|
|
641
|
+
if (matchers.length === 0) {
|
|
642
|
+
return null;
|
|
643
|
+
}
|
|
644
|
+
if (matchers.length === 1) {
|
|
645
|
+
return matchers[0];
|
|
646
|
+
}
|
|
647
|
+
return anyOf(...matchers);
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Creates match context from a git commit.
|
|
651
|
+
*
|
|
652
|
+
* Extracts scope from conventional commit message if present.
|
|
653
|
+
*
|
|
654
|
+
* @param commit - Git commit to create context for
|
|
655
|
+
* @param scope - Pre-parsed scope (optional, saves re-parsing)
|
|
656
|
+
* @returns Match context for use with matchers
|
|
657
|
+
*/
|
|
658
|
+
function createMatchContext(commit, scope) {
|
|
659
|
+
return {
|
|
660
|
+
commit,
|
|
661
|
+
scope,
|
|
662
|
+
subject: commit.subject,
|
|
663
|
+
message: commit.message,
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Evaluates a commit against an infrastructure matcher.
|
|
668
|
+
*
|
|
669
|
+
* @param commit - Git commit to evaluate
|
|
670
|
+
* @param matcher - Matcher function to apply
|
|
671
|
+
* @param scope - Pre-parsed scope (optional)
|
|
672
|
+
* @returns True if commit matches infrastructure criteria
|
|
673
|
+
*/
|
|
674
|
+
function evaluateInfrastructure(commit, matcher, scope) {
|
|
675
|
+
const context = createMatchContext(commit, scope);
|
|
676
|
+
return matcher(context);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
exports.CI_SCOPE_MATCHER = CI_SCOPE_MATCHER;
|
|
680
|
+
exports.DEFAULT_EXCLUDE_SCOPES = DEFAULT_EXCLUDE_SCOPES;
|
|
681
|
+
exports.DEFAULT_INFRA_SCOPE_MATCHER = DEFAULT_INFRA_SCOPE_MATCHER;
|
|
682
|
+
exports.TOOLING_SCOPE_MATCHER = TOOLING_SCOPE_MATCHER;
|
|
683
|
+
exports.TOOL_PREFIX_MATCHER = TOOL_PREFIX_MATCHER;
|
|
684
|
+
exports.allOf = allOf;
|
|
685
|
+
exports.anyOf = anyOf;
|
|
686
|
+
exports.buildInfrastructureMatcher = buildInfrastructureMatcher;
|
|
687
|
+
exports.classifyCommit = classifyCommit;
|
|
688
|
+
exports.classifyCommits = classifyCommits;
|
|
689
|
+
exports.createClassificationContext = createClassificationContext;
|
|
690
|
+
exports.createClassifiedCommit = createClassifiedCommit;
|
|
691
|
+
exports.createEmptyClassificationSummary = createEmptyClassificationSummary;
|
|
692
|
+
exports.createMatchContext = createMatchContext;
|
|
693
|
+
exports.deriveProjectScopes = deriveProjectScopes;
|
|
694
|
+
exports.evaluateInfrastructure = evaluateInfrastructure;
|
|
695
|
+
exports.extractConventionalCommits = extractConventionalCommits;
|
|
696
|
+
exports.filterIncluded = filterIncluded;
|
|
697
|
+
exports.messageMatcher = messageMatcher;
|
|
698
|
+
exports.not = not;
|
|
699
|
+
exports.scopeIsExcluded = scopeIsExcluded;
|
|
700
|
+
exports.scopeMatcher = scopeMatcher;
|
|
701
|
+
exports.scopeMatchesProject = scopeMatchesProject;
|
|
702
|
+
exports.scopePrefixMatcher = scopePrefixMatcher;
|
|
703
|
+
exports.scopeRegexMatcher = scopeRegexMatcher;
|
|
704
|
+
exports.toChangelogCommit = toChangelogCommit;
|
|
705
|
+
//# sourceMappingURL=index.cjs.js.map
|