@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.
Files changed (158) hide show
  1. package/ARCHITECTURE.md +50 -1
  2. package/CHANGELOG.md +23 -23
  3. package/README.md +12 -9
  4. package/changelog/index.cjs.js +23 -2
  5. package/changelog/index.cjs.js.map +1 -1
  6. package/changelog/index.esm.js +23 -2
  7. package/changelog/index.esm.js.map +1 -1
  8. package/changelog/models/entry.d.ts +5 -0
  9. package/changelog/models/entry.d.ts.map +1 -1
  10. package/changelog/models/index.cjs.js +2 -0
  11. package/changelog/models/index.cjs.js.map +1 -1
  12. package/changelog/models/index.esm.js +2 -0
  13. package/changelog/models/index.esm.js.map +1 -1
  14. package/changelog/operations/index.cjs.js.map +1 -1
  15. package/changelog/operations/index.esm.js.map +1 -1
  16. package/changelog/parse/index.cjs.js +23 -2
  17. package/changelog/parse/index.cjs.js.map +1 -1
  18. package/changelog/parse/index.esm.js +23 -2
  19. package/changelog/parse/index.esm.js.map +1 -1
  20. package/changelog/parse/line.d.ts.map +1 -1
  21. package/commits/classify/classifier.d.ts +73 -0
  22. package/commits/classify/classifier.d.ts.map +1 -0
  23. package/commits/classify/index.cjs.js +705 -0
  24. package/commits/classify/index.cjs.js.map +1 -0
  25. package/commits/classify/index.d.ts +8 -0
  26. package/commits/classify/index.d.ts.map +1 -0
  27. package/commits/classify/index.esm.js +678 -0
  28. package/commits/classify/index.esm.js.map +1 -0
  29. package/commits/classify/infrastructure.d.ts +205 -0
  30. package/commits/classify/infrastructure.d.ts.map +1 -0
  31. package/commits/classify/models.d.ts +108 -0
  32. package/commits/classify/models.d.ts.map +1 -0
  33. package/commits/classify/project-scopes.d.ts +59 -0
  34. package/commits/classify/project-scopes.d.ts.map +1 -0
  35. package/commits/index.cjs.js +702 -0
  36. package/commits/index.cjs.js.map +1 -1
  37. package/commits/index.d.ts +1 -0
  38. package/commits/index.d.ts.map +1 -1
  39. package/commits/index.esm.js +677 -1
  40. package/commits/index.esm.js.map +1 -1
  41. package/flow/executor/execute.d.ts +6 -0
  42. package/flow/executor/execute.d.ts.map +1 -1
  43. package/flow/executor/index.cjs.js +1604 -42
  44. package/flow/executor/index.cjs.js.map +1 -1
  45. package/flow/executor/index.esm.js +1610 -48
  46. package/flow/executor/index.esm.js.map +1 -1
  47. package/flow/index.cjs.js +6651 -2893
  48. package/flow/index.cjs.js.map +1 -1
  49. package/flow/index.esm.js +6655 -2899
  50. package/flow/index.esm.js.map +1 -1
  51. package/flow/models/index.cjs.js +125 -0
  52. package/flow/models/index.cjs.js.map +1 -1
  53. package/flow/models/index.esm.js +125 -0
  54. package/flow/models/index.esm.js.map +1 -1
  55. package/flow/models/types.d.ts +148 -3
  56. package/flow/models/types.d.ts.map +1 -1
  57. package/flow/presets/conventional.d.ts +9 -8
  58. package/flow/presets/conventional.d.ts.map +1 -1
  59. package/flow/presets/independent.d.ts.map +1 -1
  60. package/flow/presets/index.cjs.js +3588 -298
  61. package/flow/presets/index.cjs.js.map +1 -1
  62. package/flow/presets/index.esm.js +3588 -298
  63. package/flow/presets/index.esm.js.map +1 -1
  64. package/flow/presets/synced.d.ts.map +1 -1
  65. package/flow/steps/analyze-commits.d.ts +9 -6
  66. package/flow/steps/analyze-commits.d.ts.map +1 -1
  67. package/flow/steps/calculate-bump.d.ts.map +1 -1
  68. package/flow/steps/fetch-registry.d.ts.map +1 -1
  69. package/flow/steps/generate-changelog.d.ts.map +1 -1
  70. package/flow/steps/index.cjs.js +3604 -318
  71. package/flow/steps/index.cjs.js.map +1 -1
  72. package/flow/steps/index.d.ts +1 -0
  73. package/flow/steps/index.d.ts.map +1 -1
  74. package/flow/steps/index.esm.js +3603 -319
  75. package/flow/steps/index.esm.js.map +1 -1
  76. package/flow/steps/resolve-repository.d.ts +36 -0
  77. package/flow/steps/resolve-repository.d.ts.map +1 -0
  78. package/flow/steps/update-packages.d.ts.map +1 -1
  79. package/git/factory.d.ts +14 -0
  80. package/git/factory.d.ts.map +1 -1
  81. package/git/index.cjs.js +65 -0
  82. package/git/index.cjs.js.map +1 -1
  83. package/git/index.esm.js +66 -2
  84. package/git/index.esm.js.map +1 -1
  85. package/git/operations/index.cjs.js +40 -0
  86. package/git/operations/index.cjs.js.map +1 -1
  87. package/git/operations/index.d.ts +1 -1
  88. package/git/operations/index.d.ts.map +1 -1
  89. package/git/operations/index.esm.js +41 -2
  90. package/git/operations/index.esm.js.map +1 -1
  91. package/git/operations/log.d.ts +23 -0
  92. package/git/operations/log.d.ts.map +1 -1
  93. package/index.cjs.js +6962 -4413
  94. package/index.cjs.js.map +1 -1
  95. package/index.esm.js +6964 -4415
  96. package/index.esm.js.map +1 -1
  97. package/package.json +26 -1
  98. package/registry/index.cjs.js +3 -3
  99. package/registry/index.cjs.js.map +1 -1
  100. package/registry/index.esm.js +3 -3
  101. package/registry/index.esm.js.map +1 -1
  102. package/registry/models/index.cjs.js +2 -0
  103. package/registry/models/index.cjs.js.map +1 -1
  104. package/registry/models/index.esm.js +2 -0
  105. package/registry/models/index.esm.js.map +1 -1
  106. package/registry/models/version-info.d.ts +10 -0
  107. package/registry/models/version-info.d.ts.map +1 -1
  108. package/registry/npm/client.d.ts.map +1 -1
  109. package/registry/npm/index.cjs.js +1 -3
  110. package/registry/npm/index.cjs.js.map +1 -1
  111. package/registry/npm/index.esm.js +1 -3
  112. package/registry/npm/index.esm.js.map +1 -1
  113. package/repository/index.cjs.js +998 -0
  114. package/repository/index.cjs.js.map +1 -0
  115. package/repository/index.d.ts +4 -0
  116. package/repository/index.d.ts.map +1 -0
  117. package/repository/index.esm.js +981 -0
  118. package/repository/index.esm.js.map +1 -0
  119. package/repository/models/index.cjs.js +301 -0
  120. package/repository/models/index.cjs.js.map +1 -0
  121. package/repository/models/index.d.ts +7 -0
  122. package/repository/models/index.d.ts.map +1 -0
  123. package/repository/models/index.esm.js +290 -0
  124. package/repository/models/index.esm.js.map +1 -0
  125. package/repository/models/platform.d.ts +58 -0
  126. package/repository/models/platform.d.ts.map +1 -0
  127. package/repository/models/repository-config.d.ts +132 -0
  128. package/repository/models/repository-config.d.ts.map +1 -0
  129. package/repository/models/resolution.d.ts +121 -0
  130. package/repository/models/resolution.d.ts.map +1 -0
  131. package/repository/parse/index.cjs.js +755 -0
  132. package/repository/parse/index.cjs.js.map +1 -0
  133. package/repository/parse/index.d.ts +5 -0
  134. package/repository/parse/index.d.ts.map +1 -0
  135. package/repository/parse/index.esm.js +749 -0
  136. package/repository/parse/index.esm.js.map +1 -0
  137. package/repository/parse/package-json.d.ts +100 -0
  138. package/repository/parse/package-json.d.ts.map +1 -0
  139. package/repository/parse/url.d.ts +81 -0
  140. package/repository/parse/url.d.ts.map +1 -0
  141. package/repository/url/compare.d.ts +84 -0
  142. package/repository/url/compare.d.ts.map +1 -0
  143. package/repository/url/index.cjs.js +178 -0
  144. package/repository/url/index.cjs.js.map +1 -0
  145. package/repository/url/index.d.ts +3 -0
  146. package/repository/url/index.d.ts.map +1 -0
  147. package/repository/url/index.esm.js +176 -0
  148. package/repository/url/index.esm.js.map +1 -0
  149. package/workspace/discovery/index.cjs.js +324 -330
  150. package/workspace/discovery/index.cjs.js.map +1 -1
  151. package/workspace/discovery/index.esm.js +324 -330
  152. package/workspace/discovery/index.esm.js.map +1 -1
  153. package/workspace/discovery/packages.d.ts +0 -6
  154. package/workspace/discovery/packages.d.ts.map +1 -1
  155. package/workspace/index.cjs.js +0 -6
  156. package/workspace/index.cjs.js.map +1 -1
  157. package/workspace/index.esm.js +0 -6
  158. 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