@absolutejs/changelog 0.4.0 → 0.5.1
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/CHANGELOG.md +21 -0
- package/changelog.json +52 -0
- package/dist/cli.js +99 -37
- package/dist/cli.js.map +7 -7
- package/dist/index.d.ts +2 -2
- package/dist/index.js +96 -35
- package/dist/index.js.map +6 -6
- package/dist/reconcile.d.ts +5 -2
- package/dist/registry.d.ts +14 -11
- package/dist/surface.d.ts +28 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,27 @@ This file is generated by `absolute-changelog` from the entries in
|
|
|
6
6
|
`changelog/`. Edit an entry, not this file — and add new ones under
|
|
7
7
|
`changelog/unreleased/`.
|
|
8
8
|
|
|
9
|
+
## 0.5.1 — 2026-09-10
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **Report a symbols list that is not one, trim a version where it is read, and refuse a fractional contract** (`parseChangelog`)
|
|
14
|
+
|
|
15
|
+
## 0.5.0 — 2026-09-10
|
|
16
|
+
|
|
17
|
+
### Breaking
|
|
18
|
+
|
|
19
|
+
- **A surface records which entry points offer each name, so a name leaving one is a change** (`PackageSurface`, `SurfaceName`, `surfaceOfFiles`, `typeEntriesOf`, `TypeEntry`, `SurfaceDiff`, `Withdrawal`, `Unannounced`)
|
|
20
|
+
_Migration:_ surfaceOfFiles takes { path, specifier } entries and its values are { declaration, entries }; diffSurface reports withdrawn alongside removed.
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **Cache what the registry said for five minutes, so a page asking about twenty packages asks once each** (`forgetRegistry`, `packageVersions`)
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- **Tell a package that has never been published from a registry that will not answer, so a first release is not refused forever** (`packageVersions`)
|
|
29
|
+
|
|
9
30
|
## 0.4.0 — 2026-09-10
|
|
10
31
|
|
|
11
32
|
### Added
|
package/changelog.json
CHANGED
|
@@ -2,6 +2,58 @@
|
|
|
2
2
|
"contract": 1,
|
|
3
3
|
"name": "@absolutejs/changelog",
|
|
4
4
|
"releases": [
|
|
5
|
+
{
|
|
6
|
+
"changes": [
|
|
7
|
+
{
|
|
8
|
+
"kind": "fixed",
|
|
9
|
+
"summary": "Report a symbols list that is not one, trim a version where it is read, and refuse a fractional contract",
|
|
10
|
+
"symbols": [
|
|
11
|
+
"parseChangelog"
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"date": "2026-09-10",
|
|
16
|
+
"version": "0.5.1"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"changes": [
|
|
20
|
+
{
|
|
21
|
+
"kind": "breaking",
|
|
22
|
+
"migration": {
|
|
23
|
+
"instruction": "surfaceOfFiles takes { path, specifier } entries and its values are { declaration, entries }; diffSurface reports withdrawn alongside removed.",
|
|
24
|
+
"manual": true
|
|
25
|
+
},
|
|
26
|
+
"summary": "A surface records which entry points offer each name, so a name leaving one is a change",
|
|
27
|
+
"symbols": [
|
|
28
|
+
"PackageSurface",
|
|
29
|
+
"SurfaceName",
|
|
30
|
+
"surfaceOfFiles",
|
|
31
|
+
"typeEntriesOf",
|
|
32
|
+
"TypeEntry",
|
|
33
|
+
"SurfaceDiff",
|
|
34
|
+
"Withdrawal",
|
|
35
|
+
"Unannounced"
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"kind": "added",
|
|
40
|
+
"summary": "Cache what the registry said for five minutes, so a page asking about twenty packages asks once each",
|
|
41
|
+
"symbols": [
|
|
42
|
+
"forgetRegistry",
|
|
43
|
+
"packageVersions"
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"kind": "fixed",
|
|
48
|
+
"summary": "Tell a package that has never been published from a registry that will not answer, so a first release is not refused forever",
|
|
49
|
+
"symbols": [
|
|
50
|
+
"packageVersions"
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
"date": "2026-09-10",
|
|
55
|
+
"version": "0.5.0"
|
|
56
|
+
},
|
|
5
57
|
{
|
|
6
58
|
"changes": [
|
|
7
59
|
{
|
package/dist/cli.js
CHANGED
|
@@ -113,7 +113,14 @@ var isKind = (value) => typeof value === "string" && CHANGE_KINDS.some((known) =
|
|
|
113
113
|
var isDisruptiveKind = (kind) => kind === "breaking" || kind === "removed";
|
|
114
114
|
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
115
115
|
var isText = (value) => typeof value === "string" && value.trim().length > 0;
|
|
116
|
-
var textList = (value
|
|
116
|
+
var textList = (value, where, problems) => {
|
|
117
|
+
if (value === undefined)
|
|
118
|
+
return;
|
|
119
|
+
if (Array.isArray(value) && value.length > 0 && value.every(isText))
|
|
120
|
+
return value.filter((entry) => typeof entry === "string");
|
|
121
|
+
problems.push(`${where}: symbols is not a list of names`);
|
|
122
|
+
return;
|
|
123
|
+
};
|
|
117
124
|
var readMigration = (value, where, problems) => {
|
|
118
125
|
if (!isRecord(value) || !isText(value.instruction)) {
|
|
119
126
|
problems.push(`${where}: migration needs an instruction`);
|
|
@@ -162,7 +169,7 @@ var readChange = (value, where, problems) => {
|
|
|
162
169
|
...isText(value.reference) ? { reference: value.reference } : {},
|
|
163
170
|
summary: value.summary
|
|
164
171
|
};
|
|
165
|
-
const symbols = textList(value.symbols);
|
|
172
|
+
const symbols = textList(value.symbols, where, problems);
|
|
166
173
|
const [first, ...rest] = symbols ?? [];
|
|
167
174
|
if (kind === "breaking" || kind === "removed") {
|
|
168
175
|
const migration = readMigration(value.migration, where, problems);
|
|
@@ -193,7 +200,8 @@ var readRelease = (value, where, problems) => {
|
|
|
193
200
|
problems.push(`${where}: not an object`);
|
|
194
201
|
return;
|
|
195
202
|
}
|
|
196
|
-
|
|
203
|
+
const version = isText(value.version) ? value.version.trim() : undefined;
|
|
204
|
+
if (version === undefined || parseVersion(version) === undefined) {
|
|
197
205
|
problems.push(`${where}: version is not a version`);
|
|
198
206
|
return;
|
|
199
207
|
}
|
|
@@ -216,7 +224,7 @@ var readRelease = (value, where, problems) => {
|
|
|
216
224
|
changes: [head, ...tail],
|
|
217
225
|
date: value.date,
|
|
218
226
|
...isText(value.note) ? { note: value.note } : {},
|
|
219
|
-
version
|
|
227
|
+
version
|
|
220
228
|
};
|
|
221
229
|
return release;
|
|
222
230
|
};
|
|
@@ -236,7 +244,7 @@ var parseChangelog = (value) => {
|
|
|
236
244
|
return { problems: ["not an object"] };
|
|
237
245
|
if (!isText(value.name))
|
|
238
246
|
problems.push("needs a package name");
|
|
239
|
-
if (
|
|
247
|
+
if (!Number.isInteger(value.contract) || Number(value.contract) > CHANGELOG_CONTRACT)
|
|
240
248
|
problems.push(`contract ${JSON.stringify(value.contract)} is newer than ${CHANGELOG_CONTRACT}, which this reader understands`);
|
|
241
249
|
const raw = value.releases;
|
|
242
250
|
if (!Array.isArray(raw))
|
|
@@ -438,33 +446,55 @@ var surfaceOfArchive = (archive) => {
|
|
|
438
446
|
};
|
|
439
447
|
var surfaceOfFiles = (files, entries) => {
|
|
440
448
|
const surface = new Map;
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
449
|
+
const offered = entries.flatMap((entry) => [...exportsOf(files, entry.path, new Set)].map(([name, declaration]) => ({ declaration, name, specifier: entry.specifier })));
|
|
450
|
+
for (const { declaration, name, specifier } of offered) {
|
|
451
|
+
const known = surface.get(name);
|
|
452
|
+
if (known === undefined)
|
|
453
|
+
surface.set(name, { declaration, entries: [specifier] });
|
|
454
|
+
else if (!known.entries.includes(specifier))
|
|
455
|
+
known.entries.push(specifier);
|
|
456
|
+
}
|
|
457
|
+
for (const value of surface.values())
|
|
458
|
+
value.entries.sort();
|
|
445
459
|
return surface;
|
|
446
460
|
};
|
|
447
|
-
var
|
|
461
|
+
var exportEntries = (value, packageName, specifier) => {
|
|
462
|
+
const here = specifier ?? packageName;
|
|
448
463
|
if (typeof value === "string")
|
|
449
|
-
return [value];
|
|
464
|
+
return [{ specifier: here, value }];
|
|
450
465
|
if (typeof value !== "object" || value === null)
|
|
451
466
|
return [];
|
|
452
|
-
return Object.entries(value).flatMap(([key, nested]) => key
|
|
467
|
+
return Object.entries(value).flatMap(([key, nested]) => key.startsWith(".") ? exportEntries(nested, packageName, key === "." ? packageName : `${packageName}${key.slice(1)}`) : exportEntries(nested, packageName, here));
|
|
453
468
|
};
|
|
454
469
|
var typeEntriesOf = (manifest, files) => {
|
|
455
470
|
const record = typeof manifest === "object" && manifest !== null ? new Map(Object.entries(manifest)) : new Map;
|
|
456
|
-
const
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
471
|
+
const named = record.get("name");
|
|
472
|
+
const packageName = typeof named === "string" ? named : "";
|
|
473
|
+
const resolved = (value, specifier) => {
|
|
474
|
+
if (typeof value !== "string")
|
|
475
|
+
return [];
|
|
476
|
+
const cleaned = value.replace(/^\.\//u, "");
|
|
477
|
+
const candidates = DECLARATION.test(cleaned) ? [cleaned] : [
|
|
478
|
+
cleaned.replace(/\.[cm]?js$/u, ".d.ts"),
|
|
479
|
+
cleaned.replace(/\.[cm]?js$/u, ".d.mts"),
|
|
480
|
+
cleaned.replace(/\.[cm]?js$/u, ".d.cts")
|
|
481
|
+
];
|
|
482
|
+
return candidates.filter((candidate) => files.has(candidate)).map((resolvedPath) => ({ path: resolvedPath, specifier }));
|
|
483
|
+
};
|
|
484
|
+
const found = [
|
|
485
|
+
...resolved(record.get("types"), packageName),
|
|
486
|
+
...resolved(record.get("typings"), packageName),
|
|
487
|
+
...exportEntries(record.get("exports"), packageName).flatMap((entry) => resolved(entry.value, entry.specifier))
|
|
460
488
|
];
|
|
461
|
-
const
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
489
|
+
const fallback = found.length === 0 && files.has("dist/index.d.ts") ? [{ path: "dist/index.d.ts", specifier: packageName }] : [];
|
|
490
|
+
const seen = new Set;
|
|
491
|
+
return [...found, ...fallback].filter((entry) => {
|
|
492
|
+
const key = `${entry.specifier}\x00${entry.path}`;
|
|
493
|
+
if (seen.has(key))
|
|
494
|
+
return false;
|
|
495
|
+
seen.add(key);
|
|
496
|
+
return true;
|
|
497
|
+
});
|
|
468
498
|
};
|
|
469
499
|
var SHAPE = {
|
|
470
500
|
members: "members",
|
|
@@ -519,23 +549,33 @@ var changeShape = (change) => {
|
|
|
519
549
|
};
|
|
520
550
|
var diffSurface = (before, after) => {
|
|
521
551
|
const removed = [];
|
|
552
|
+
const withdrawn = [];
|
|
522
553
|
const changed = [];
|
|
523
|
-
for (const [name,
|
|
554
|
+
for (const [name, was] of before) {
|
|
524
555
|
const now = after.get(name);
|
|
525
556
|
if (now === undefined)
|
|
526
557
|
removed.push(name);
|
|
527
|
-
|
|
558
|
+
if (now === undefined)
|
|
559
|
+
continue;
|
|
560
|
+
const lost = was.entries.filter((entry) => !now.entries.includes(entry));
|
|
561
|
+
if (lost.length > 0)
|
|
562
|
+
withdrawn.push({ from: lost, name });
|
|
563
|
+
if (now.declaration !== was.declaration)
|
|
528
564
|
changed.push({
|
|
529
|
-
after: now,
|
|
530
|
-
before: declaration,
|
|
565
|
+
after: now.declaration,
|
|
566
|
+
before: was.declaration,
|
|
531
567
|
name,
|
|
532
|
-
shape: changeShape({
|
|
568
|
+
shape: changeShape({
|
|
569
|
+
after: now.declaration,
|
|
570
|
+
before: was.declaration
|
|
571
|
+
})
|
|
533
572
|
});
|
|
534
573
|
}
|
|
535
574
|
return {
|
|
536
575
|
added: [...after.keys()].filter((name) => !before.has(name)),
|
|
537
576
|
changed,
|
|
538
|
-
removed
|
|
577
|
+
removed,
|
|
578
|
+
withdrawn
|
|
539
579
|
};
|
|
540
580
|
};
|
|
541
581
|
var notable = (diff) => diff.changed.filter((change) => change.shape === "parameters" || change.shape === "members");
|
|
@@ -671,10 +711,11 @@ var describeReconciliation = (found) => {
|
|
|
671
711
|
members: "changed the members of its type",
|
|
672
712
|
parameters: "changed what it is called with",
|
|
673
713
|
removed: "is no longer exported",
|
|
674
|
-
types: "changed its declaration"
|
|
714
|
+
types: "changed its declaration",
|
|
715
|
+
withdrawn: "is no longer exported from"
|
|
675
716
|
};
|
|
676
717
|
for (const entry of found.unannounced)
|
|
677
|
-
lines.push(`\`${entry.name}\` ${moved[entry.kind]} and no entry says so`);
|
|
718
|
+
lines.push(entry.kind === "withdrawn" ? `\`${entry.name}\` ${moved.withdrawn} ${(entry.from ?? []).join(", ")} and no entry says so` : `\`${entry.name}\` ${moved[entry.kind]} and no entry says so`);
|
|
678
719
|
for (const name of found.unknownSymbols)
|
|
679
720
|
lines.push(`an entry names \`${name}\`, which this package does not export`);
|
|
680
721
|
lines.push(...found.wrongMigrations);
|
|
@@ -685,6 +726,7 @@ var reconcile = (input) => {
|
|
|
685
726
|
const mentioned = new Set(input.changes.flatMap((change) => [...change.symbols ?? []]));
|
|
686
727
|
const unannounced = [
|
|
687
728
|
...input.diff.removed.filter((name) => !disruptive.has(name)).map((name) => ({ kind: "removed", name })),
|
|
729
|
+
...input.diff.withdrawn.filter((entry) => !disruptive.has(entry.name)).map((entry) => ({ from: entry.from, kind: "withdrawn", name: entry.name })),
|
|
688
730
|
...input.diff.changed.filter((change) => !mentioned.has(change.name) && change.shape !== "unknown").map((change) => ({ kind: change.shape, name: change.name }))
|
|
689
731
|
];
|
|
690
732
|
const unknownSymbols = [
|
|
@@ -710,6 +752,8 @@ var MAX_ARCHIVE_BYTES = MAX_ARCHIVE_MEBIBYTES * MEBIBYTE2;
|
|
|
710
752
|
var MAX_DOWNLOAD_MEBIBYTES = 32;
|
|
711
753
|
var MAX_DOWNLOAD_BYTES = MAX_DOWNLOAD_MEBIBYTES * MEBIBYTE2;
|
|
712
754
|
var TIMEOUT_MS = 20000;
|
|
755
|
+
var NOT_FOUND = 404;
|
|
756
|
+
var NOTHING_PUBLISHED = { published: [], tarballs: {} };
|
|
713
757
|
var PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u;
|
|
714
758
|
var MAX_NAME_LENGTH = 214;
|
|
715
759
|
var isPackageName = (name) => name.length <= MAX_NAME_LENGTH && PACKAGE_NAME.test(name);
|
|
@@ -753,9 +797,25 @@ var packageArchive = async (name, version, options = {}) => {
|
|
|
753
797
|
return;
|
|
754
798
|
}
|
|
755
799
|
};
|
|
800
|
+
var packuments = new Map;
|
|
801
|
+
var SECONDS_PER_MINUTE = 60;
|
|
802
|
+
var MILLISECONDS_PER_SECOND = 1000;
|
|
803
|
+
var PACKUMENT_MINUTES = 5;
|
|
804
|
+
var PACKUMENT_TTL_MS = PACKUMENT_MINUTES * SECONDS_PER_MINUTE * MILLISECONDS_PER_SECOND;
|
|
805
|
+
var forgetRegistry = () => packuments.clear();
|
|
756
806
|
var packageVersions = async (name, options = {}) => {
|
|
757
807
|
if (!isPackageName(name))
|
|
758
808
|
return;
|
|
809
|
+
const cacheable = options.fetch === undefined && options.registry === undefined;
|
|
810
|
+
const known = cacheable ? packuments.get(name) : undefined;
|
|
811
|
+
if (known !== undefined && Date.now() - known.asked < PACKUMENT_TTL_MS)
|
|
812
|
+
return known.value;
|
|
813
|
+
const value = askRegistry(name, options);
|
|
814
|
+
if (cacheable)
|
|
815
|
+
packuments.set(name, { asked: Date.now(), value });
|
|
816
|
+
return value;
|
|
817
|
+
};
|
|
818
|
+
var askRegistry = async (name, options) => {
|
|
759
819
|
const call = options.fetch ?? fetch;
|
|
760
820
|
const url = `${options.registry ?? DEFAULT_REGISTRY}/${name.replace("/", "%2f")}`;
|
|
761
821
|
try {
|
|
@@ -764,9 +824,9 @@ var packageVersions = async (name, options = {}) => {
|
|
|
764
824
|
signal: AbortSignal.timeout(TIMEOUT_MS)
|
|
765
825
|
});
|
|
766
826
|
const response = abbreviated.ok ? abbreviated : await call(url, { signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
767
|
-
if (
|
|
768
|
-
return;
|
|
769
|
-
return
|
|
827
|
+
if (response.ok)
|
|
828
|
+
return readPackument(await response.json());
|
|
829
|
+
return response.status === NOT_FOUND ? NOTHING_PUBLISHED : undefined;
|
|
770
830
|
} catch {
|
|
771
831
|
return;
|
|
772
832
|
}
|
|
@@ -1111,9 +1171,11 @@ var check = async (root, flags) => {
|
|
|
1111
1171
|
const versions = offline ? undefined : await packageVersions(manifest.name ?? "");
|
|
1112
1172
|
if (!offline && versions === undefined)
|
|
1113
1173
|
failures.push(`the registry did not answer for ${manifest.name}, so nothing was compared \u2014 pass --offline to skip the comparison deliberately`);
|
|
1174
|
+
if (versions !== undefined && versions.published.length === 0)
|
|
1175
|
+
console.warn(` (${manifest.name} has never been published, so there was nothing to compare)`);
|
|
1114
1176
|
const baseline = (versions?.published ?? []).filter((version) => compareVersions(version, manifest.version) <= 0).sort(compareVersions).at(LAST2);
|
|
1115
|
-
if (versions !== undefined && baseline === undefined && !offline)
|
|
1116
|
-
console.warn(` (nothing published at or below ${manifest.version}
|
|
1177
|
+
if (versions !== undefined && versions.published.length > 0 && baseline === undefined && !offline)
|
|
1178
|
+
console.warn(` (nothing published at or below ${manifest.version}, so there was nothing to compare)`);
|
|
1117
1179
|
if (built !== undefined && baseline !== undefined) {
|
|
1118
1180
|
const published = await publishedSurface(manifest.name ?? "", baseline);
|
|
1119
1181
|
if (published === undefined)
|
|
@@ -1249,5 +1311,5 @@ await main().catch((error) => {
|
|
|
1249
1311
|
process.exit(1);
|
|
1250
1312
|
});
|
|
1251
1313
|
|
|
1252
|
-
//# debugId=
|
|
1314
|
+
//# debugId=3D35EBE83A76D8A664756E2164756E21
|
|
1253
1315
|
//# sourceMappingURL=cli.js.map
|