@hyperfrontend/versioning 0.1.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 +593 -0
- package/CHANGELOG.md +35 -0
- package/FUNDING.md +141 -0
- package/LICENSE.md +21 -0
- package/README.md +195 -0
- package/SECURITY.md +82 -0
- package/changelog/compare/diff.d.ts +128 -0
- package/changelog/compare/diff.d.ts.map +1 -0
- package/changelog/compare/index.cjs.js +628 -0
- package/changelog/compare/index.cjs.js.map +1 -0
- package/changelog/compare/index.d.ts +4 -0
- package/changelog/compare/index.d.ts.map +1 -0
- package/changelog/compare/index.esm.js +612 -0
- package/changelog/compare/index.esm.js.map +1 -0
- package/changelog/compare/is-equal.d.ts +114 -0
- package/changelog/compare/is-equal.d.ts.map +1 -0
- package/changelog/index.cjs.js +6448 -0
- package/changelog/index.cjs.js.map +1 -0
- package/changelog/index.d.ts +6 -0
- package/changelog/index.d.ts.map +1 -0
- package/changelog/index.esm.js +6358 -0
- package/changelog/index.esm.js.map +1 -0
- package/changelog/models/changelog.d.ts +86 -0
- package/changelog/models/changelog.d.ts.map +1 -0
- package/changelog/models/commit-ref.d.ts +51 -0
- package/changelog/models/commit-ref.d.ts.map +1 -0
- package/changelog/models/entry.d.ts +84 -0
- package/changelog/models/entry.d.ts.map +1 -0
- package/changelog/models/index.cjs.js +2043 -0
- package/changelog/models/index.cjs.js.map +1 -0
- package/changelog/models/index.d.ts +11 -0
- package/changelog/models/index.d.ts.map +1 -0
- package/changelog/models/index.esm.js +2026 -0
- package/changelog/models/index.esm.js.map +1 -0
- package/changelog/models/schema.d.ts +68 -0
- package/changelog/models/schema.d.ts.map +1 -0
- package/changelog/models/section.d.ts +25 -0
- package/changelog/models/section.d.ts.map +1 -0
- package/changelog/operations/add-entry.d.ts +56 -0
- package/changelog/operations/add-entry.d.ts.map +1 -0
- package/changelog/operations/add-item.d.ts +18 -0
- package/changelog/operations/add-item.d.ts.map +1 -0
- package/changelog/operations/filter-by-predicate.d.ts +81 -0
- package/changelog/operations/filter-by-predicate.d.ts.map +1 -0
- package/changelog/operations/filter-by-range.d.ts +63 -0
- package/changelog/operations/filter-by-range.d.ts.map +1 -0
- package/changelog/operations/filter-entries.d.ts +9 -0
- package/changelog/operations/filter-entries.d.ts.map +1 -0
- package/changelog/operations/index.cjs.js +2455 -0
- package/changelog/operations/index.cjs.js.map +1 -0
- package/changelog/operations/index.d.ts +15 -0
- package/changelog/operations/index.d.ts.map +1 -0
- package/changelog/operations/index.esm.js +2411 -0
- package/changelog/operations/index.esm.js.map +1 -0
- package/changelog/operations/merge.d.ts +88 -0
- package/changelog/operations/merge.d.ts.map +1 -0
- package/changelog/operations/remove-entry.d.ts +45 -0
- package/changelog/operations/remove-entry.d.ts.map +1 -0
- package/changelog/operations/remove-section.d.ts +50 -0
- package/changelog/operations/remove-section.d.ts.map +1 -0
- package/changelog/operations/transform.d.ts +143 -0
- package/changelog/operations/transform.d.ts.map +1 -0
- package/changelog/parse/index.cjs.js +1282 -0
- package/changelog/parse/index.cjs.js.map +1 -0
- package/changelog/parse/index.d.ts +5 -0
- package/changelog/parse/index.d.ts.map +1 -0
- package/changelog/parse/index.esm.js +1275 -0
- package/changelog/parse/index.esm.js.map +1 -0
- package/changelog/parse/line.d.ts +48 -0
- package/changelog/parse/line.d.ts.map +1 -0
- package/changelog/parse/parser.d.ts +16 -0
- package/changelog/parse/parser.d.ts.map +1 -0
- package/changelog/parse/tokenizer.d.ts +49 -0
- package/changelog/parse/tokenizer.d.ts.map +1 -0
- package/changelog/serialize/index.cjs.js +574 -0
- package/changelog/serialize/index.cjs.js.map +1 -0
- package/changelog/serialize/index.d.ts +6 -0
- package/changelog/serialize/index.d.ts.map +1 -0
- package/changelog/serialize/index.esm.js +564 -0
- package/changelog/serialize/index.esm.js.map +1 -0
- package/changelog/serialize/templates.d.ts +81 -0
- package/changelog/serialize/templates.d.ts.map +1 -0
- package/changelog/serialize/to-json.d.ts +57 -0
- package/changelog/serialize/to-json.d.ts.map +1 -0
- package/changelog/serialize/to-string.d.ts +30 -0
- package/changelog/serialize/to-string.d.ts.map +1 -0
- package/commits/index.cjs.js +648 -0
- package/commits/index.cjs.js.map +1 -0
- package/commits/index.d.ts +3 -0
- package/commits/index.d.ts.map +1 -0
- package/commits/index.esm.js +629 -0
- package/commits/index.esm.js.map +1 -0
- package/commits/models/breaking.d.ts +39 -0
- package/commits/models/breaking.d.ts.map +1 -0
- package/commits/models/commit-type.d.ts +32 -0
- package/commits/models/commit-type.d.ts.map +1 -0
- package/commits/models/conventional.d.ts +49 -0
- package/commits/models/conventional.d.ts.map +1 -0
- package/commits/models/index.cjs.js +207 -0
- package/commits/models/index.cjs.js.map +1 -0
- package/commits/models/index.d.ts +7 -0
- package/commits/models/index.d.ts.map +1 -0
- package/commits/models/index.esm.js +193 -0
- package/commits/models/index.esm.js.map +1 -0
- package/commits/parse/body.d.ts +18 -0
- package/commits/parse/body.d.ts.map +1 -0
- package/commits/parse/footer.d.ts +16 -0
- package/commits/parse/footer.d.ts.map +1 -0
- package/commits/parse/header.d.ts +15 -0
- package/commits/parse/header.d.ts.map +1 -0
- package/commits/parse/index.cjs.js +505 -0
- package/commits/parse/index.cjs.js.map +1 -0
- package/commits/parse/index.d.ts +5 -0
- package/commits/parse/index.d.ts.map +1 -0
- package/commits/parse/index.esm.js +499 -0
- package/commits/parse/index.esm.js.map +1 -0
- package/commits/parse/message.d.ts +17 -0
- package/commits/parse/message.d.ts.map +1 -0
- package/commits/utils/replace-char.d.ts +19 -0
- package/commits/utils/replace-char.d.ts.map +1 -0
- package/flow/executor/execute.d.ts +72 -0
- package/flow/executor/execute.d.ts.map +1 -0
- package/flow/executor/index.cjs.js +4402 -0
- package/flow/executor/index.cjs.js.map +1 -0
- package/flow/executor/index.d.ts +3 -0
- package/flow/executor/index.d.ts.map +1 -0
- package/flow/executor/index.esm.js +4398 -0
- package/flow/executor/index.esm.js.map +1 -0
- package/flow/factory.d.ts +58 -0
- package/flow/factory.d.ts.map +1 -0
- package/flow/index.cjs.js +8506 -0
- package/flow/index.cjs.js.map +1 -0
- package/flow/index.d.ts +7 -0
- package/flow/index.d.ts.map +1 -0
- package/flow/index.esm.js +8451 -0
- package/flow/index.esm.js.map +1 -0
- package/flow/models/flow.d.ts +130 -0
- package/flow/models/flow.d.ts.map +1 -0
- package/flow/models/index.cjs.js +285 -0
- package/flow/models/index.cjs.js.map +1 -0
- package/flow/models/index.d.ts +7 -0
- package/flow/models/index.d.ts.map +1 -0
- package/flow/models/index.esm.js +268 -0
- package/flow/models/index.esm.js.map +1 -0
- package/flow/models/step.d.ts +108 -0
- package/flow/models/step.d.ts.map +1 -0
- package/flow/models/types.d.ts +150 -0
- package/flow/models/types.d.ts.map +1 -0
- package/flow/presets/conventional.d.ts +59 -0
- package/flow/presets/conventional.d.ts.map +1 -0
- package/flow/presets/independent.d.ts +61 -0
- package/flow/presets/independent.d.ts.map +1 -0
- package/flow/presets/index.cjs.js +3903 -0
- package/flow/presets/index.cjs.js.map +1 -0
- package/flow/presets/index.d.ts +4 -0
- package/flow/presets/index.d.ts.map +1 -0
- package/flow/presets/index.esm.js +3889 -0
- package/flow/presets/index.esm.js.map +1 -0
- package/flow/presets/synced.d.ts +65 -0
- package/flow/presets/synced.d.ts.map +1 -0
- package/flow/steps/analyze-commits.d.ts +19 -0
- package/flow/steps/analyze-commits.d.ts.map +1 -0
- package/flow/steps/calculate-bump.d.ts +27 -0
- package/flow/steps/calculate-bump.d.ts.map +1 -0
- package/flow/steps/create-commit.d.ts +16 -0
- package/flow/steps/create-commit.d.ts.map +1 -0
- package/flow/steps/create-tag.d.ts +22 -0
- package/flow/steps/create-tag.d.ts.map +1 -0
- package/flow/steps/fetch-registry.d.ts +19 -0
- package/flow/steps/fetch-registry.d.ts.map +1 -0
- package/flow/steps/generate-changelog.d.ts +25 -0
- package/flow/steps/generate-changelog.d.ts.map +1 -0
- package/flow/steps/index.cjs.js +3523 -0
- package/flow/steps/index.cjs.js.map +1 -0
- package/flow/steps/index.d.ts +8 -0
- package/flow/steps/index.d.ts.map +1 -0
- package/flow/steps/index.esm.js +3504 -0
- package/flow/steps/index.esm.js.map +1 -0
- package/flow/steps/update-packages.d.ts +25 -0
- package/flow/steps/update-packages.d.ts.map +1 -0
- package/flow/utils/interpolate.d.ts +11 -0
- package/flow/utils/interpolate.d.ts.map +1 -0
- package/git/factory.d.ts +233 -0
- package/git/factory.d.ts.map +1 -0
- package/git/index.cjs.js +2863 -0
- package/git/index.cjs.js.map +1 -0
- package/git/index.d.ts +5 -0
- package/git/index.d.ts.map +1 -0
- package/git/index.esm.js +2785 -0
- package/git/index.esm.js.map +1 -0
- package/git/models/commit.d.ts +129 -0
- package/git/models/commit.d.ts.map +1 -0
- package/git/models/index.cjs.js +755 -0
- package/git/models/index.cjs.js.map +1 -0
- package/git/models/index.d.ts +7 -0
- package/git/models/index.d.ts.map +1 -0
- package/git/models/index.esm.js +729 -0
- package/git/models/index.esm.js.map +1 -0
- package/git/models/ref.d.ts +120 -0
- package/git/models/ref.d.ts.map +1 -0
- package/git/models/tag.d.ts +141 -0
- package/git/models/tag.d.ts.map +1 -0
- package/git/operations/commit.d.ts +97 -0
- package/git/operations/commit.d.ts.map +1 -0
- package/git/operations/head-info.d.ts +29 -0
- package/git/operations/head-info.d.ts.map +1 -0
- package/git/operations/index.cjs.js +1954 -0
- package/git/operations/index.cjs.js.map +1 -0
- package/git/operations/index.d.ts +14 -0
- package/git/operations/index.d.ts.map +1 -0
- package/git/operations/index.esm.js +1903 -0
- package/git/operations/index.esm.js.map +1 -0
- package/git/operations/log.d.ts +104 -0
- package/git/operations/log.d.ts.map +1 -0
- package/git/operations/manage-tags.d.ts +60 -0
- package/git/operations/manage-tags.d.ts.map +1 -0
- package/git/operations/query-tags.d.ts +88 -0
- package/git/operations/query-tags.d.ts.map +1 -0
- package/git/operations/stage.d.ts +66 -0
- package/git/operations/stage.d.ts.map +1 -0
- package/git/operations/status.d.ts +173 -0
- package/git/operations/status.d.ts.map +1 -0
- package/index.cjs.js +16761 -0
- package/index.cjs.js.map +1 -0
- package/index.d.ts +102 -0
- package/index.d.ts.map +1 -0
- package/index.esm.js +16427 -0
- package/index.esm.js.map +1 -0
- package/package.json +200 -0
- package/registry/factory.d.ts +18 -0
- package/registry/factory.d.ts.map +1 -0
- package/registry/index.cjs.js +543 -0
- package/registry/index.cjs.js.map +1 -0
- package/registry/index.d.ts +5 -0
- package/registry/index.d.ts.map +1 -0
- package/registry/index.esm.js +535 -0
- package/registry/index.esm.js.map +1 -0
- package/registry/models/index.cjs.js +69 -0
- package/registry/models/index.cjs.js.map +1 -0
- package/registry/models/index.d.ts +6 -0
- package/registry/models/index.d.ts.map +1 -0
- package/registry/models/index.esm.js +66 -0
- package/registry/models/index.esm.js.map +1 -0
- package/registry/models/package-info.d.ts +55 -0
- package/registry/models/package-info.d.ts.map +1 -0
- package/registry/models/registry.d.ts +62 -0
- package/registry/models/registry.d.ts.map +1 -0
- package/registry/models/version-info.d.ts +67 -0
- package/registry/models/version-info.d.ts.map +1 -0
- package/registry/npm/cache.d.ts +50 -0
- package/registry/npm/cache.d.ts.map +1 -0
- package/registry/npm/client.d.ts +30 -0
- package/registry/npm/client.d.ts.map +1 -0
- package/registry/npm/index.cjs.js +456 -0
- package/registry/npm/index.cjs.js.map +1 -0
- package/registry/npm/index.d.ts +4 -0
- package/registry/npm/index.d.ts.map +1 -0
- package/registry/npm/index.esm.js +451 -0
- package/registry/npm/index.esm.js.map +1 -0
- package/semver/compare/compare.d.ts +100 -0
- package/semver/compare/compare.d.ts.map +1 -0
- package/semver/compare/index.cjs.js +386 -0
- package/semver/compare/index.cjs.js.map +1 -0
- package/semver/compare/index.d.ts +3 -0
- package/semver/compare/index.d.ts.map +1 -0
- package/semver/compare/index.esm.js +370 -0
- package/semver/compare/index.esm.js.map +1 -0
- package/semver/compare/sort.d.ts +36 -0
- package/semver/compare/sort.d.ts.map +1 -0
- package/semver/format/index.cjs.js +58 -0
- package/semver/format/index.cjs.js.map +1 -0
- package/semver/format/index.d.ts +2 -0
- package/semver/format/index.d.ts.map +1 -0
- package/semver/format/index.esm.js +53 -0
- package/semver/format/index.esm.js.map +1 -0
- package/semver/format/to-string.d.ts +31 -0
- package/semver/format/to-string.d.ts.map +1 -0
- package/semver/increment/bump.d.ts +37 -0
- package/semver/increment/bump.d.ts.map +1 -0
- package/semver/increment/index.cjs.js +223 -0
- package/semver/increment/index.cjs.js.map +1 -0
- package/semver/increment/index.d.ts +2 -0
- package/semver/increment/index.d.ts.map +1 -0
- package/semver/increment/index.esm.js +219 -0
- package/semver/increment/index.esm.js.map +1 -0
- package/semver/index.cjs.js +1499 -0
- package/semver/index.cjs.js.map +1 -0
- package/semver/index.d.ts +6 -0
- package/semver/index.d.ts.map +1 -0
- package/semver/index.esm.js +1458 -0
- package/semver/index.esm.js.map +1 -0
- package/semver/models/index.cjs.js +153 -0
- package/semver/models/index.cjs.js.map +1 -0
- package/semver/models/index.d.ts +5 -0
- package/semver/models/index.d.ts.map +1 -0
- package/semver/models/index.esm.js +139 -0
- package/semver/models/index.esm.js.map +1 -0
- package/semver/models/range.d.ts +83 -0
- package/semver/models/range.d.ts.map +1 -0
- package/semver/models/version.d.ts +78 -0
- package/semver/models/version.d.ts.map +1 -0
- package/semver/parse/index.cjs.js +799 -0
- package/semver/parse/index.cjs.js.map +1 -0
- package/semver/parse/index.d.ts +5 -0
- package/semver/parse/index.d.ts.map +1 -0
- package/semver/parse/index.esm.js +793 -0
- package/semver/parse/index.esm.js.map +1 -0
- package/semver/parse/range.d.ts +38 -0
- package/semver/parse/range.d.ts.map +1 -0
- package/semver/parse/version.d.ts +49 -0
- package/semver/parse/version.d.ts.map +1 -0
- package/workspace/discovery/changelog-path.d.ts +21 -0
- package/workspace/discovery/changelog-path.d.ts.map +1 -0
- package/workspace/discovery/dependencies.d.ts +145 -0
- package/workspace/discovery/dependencies.d.ts.map +1 -0
- package/workspace/discovery/discover-changelogs.d.ts +76 -0
- package/workspace/discovery/discover-changelogs.d.ts.map +1 -0
- package/workspace/discovery/index.cjs.js +2300 -0
- package/workspace/discovery/index.cjs.js.map +1 -0
- package/workspace/discovery/index.d.ts +13 -0
- package/workspace/discovery/index.d.ts.map +1 -0
- package/workspace/discovery/index.esm.js +2283 -0
- package/workspace/discovery/index.esm.js.map +1 -0
- package/workspace/discovery/packages.d.ts +83 -0
- package/workspace/discovery/packages.d.ts.map +1 -0
- package/workspace/index.cjs.js +4445 -0
- package/workspace/index.cjs.js.map +1 -0
- package/workspace/index.d.ts +52 -0
- package/workspace/index.d.ts.map +1 -0
- package/workspace/index.esm.js +4394 -0
- package/workspace/index.esm.js.map +1 -0
- package/workspace/models/index.cjs.js +284 -0
- package/workspace/models/index.cjs.js.map +1 -0
- package/workspace/models/index.d.ts +10 -0
- package/workspace/models/index.d.ts.map +1 -0
- package/workspace/models/index.esm.js +261 -0
- package/workspace/models/index.esm.js.map +1 -0
- package/workspace/models/project.d.ts +118 -0
- package/workspace/models/project.d.ts.map +1 -0
- package/workspace/models/workspace.d.ts +139 -0
- package/workspace/models/workspace.d.ts.map +1 -0
- package/workspace/operations/batch-update.d.ts +99 -0
- package/workspace/operations/batch-update.d.ts.map +1 -0
- package/workspace/operations/cascade-bump.d.ts +125 -0
- package/workspace/operations/cascade-bump.d.ts.map +1 -0
- package/workspace/operations/index.cjs.js +2675 -0
- package/workspace/operations/index.cjs.js.map +1 -0
- package/workspace/operations/index.d.ts +12 -0
- package/workspace/operations/index.d.ts.map +1 -0
- package/workspace/operations/index.esm.js +2663 -0
- package/workspace/operations/index.esm.js.map +1 -0
- package/workspace/operations/validate.d.ts +85 -0
- package/workspace/operations/validate.d.ts.map +1 -0
|
@@ -0,0 +1,2411 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safe copies of Date built-in via factory function and static methods.
|
|
3
|
+
*
|
|
4
|
+
* Since constructors cannot be safely captured via Object.assign, this module
|
|
5
|
+
* provides a factory function that uses Reflect.construct internally.
|
|
6
|
+
*
|
|
7
|
+
* These references are captured at module initialization time to protect against
|
|
8
|
+
* prototype pollution attacks. Import only what you need for tree-shaking.
|
|
9
|
+
*
|
|
10
|
+
* @module @hyperfrontend/immutable-api-utils/built-in-copy/date
|
|
11
|
+
*/
|
|
12
|
+
// Capture references at module initialization time
|
|
13
|
+
const _Date = globalThis.Date;
|
|
14
|
+
const _Reflect$3 = globalThis.Reflect;
|
|
15
|
+
function createDate(...args) {
|
|
16
|
+
return _Reflect$3.construct(_Date, args);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Safe copies of Error built-ins via factory functions.
|
|
21
|
+
*
|
|
22
|
+
* Since constructors cannot be safely captured via Object.assign, this module
|
|
23
|
+
* provides factory functions that use Reflect.construct internally.
|
|
24
|
+
*
|
|
25
|
+
* These references are captured at module initialization time to protect against
|
|
26
|
+
* prototype pollution attacks. Import only what you need for tree-shaking.
|
|
27
|
+
*
|
|
28
|
+
* @module @hyperfrontend/immutable-api-utils/built-in-copy/error
|
|
29
|
+
*/
|
|
30
|
+
// Capture references at module initialization time
|
|
31
|
+
const _Error = globalThis.Error;
|
|
32
|
+
const _Reflect$2 = globalThis.Reflect;
|
|
33
|
+
/**
|
|
34
|
+
* (Safe copy) Creates a new Error using the captured Error constructor.
|
|
35
|
+
* Use this instead of `new Error()`.
|
|
36
|
+
*
|
|
37
|
+
* @param message - Optional error message.
|
|
38
|
+
* @param options - Optional error options.
|
|
39
|
+
* @returns A new Error instance.
|
|
40
|
+
*/
|
|
41
|
+
const createError = (message, options) => _Reflect$2.construct(_Error, [message, options]);
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Creates a new changelog item.
|
|
45
|
+
*
|
|
46
|
+
* @param description - The description text of the change
|
|
47
|
+
* @param options - Optional configuration for scope, commits, references, and breaking flag
|
|
48
|
+
* @returns A new ChangelogItem object
|
|
49
|
+
*/
|
|
50
|
+
/**
|
|
51
|
+
* Creates a new changelog entry.
|
|
52
|
+
*
|
|
53
|
+
* @param version - The version string (e.g., '1.0.0')
|
|
54
|
+
* @param options - Optional configuration for date, sections, and other properties
|
|
55
|
+
* @returns A new ChangelogEntry object
|
|
56
|
+
*/
|
|
57
|
+
function createChangelogEntry(version, options) {
|
|
58
|
+
return {
|
|
59
|
+
version,
|
|
60
|
+
date: options?.date ?? null,
|
|
61
|
+
unreleased: options?.unreleased,
|
|
62
|
+
compareUrl: options?.compareUrl,
|
|
63
|
+
sections: options?.sections ?? [],
|
|
64
|
+
rawContent: options?.rawContent,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Creates an unreleased changelog entry.
|
|
69
|
+
*
|
|
70
|
+
* @param sections - Optional array of changelog sections
|
|
71
|
+
* @returns A new ChangelogEntry object marked as unreleased
|
|
72
|
+
*/
|
|
73
|
+
function createUnreleasedEntry(sections = []) {
|
|
74
|
+
return {
|
|
75
|
+
version: 'Unreleased',
|
|
76
|
+
date: null,
|
|
77
|
+
unreleased: true,
|
|
78
|
+
sections,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Changelog Entry Addition
|
|
84
|
+
*
|
|
85
|
+
* Functions for adding new entries to a changelog.
|
|
86
|
+
*/
|
|
87
|
+
/**
|
|
88
|
+
* Adds a new entry to a changelog.
|
|
89
|
+
*
|
|
90
|
+
* @param changelog - The changelog to add to
|
|
91
|
+
* @param entry - The entry to add
|
|
92
|
+
* @param options - Optional add options
|
|
93
|
+
* @returns A new changelog with the entry added
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* const newChangelog = addEntry(changelog, {
|
|
98
|
+
* version: '1.2.0',
|
|
99
|
+
* date: '2024-01-15',
|
|
100
|
+
* unreleased: false,
|
|
101
|
+
* sections: [...]
|
|
102
|
+
* })
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
function addEntry(changelog, entry, options) {
|
|
106
|
+
const position = options?.position ?? 'start';
|
|
107
|
+
const replaceExisting = options?.replaceExisting ?? false;
|
|
108
|
+
const updateMetadata = options?.updateMetadata ?? false;
|
|
109
|
+
// Check for existing entry
|
|
110
|
+
const existingIndex = changelog.entries.findIndex((e) => e.version === entry.version);
|
|
111
|
+
if (existingIndex !== -1 && !replaceExisting) {
|
|
112
|
+
throw createError(`Entry with version "${entry.version}" already exists. Use replaceExisting: true to replace.`);
|
|
113
|
+
}
|
|
114
|
+
let newEntries;
|
|
115
|
+
if (existingIndex !== -1 && replaceExisting) {
|
|
116
|
+
// Replace existing entry
|
|
117
|
+
newEntries = [...changelog.entries];
|
|
118
|
+
newEntries[existingIndex] = entry;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
// Add new entry
|
|
122
|
+
const insertIndex = position === 'start' ? 0 : position === 'end' ? changelog.entries.length : position;
|
|
123
|
+
newEntries = [...changelog.entries];
|
|
124
|
+
newEntries.splice(insertIndex, 0, entry);
|
|
125
|
+
}
|
|
126
|
+
// Build new metadata if requested
|
|
127
|
+
const metadata = updateMetadata ? { ...changelog.metadata, warnings: [] } : changelog.metadata;
|
|
128
|
+
return {
|
|
129
|
+
...changelog,
|
|
130
|
+
entries: newEntries,
|
|
131
|
+
metadata,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Adds or updates an unreleased entry.
|
|
136
|
+
*
|
|
137
|
+
* @param changelog - The changelog to add the unreleased entry to
|
|
138
|
+
* @param sections - Sections to include in the unreleased entry
|
|
139
|
+
* @returns A new changelog with unreleased entry added/updated
|
|
140
|
+
*/
|
|
141
|
+
function addUnreleasedEntry(changelog, sections) {
|
|
142
|
+
const unreleasedEntry = createUnreleasedEntry(sections);
|
|
143
|
+
return addEntry(changelog, unreleasedEntry, { position: 'start', replaceExisting: true });
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Creates a new release entry from the current unreleased entry.
|
|
147
|
+
*
|
|
148
|
+
* @param changelog - The changelog containing the unreleased entry
|
|
149
|
+
* @param version - The version number for the release
|
|
150
|
+
* @param date - The release date (defaults to today)
|
|
151
|
+
* @param compareUrl - Optional comparison URL
|
|
152
|
+
* @returns A new changelog with the unreleased entry converted to a release
|
|
153
|
+
*/
|
|
154
|
+
function releaseUnreleased(changelog, version, date, compareUrl) {
|
|
155
|
+
const unreleasedIndex = changelog.entries.findIndex((e) => e.unreleased);
|
|
156
|
+
if (unreleasedIndex === -1) {
|
|
157
|
+
throw createError('No unreleased entry found');
|
|
158
|
+
}
|
|
159
|
+
const unreleased = changelog.entries[unreleasedIndex];
|
|
160
|
+
const releaseDate = date ?? createDate().toISOString().split('T')[0];
|
|
161
|
+
const releaseEntry = createChangelogEntry(version, {
|
|
162
|
+
date: releaseDate,
|
|
163
|
+
unreleased: false,
|
|
164
|
+
compareUrl,
|
|
165
|
+
sections: unreleased.sections,
|
|
166
|
+
});
|
|
167
|
+
const newEntries = [...changelog.entries];
|
|
168
|
+
newEntries[unreleasedIndex] = releaseEntry;
|
|
169
|
+
return {
|
|
170
|
+
...changelog,
|
|
171
|
+
entries: newEntries,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Changelog Item Addition
|
|
177
|
+
*
|
|
178
|
+
* Functions for adding items to changelog entries.
|
|
179
|
+
*/
|
|
180
|
+
/**
|
|
181
|
+
* Adds an item to a specific section within an entry.
|
|
182
|
+
*
|
|
183
|
+
* @param changelog - The changelog containing the entry to modify
|
|
184
|
+
* @param version - The version identifier of the entry to update
|
|
185
|
+
* @param sectionType - Category identifier for grouping changes (e.g., 'features', 'fixes')
|
|
186
|
+
* @param item - Description of the change with optional scope and metadata
|
|
187
|
+
* @returns A new changelog with the item added
|
|
188
|
+
*/
|
|
189
|
+
function addItemToEntry(changelog, version, sectionType, item) {
|
|
190
|
+
const entryIndex = changelog.entries.findIndex((e) => e.version === version);
|
|
191
|
+
if (entryIndex === -1) {
|
|
192
|
+
throw createError(`Entry with version "${version}" not found`);
|
|
193
|
+
}
|
|
194
|
+
const entry = changelog.entries[entryIndex];
|
|
195
|
+
const sectionIndex = entry.sections.findIndex((s) => s.type === sectionType);
|
|
196
|
+
let newSections;
|
|
197
|
+
if (sectionIndex === -1) {
|
|
198
|
+
// Create new section
|
|
199
|
+
newSections = [
|
|
200
|
+
...entry.sections,
|
|
201
|
+
{
|
|
202
|
+
type: sectionType,
|
|
203
|
+
heading: sectionType.charAt(0).toUpperCase() + sectionType.slice(1),
|
|
204
|
+
items: [item],
|
|
205
|
+
},
|
|
206
|
+
];
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
// Add to existing section
|
|
210
|
+
newSections = [...entry.sections];
|
|
211
|
+
newSections[sectionIndex] = {
|
|
212
|
+
...newSections[sectionIndex],
|
|
213
|
+
items: [...newSections[sectionIndex].items, item],
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
const newEntry = {
|
|
217
|
+
...entry,
|
|
218
|
+
sections: newSections,
|
|
219
|
+
};
|
|
220
|
+
const newEntries = [...changelog.entries];
|
|
221
|
+
newEntries[entryIndex] = newEntry;
|
|
222
|
+
return {
|
|
223
|
+
...changelog,
|
|
224
|
+
entries: newEntries,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Safe copies of Set built-in via factory function.
|
|
230
|
+
*
|
|
231
|
+
* Since constructors cannot be safely captured via Object.assign, this module
|
|
232
|
+
* provides a factory function that uses Reflect.construct internally.
|
|
233
|
+
*
|
|
234
|
+
* These references are captured at module initialization time to protect against
|
|
235
|
+
* prototype pollution attacks. Import only what you need for tree-shaking.
|
|
236
|
+
*
|
|
237
|
+
* @module @hyperfrontend/immutable-api-utils/built-in-copy/set
|
|
238
|
+
*/
|
|
239
|
+
// Capture references at module initialization time
|
|
240
|
+
const _Set = globalThis.Set;
|
|
241
|
+
const _Reflect$1 = globalThis.Reflect;
|
|
242
|
+
/**
|
|
243
|
+
* (Safe copy) Creates a new Set using the captured Set constructor.
|
|
244
|
+
* Use this instead of `new Set()`.
|
|
245
|
+
*
|
|
246
|
+
* @param iterable - Optional iterable of values.
|
|
247
|
+
* @returns A new Set instance.
|
|
248
|
+
*/
|
|
249
|
+
const createSet = (iterable) => _Reflect$1.construct(_Set, iterable ? [iterable] : []);
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Changelog Entry Removal
|
|
253
|
+
*
|
|
254
|
+
* Functions for removing entries from a changelog.
|
|
255
|
+
*/
|
|
256
|
+
/**
|
|
257
|
+
* Removes an entry from a changelog by version.
|
|
258
|
+
*
|
|
259
|
+
* @param changelog - The changelog to remove from
|
|
260
|
+
* @param version - The version to remove
|
|
261
|
+
* @param options - Optional removal options
|
|
262
|
+
* @returns A new changelog without the specified entry
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* ```ts
|
|
266
|
+
* const newChangelog = removeEntry(changelog, '1.0.0')
|
|
267
|
+
* ```
|
|
268
|
+
*/
|
|
269
|
+
function removeEntry(changelog, version, options) {
|
|
270
|
+
const throwIfNotFound = options?.throwIfNotFound ?? true;
|
|
271
|
+
const entryIndex = changelog.entries.findIndex((e) => e.version === version);
|
|
272
|
+
if (entryIndex === -1) {
|
|
273
|
+
if (throwIfNotFound) {
|
|
274
|
+
throw createError(`Entry with version "${version}" not found`);
|
|
275
|
+
}
|
|
276
|
+
return changelog;
|
|
277
|
+
}
|
|
278
|
+
const newEntries = [...changelog.entries];
|
|
279
|
+
newEntries.splice(entryIndex, 1);
|
|
280
|
+
return {
|
|
281
|
+
...changelog,
|
|
282
|
+
entries: newEntries,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Removes multiple entries from a changelog.
|
|
287
|
+
*
|
|
288
|
+
* @param changelog - The changelog to remove from
|
|
289
|
+
* @param versions - The versions to remove
|
|
290
|
+
* @param options - Optional removal options
|
|
291
|
+
* @returns A new changelog without the specified entries
|
|
292
|
+
*/
|
|
293
|
+
function removeEntries(changelog, versions, options) {
|
|
294
|
+
const versionsSet = createSet(versions);
|
|
295
|
+
const newEntries = changelog.entries.filter((e) => !versionsSet.has(e.version));
|
|
296
|
+
if (options?.throwIfNotFound !== false) {
|
|
297
|
+
const removedVersions = changelog.entries.filter((e) => versionsSet.has(e.version)).map((e) => e.version);
|
|
298
|
+
const notFoundVersions = versions.filter((v) => !removedVersions.includes(v));
|
|
299
|
+
if (notFoundVersions.length > 0) {
|
|
300
|
+
throw createError(`Entries not found for versions: ${notFoundVersions.join(', ')}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
...changelog,
|
|
305
|
+
entries: newEntries,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Removes the unreleased entry if it exists.
|
|
310
|
+
*
|
|
311
|
+
* @param changelog - The changelog to remove the unreleased entry from
|
|
312
|
+
* @param options - Optional removal options
|
|
313
|
+
* @returns A new changelog without the unreleased entry
|
|
314
|
+
*/
|
|
315
|
+
function removeUnreleased(changelog, options) {
|
|
316
|
+
const unreleasedIndex = changelog.entries.findIndex((e) => e.unreleased);
|
|
317
|
+
if (unreleasedIndex === -1) {
|
|
318
|
+
if (options?.throwIfNotFound ?? true) {
|
|
319
|
+
throw createError('No unreleased entry found');
|
|
320
|
+
}
|
|
321
|
+
return changelog;
|
|
322
|
+
}
|
|
323
|
+
const newEntries = [...changelog.entries];
|
|
324
|
+
newEntries.splice(unreleasedIndex, 1);
|
|
325
|
+
return {
|
|
326
|
+
...changelog,
|
|
327
|
+
entries: newEntries,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Changelog Section/Item Removal
|
|
333
|
+
*
|
|
334
|
+
* Functions for removing sections and items from changelog entries.
|
|
335
|
+
*/
|
|
336
|
+
/**
|
|
337
|
+
* Removes a section from an entry.
|
|
338
|
+
*
|
|
339
|
+
* @param changelog - The changelog containing the entry to modify
|
|
340
|
+
* @param version - The version of the entry to modify
|
|
341
|
+
* @param sectionType - The section type to remove
|
|
342
|
+
* @param options - Optional removal options
|
|
343
|
+
* @returns A new changelog without the specified section
|
|
344
|
+
*/
|
|
345
|
+
function removeSection(changelog, version, sectionType, options) {
|
|
346
|
+
const throwIfNotFound = options?.throwIfNotFound ?? true;
|
|
347
|
+
const entryIndex = changelog.entries.findIndex((e) => e.version === version);
|
|
348
|
+
if (entryIndex === -1) {
|
|
349
|
+
if (throwIfNotFound) {
|
|
350
|
+
throw createError(`Entry with version "${version}" not found`);
|
|
351
|
+
}
|
|
352
|
+
return changelog;
|
|
353
|
+
}
|
|
354
|
+
const entry = changelog.entries[entryIndex];
|
|
355
|
+
const sectionIndex = entry.sections.findIndex((s) => s.type === sectionType);
|
|
356
|
+
if (sectionIndex === -1) {
|
|
357
|
+
if (throwIfNotFound) {
|
|
358
|
+
throw createError(`Section with type "${sectionType}" not found in version "${version}"`);
|
|
359
|
+
}
|
|
360
|
+
return changelog;
|
|
361
|
+
}
|
|
362
|
+
const newSections = [...entry.sections];
|
|
363
|
+
newSections.splice(sectionIndex, 1);
|
|
364
|
+
const newEntry = {
|
|
365
|
+
...entry,
|
|
366
|
+
sections: newSections,
|
|
367
|
+
};
|
|
368
|
+
const newEntries = [...changelog.entries];
|
|
369
|
+
newEntries[entryIndex] = newEntry;
|
|
370
|
+
return {
|
|
371
|
+
...changelog,
|
|
372
|
+
entries: newEntries,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Removes an item from an entry by description.
|
|
377
|
+
*
|
|
378
|
+
* @param changelog - The changelog containing the entry to modify
|
|
379
|
+
* @param version - The version of the entry to modify
|
|
380
|
+
* @param sectionType - The section type containing the item
|
|
381
|
+
* @param itemDescription - The description of the item to remove
|
|
382
|
+
* @param options - Optional removal options
|
|
383
|
+
* @returns A new changelog without the specified item
|
|
384
|
+
*/
|
|
385
|
+
function removeItem(changelog, version, sectionType, itemDescription, options) {
|
|
386
|
+
const throwIfNotFound = options?.throwIfNotFound ?? true;
|
|
387
|
+
const entryIndex = changelog.entries.findIndex((e) => e.version === version);
|
|
388
|
+
if (entryIndex === -1) {
|
|
389
|
+
if (throwIfNotFound) {
|
|
390
|
+
throw createError(`Entry with version "${version}" not found`);
|
|
391
|
+
}
|
|
392
|
+
return changelog;
|
|
393
|
+
}
|
|
394
|
+
const entry = changelog.entries[entryIndex];
|
|
395
|
+
const sectionIndex = entry.sections.findIndex((s) => s.type === sectionType);
|
|
396
|
+
if (sectionIndex === -1) {
|
|
397
|
+
if (throwIfNotFound) {
|
|
398
|
+
throw createError(`Section with type "${sectionType}" not found in version "${version}"`);
|
|
399
|
+
}
|
|
400
|
+
return changelog;
|
|
401
|
+
}
|
|
402
|
+
const section = entry.sections[sectionIndex];
|
|
403
|
+
const itemIndex = section.items.findIndex((i) => i.description === itemDescription);
|
|
404
|
+
if (itemIndex === -1) {
|
|
405
|
+
if (throwIfNotFound) {
|
|
406
|
+
throw createError(`Item with description "${itemDescription}" not found in section "${sectionType}"`);
|
|
407
|
+
}
|
|
408
|
+
return changelog;
|
|
409
|
+
}
|
|
410
|
+
const newItems = [...section.items];
|
|
411
|
+
newItems.splice(itemIndex, 1);
|
|
412
|
+
const newSection = {
|
|
413
|
+
...section,
|
|
414
|
+
items: newItems,
|
|
415
|
+
};
|
|
416
|
+
const newSections = [...entry.sections];
|
|
417
|
+
newSections[sectionIndex] = newSection;
|
|
418
|
+
const newEntry = {
|
|
419
|
+
...entry,
|
|
420
|
+
sections: newSections,
|
|
421
|
+
};
|
|
422
|
+
const newEntries = [...changelog.entries];
|
|
423
|
+
newEntries[entryIndex] = newEntry;
|
|
424
|
+
return {
|
|
425
|
+
...changelog,
|
|
426
|
+
entries: newEntries,
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Removes empty sections from all entries.
|
|
431
|
+
*
|
|
432
|
+
* @param changelog - The changelog to remove empty sections from
|
|
433
|
+
* @returns A new changelog with empty sections removed
|
|
434
|
+
*/
|
|
435
|
+
function removeEmptySections(changelog) {
|
|
436
|
+
const newEntries = changelog.entries.map((entry) => {
|
|
437
|
+
const nonEmptySections = entry.sections.filter((s) => s.items.length > 0);
|
|
438
|
+
if (nonEmptySections.length === entry.sections.length) {
|
|
439
|
+
return entry; // No change
|
|
440
|
+
}
|
|
441
|
+
return {
|
|
442
|
+
...entry,
|
|
443
|
+
sections: nonEmptySections,
|
|
444
|
+
};
|
|
445
|
+
});
|
|
446
|
+
return {
|
|
447
|
+
...changelog,
|
|
448
|
+
entries: newEntries,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Removes empty entries (entries with no sections or only empty sections).
|
|
453
|
+
*
|
|
454
|
+
* @param changelog - The changelog to remove empty entries from
|
|
455
|
+
* @param keepUnreleased - Whether to keep an empty unreleased entry (default: true)
|
|
456
|
+
* @returns A new changelog with empty entries removed
|
|
457
|
+
*/
|
|
458
|
+
function removeEmptyEntries(changelog, keepUnreleased = true) {
|
|
459
|
+
const newEntries = changelog.entries.filter((entry) => {
|
|
460
|
+
// Check if entry has any items
|
|
461
|
+
const hasItems = entry.sections.some((s) => s.items.length > 0);
|
|
462
|
+
if (hasItems)
|
|
463
|
+
return true;
|
|
464
|
+
if (keepUnreleased && entry.unreleased)
|
|
465
|
+
return true;
|
|
466
|
+
if (entry.rawContent)
|
|
467
|
+
return true;
|
|
468
|
+
return false;
|
|
469
|
+
});
|
|
470
|
+
return {
|
|
471
|
+
...changelog,
|
|
472
|
+
entries: newEntries,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Changelog Predicate-Based Filtering
|
|
478
|
+
*
|
|
479
|
+
* Functions for filtering changelog entries, sections, and items using predicates.
|
|
480
|
+
*/
|
|
481
|
+
/**
|
|
482
|
+
* Filters entries using a predicate function.
|
|
483
|
+
*
|
|
484
|
+
* @param changelog - The changelog to filter
|
|
485
|
+
* @param predicate - Function that returns true for entries to keep
|
|
486
|
+
* @returns A new changelog with filtered entries
|
|
487
|
+
*
|
|
488
|
+
* @example
|
|
489
|
+
* ```ts
|
|
490
|
+
* const filtered = filterEntries(changelog, (entry) => !entry.unreleased)
|
|
491
|
+
* ```
|
|
492
|
+
*/
|
|
493
|
+
function filterEntries(changelog, predicate) {
|
|
494
|
+
const newEntries = changelog.entries.filter(predicate);
|
|
495
|
+
return {
|
|
496
|
+
...changelog,
|
|
497
|
+
entries: newEntries,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Filters entries that have breaking changes.
|
|
502
|
+
*
|
|
503
|
+
* @param changelog - The changelog to filter
|
|
504
|
+
* @returns A new changelog with only entries containing breaking changes
|
|
505
|
+
*/
|
|
506
|
+
function filterBreakingChanges(changelog) {
|
|
507
|
+
return filterEntries(changelog, (entry) => {
|
|
508
|
+
// Check for breaking section
|
|
509
|
+
const hasBreakingSection = entry.sections.some((s) => s.type === 'breaking');
|
|
510
|
+
if (hasBreakingSection)
|
|
511
|
+
return true;
|
|
512
|
+
// Check for breaking items in other sections
|
|
513
|
+
return entry.sections.some((section) => section.items.some((item) => item.breaking));
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Filters sections within each entry.
|
|
518
|
+
*
|
|
519
|
+
* @param changelog - The changelog to filter
|
|
520
|
+
* @param predicate - Function that returns true for sections to keep
|
|
521
|
+
* @returns A new changelog with filtered sections
|
|
522
|
+
*/
|
|
523
|
+
function filterSections(changelog, predicate) {
|
|
524
|
+
const newEntries = changelog.entries.map((entry) => ({
|
|
525
|
+
...entry,
|
|
526
|
+
sections: entry.sections.filter((section) => predicate(section, entry)),
|
|
527
|
+
}));
|
|
528
|
+
return { ...changelog, entries: newEntries };
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Keeps only specified section types.
|
|
532
|
+
*
|
|
533
|
+
* @param changelog - The changelog to filter
|
|
534
|
+
* @param types - Section types to keep
|
|
535
|
+
* @returns A new changelog with only specified section types
|
|
536
|
+
*/
|
|
537
|
+
function filterSectionTypes(changelog, types) {
|
|
538
|
+
const typeSet = createSet(types);
|
|
539
|
+
return filterSections(changelog, (section) => typeSet.has(section.type));
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Filters items within sections.
|
|
543
|
+
*
|
|
544
|
+
* @param changelog - The changelog to filter
|
|
545
|
+
* @param predicate - Function that returns true for items to keep
|
|
546
|
+
* @returns A new changelog with filtered items
|
|
547
|
+
*/
|
|
548
|
+
function filterItems(changelog, predicate) {
|
|
549
|
+
const newEntries = changelog.entries.map((entry) => ({
|
|
550
|
+
...entry,
|
|
551
|
+
sections: entry.sections.map((section) => ({
|
|
552
|
+
...section,
|
|
553
|
+
items: section.items.filter((item) => predicate(item, section, entry)),
|
|
554
|
+
})),
|
|
555
|
+
}));
|
|
556
|
+
return { ...changelog, entries: newEntries };
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Filters items by scope.
|
|
560
|
+
*
|
|
561
|
+
* @param changelog - The changelog to filter
|
|
562
|
+
* @param scopes - Scopes to include
|
|
563
|
+
* @returns A new changelog with only items matching the scopes
|
|
564
|
+
*/
|
|
565
|
+
function filterByScope(changelog, scopes) {
|
|
566
|
+
const scopeSet = createSet(scopes);
|
|
567
|
+
return filterItems(changelog, (item) => {
|
|
568
|
+
if (!item.scope)
|
|
569
|
+
return false;
|
|
570
|
+
return scopeSet.has(item.scope);
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Excludes items by scope.
|
|
575
|
+
*
|
|
576
|
+
* @param changelog - The changelog to filter
|
|
577
|
+
* @param scopes - Scopes to exclude
|
|
578
|
+
* @returns A new changelog without items matching the scopes
|
|
579
|
+
*/
|
|
580
|
+
function excludeByScope(changelog, scopes) {
|
|
581
|
+
const scopeSet = createSet(scopes);
|
|
582
|
+
return filterItems(changelog, (item) => {
|
|
583
|
+
if (!item.scope)
|
|
584
|
+
return true;
|
|
585
|
+
return !scopeSet.has(item.scope);
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Safe copies of Math built-in methods.
|
|
591
|
+
*
|
|
592
|
+
* These references are captured at module initialization time to protect against
|
|
593
|
+
* prototype pollution attacks. Import only what you need for tree-shaking.
|
|
594
|
+
*
|
|
595
|
+
* @module @hyperfrontend/immutable-api-utils/built-in-copy/math
|
|
596
|
+
*/
|
|
597
|
+
// Capture references at module initialization time
|
|
598
|
+
const _Math = globalThis.Math;
|
|
599
|
+
// ============================================================================
|
|
600
|
+
// Min/Max
|
|
601
|
+
// ============================================================================
|
|
602
|
+
/**
|
|
603
|
+
* (Safe copy) Returns the larger of zero or more numbers.
|
|
604
|
+
*/
|
|
605
|
+
const max = _Math.max;
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Safe copies of Number built-in methods and constants.
|
|
609
|
+
*
|
|
610
|
+
* These references are captured at module initialization time to protect against
|
|
611
|
+
* prototype pollution attacks. Import only what you need for tree-shaking.
|
|
612
|
+
*
|
|
613
|
+
* @module @hyperfrontend/immutable-api-utils/built-in-copy/number
|
|
614
|
+
*/
|
|
615
|
+
// Capture references at module initialization time
|
|
616
|
+
const _parseInt = globalThis.parseInt;
|
|
617
|
+
const _isNaN = globalThis.isNaN;
|
|
618
|
+
// ============================================================================
|
|
619
|
+
// Parsing
|
|
620
|
+
// ============================================================================
|
|
621
|
+
/**
|
|
622
|
+
* (Safe copy) Parses a string and returns an integer.
|
|
623
|
+
*/
|
|
624
|
+
const parseInt = _parseInt;
|
|
625
|
+
// ============================================================================
|
|
626
|
+
// Global Type Checking (legacy, less strict)
|
|
627
|
+
// ============================================================================
|
|
628
|
+
/**
|
|
629
|
+
* (Safe copy) Global isNaN function (coerces to number first, less strict than Number.isNaN).
|
|
630
|
+
*/
|
|
631
|
+
const globalIsNaN = _isNaN;
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Compares two semantic versions.
|
|
635
|
+
*
|
|
636
|
+
* @param a - First version
|
|
637
|
+
* @param b - Second version
|
|
638
|
+
* @returns -1 if a < b, 0 if a == b, 1 if a > b
|
|
639
|
+
*
|
|
640
|
+
* @example
|
|
641
|
+
* compare(parseVersion('1.0.0'), parseVersion('2.0.0')) // -1
|
|
642
|
+
* compare(parseVersion('1.0.0'), parseVersion('1.0.0')) // 0
|
|
643
|
+
* compare(parseVersion('2.0.0'), parseVersion('1.0.0')) // 1
|
|
644
|
+
*/
|
|
645
|
+
function compare(a, b) {
|
|
646
|
+
// Compare major, minor, patch
|
|
647
|
+
if (a.major !== b.major) {
|
|
648
|
+
return a.major < b.major ? -1 : 1;
|
|
649
|
+
}
|
|
650
|
+
if (a.minor !== b.minor) {
|
|
651
|
+
return a.minor < b.minor ? -1 : 1;
|
|
652
|
+
}
|
|
653
|
+
if (a.patch !== b.patch) {
|
|
654
|
+
return a.patch < b.patch ? -1 : 1;
|
|
655
|
+
}
|
|
656
|
+
// Compare prerelease
|
|
657
|
+
// Version with prerelease has lower precedence than release
|
|
658
|
+
if (a.prerelease.length === 0 && b.prerelease.length > 0) {
|
|
659
|
+
return 1; // a is release, b is prerelease -> a > b
|
|
660
|
+
}
|
|
661
|
+
if (a.prerelease.length > 0 && b.prerelease.length === 0) {
|
|
662
|
+
return -1; // a is prerelease, b is release -> a < b
|
|
663
|
+
}
|
|
664
|
+
// Both have prerelease - compare identifiers
|
|
665
|
+
const maxLen = max(a.prerelease.length, b.prerelease.length);
|
|
666
|
+
for (let i = 0; i < maxLen; i++) {
|
|
667
|
+
const aId = a.prerelease[i];
|
|
668
|
+
const bId = b.prerelease[i];
|
|
669
|
+
// Shorter prerelease array has lower precedence
|
|
670
|
+
if (aId === undefined && bId !== undefined) {
|
|
671
|
+
return -1;
|
|
672
|
+
}
|
|
673
|
+
if (aId !== undefined && bId === undefined) {
|
|
674
|
+
return 1;
|
|
675
|
+
}
|
|
676
|
+
if (aId === undefined || bId === undefined) {
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
// Compare identifiers
|
|
680
|
+
const cmp = compareIdentifiers(aId, bId);
|
|
681
|
+
if (cmp !== 0) {
|
|
682
|
+
return cmp;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return 0;
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Checks if a version satisfies a comparator.
|
|
689
|
+
*
|
|
690
|
+
* @param version - Version to check
|
|
691
|
+
* @param comparator - Comparator to test against
|
|
692
|
+
* @returns True if version satisfies the comparator
|
|
693
|
+
*/
|
|
694
|
+
function satisfiesComparator(version, comparator) {
|
|
695
|
+
const cmp = compare(version, comparator.version);
|
|
696
|
+
switch (comparator.operator) {
|
|
697
|
+
case '=':
|
|
698
|
+
return cmp === 0;
|
|
699
|
+
case '>':
|
|
700
|
+
return cmp === 1;
|
|
701
|
+
case '>=':
|
|
702
|
+
return cmp >= 0;
|
|
703
|
+
case '<':
|
|
704
|
+
return cmp === -1;
|
|
705
|
+
case '<=':
|
|
706
|
+
return cmp <= 0;
|
|
707
|
+
case '^':
|
|
708
|
+
case '~':
|
|
709
|
+
// These should have been expanded during parsing
|
|
710
|
+
// If we encounter them here, treat as >=
|
|
711
|
+
return cmp >= 0;
|
|
712
|
+
default:
|
|
713
|
+
return false;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Checks if a version satisfies a range.
|
|
718
|
+
*
|
|
719
|
+
* @param version - Version to check
|
|
720
|
+
* @param range - Range to test against
|
|
721
|
+
* @returns True if version satisfies the range
|
|
722
|
+
*
|
|
723
|
+
* @example
|
|
724
|
+
* satisfies(parseVersion('1.2.3'), parseRange('^1.0.0')) // true
|
|
725
|
+
* satisfies(parseVersion('2.0.0'), parseRange('^1.0.0')) // false
|
|
726
|
+
*/
|
|
727
|
+
function satisfies(version, range) {
|
|
728
|
+
// Empty range matches any
|
|
729
|
+
if (range.sets.length === 0) {
|
|
730
|
+
return true;
|
|
731
|
+
}
|
|
732
|
+
// OR logic: at least one set must be satisfied
|
|
733
|
+
for (const set of range.sets) {
|
|
734
|
+
// AND logic: all comparators in set must be satisfied
|
|
735
|
+
let allSatisfied = true;
|
|
736
|
+
// Empty comparator set matches any
|
|
737
|
+
if (set.comparators.length === 0) {
|
|
738
|
+
return true;
|
|
739
|
+
}
|
|
740
|
+
for (const comp of set.comparators) {
|
|
741
|
+
if (!satisfiesComparator(version, comp)) {
|
|
742
|
+
allSatisfied = false;
|
|
743
|
+
break;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
if (allSatisfied) {
|
|
747
|
+
return true;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
return false;
|
|
751
|
+
}
|
|
752
|
+
// ============================================================================
|
|
753
|
+
// Internal helpers
|
|
754
|
+
// ============================================================================
|
|
755
|
+
/**
|
|
756
|
+
* Compares two prerelease identifiers.
|
|
757
|
+
* Numeric identifiers have lower precedence than alphanumeric.
|
|
758
|
+
* Numeric identifiers are compared numerically.
|
|
759
|
+
* Alphanumeric identifiers are compared lexically.
|
|
760
|
+
*
|
|
761
|
+
* @param a - First prerelease identifier
|
|
762
|
+
* @param b - Second prerelease identifier
|
|
763
|
+
* @returns -1 if a < b, 0 if equal, 1 if a > b
|
|
764
|
+
*/
|
|
765
|
+
function compareIdentifiers(a, b) {
|
|
766
|
+
const aIsNumeric = isNumeric(a);
|
|
767
|
+
const bIsNumeric = isNumeric(b);
|
|
768
|
+
// Numeric identifiers have lower precedence
|
|
769
|
+
if (aIsNumeric && !bIsNumeric) {
|
|
770
|
+
return -1;
|
|
771
|
+
}
|
|
772
|
+
if (!aIsNumeric && bIsNumeric) {
|
|
773
|
+
return 1;
|
|
774
|
+
}
|
|
775
|
+
// Both numeric - compare as numbers
|
|
776
|
+
if (aIsNumeric && bIsNumeric) {
|
|
777
|
+
const aNum = parseInt(a, 10);
|
|
778
|
+
const bNum = parseInt(b, 10);
|
|
779
|
+
if (aNum < bNum)
|
|
780
|
+
return -1;
|
|
781
|
+
if (aNum > bNum)
|
|
782
|
+
return 1;
|
|
783
|
+
return 0;
|
|
784
|
+
}
|
|
785
|
+
// Both alphanumeric - compare lexically
|
|
786
|
+
if (a < b)
|
|
787
|
+
return -1;
|
|
788
|
+
if (a > b)
|
|
789
|
+
return 1;
|
|
790
|
+
return 0;
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Checks if a string consists only of digits.
|
|
794
|
+
*
|
|
795
|
+
* @param str - String to check for numeric content
|
|
796
|
+
* @returns True if string contains only digits
|
|
797
|
+
*/
|
|
798
|
+
function isNumeric(str) {
|
|
799
|
+
if (str.length === 0)
|
|
800
|
+
return false;
|
|
801
|
+
for (let i = 0; i < str.length; i++) {
|
|
802
|
+
const code = str.charCodeAt(i);
|
|
803
|
+
if (code < 48 || code > 57) {
|
|
804
|
+
return false;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
return true;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* Creates a new Comparator.
|
|
812
|
+
*
|
|
813
|
+
* @param operator - The comparison operator
|
|
814
|
+
* @param version - The version to compare against
|
|
815
|
+
* @returns A new Comparator
|
|
816
|
+
*/
|
|
817
|
+
function createComparator(operator, version) {
|
|
818
|
+
return { operator, version };
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Creates a new ComparatorSet.
|
|
822
|
+
*
|
|
823
|
+
* @param comparators - Array of comparators (AND logic)
|
|
824
|
+
* @returns A new ComparatorSet
|
|
825
|
+
*/
|
|
826
|
+
function createComparatorSet(comparators) {
|
|
827
|
+
return { comparators };
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Creates a new Range.
|
|
831
|
+
*
|
|
832
|
+
* @param sets - Array of comparator sets (OR logic)
|
|
833
|
+
* @param raw - Original raw string
|
|
834
|
+
* @returns A new Range
|
|
835
|
+
*/
|
|
836
|
+
function createRange(sets, raw) {
|
|
837
|
+
return { sets, raw };
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* Creates a new SemVer object.
|
|
842
|
+
*
|
|
843
|
+
* @param options - Version components
|
|
844
|
+
* @returns A new SemVer object
|
|
845
|
+
*/
|
|
846
|
+
function createSemVer(options) {
|
|
847
|
+
return {
|
|
848
|
+
major: options.major,
|
|
849
|
+
minor: options.minor,
|
|
850
|
+
patch: options.patch,
|
|
851
|
+
prerelease: options.prerelease ?? [],
|
|
852
|
+
build: options.build ?? [],
|
|
853
|
+
raw: options.raw,
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* Maximum range string length.
|
|
859
|
+
*/
|
|
860
|
+
const MAX_RANGE_LENGTH = 1024;
|
|
861
|
+
/**
|
|
862
|
+
* Parses a semver range string.
|
|
863
|
+
*
|
|
864
|
+
* Supports:
|
|
865
|
+
* - Exact: 1.2.3, =1.2.3
|
|
866
|
+
* - Comparators: >1.0.0, >=1.0.0, <2.0.0, <=2.0.0
|
|
867
|
+
* - Caret: ^1.2.3 (compatible with version)
|
|
868
|
+
* - Tilde: ~1.2.3 (approximately equivalent)
|
|
869
|
+
* - X-ranges: 1.x, 1.2.x, *
|
|
870
|
+
* - Hyphen ranges: 1.0.0 - 2.0.0
|
|
871
|
+
* - OR: 1.0.0 || 2.0.0
|
|
872
|
+
* - AND: >=1.0.0 <2.0.0
|
|
873
|
+
*
|
|
874
|
+
* @param input - The range string to parse
|
|
875
|
+
* @returns A ParseRangeResult with the parsed range or error
|
|
876
|
+
*/
|
|
877
|
+
function parseRange(input) {
|
|
878
|
+
if (!input || typeof input !== 'string') {
|
|
879
|
+
return { success: false, error: 'Range string is required' };
|
|
880
|
+
}
|
|
881
|
+
if (input.length > MAX_RANGE_LENGTH) {
|
|
882
|
+
return { success: false, error: `Range string exceeds maximum length of ${MAX_RANGE_LENGTH}` };
|
|
883
|
+
}
|
|
884
|
+
// Trim whitespace
|
|
885
|
+
const trimmed = input.trim();
|
|
886
|
+
// Handle wildcard/any
|
|
887
|
+
if (trimmed === '' || trimmed === '*' || trimmed.toLowerCase() === 'x') {
|
|
888
|
+
return { success: true, range: createRange([], input) };
|
|
889
|
+
}
|
|
890
|
+
// Split by || for OR logic
|
|
891
|
+
const orParts = splitByOr(trimmed);
|
|
892
|
+
const sets = [];
|
|
893
|
+
for (const part of orParts) {
|
|
894
|
+
const setResult = parseComparatorSet(part.trim());
|
|
895
|
+
if (!setResult.success) {
|
|
896
|
+
return { success: false, error: setResult.error };
|
|
897
|
+
}
|
|
898
|
+
if (setResult.set) {
|
|
899
|
+
sets.push(setResult.set);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
return { success: true, range: createRange(sets, input) };
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Splits a string by || delimiter, respecting nesting.
|
|
906
|
+
*
|
|
907
|
+
* @param input - Range string containing OR groups
|
|
908
|
+
* @returns Array of OR-separated parts
|
|
909
|
+
*/
|
|
910
|
+
function splitByOr(input) {
|
|
911
|
+
const parts = [];
|
|
912
|
+
let current = '';
|
|
913
|
+
let pos = 0;
|
|
914
|
+
while (pos < input.length) {
|
|
915
|
+
if (input[pos] === '|' && pos + 1 < input.length && input[pos + 1] === '|') {
|
|
916
|
+
parts.push(current);
|
|
917
|
+
current = '';
|
|
918
|
+
pos += 2;
|
|
919
|
+
}
|
|
920
|
+
else {
|
|
921
|
+
current += input[pos];
|
|
922
|
+
pos++;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
parts.push(current);
|
|
926
|
+
return parts;
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Parses a single comparator set (space-separated comparators = AND logic).
|
|
930
|
+
*
|
|
931
|
+
* @param input - Comparator set string
|
|
932
|
+
* @returns Parsed set result
|
|
933
|
+
*/
|
|
934
|
+
function parseComparatorSet(input) {
|
|
935
|
+
if (!input || input.trim() === '') {
|
|
936
|
+
return { success: true }; // Empty set matches any
|
|
937
|
+
}
|
|
938
|
+
const trimmed = input.trim();
|
|
939
|
+
// Check for hyphen range: "1.0.0 - 2.0.0"
|
|
940
|
+
const hyphenMatch = parseHyphenRange(trimmed);
|
|
941
|
+
if (hyphenMatch.isHyphenRange) {
|
|
942
|
+
if (!hyphenMatch.success) {
|
|
943
|
+
return { success: false, error: hyphenMatch.error };
|
|
944
|
+
}
|
|
945
|
+
return { success: true, set: hyphenMatch.set };
|
|
946
|
+
}
|
|
947
|
+
// Split by whitespace for AND logic
|
|
948
|
+
const tokens = splitByWhitespace(trimmed);
|
|
949
|
+
const comparators = [];
|
|
950
|
+
for (const token of tokens) {
|
|
951
|
+
const compResult = parseSingleComparator(token);
|
|
952
|
+
if (!compResult.success) {
|
|
953
|
+
return { success: false, error: compResult.error };
|
|
954
|
+
}
|
|
955
|
+
if (compResult.comparators) {
|
|
956
|
+
comparators.push(...compResult.comparators);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
if (comparators.length === 0) {
|
|
960
|
+
return { success: true }; // Empty matches any
|
|
961
|
+
}
|
|
962
|
+
return { success: true, set: createComparatorSet(comparators) };
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* Checks for and parses hyphen ranges like "1.0.0 - 2.0.0".
|
|
966
|
+
*
|
|
967
|
+
* @param input - Potential hyphen range string
|
|
968
|
+
* @returns Hyphen range parsing result
|
|
969
|
+
*/
|
|
970
|
+
function parseHyphenRange(input) {
|
|
971
|
+
// Look for " - " (space-hyphen-space)
|
|
972
|
+
let hyphenPos = -1;
|
|
973
|
+
for (let i = 0; i < input.length - 2; i++) {
|
|
974
|
+
if (input[i] === ' ' && input[i + 1] === '-' && input[i + 2] === ' ') {
|
|
975
|
+
hyphenPos = i;
|
|
976
|
+
break;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
if (hyphenPos === -1) {
|
|
980
|
+
return { isHyphenRange: false, success: true };
|
|
981
|
+
}
|
|
982
|
+
const leftPart = input.slice(0, hyphenPos).trim();
|
|
983
|
+
const rightPart = input.slice(hyphenPos + 3).trim();
|
|
984
|
+
const leftVersion = parseSimpleVersion(leftPart);
|
|
985
|
+
if (!leftVersion) {
|
|
986
|
+
return { isHyphenRange: true, success: false, error: `Invalid left side of hyphen range: "${leftPart}"` };
|
|
987
|
+
}
|
|
988
|
+
const rightVersion = parseSimpleVersion(rightPart);
|
|
989
|
+
if (!rightVersion) {
|
|
990
|
+
return { isHyphenRange: true, success: false, error: `Invalid right side of hyphen range: "${rightPart}"` };
|
|
991
|
+
}
|
|
992
|
+
// Hyphen range: >=left <=right
|
|
993
|
+
const comparators = [createComparator('>=', leftVersion), createComparator('<=', rightVersion)];
|
|
994
|
+
return {
|
|
995
|
+
isHyphenRange: true,
|
|
996
|
+
success: true,
|
|
997
|
+
set: createComparatorSet(comparators),
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Splits by whitespace.
|
|
1002
|
+
*
|
|
1003
|
+
* @param input - String to split
|
|
1004
|
+
* @returns Array of whitespace-separated tokens
|
|
1005
|
+
*/
|
|
1006
|
+
function splitByWhitespace(input) {
|
|
1007
|
+
const tokens = [];
|
|
1008
|
+
let current = '';
|
|
1009
|
+
for (const char of input) {
|
|
1010
|
+
if (char === ' ' || char === '\t') {
|
|
1011
|
+
if (current) {
|
|
1012
|
+
tokens.push(current);
|
|
1013
|
+
current = '';
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
else {
|
|
1017
|
+
current += char;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
if (current) {
|
|
1021
|
+
tokens.push(current);
|
|
1022
|
+
}
|
|
1023
|
+
return tokens;
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Parses a single comparator token (e.g., ">=1.0.0", "^1.2.3", "~1.0").
|
|
1027
|
+
*
|
|
1028
|
+
* @param token - Comparator token to parse
|
|
1029
|
+
* @returns Parsed comparator result
|
|
1030
|
+
*/
|
|
1031
|
+
function parseSingleComparator(token) {
|
|
1032
|
+
let pos = 0;
|
|
1033
|
+
let operator = '=';
|
|
1034
|
+
// Parse operator
|
|
1035
|
+
if (token[pos] === '^') {
|
|
1036
|
+
operator = '^';
|
|
1037
|
+
pos++;
|
|
1038
|
+
}
|
|
1039
|
+
else if (token[pos] === '~') {
|
|
1040
|
+
operator = '~';
|
|
1041
|
+
pos++;
|
|
1042
|
+
}
|
|
1043
|
+
else if (token[pos] === '>') {
|
|
1044
|
+
if (token[pos + 1] === '=') {
|
|
1045
|
+
operator = '>=';
|
|
1046
|
+
pos += 2;
|
|
1047
|
+
}
|
|
1048
|
+
else {
|
|
1049
|
+
operator = '>';
|
|
1050
|
+
pos++;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
else if (token[pos] === '<') {
|
|
1054
|
+
if (token[pos + 1] === '=') {
|
|
1055
|
+
operator = '<=';
|
|
1056
|
+
pos += 2;
|
|
1057
|
+
}
|
|
1058
|
+
else {
|
|
1059
|
+
operator = '<';
|
|
1060
|
+
pos++;
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
else if (token[pos] === '=') {
|
|
1064
|
+
operator = '=';
|
|
1065
|
+
pos++;
|
|
1066
|
+
}
|
|
1067
|
+
const versionPart = token.slice(pos);
|
|
1068
|
+
// Handle wildcards: *, x, X
|
|
1069
|
+
if (versionPart === '*' || versionPart.toLowerCase() === 'x') {
|
|
1070
|
+
// Wildcard matches any - return empty (will be handled as match-all)
|
|
1071
|
+
return { success: true, comparators: [] };
|
|
1072
|
+
}
|
|
1073
|
+
// Handle x-ranges: 1.x, 1.2.x
|
|
1074
|
+
if (versionPart.includes('x') || versionPart.includes('X') || versionPart.includes('*')) {
|
|
1075
|
+
return parseXRange(versionPart);
|
|
1076
|
+
}
|
|
1077
|
+
// Parse version
|
|
1078
|
+
const version = parseSimpleVersion(versionPart);
|
|
1079
|
+
if (!version) {
|
|
1080
|
+
return { success: false, error: `Invalid version in comparator: "${versionPart}"` };
|
|
1081
|
+
}
|
|
1082
|
+
// For caret and tilde, expand to range
|
|
1083
|
+
if (operator === '^') {
|
|
1084
|
+
return expandCaretRange(version);
|
|
1085
|
+
}
|
|
1086
|
+
if (operator === '~') {
|
|
1087
|
+
return expandTildeRange(version);
|
|
1088
|
+
}
|
|
1089
|
+
return { success: true, comparators: [createComparator(operator, version)] };
|
|
1090
|
+
}
|
|
1091
|
+
/**
|
|
1092
|
+
* Parses x-ranges like 1.x, 1.2.x, etc.
|
|
1093
|
+
*
|
|
1094
|
+
* @param input - X-range string to parse
|
|
1095
|
+
* @param _operator - Range operator (unused but kept for interface consistency)
|
|
1096
|
+
* @returns Comparator result
|
|
1097
|
+
*/
|
|
1098
|
+
function parseXRange(input, _operator) {
|
|
1099
|
+
const parts = input.split('.');
|
|
1100
|
+
const nums = [];
|
|
1101
|
+
for (const part of parts) {
|
|
1102
|
+
const lower = part.toLowerCase();
|
|
1103
|
+
if (lower === 'x' || lower === '*' || lower === '') {
|
|
1104
|
+
break;
|
|
1105
|
+
}
|
|
1106
|
+
const num = parseInt(part, 10);
|
|
1107
|
+
if (globalIsNaN(num) || num < 0) {
|
|
1108
|
+
return { success: false, error: `Invalid x-range: "${input}"` };
|
|
1109
|
+
}
|
|
1110
|
+
nums.push(num);
|
|
1111
|
+
}
|
|
1112
|
+
if (nums.length === 0) {
|
|
1113
|
+
// * or X alone - match any
|
|
1114
|
+
return { success: true, comparators: [] };
|
|
1115
|
+
}
|
|
1116
|
+
if (nums.length === 1) {
|
|
1117
|
+
// 1.x or 1.* -> >=1.0.0 <2.0.0
|
|
1118
|
+
const lower = createSemVer({ major: nums[0], minor: 0, patch: 0 });
|
|
1119
|
+
const upper = createSemVer({ major: nums[0] + 1, minor: 0, patch: 0 });
|
|
1120
|
+
return { success: true, comparators: [createComparator('>=', lower), createComparator('<', upper)] };
|
|
1121
|
+
}
|
|
1122
|
+
// 1.2.x -> >=1.2.0 <1.3.0
|
|
1123
|
+
const lower = createSemVer({ major: nums[0], minor: nums[1], patch: 0 });
|
|
1124
|
+
const upper = createSemVer({ major: nums[0], minor: nums[1] + 1, patch: 0 });
|
|
1125
|
+
return { success: true, comparators: [createComparator('>=', lower), createComparator('<', upper)] };
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Expands caret range: ^1.2.3 -> >=1.2.3 <2.0.0
|
|
1129
|
+
*
|
|
1130
|
+
* @param version - Base version for caret range
|
|
1131
|
+
* @returns Expanded comparator result
|
|
1132
|
+
*/
|
|
1133
|
+
function expandCaretRange(version) {
|
|
1134
|
+
let upperMajor = version.major;
|
|
1135
|
+
let upperMinor = 0;
|
|
1136
|
+
let upperPatch = 0;
|
|
1137
|
+
if (version.major === 0) {
|
|
1138
|
+
if (version.minor === 0) {
|
|
1139
|
+
// ^0.0.x -> >=0.0.x <0.0.(x+1)
|
|
1140
|
+
upperPatch = version.patch + 1;
|
|
1141
|
+
upperMinor = version.minor;
|
|
1142
|
+
}
|
|
1143
|
+
else {
|
|
1144
|
+
// ^0.x.y -> >=0.x.y <0.(x+1).0
|
|
1145
|
+
upperMinor = version.minor + 1;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
else {
|
|
1149
|
+
// ^x.y.z -> >=x.y.z <(x+1).0.0
|
|
1150
|
+
upperMajor = version.major + 1;
|
|
1151
|
+
}
|
|
1152
|
+
const upper = createSemVer({ major: upperMajor, minor: upperMinor, patch: upperPatch });
|
|
1153
|
+
return { success: true, comparators: [createComparator('>=', version), createComparator('<', upper)] };
|
|
1154
|
+
}
|
|
1155
|
+
/**
|
|
1156
|
+
* Expands tilde range: ~1.2.3 -> >=1.2.3 <1.3.0
|
|
1157
|
+
*
|
|
1158
|
+
* @param version - Base version for tilde range
|
|
1159
|
+
* @returns Expanded comparator result
|
|
1160
|
+
*/
|
|
1161
|
+
function expandTildeRange(version) {
|
|
1162
|
+
const upper = createSemVer({
|
|
1163
|
+
major: version.major,
|
|
1164
|
+
minor: version.minor + 1,
|
|
1165
|
+
patch: 0,
|
|
1166
|
+
});
|
|
1167
|
+
return { success: true, comparators: [createComparator('>=', version), createComparator('<', upper)] };
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* Parses a simple version string (no range operators).
|
|
1171
|
+
* More lenient - accepts partial versions.
|
|
1172
|
+
*
|
|
1173
|
+
* @param input - Version string to parse
|
|
1174
|
+
* @returns Parsed SemVer or null if invalid
|
|
1175
|
+
*/
|
|
1176
|
+
function parseSimpleVersion(input) {
|
|
1177
|
+
if (!input)
|
|
1178
|
+
return null;
|
|
1179
|
+
let pos = 0;
|
|
1180
|
+
// Skip leading v
|
|
1181
|
+
if (input[pos] === 'v' || input[pos] === 'V') {
|
|
1182
|
+
pos++;
|
|
1183
|
+
}
|
|
1184
|
+
const parts = input.slice(pos).split('.');
|
|
1185
|
+
if (parts.length === 0)
|
|
1186
|
+
return null;
|
|
1187
|
+
const nums = [];
|
|
1188
|
+
for (const part of parts) {
|
|
1189
|
+
// Stop at prerelease or build
|
|
1190
|
+
const dashIdx = part.indexOf('-');
|
|
1191
|
+
const plusIdx = part.indexOf('+');
|
|
1192
|
+
let numPart = part;
|
|
1193
|
+
if (dashIdx !== -1) {
|
|
1194
|
+
numPart = part.slice(0, dashIdx);
|
|
1195
|
+
}
|
|
1196
|
+
else if (plusIdx !== -1) {
|
|
1197
|
+
numPart = part.slice(0, plusIdx);
|
|
1198
|
+
}
|
|
1199
|
+
if (numPart === '' || numPart.toLowerCase() === 'x' || numPart === '*') {
|
|
1200
|
+
break;
|
|
1201
|
+
}
|
|
1202
|
+
const num = parseInt(numPart, 10);
|
|
1203
|
+
if (globalIsNaN(num) || num < 0)
|
|
1204
|
+
return null;
|
|
1205
|
+
nums.push(num);
|
|
1206
|
+
}
|
|
1207
|
+
if (nums.length === 0)
|
|
1208
|
+
return null;
|
|
1209
|
+
return createSemVer({
|
|
1210
|
+
major: nums[0],
|
|
1211
|
+
minor: nums[1] ?? 0,
|
|
1212
|
+
patch: nums[2] ?? 0,
|
|
1213
|
+
prerelease: [],
|
|
1214
|
+
build: [],
|
|
1215
|
+
raw: input,
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
/**
|
|
1220
|
+
* Maximum version string length to prevent memory exhaustion.
|
|
1221
|
+
*/
|
|
1222
|
+
const MAX_VERSION_LENGTH = 256;
|
|
1223
|
+
/**
|
|
1224
|
+
* Parses a semantic version string.
|
|
1225
|
+
*
|
|
1226
|
+
* Accepts versions in the format: MAJOR.MINOR.PATCH[-prerelease][+build]
|
|
1227
|
+
* Optional leading 'v' or '=' prefixes are stripped.
|
|
1228
|
+
*
|
|
1229
|
+
* @param input - The version string to parse
|
|
1230
|
+
* @returns A ParseVersionResult with the parsed version or error
|
|
1231
|
+
*
|
|
1232
|
+
* @example
|
|
1233
|
+
* parseVersion('1.2.3') // { success: true, version: { major: 1, minor: 2, patch: 3, ... } }
|
|
1234
|
+
* parseVersion('v1.0.0-alpha.1+build.123') // { success: true, ... }
|
|
1235
|
+
* parseVersion('invalid') // { success: false, error: '...' }
|
|
1236
|
+
*/
|
|
1237
|
+
function parseVersion(input) {
|
|
1238
|
+
// Input validation
|
|
1239
|
+
if (!input || typeof input !== 'string') {
|
|
1240
|
+
return { success: false, error: 'Version string is required' };
|
|
1241
|
+
}
|
|
1242
|
+
if (input.length > MAX_VERSION_LENGTH) {
|
|
1243
|
+
return { success: false, error: `Version string exceeds maximum length of ${MAX_VERSION_LENGTH}` };
|
|
1244
|
+
}
|
|
1245
|
+
// Strip leading whitespace
|
|
1246
|
+
let pos = 0;
|
|
1247
|
+
while (pos < input.length && isWhitespace(input.charCodeAt(pos))) {
|
|
1248
|
+
pos++;
|
|
1249
|
+
}
|
|
1250
|
+
// Strip trailing whitespace
|
|
1251
|
+
let end = input.length;
|
|
1252
|
+
while (end > pos && isWhitespace(input.charCodeAt(end - 1))) {
|
|
1253
|
+
end--;
|
|
1254
|
+
}
|
|
1255
|
+
// Strip optional leading 'v' or '='
|
|
1256
|
+
if (pos < end) {
|
|
1257
|
+
const code = input.charCodeAt(pos);
|
|
1258
|
+
if (code === 118 || code === 86) {
|
|
1259
|
+
// 'v' or 'V'
|
|
1260
|
+
pos++;
|
|
1261
|
+
}
|
|
1262
|
+
else if (code === 61) {
|
|
1263
|
+
// '='
|
|
1264
|
+
pos++;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
// Parse major version
|
|
1268
|
+
const majorResult = parseNumericIdentifier(input, pos, end);
|
|
1269
|
+
if (!majorResult.success) {
|
|
1270
|
+
return { success: false, error: majorResult.error ?? 'Invalid major version' };
|
|
1271
|
+
}
|
|
1272
|
+
pos = majorResult.endPos;
|
|
1273
|
+
// Expect dot
|
|
1274
|
+
if (pos >= end || input.charCodeAt(pos) !== 46) {
|
|
1275
|
+
// '.'
|
|
1276
|
+
return { success: false, error: 'Expected "." after major version' };
|
|
1277
|
+
}
|
|
1278
|
+
pos++;
|
|
1279
|
+
// Parse minor version
|
|
1280
|
+
const minorResult = parseNumericIdentifier(input, pos, end);
|
|
1281
|
+
if (!minorResult.success) {
|
|
1282
|
+
return { success: false, error: minorResult.error ?? 'Invalid minor version' };
|
|
1283
|
+
}
|
|
1284
|
+
pos = minorResult.endPos;
|
|
1285
|
+
// Expect dot
|
|
1286
|
+
if (pos >= end || input.charCodeAt(pos) !== 46) {
|
|
1287
|
+
// '.'
|
|
1288
|
+
return { success: false, error: 'Expected "." after minor version' };
|
|
1289
|
+
}
|
|
1290
|
+
pos++;
|
|
1291
|
+
// Parse patch version
|
|
1292
|
+
const patchResult = parseNumericIdentifier(input, pos, end);
|
|
1293
|
+
if (!patchResult.success) {
|
|
1294
|
+
return { success: false, error: patchResult.error ?? 'Invalid patch version' };
|
|
1295
|
+
}
|
|
1296
|
+
pos = patchResult.endPos;
|
|
1297
|
+
// Parse optional prerelease
|
|
1298
|
+
const prerelease = [];
|
|
1299
|
+
if (pos < end && input.charCodeAt(pos) === 45) {
|
|
1300
|
+
// '-'
|
|
1301
|
+
pos++;
|
|
1302
|
+
const prereleaseResult = parseIdentifiers(input, pos, end, [43]); // Stop at '+'
|
|
1303
|
+
if (!prereleaseResult.success) {
|
|
1304
|
+
return { success: false, error: prereleaseResult.error ?? 'Invalid prerelease' };
|
|
1305
|
+
}
|
|
1306
|
+
prerelease.push(...prereleaseResult.identifiers);
|
|
1307
|
+
pos = prereleaseResult.endPos;
|
|
1308
|
+
}
|
|
1309
|
+
// Parse optional build metadata
|
|
1310
|
+
const build = [];
|
|
1311
|
+
if (pos < end && input.charCodeAt(pos) === 43) {
|
|
1312
|
+
// '+'
|
|
1313
|
+
pos++;
|
|
1314
|
+
const buildResult = parseIdentifiers(input, pos, end, []);
|
|
1315
|
+
if (!buildResult.success) {
|
|
1316
|
+
return { success: false, error: buildResult.error ?? 'Invalid build metadata' };
|
|
1317
|
+
}
|
|
1318
|
+
build.push(...buildResult.identifiers);
|
|
1319
|
+
pos = buildResult.endPos;
|
|
1320
|
+
}
|
|
1321
|
+
// Check for trailing characters
|
|
1322
|
+
if (pos < end) {
|
|
1323
|
+
return { success: false, error: `Unexpected character at position ${pos}: "${input[pos]}"` };
|
|
1324
|
+
}
|
|
1325
|
+
return {
|
|
1326
|
+
success: true,
|
|
1327
|
+
version: createSemVer({
|
|
1328
|
+
major: majorResult.value,
|
|
1329
|
+
minor: minorResult.value,
|
|
1330
|
+
patch: patchResult.value,
|
|
1331
|
+
prerelease,
|
|
1332
|
+
build,
|
|
1333
|
+
raw: input,
|
|
1334
|
+
}),
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* Parses a numeric identifier (non-negative integer, no leading zeros except for "0").
|
|
1339
|
+
*
|
|
1340
|
+
* @param input - Input string to parse
|
|
1341
|
+
* @param start - Start position in the input
|
|
1342
|
+
* @param end - End position in the input
|
|
1343
|
+
* @returns Numeric parsing result
|
|
1344
|
+
*/
|
|
1345
|
+
function parseNumericIdentifier(input, start, end) {
|
|
1346
|
+
if (start >= end) {
|
|
1347
|
+
return { success: false, value: 0, endPos: start, error: 'Expected numeric identifier' };
|
|
1348
|
+
}
|
|
1349
|
+
let pos = start;
|
|
1350
|
+
const firstCode = input.charCodeAt(pos);
|
|
1351
|
+
// Must start with a digit
|
|
1352
|
+
if (!isDigit(firstCode)) {
|
|
1353
|
+
return { success: false, value: 0, endPos: pos, error: 'Expected digit' };
|
|
1354
|
+
}
|
|
1355
|
+
// Check for leading zero (only "0" is valid, not "01", "007", etc.)
|
|
1356
|
+
if (firstCode === 48 && pos + 1 < end && isDigit(input.charCodeAt(pos + 1))) {
|
|
1357
|
+
return { success: false, value: 0, endPos: pos, error: 'Numeric identifier cannot have leading zeros' };
|
|
1358
|
+
}
|
|
1359
|
+
// Consume digits
|
|
1360
|
+
let value = 0;
|
|
1361
|
+
while (pos < end && isDigit(input.charCodeAt(pos))) {
|
|
1362
|
+
value = value * 10 + (input.charCodeAt(pos) - 48);
|
|
1363
|
+
pos++;
|
|
1364
|
+
// Prevent overflow
|
|
1365
|
+
if (value > Number.MAX_SAFE_INTEGER) {
|
|
1366
|
+
return { success: false, value: 0, endPos: pos, error: 'Numeric identifier is too large' };
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
return { success: true, value, endPos: pos };
|
|
1370
|
+
}
|
|
1371
|
+
/**
|
|
1372
|
+
* Parses dot-separated identifiers (for prerelease/build).
|
|
1373
|
+
*
|
|
1374
|
+
* @param input - Input string to parse
|
|
1375
|
+
* @param start - Start position in the input
|
|
1376
|
+
* @param end - End position in the input
|
|
1377
|
+
* @param stopCodes - Character codes that signal end of identifiers
|
|
1378
|
+
* @returns Identifiers parsing result
|
|
1379
|
+
*/
|
|
1380
|
+
function parseIdentifiers(input, start, end, stopCodes) {
|
|
1381
|
+
const identifiers = [];
|
|
1382
|
+
let pos = start;
|
|
1383
|
+
while (pos < end) {
|
|
1384
|
+
// Check for stop characters
|
|
1385
|
+
if (stopCodes.includes(input.charCodeAt(pos))) {
|
|
1386
|
+
break;
|
|
1387
|
+
}
|
|
1388
|
+
// Parse one identifier
|
|
1389
|
+
const identStart = pos;
|
|
1390
|
+
while (pos < end) {
|
|
1391
|
+
const code = input.charCodeAt(pos);
|
|
1392
|
+
// Stop at dot or stop characters
|
|
1393
|
+
if (code === 46 || stopCodes.includes(code)) {
|
|
1394
|
+
break;
|
|
1395
|
+
}
|
|
1396
|
+
// Must be alphanumeric or hyphen
|
|
1397
|
+
if (!isAlphanumeric(code) && code !== 45) {
|
|
1398
|
+
return { success: false, identifiers: [], endPos: pos, error: `Invalid character in identifier: "${input[pos]}"` };
|
|
1399
|
+
}
|
|
1400
|
+
pos++;
|
|
1401
|
+
}
|
|
1402
|
+
// Empty identifier is not allowed
|
|
1403
|
+
if (pos === identStart) {
|
|
1404
|
+
return { success: false, identifiers: [], endPos: pos, error: 'Empty identifier' };
|
|
1405
|
+
}
|
|
1406
|
+
identifiers.push(input.slice(identStart, pos));
|
|
1407
|
+
// Consume dot separator
|
|
1408
|
+
if (pos < end && input.charCodeAt(pos) === 46) {
|
|
1409
|
+
pos++;
|
|
1410
|
+
// Dot at end is invalid
|
|
1411
|
+
if (pos >= end || stopCodes.includes(input.charCodeAt(pos))) {
|
|
1412
|
+
return { success: false, identifiers: [], endPos: pos, error: 'Identifier expected after dot' };
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
return { success: true, identifiers, endPos: pos };
|
|
1417
|
+
}
|
|
1418
|
+
/**
|
|
1419
|
+
* Checks if a character code is a digit (0-9).
|
|
1420
|
+
*
|
|
1421
|
+
* @param code - Character code to check
|
|
1422
|
+
* @returns True if the code represents a digit
|
|
1423
|
+
*/
|
|
1424
|
+
function isDigit(code) {
|
|
1425
|
+
return code >= 48 && code <= 57;
|
|
1426
|
+
}
|
|
1427
|
+
/**
|
|
1428
|
+
* Checks if a character code is alphanumeric or hyphen.
|
|
1429
|
+
*
|
|
1430
|
+
* @param code - Character code to check
|
|
1431
|
+
* @returns True if the code represents an alphanumeric character
|
|
1432
|
+
*/
|
|
1433
|
+
function isAlphanumeric(code) {
|
|
1434
|
+
return ((code >= 48 && code <= 57) || // 0-9
|
|
1435
|
+
(code >= 65 && code <= 90) || // A-Z
|
|
1436
|
+
(code >= 97 && code <= 122) // a-z
|
|
1437
|
+
);
|
|
1438
|
+
}
|
|
1439
|
+
/**
|
|
1440
|
+
* Checks if a character code is whitespace.
|
|
1441
|
+
*
|
|
1442
|
+
* @param code - Character code to check
|
|
1443
|
+
* @returns True if the code represents whitespace
|
|
1444
|
+
*/
|
|
1445
|
+
function isWhitespace(code) {
|
|
1446
|
+
return code === 32 || code === 9 || code === 10 || code === 13;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
/**
|
|
1450
|
+
* Changelog Range-Based Filtering
|
|
1451
|
+
*
|
|
1452
|
+
* Functions for filtering changelog entries by version range, date range, and count.
|
|
1453
|
+
*/
|
|
1454
|
+
/**
|
|
1455
|
+
* Filters entries by version range using semver.
|
|
1456
|
+
*
|
|
1457
|
+
* @param changelog - The changelog to filter
|
|
1458
|
+
* @param range - Semver range string (e.g., '>=1.0.0 <2.0.0')
|
|
1459
|
+
* @returns A new changelog with entries matching the range
|
|
1460
|
+
*
|
|
1461
|
+
* @example
|
|
1462
|
+
* ```ts
|
|
1463
|
+
* const majors = filterByVersionRange(changelog, '>=1.0.0 <2.0.0')
|
|
1464
|
+
* ```
|
|
1465
|
+
*/
|
|
1466
|
+
function filterByVersionRange(changelog, range) {
|
|
1467
|
+
const rangeResult = parseRange(range);
|
|
1468
|
+
if (!rangeResult.success) {
|
|
1469
|
+
throw createError(`Invalid version range: ${range}`);
|
|
1470
|
+
}
|
|
1471
|
+
return filterEntries(changelog, (entry) => {
|
|
1472
|
+
// Skip unreleased
|
|
1473
|
+
if (entry.unreleased)
|
|
1474
|
+
return false;
|
|
1475
|
+
const versionResult = parseVersion(entry.version);
|
|
1476
|
+
if (!versionResult.success)
|
|
1477
|
+
return false;
|
|
1478
|
+
return satisfies(versionResult.version, rangeResult.range);
|
|
1479
|
+
});
|
|
1480
|
+
}
|
|
1481
|
+
/**
|
|
1482
|
+
* Filters entries from a start version.
|
|
1483
|
+
*
|
|
1484
|
+
* @param changelog - The changelog to filter
|
|
1485
|
+
* @param startVersion - The minimum version (inclusive)
|
|
1486
|
+
* @returns A new changelog with entries >= startVersion
|
|
1487
|
+
*/
|
|
1488
|
+
function filterFromVersion(changelog, startVersion) {
|
|
1489
|
+
const startResult = parseVersion(startVersion);
|
|
1490
|
+
if (!startResult.success) {
|
|
1491
|
+
throw createError(`Invalid start version: ${startVersion}`);
|
|
1492
|
+
}
|
|
1493
|
+
return filterEntries(changelog, (entry) => {
|
|
1494
|
+
if (entry.unreleased)
|
|
1495
|
+
return true;
|
|
1496
|
+
const versionResult = parseVersion(entry.version);
|
|
1497
|
+
if (!versionResult.success)
|
|
1498
|
+
return false;
|
|
1499
|
+
return compare(versionResult.version, startResult.version) >= 0;
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1502
|
+
/**
|
|
1503
|
+
* Filters entries up to an end version.
|
|
1504
|
+
*
|
|
1505
|
+
* @param changelog - The changelog to apply the version filter to
|
|
1506
|
+
* @param endVersion - The maximum version (inclusive)
|
|
1507
|
+
* @returns A new changelog with entries <= endVersion
|
|
1508
|
+
*/
|
|
1509
|
+
function filterToVersion(changelog, endVersion) {
|
|
1510
|
+
const endResult = parseVersion(endVersion);
|
|
1511
|
+
if (!endResult.success) {
|
|
1512
|
+
throw createError(`Invalid end version: ${endVersion}`);
|
|
1513
|
+
}
|
|
1514
|
+
return filterEntries(changelog, (entry) => {
|
|
1515
|
+
if (entry.unreleased)
|
|
1516
|
+
return false;
|
|
1517
|
+
const versionResult = parseVersion(entry.version);
|
|
1518
|
+
if (!versionResult.success)
|
|
1519
|
+
return false;
|
|
1520
|
+
return compare(versionResult.version, endResult.version) <= 0;
|
|
1521
|
+
});
|
|
1522
|
+
}
|
|
1523
|
+
/**
|
|
1524
|
+
* Gets entries within a version range (inclusive).
|
|
1525
|
+
*
|
|
1526
|
+
* @param changelog - The changelog to filter
|
|
1527
|
+
* @param startVersion - The minimum version (inclusive)
|
|
1528
|
+
* @param endVersion - The maximum version (inclusive)
|
|
1529
|
+
* @returns A new changelog with entries in the range
|
|
1530
|
+
*/
|
|
1531
|
+
function filterVersionRange(changelog, startVersion, endVersion) {
|
|
1532
|
+
const startResult = parseVersion(startVersion);
|
|
1533
|
+
const endResult = parseVersion(endVersion);
|
|
1534
|
+
if (!startResult.success) {
|
|
1535
|
+
throw createError(`Invalid start version: ${startVersion}`);
|
|
1536
|
+
}
|
|
1537
|
+
if (!endResult.success) {
|
|
1538
|
+
throw createError(`Invalid end version: ${endVersion}`);
|
|
1539
|
+
}
|
|
1540
|
+
return filterEntries(changelog, (entry) => {
|
|
1541
|
+
if (entry.unreleased)
|
|
1542
|
+
return false;
|
|
1543
|
+
const versionResult = parseVersion(entry.version);
|
|
1544
|
+
if (!versionResult.success)
|
|
1545
|
+
return false;
|
|
1546
|
+
const cmpStart = compare(versionResult.version, startResult.version);
|
|
1547
|
+
const cmpEnd = compare(versionResult.version, endResult.version);
|
|
1548
|
+
return cmpStart >= 0 && cmpEnd <= 0;
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1551
|
+
/**
|
|
1552
|
+
* Gets the N most recent entries.
|
|
1553
|
+
*
|
|
1554
|
+
* @param changelog - The changelog to filter
|
|
1555
|
+
* @param count - Number of entries to keep
|
|
1556
|
+
* @param includeUnreleased - Whether to include unreleased in count (default: false)
|
|
1557
|
+
* @returns A new changelog with only the most recent entries
|
|
1558
|
+
*/
|
|
1559
|
+
function filterRecentEntries(changelog, count, includeUnreleased = false) {
|
|
1560
|
+
if (count <= 0) {
|
|
1561
|
+
return { ...changelog, entries: [] };
|
|
1562
|
+
}
|
|
1563
|
+
const entries = includeUnreleased
|
|
1564
|
+
? changelog.entries.slice(0, count)
|
|
1565
|
+
: (() => {
|
|
1566
|
+
const result = [];
|
|
1567
|
+
let remaining = count;
|
|
1568
|
+
for (const entry of changelog.entries) {
|
|
1569
|
+
if (entry.unreleased) {
|
|
1570
|
+
result.push(entry);
|
|
1571
|
+
}
|
|
1572
|
+
else if (remaining > 0) {
|
|
1573
|
+
result.push(entry);
|
|
1574
|
+
remaining--;
|
|
1575
|
+
}
|
|
1576
|
+
if (remaining <= 0 && !entry.unreleased)
|
|
1577
|
+
break;
|
|
1578
|
+
}
|
|
1579
|
+
return result;
|
|
1580
|
+
})();
|
|
1581
|
+
return { ...changelog, entries };
|
|
1582
|
+
}
|
|
1583
|
+
/**
|
|
1584
|
+
* Filters entries by release date.
|
|
1585
|
+
*
|
|
1586
|
+
* @param changelog - The changelog to filter
|
|
1587
|
+
* @param startDate - Start date (inclusive, ISO format)
|
|
1588
|
+
* @param endDate - End date (inclusive, ISO format)
|
|
1589
|
+
* @returns A new changelog with entries in the date range
|
|
1590
|
+
*/
|
|
1591
|
+
function filterByDateRange(changelog, startDate, endDate) {
|
|
1592
|
+
return filterEntries(changelog, (entry) => {
|
|
1593
|
+
if (!entry.date)
|
|
1594
|
+
return false;
|
|
1595
|
+
if (startDate && entry.date < startDate)
|
|
1596
|
+
return false;
|
|
1597
|
+
if (endDate && entry.date > endDate)
|
|
1598
|
+
return false;
|
|
1599
|
+
return true;
|
|
1600
|
+
});
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
/**
|
|
1604
|
+
* Safe copies of Map built-in via factory function.
|
|
1605
|
+
*
|
|
1606
|
+
* Since constructors cannot be safely captured via Object.assign, this module
|
|
1607
|
+
* provides a factory function that uses Reflect.construct internally.
|
|
1608
|
+
*
|
|
1609
|
+
* These references are captured at module initialization time to protect against
|
|
1610
|
+
* prototype pollution attacks. Import only what you need for tree-shaking.
|
|
1611
|
+
*
|
|
1612
|
+
* @module @hyperfrontend/immutable-api-utils/built-in-copy/map
|
|
1613
|
+
*/
|
|
1614
|
+
// Capture references at module initialization time
|
|
1615
|
+
const _Map = globalThis.Map;
|
|
1616
|
+
const _Reflect = globalThis.Reflect;
|
|
1617
|
+
/**
|
|
1618
|
+
* (Safe copy) Creates a new Map using the captured Map constructor.
|
|
1619
|
+
* Use this instead of `new Map()`.
|
|
1620
|
+
*
|
|
1621
|
+
* @param iterable - Optional iterable of key-value pairs.
|
|
1622
|
+
* @returns A new Map instance.
|
|
1623
|
+
*/
|
|
1624
|
+
const createMap = (iterable) => _Reflect.construct(_Map, iterable ? [iterable] : []);
|
|
1625
|
+
|
|
1626
|
+
/**
|
|
1627
|
+
* Changelog Equality Comparison
|
|
1628
|
+
*
|
|
1629
|
+
* Functions for comparing changelogs for structural equality.
|
|
1630
|
+
* Uses deep comparison without relying on JSON serialization.
|
|
1631
|
+
*/
|
|
1632
|
+
/**
|
|
1633
|
+
* Checks if two changelog entries are equal.
|
|
1634
|
+
*
|
|
1635
|
+
* @param a - First entry
|
|
1636
|
+
* @param b - Second entry
|
|
1637
|
+
* @returns True if entries are equal
|
|
1638
|
+
*/
|
|
1639
|
+
function isEntryEqual(a, b) {
|
|
1640
|
+
if (a.version !== b.version)
|
|
1641
|
+
return false;
|
|
1642
|
+
if (a.date !== b.date)
|
|
1643
|
+
return false;
|
|
1644
|
+
if (a.unreleased !== b.unreleased)
|
|
1645
|
+
return false;
|
|
1646
|
+
if (a.compareUrl !== b.compareUrl)
|
|
1647
|
+
return false;
|
|
1648
|
+
if (a.rawContent !== b.rawContent)
|
|
1649
|
+
return false;
|
|
1650
|
+
// Check sections
|
|
1651
|
+
if (a.sections.length !== b.sections.length)
|
|
1652
|
+
return false;
|
|
1653
|
+
for (let i = 0; i < a.sections.length; i++) {
|
|
1654
|
+
if (!isSectionEqual(a.sections[i], b.sections[i]))
|
|
1655
|
+
return false;
|
|
1656
|
+
}
|
|
1657
|
+
return true;
|
|
1658
|
+
}
|
|
1659
|
+
/**
|
|
1660
|
+
* Checks if two changelog sections are equal.
|
|
1661
|
+
*
|
|
1662
|
+
* @param a - First section
|
|
1663
|
+
* @param b - Second section
|
|
1664
|
+
* @returns True if sections are equal
|
|
1665
|
+
*/
|
|
1666
|
+
function isSectionEqual(a, b) {
|
|
1667
|
+
if (a.type !== b.type)
|
|
1668
|
+
return false;
|
|
1669
|
+
if (a.heading !== b.heading)
|
|
1670
|
+
return false;
|
|
1671
|
+
// Check items
|
|
1672
|
+
if (a.items.length !== b.items.length)
|
|
1673
|
+
return false;
|
|
1674
|
+
for (let i = 0; i < a.items.length; i++) {
|
|
1675
|
+
if (!isItemEqual(a.items[i], b.items[i]))
|
|
1676
|
+
return false;
|
|
1677
|
+
}
|
|
1678
|
+
return true;
|
|
1679
|
+
}
|
|
1680
|
+
/**
|
|
1681
|
+
* Checks if two changelog items are equal.
|
|
1682
|
+
*
|
|
1683
|
+
* @param a - First item
|
|
1684
|
+
* @param b - Second item
|
|
1685
|
+
* @returns True if items are equal
|
|
1686
|
+
*/
|
|
1687
|
+
function isItemEqual(a, b) {
|
|
1688
|
+
if (a.scope !== b.scope)
|
|
1689
|
+
return false;
|
|
1690
|
+
if (a.description !== b.description)
|
|
1691
|
+
return false;
|
|
1692
|
+
if (a.breaking !== b.breaking)
|
|
1693
|
+
return false;
|
|
1694
|
+
// Check commits
|
|
1695
|
+
if (a.commits.length !== b.commits.length)
|
|
1696
|
+
return false;
|
|
1697
|
+
for (let i = 0; i < a.commits.length; i++) {
|
|
1698
|
+
if (!isCommitRefEqual(a.commits[i], b.commits[i]))
|
|
1699
|
+
return false;
|
|
1700
|
+
}
|
|
1701
|
+
// Check references
|
|
1702
|
+
if (a.references.length !== b.references.length)
|
|
1703
|
+
return false;
|
|
1704
|
+
for (let i = 0; i < a.references.length; i++) {
|
|
1705
|
+
if (!isIssueRefEqual(a.references[i], b.references[i]))
|
|
1706
|
+
return false;
|
|
1707
|
+
}
|
|
1708
|
+
return true;
|
|
1709
|
+
}
|
|
1710
|
+
/**
|
|
1711
|
+
* Checks if two commit references are equal.
|
|
1712
|
+
*
|
|
1713
|
+
* @param a - First commit ref
|
|
1714
|
+
* @param b - Second commit ref
|
|
1715
|
+
* @returns True if commit refs are equal
|
|
1716
|
+
*/
|
|
1717
|
+
function isCommitRefEqual(a, b) {
|
|
1718
|
+
return a.hash === b.hash && a.shortHash === b.shortHash && a.url === b.url;
|
|
1719
|
+
}
|
|
1720
|
+
/**
|
|
1721
|
+
* Checks if two issue references are equal.
|
|
1722
|
+
*
|
|
1723
|
+
* @param a - First issue ref
|
|
1724
|
+
* @param b - Second issue ref
|
|
1725
|
+
* @returns True if issue refs are equal
|
|
1726
|
+
*/
|
|
1727
|
+
function isIssueRefEqual(a, b) {
|
|
1728
|
+
return a.number === b.number && a.type === b.type && a.url === b.url;
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
/**
|
|
1732
|
+
* Changelog Merging
|
|
1733
|
+
*
|
|
1734
|
+
* Functions for merging multiple changelogs or changelog entries.
|
|
1735
|
+
*/
|
|
1736
|
+
/**
|
|
1737
|
+
* Default merge options.
|
|
1738
|
+
*/
|
|
1739
|
+
const DEFAULT_MERGE_OPTIONS = {
|
|
1740
|
+
entryStrategy: 'union',
|
|
1741
|
+
sectionStrategy: 'union',
|
|
1742
|
+
itemStrategy: 'union',
|
|
1743
|
+
useSourceHeader: true,
|
|
1744
|
+
sortByVersion: true,
|
|
1745
|
+
removeDuplicates: true,
|
|
1746
|
+
};
|
|
1747
|
+
/**
|
|
1748
|
+
* Merges two changelogs together.
|
|
1749
|
+
*
|
|
1750
|
+
* @param source - The source changelog
|
|
1751
|
+
* @param target - The target changelog
|
|
1752
|
+
* @param options - Optional merge options
|
|
1753
|
+
* @returns The merge result with merged changelog and stats
|
|
1754
|
+
*
|
|
1755
|
+
* @example
|
|
1756
|
+
* ```ts
|
|
1757
|
+
* const result = mergeChangelogs(mainChangelog, branchChangelog)
|
|
1758
|
+
* console.log(`Merged ${result.stats.merged} entries`)
|
|
1759
|
+
* ```
|
|
1760
|
+
*/
|
|
1761
|
+
function mergeChangelogs(source, target, options) {
|
|
1762
|
+
const opts = resolveOptions(options);
|
|
1763
|
+
// Build version maps
|
|
1764
|
+
const sourceByVersion = createMap();
|
|
1765
|
+
const targetByVersion = createMap();
|
|
1766
|
+
for (const entry of source.entries) {
|
|
1767
|
+
sourceByVersion.set(entry.version, entry);
|
|
1768
|
+
}
|
|
1769
|
+
for (const entry of target.entries) {
|
|
1770
|
+
targetByVersion.set(entry.version, entry);
|
|
1771
|
+
}
|
|
1772
|
+
const mergedEntries = [];
|
|
1773
|
+
const allVersions = createSet([...sourceByVersion.keys(), ...targetByVersion.keys()]);
|
|
1774
|
+
let sourceOnly = 0;
|
|
1775
|
+
let targetOnly = 0;
|
|
1776
|
+
let merged = 0;
|
|
1777
|
+
let conflictsResolved = 0;
|
|
1778
|
+
for (const version of allVersions) {
|
|
1779
|
+
const sourceEntry = sourceByVersion.get(version);
|
|
1780
|
+
const targetEntry = targetByVersion.get(version);
|
|
1781
|
+
if (sourceEntry && !targetEntry) {
|
|
1782
|
+
// Only in source
|
|
1783
|
+
mergedEntries.push(sourceEntry);
|
|
1784
|
+
sourceOnly++;
|
|
1785
|
+
}
|
|
1786
|
+
else if (!sourceEntry && targetEntry) {
|
|
1787
|
+
// Only in target
|
|
1788
|
+
mergedEntries.push(targetEntry);
|
|
1789
|
+
targetOnly++;
|
|
1790
|
+
}
|
|
1791
|
+
else if (sourceEntry && targetEntry) {
|
|
1792
|
+
// In both - merge
|
|
1793
|
+
if (isEntryEqual(sourceEntry, targetEntry)) {
|
|
1794
|
+
mergedEntries.push(sourceEntry);
|
|
1795
|
+
}
|
|
1796
|
+
else {
|
|
1797
|
+
const mergedEntry = mergeEntries(sourceEntry, targetEntry, opts);
|
|
1798
|
+
mergedEntries.push(mergedEntry);
|
|
1799
|
+
conflictsResolved++;
|
|
1800
|
+
}
|
|
1801
|
+
merged++;
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
// Sort by version if requested
|
|
1805
|
+
let finalEntries = mergedEntries;
|
|
1806
|
+
if (opts.sortByVersion) {
|
|
1807
|
+
finalEntries = sortEntriesByVersion(mergedEntries);
|
|
1808
|
+
}
|
|
1809
|
+
// Build header
|
|
1810
|
+
const header = opts.useSourceHeader ? source.header : target.header;
|
|
1811
|
+
// Build metadata
|
|
1812
|
+
const metadata = {
|
|
1813
|
+
format: source.metadata.format,
|
|
1814
|
+
isConventional: source.metadata.isConventional || target.metadata.isConventional,
|
|
1815
|
+
repositoryUrl: source.metadata.repositoryUrl ?? target.metadata.repositoryUrl,
|
|
1816
|
+
packageName: source.metadata.packageName ?? target.metadata.packageName,
|
|
1817
|
+
warnings: [...source.metadata.warnings, ...target.metadata.warnings],
|
|
1818
|
+
};
|
|
1819
|
+
const changelog = {
|
|
1820
|
+
header,
|
|
1821
|
+
entries: finalEntries,
|
|
1822
|
+
metadata,
|
|
1823
|
+
};
|
|
1824
|
+
return {
|
|
1825
|
+
changelog,
|
|
1826
|
+
stats: {
|
|
1827
|
+
totalEntries: finalEntries.length,
|
|
1828
|
+
sourceOnly,
|
|
1829
|
+
targetOnly,
|
|
1830
|
+
merged,
|
|
1831
|
+
conflictsResolved,
|
|
1832
|
+
},
|
|
1833
|
+
};
|
|
1834
|
+
}
|
|
1835
|
+
/**
|
|
1836
|
+
* Merges two changelog entries.
|
|
1837
|
+
*
|
|
1838
|
+
* @param source - Source entry
|
|
1839
|
+
* @param target - Target entry
|
|
1840
|
+
* @param opts - Resolved merge options
|
|
1841
|
+
* @returns Merged entry
|
|
1842
|
+
*/
|
|
1843
|
+
function mergeEntries(source, target, opts) {
|
|
1844
|
+
// Use strategy for scalar values
|
|
1845
|
+
const date = opts.entryStrategy === 'target' ? target.date : source.date;
|
|
1846
|
+
const compareUrl = opts.entryStrategy === 'target' ? target.compareUrl : source.compareUrl;
|
|
1847
|
+
const unreleased = source.unreleased || target.unreleased;
|
|
1848
|
+
// Merge sections
|
|
1849
|
+
const sections = mergeSections(source.sections, target.sections, opts);
|
|
1850
|
+
return {
|
|
1851
|
+
version: source.version,
|
|
1852
|
+
date,
|
|
1853
|
+
unreleased,
|
|
1854
|
+
compareUrl,
|
|
1855
|
+
sections,
|
|
1856
|
+
};
|
|
1857
|
+
}
|
|
1858
|
+
/**
|
|
1859
|
+
* Merges section arrays.
|
|
1860
|
+
*
|
|
1861
|
+
* @param sourceSections - Array of sections from the source changelog
|
|
1862
|
+
* @param targetSections - Array of sections from the target changelog
|
|
1863
|
+
* @param opts - Resolved merge options
|
|
1864
|
+
* @returns Merged sections
|
|
1865
|
+
*/
|
|
1866
|
+
function mergeSections(sourceSections, targetSections, opts) {
|
|
1867
|
+
const sourceByType = createMap();
|
|
1868
|
+
const targetByType = createMap();
|
|
1869
|
+
for (const section of sourceSections) {
|
|
1870
|
+
sourceByType.set(section.type, section);
|
|
1871
|
+
}
|
|
1872
|
+
for (const section of targetSections) {
|
|
1873
|
+
targetByType.set(section.type, section);
|
|
1874
|
+
}
|
|
1875
|
+
const result = [];
|
|
1876
|
+
const allTypes = createSet([...sourceByType.keys(), ...targetByType.keys()]);
|
|
1877
|
+
for (const type of allTypes) {
|
|
1878
|
+
const sourceSection = sourceByType.get(type);
|
|
1879
|
+
const targetSection = targetByType.get(type);
|
|
1880
|
+
if (sourceSection && !targetSection) {
|
|
1881
|
+
result.push(sourceSection);
|
|
1882
|
+
}
|
|
1883
|
+
else if (!sourceSection && targetSection) {
|
|
1884
|
+
result.push(targetSection);
|
|
1885
|
+
}
|
|
1886
|
+
else if (sourceSection && targetSection) {
|
|
1887
|
+
if (isSectionEqual(sourceSection, targetSection)) {
|
|
1888
|
+
result.push(sourceSection);
|
|
1889
|
+
}
|
|
1890
|
+
else {
|
|
1891
|
+
const mergedSection = mergeSection(sourceSection, targetSection, opts);
|
|
1892
|
+
result.push(mergedSection);
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
return result;
|
|
1897
|
+
}
|
|
1898
|
+
/**
|
|
1899
|
+
* Merges two sections.
|
|
1900
|
+
*
|
|
1901
|
+
* @param source - Section from the source changelog
|
|
1902
|
+
* @param target - Section from the target changelog
|
|
1903
|
+
* @param opts - Resolved merge options
|
|
1904
|
+
* @returns Merged section
|
|
1905
|
+
*/
|
|
1906
|
+
function mergeSection(source, target, opts) {
|
|
1907
|
+
const heading = opts.sectionStrategy === 'target' ? target.heading : source.heading;
|
|
1908
|
+
const items = mergeItems(source.items, target.items, opts);
|
|
1909
|
+
return {
|
|
1910
|
+
type: source.type,
|
|
1911
|
+
heading,
|
|
1912
|
+
items,
|
|
1913
|
+
};
|
|
1914
|
+
}
|
|
1915
|
+
/**
|
|
1916
|
+
* Merges item arrays.
|
|
1917
|
+
*
|
|
1918
|
+
* @param sourceItems - Array of items from the source section
|
|
1919
|
+
* @param targetItems - Array of items from the target section
|
|
1920
|
+
* @param opts - Resolved merge options
|
|
1921
|
+
* @returns Merged items
|
|
1922
|
+
*/
|
|
1923
|
+
function mergeItems(sourceItems, targetItems, opts) {
|
|
1924
|
+
if (opts.itemStrategy === 'source') {
|
|
1925
|
+
return [...sourceItems];
|
|
1926
|
+
}
|
|
1927
|
+
if (opts.itemStrategy === 'target') {
|
|
1928
|
+
return [...targetItems];
|
|
1929
|
+
}
|
|
1930
|
+
// Union strategy - combine all items
|
|
1931
|
+
const result = [...sourceItems];
|
|
1932
|
+
for (const targetItem of targetItems) {
|
|
1933
|
+
// Check if item already exists in source
|
|
1934
|
+
const exists = sourceItems.some((sourceItem) => isItemEqual(sourceItem, targetItem));
|
|
1935
|
+
if (!exists) {
|
|
1936
|
+
// Check by description only (for 'latest' strategy)
|
|
1937
|
+
if (opts.itemStrategy === 'latest') {
|
|
1938
|
+
const existingIndex = result.findIndex((item) => item.description === targetItem.description);
|
|
1939
|
+
if (existingIndex !== -1) {
|
|
1940
|
+
// Replace with target (newer)
|
|
1941
|
+
result[existingIndex] = targetItem;
|
|
1942
|
+
continue;
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
result.push(targetItem);
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
return result;
|
|
1949
|
+
}
|
|
1950
|
+
/**
|
|
1951
|
+
* Resolves merge options with defaults.
|
|
1952
|
+
*
|
|
1953
|
+
* @param options - Optional merge options to resolve with defaults
|
|
1954
|
+
* @returns Fully resolved merge options with all required fields
|
|
1955
|
+
*/
|
|
1956
|
+
function resolveOptions(options) {
|
|
1957
|
+
if (!options) {
|
|
1958
|
+
return DEFAULT_MERGE_OPTIONS;
|
|
1959
|
+
}
|
|
1960
|
+
return {
|
|
1961
|
+
entryStrategy: options.entryStrategy ?? DEFAULT_MERGE_OPTIONS.entryStrategy,
|
|
1962
|
+
sectionStrategy: options.sectionStrategy ?? DEFAULT_MERGE_OPTIONS.sectionStrategy,
|
|
1963
|
+
itemStrategy: options.itemStrategy ?? DEFAULT_MERGE_OPTIONS.itemStrategy,
|
|
1964
|
+
useSourceHeader: options.useSourceHeader ?? DEFAULT_MERGE_OPTIONS.useSourceHeader,
|
|
1965
|
+
sortByVersion: options.sortByVersion ?? DEFAULT_MERGE_OPTIONS.sortByVersion,
|
|
1966
|
+
removeDuplicates: options.removeDuplicates ?? DEFAULT_MERGE_OPTIONS.removeDuplicates,
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
/**
|
|
1970
|
+
* Sorts entries by version (descending - newest first).
|
|
1971
|
+
*
|
|
1972
|
+
* @param entries - Entries to sort
|
|
1973
|
+
* @returns Sorted entries
|
|
1974
|
+
*/
|
|
1975
|
+
function sortEntriesByVersion(entries) {
|
|
1976
|
+
return [...entries].sort((a, b) => {
|
|
1977
|
+
// Unreleased always comes first
|
|
1978
|
+
if (a.unreleased && !b.unreleased)
|
|
1979
|
+
return -1;
|
|
1980
|
+
if (!a.unreleased && b.unreleased)
|
|
1981
|
+
return 1;
|
|
1982
|
+
if (a.unreleased && b.unreleased)
|
|
1983
|
+
return 0;
|
|
1984
|
+
// Parse versions for comparison
|
|
1985
|
+
const aResult = parseVersion(a.version);
|
|
1986
|
+
const bResult = parseVersion(b.version);
|
|
1987
|
+
if (!aResult.success || !bResult.success) {
|
|
1988
|
+
// Fall back to string comparison
|
|
1989
|
+
return b.version.localeCompare(a.version);
|
|
1990
|
+
}
|
|
1991
|
+
// Sort descending (newest first)
|
|
1992
|
+
return compare(bResult.version, aResult.version);
|
|
1993
|
+
});
|
|
1994
|
+
}
|
|
1995
|
+
/**
|
|
1996
|
+
* Appends entries from target to source (no conflict resolution).
|
|
1997
|
+
*
|
|
1998
|
+
* @param source - The base changelog to append to
|
|
1999
|
+
* @param target - The changelog whose entries will be appended
|
|
2000
|
+
* @param position - Where to insert ('start' or 'end')
|
|
2001
|
+
* @returns A new changelog with combined entries
|
|
2002
|
+
*/
|
|
2003
|
+
function appendChangelog(source, target, position = 'end') {
|
|
2004
|
+
const entries = position === 'start' ? [...target.entries, ...source.entries] : [...source.entries, ...target.entries];
|
|
2005
|
+
return {
|
|
2006
|
+
...source,
|
|
2007
|
+
entries,
|
|
2008
|
+
};
|
|
2009
|
+
}
|
|
2010
|
+
/**
|
|
2011
|
+
* Combines multiple changelogs into one.
|
|
2012
|
+
*
|
|
2013
|
+
* @param changelogs - Array of changelogs to combine
|
|
2014
|
+
* @param options - Optional merge options
|
|
2015
|
+
* @returns The combined changelog
|
|
2016
|
+
*/
|
|
2017
|
+
function combineChangelogs(changelogs, options) {
|
|
2018
|
+
if (changelogs.length === 0) {
|
|
2019
|
+
throw createError('At least one changelog is required');
|
|
2020
|
+
}
|
|
2021
|
+
if (changelogs.length === 1) {
|
|
2022
|
+
return changelogs[0];
|
|
2023
|
+
}
|
|
2024
|
+
let result = changelogs[0];
|
|
2025
|
+
for (let i = 1; i < changelogs.length; i++) {
|
|
2026
|
+
const mergeResult = mergeChangelogs(result, changelogs[i], options);
|
|
2027
|
+
result = mergeResult.changelog;
|
|
2028
|
+
}
|
|
2029
|
+
return result;
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
/**
|
|
2033
|
+
* Maps section headings to their canonical types.
|
|
2034
|
+
* Used during parsing to normalize different heading styles.
|
|
2035
|
+
*/
|
|
2036
|
+
/**
|
|
2037
|
+
* Standard section headings for serialization.
|
|
2038
|
+
* Maps section types to their preferred heading text.
|
|
2039
|
+
*/
|
|
2040
|
+
const SECTION_HEADINGS = {
|
|
2041
|
+
breaking: 'Breaking Changes',
|
|
2042
|
+
features: 'Features',
|
|
2043
|
+
fixes: 'Bug Fixes',
|
|
2044
|
+
performance: 'Performance',
|
|
2045
|
+
documentation: 'Documentation',
|
|
2046
|
+
deprecations: 'Deprecations',
|
|
2047
|
+
refactoring: 'Refactoring',
|
|
2048
|
+
tests: 'Tests',
|
|
2049
|
+
build: 'Build',
|
|
2050
|
+
ci: 'CI',
|
|
2051
|
+
chores: 'Chores',
|
|
2052
|
+
other: 'Other',
|
|
2053
|
+
};
|
|
2054
|
+
|
|
2055
|
+
/**
|
|
2056
|
+
* Changelog Transformation Helpers
|
|
2057
|
+
*
|
|
2058
|
+
* Generic transformation functions for modifying changelogs.
|
|
2059
|
+
*/
|
|
2060
|
+
/**
|
|
2061
|
+
* Transforms all entries in a changelog.
|
|
2062
|
+
*
|
|
2063
|
+
* @param changelog - The changelog to transform
|
|
2064
|
+
* @param transformer - Function to transform each entry
|
|
2065
|
+
* @returns A new changelog with transformed entries
|
|
2066
|
+
*
|
|
2067
|
+
* @example
|
|
2068
|
+
* ```ts
|
|
2069
|
+
* const transformed = transformEntries(changelog, (entry) => ({
|
|
2070
|
+
* ...entry,
|
|
2071
|
+
* date: entry.date?.toUpperCase()
|
|
2072
|
+
* }))
|
|
2073
|
+
* ```
|
|
2074
|
+
*/
|
|
2075
|
+
function transformEntries(changelog, transformer) {
|
|
2076
|
+
const newEntries = changelog.entries.map(transformer);
|
|
2077
|
+
return {
|
|
2078
|
+
...changelog,
|
|
2079
|
+
entries: newEntries,
|
|
2080
|
+
};
|
|
2081
|
+
}
|
|
2082
|
+
/**
|
|
2083
|
+
* Transforms all sections in all entries.
|
|
2084
|
+
*
|
|
2085
|
+
* @param changelog - The changelog to transform
|
|
2086
|
+
* @param transformer - Function to transform each section
|
|
2087
|
+
* @returns A new changelog with transformed sections
|
|
2088
|
+
*/
|
|
2089
|
+
function transformSections(changelog, transformer) {
|
|
2090
|
+
const newEntries = changelog.entries.map((entry) => ({
|
|
2091
|
+
...entry,
|
|
2092
|
+
sections: entry.sections.map((section) => transformer(section, entry)),
|
|
2093
|
+
}));
|
|
2094
|
+
return {
|
|
2095
|
+
...changelog,
|
|
2096
|
+
entries: newEntries,
|
|
2097
|
+
};
|
|
2098
|
+
}
|
|
2099
|
+
/**
|
|
2100
|
+
* Transforms all items in all sections.
|
|
2101
|
+
*
|
|
2102
|
+
* @param changelog - The changelog to transform
|
|
2103
|
+
* @param transformer - Function to transform each item
|
|
2104
|
+
* @returns A new changelog with transformed items
|
|
2105
|
+
*/
|
|
2106
|
+
function transformItems(changelog, transformer) {
|
|
2107
|
+
const newEntries = changelog.entries.map((entry) => ({
|
|
2108
|
+
...entry,
|
|
2109
|
+
sections: entry.sections.map((section) => ({
|
|
2110
|
+
...section,
|
|
2111
|
+
items: section.items.map((item) => transformer(item, section, entry)),
|
|
2112
|
+
})),
|
|
2113
|
+
}));
|
|
2114
|
+
return {
|
|
2115
|
+
...changelog,
|
|
2116
|
+
entries: newEntries,
|
|
2117
|
+
};
|
|
2118
|
+
}
|
|
2119
|
+
/**
|
|
2120
|
+
* Updates the header of a changelog.
|
|
2121
|
+
*
|
|
2122
|
+
* @param changelog - The changelog to update
|
|
2123
|
+
* @param updates - Partial header updates
|
|
2124
|
+
* @returns A new changelog with updated header
|
|
2125
|
+
*/
|
|
2126
|
+
function updateHeader(changelog, updates) {
|
|
2127
|
+
return {
|
|
2128
|
+
...changelog,
|
|
2129
|
+
header: {
|
|
2130
|
+
...changelog.header,
|
|
2131
|
+
...updates,
|
|
2132
|
+
},
|
|
2133
|
+
};
|
|
2134
|
+
}
|
|
2135
|
+
/**
|
|
2136
|
+
* Updates the metadata of a changelog.
|
|
2137
|
+
*
|
|
2138
|
+
* @param changelog - The changelog to update
|
|
2139
|
+
* @param updates - Partial metadata updates
|
|
2140
|
+
* @returns A new changelog with updated metadata
|
|
2141
|
+
*/
|
|
2142
|
+
function updateMetadata(changelog, updates) {
|
|
2143
|
+
return {
|
|
2144
|
+
...changelog,
|
|
2145
|
+
metadata: {
|
|
2146
|
+
...changelog.metadata,
|
|
2147
|
+
...updates,
|
|
2148
|
+
},
|
|
2149
|
+
};
|
|
2150
|
+
}
|
|
2151
|
+
/**
|
|
2152
|
+
* Updates a specific entry by version.
|
|
2153
|
+
*
|
|
2154
|
+
* @param changelog - The changelog to update
|
|
2155
|
+
* @param version - Version of entry to update
|
|
2156
|
+
* @param updates - Partial entry updates or transformer function
|
|
2157
|
+
* @returns A new changelog with updated entry
|
|
2158
|
+
*/
|
|
2159
|
+
function updateEntry(changelog, version, updates) {
|
|
2160
|
+
const entryIndex = changelog.entries.findIndex((e) => e.version === version);
|
|
2161
|
+
if (entryIndex === -1) {
|
|
2162
|
+
throw createError(`Entry with version "${version}" not found`);
|
|
2163
|
+
}
|
|
2164
|
+
const entry = changelog.entries[entryIndex];
|
|
2165
|
+
const updatedEntry = typeof updates === 'function' ? updates(entry) : { ...entry, ...updates };
|
|
2166
|
+
const newEntries = [...changelog.entries];
|
|
2167
|
+
newEntries[entryIndex] = updatedEntry;
|
|
2168
|
+
return {
|
|
2169
|
+
...changelog,
|
|
2170
|
+
entries: newEntries,
|
|
2171
|
+
};
|
|
2172
|
+
}
|
|
2173
|
+
/**
|
|
2174
|
+
* Sorts entries by version (descending - newest first).
|
|
2175
|
+
*
|
|
2176
|
+
* @param changelog - The changelog to sort
|
|
2177
|
+
* @returns A new changelog with sorted entries
|
|
2178
|
+
*/
|
|
2179
|
+
function sortEntries(changelog) {
|
|
2180
|
+
const sorted = [...changelog.entries].sort((a, b) => {
|
|
2181
|
+
// Unreleased always comes first
|
|
2182
|
+
if (a.unreleased && !b.unreleased)
|
|
2183
|
+
return -1;
|
|
2184
|
+
if (!a.unreleased && b.unreleased)
|
|
2185
|
+
return 1;
|
|
2186
|
+
if (a.unreleased && b.unreleased)
|
|
2187
|
+
return 0;
|
|
2188
|
+
const aResult = parseVersion(a.version);
|
|
2189
|
+
const bResult = parseVersion(b.version);
|
|
2190
|
+
if (!aResult.success || !bResult.success) {
|
|
2191
|
+
return b.version.localeCompare(a.version);
|
|
2192
|
+
}
|
|
2193
|
+
return compare(bResult.version, aResult.version);
|
|
2194
|
+
});
|
|
2195
|
+
return {
|
|
2196
|
+
...changelog,
|
|
2197
|
+
entries: sorted,
|
|
2198
|
+
};
|
|
2199
|
+
}
|
|
2200
|
+
/**
|
|
2201
|
+
* Sorts entries by date (newest first).
|
|
2202
|
+
*
|
|
2203
|
+
* @param changelog - The changelog to sort
|
|
2204
|
+
* @returns A new changelog with sorted entries
|
|
2205
|
+
*/
|
|
2206
|
+
function sortEntriesByDate(changelog) {
|
|
2207
|
+
const sorted = [...changelog.entries].sort((a, b) => {
|
|
2208
|
+
// Unreleased always comes first
|
|
2209
|
+
if (a.unreleased && !b.unreleased)
|
|
2210
|
+
return -1;
|
|
2211
|
+
if (!a.unreleased && b.unreleased)
|
|
2212
|
+
return 1;
|
|
2213
|
+
if (a.unreleased && b.unreleased)
|
|
2214
|
+
return 0;
|
|
2215
|
+
// Entries without dates go to the end
|
|
2216
|
+
if (!a.date && b.date)
|
|
2217
|
+
return 1;
|
|
2218
|
+
if (a.date && !b.date)
|
|
2219
|
+
return -1;
|
|
2220
|
+
if (!a.date && !b.date)
|
|
2221
|
+
return 0;
|
|
2222
|
+
// Compare dates (descending)
|
|
2223
|
+
return b.date.localeCompare(a.date);
|
|
2224
|
+
});
|
|
2225
|
+
return {
|
|
2226
|
+
...changelog,
|
|
2227
|
+
entries: sorted,
|
|
2228
|
+
};
|
|
2229
|
+
}
|
|
2230
|
+
/**
|
|
2231
|
+
* Reverses the order of entries.
|
|
2232
|
+
*
|
|
2233
|
+
* @param changelog - The changelog to reverse
|
|
2234
|
+
* @returns A new changelog with reversed entries
|
|
2235
|
+
*/
|
|
2236
|
+
function reverseEntries(changelog) {
|
|
2237
|
+
return {
|
|
2238
|
+
...changelog,
|
|
2239
|
+
entries: [...changelog.entries].reverse(),
|
|
2240
|
+
};
|
|
2241
|
+
}
|
|
2242
|
+
/**
|
|
2243
|
+
* Sorts sections within each entry by a specified order.
|
|
2244
|
+
*
|
|
2245
|
+
* @param changelog - The changelog to sort
|
|
2246
|
+
* @param order - Optional custom section order (defaults to standard order)
|
|
2247
|
+
* @returns A new changelog with sorted sections
|
|
2248
|
+
*/
|
|
2249
|
+
function sortSections(changelog, order) {
|
|
2250
|
+
const defaultOrder = [
|
|
2251
|
+
'breaking',
|
|
2252
|
+
'features',
|
|
2253
|
+
'fixes',
|
|
2254
|
+
'performance',
|
|
2255
|
+
'documentation',
|
|
2256
|
+
'deprecations',
|
|
2257
|
+
'refactoring',
|
|
2258
|
+
'tests',
|
|
2259
|
+
'build',
|
|
2260
|
+
'ci',
|
|
2261
|
+
'chores',
|
|
2262
|
+
'other',
|
|
2263
|
+
];
|
|
2264
|
+
const sectionOrder = order ?? defaultOrder;
|
|
2265
|
+
const orderMap = createMap();
|
|
2266
|
+
sectionOrder.forEach((type, index) => orderMap.set(type, index));
|
|
2267
|
+
const newEntries = changelog.entries.map((entry) => ({
|
|
2268
|
+
...entry,
|
|
2269
|
+
sections: [...entry.sections].sort((a, b) => {
|
|
2270
|
+
const orderA = orderMap.get(a.type) ?? Number.MAX_SAFE_INTEGER;
|
|
2271
|
+
const orderB = orderMap.get(b.type) ?? Number.MAX_SAFE_INTEGER;
|
|
2272
|
+
return orderA - orderB;
|
|
2273
|
+
}),
|
|
2274
|
+
}));
|
|
2275
|
+
return {
|
|
2276
|
+
...changelog,
|
|
2277
|
+
entries: newEntries,
|
|
2278
|
+
};
|
|
2279
|
+
}
|
|
2280
|
+
/**
|
|
2281
|
+
* Normalizes section headings to standard format.
|
|
2282
|
+
*
|
|
2283
|
+
* @param changelog - The changelog to normalize
|
|
2284
|
+
* @returns A new changelog with normalized section headings
|
|
2285
|
+
*/
|
|
2286
|
+
function normalizeSectionHeadings(changelog) {
|
|
2287
|
+
return transformSections(changelog, (section) => ({
|
|
2288
|
+
...section,
|
|
2289
|
+
heading: SECTION_HEADINGS[section.type] ?? section.heading,
|
|
2290
|
+
}));
|
|
2291
|
+
}
|
|
2292
|
+
/**
|
|
2293
|
+
* Removes duplicate items across all sections.
|
|
2294
|
+
*
|
|
2295
|
+
* @param changelog - The changelog to deduplicate
|
|
2296
|
+
* @returns A new changelog without duplicate items
|
|
2297
|
+
*/
|
|
2298
|
+
function deduplicateItems(changelog) {
|
|
2299
|
+
const newEntries = changelog.entries.map((entry) => {
|
|
2300
|
+
const seenDescriptions = createSet();
|
|
2301
|
+
const newSections = entry.sections.map((section) => {
|
|
2302
|
+
const uniqueItems = [];
|
|
2303
|
+
for (const item of section.items) {
|
|
2304
|
+
const key = `${item.scope ?? ''}:${item.description}`;
|
|
2305
|
+
if (!seenDescriptions.has(key)) {
|
|
2306
|
+
seenDescriptions.add(key);
|
|
2307
|
+
uniqueItems.push(item);
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
return {
|
|
2311
|
+
...section,
|
|
2312
|
+
items: uniqueItems,
|
|
2313
|
+
};
|
|
2314
|
+
});
|
|
2315
|
+
return {
|
|
2316
|
+
...entry,
|
|
2317
|
+
sections: newSections,
|
|
2318
|
+
};
|
|
2319
|
+
});
|
|
2320
|
+
return {
|
|
2321
|
+
...changelog,
|
|
2322
|
+
entries: newEntries,
|
|
2323
|
+
};
|
|
2324
|
+
}
|
|
2325
|
+
/**
|
|
2326
|
+
* Compacts a changelog by removing empty sections and entries.
|
|
2327
|
+
*
|
|
2328
|
+
* @param changelog - The changelog to compact
|
|
2329
|
+
* @param keepUnreleased - Whether to keep empty unreleased entry (default: true)
|
|
2330
|
+
* @returns A new compacted changelog
|
|
2331
|
+
*/
|
|
2332
|
+
function compact(changelog, keepUnreleased = true) {
|
|
2333
|
+
const newEntries = changelog.entries
|
|
2334
|
+
.map((entry) => ({
|
|
2335
|
+
...entry,
|
|
2336
|
+
sections: entry.sections.filter((s) => s.items.length > 0),
|
|
2337
|
+
}))
|
|
2338
|
+
.filter((entry) => {
|
|
2339
|
+
const hasContent = entry.sections.length > 0 || entry.rawContent;
|
|
2340
|
+
if (hasContent)
|
|
2341
|
+
return true;
|
|
2342
|
+
if (keepUnreleased && entry.unreleased)
|
|
2343
|
+
return true;
|
|
2344
|
+
return false;
|
|
2345
|
+
});
|
|
2346
|
+
return {
|
|
2347
|
+
...changelog,
|
|
2348
|
+
entries: newEntries,
|
|
2349
|
+
};
|
|
2350
|
+
}
|
|
2351
|
+
/**
|
|
2352
|
+
* Strips metadata from a changelog.
|
|
2353
|
+
*
|
|
2354
|
+
* @param changelog - The changelog to strip
|
|
2355
|
+
* @returns A new changelog with minimal metadata
|
|
2356
|
+
*/
|
|
2357
|
+
function stripMetadata(changelog) {
|
|
2358
|
+
return {
|
|
2359
|
+
...changelog,
|
|
2360
|
+
source: undefined,
|
|
2361
|
+
metadata: {
|
|
2362
|
+
format: changelog.metadata.format,
|
|
2363
|
+
isConventional: changelog.metadata.isConventional,
|
|
2364
|
+
warnings: [],
|
|
2365
|
+
},
|
|
2366
|
+
};
|
|
2367
|
+
}
|
|
2368
|
+
/**
|
|
2369
|
+
* Clones a changelog deeply (for modification without affecting original).
|
|
2370
|
+
*
|
|
2371
|
+
* @param changelog - The changelog to clone
|
|
2372
|
+
* @returns A deep copy of the changelog
|
|
2373
|
+
*/
|
|
2374
|
+
function cloneChangelog(changelog) {
|
|
2375
|
+
return {
|
|
2376
|
+
source: changelog.source,
|
|
2377
|
+
header: {
|
|
2378
|
+
title: changelog.header.title,
|
|
2379
|
+
description: [...changelog.header.description],
|
|
2380
|
+
links: changelog.header.links.map((link) => ({ ...link })),
|
|
2381
|
+
},
|
|
2382
|
+
entries: changelog.entries.map((entry) => ({
|
|
2383
|
+
version: entry.version,
|
|
2384
|
+
date: entry.date,
|
|
2385
|
+
unreleased: entry.unreleased,
|
|
2386
|
+
compareUrl: entry.compareUrl,
|
|
2387
|
+
rawContent: entry.rawContent,
|
|
2388
|
+
sections: entry.sections.map((section) => ({
|
|
2389
|
+
type: section.type,
|
|
2390
|
+
heading: section.heading,
|
|
2391
|
+
items: section.items.map((item) => ({
|
|
2392
|
+
scope: item.scope,
|
|
2393
|
+
description: item.description,
|
|
2394
|
+
breaking: item.breaking,
|
|
2395
|
+
commits: item.commits.map((commit) => ({ ...commit })),
|
|
2396
|
+
references: item.references.map((ref) => ({ ...ref })),
|
|
2397
|
+
})),
|
|
2398
|
+
})),
|
|
2399
|
+
})),
|
|
2400
|
+
metadata: {
|
|
2401
|
+
format: changelog.metadata.format,
|
|
2402
|
+
isConventional: changelog.metadata.isConventional,
|
|
2403
|
+
repositoryUrl: changelog.metadata.repositoryUrl,
|
|
2404
|
+
packageName: changelog.metadata.packageName,
|
|
2405
|
+
warnings: [...changelog.metadata.warnings],
|
|
2406
|
+
},
|
|
2407
|
+
};
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2410
|
+
export { DEFAULT_MERGE_OPTIONS, addEntry, addItemToEntry, addUnreleasedEntry, appendChangelog, cloneChangelog, combineChangelogs, compact, deduplicateItems, excludeByScope, filterBreakingChanges, filterByDateRange, filterByScope, filterByVersionRange, filterEntries, filterFromVersion, filterItems, filterRecentEntries, filterSectionTypes, filterSections, filterToVersion, filterVersionRange, mergeChangelogs, normalizeSectionHeadings, releaseUnreleased, removeEmptyEntries, removeEmptySections, removeEntries, removeEntry, removeItem, removeSection, removeUnreleased, reverseEntries, sortEntries, sortEntriesByDate, sortSections, stripMetadata, transformEntries, transformItems, transformSections, updateEntry, updateHeader, updateMetadata };
|
|
2411
|
+
//# sourceMappingURL=index.esm.js.map
|