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