@absolutejs/changelog 0.3.0 → 0.5.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/CHANGELOG.md +25 -0
- package/changelog.json +53 -0
- package/dist/cli.js +113 -41
- package/dist/cli.js.map +6 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.js +83 -30
- package/dist/index.js.map +5 -5
- 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,31 @@ 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.0 — 2026-09-10
|
|
10
|
+
|
|
11
|
+
### Breaking
|
|
12
|
+
|
|
13
|
+
- **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`)
|
|
14
|
+
_Migration:_ surfaceOfFiles takes { path, specifier } entries and its values are { declaration, entries }; diffSurface reports withdrawn alongside removed.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **Cache what the registry said for five minutes, so a page asking about twenty packages asks once each** (`forgetRegistry`, `packageVersions`)
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **Tell a package that has never been published from a registry that will not answer, so a first release is not refused forever** (`packageVersions`)
|
|
23
|
+
|
|
24
|
+
## 0.4.0 — 2026-09-10
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **A tsconfig with comments is read and wired without being reformatted**
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- **Rollout scripts: an empty --version, a JSONC tsconfig, and a repository that could not be read no longer pass in silence**
|
|
33
|
+
|
|
9
34
|
## 0.3.0 — 2026-09-10
|
|
10
35
|
|
|
11
36
|
### Breaking
|
package/changelog.json
CHANGED
|
@@ -2,6 +2,59 @@
|
|
|
2
2
|
"contract": 1,
|
|
3
3
|
"name": "@absolutejs/changelog",
|
|
4
4
|
"releases": [
|
|
5
|
+
{
|
|
6
|
+
"changes": [
|
|
7
|
+
{
|
|
8
|
+
"kind": "breaking",
|
|
9
|
+
"migration": {
|
|
10
|
+
"instruction": "surfaceOfFiles takes { path, specifier } entries and its values are { declaration, entries }; diffSurface reports withdrawn alongside removed.",
|
|
11
|
+
"manual": true
|
|
12
|
+
},
|
|
13
|
+
"summary": "A surface records which entry points offer each name, so a name leaving one is a change",
|
|
14
|
+
"symbols": [
|
|
15
|
+
"PackageSurface",
|
|
16
|
+
"SurfaceName",
|
|
17
|
+
"surfaceOfFiles",
|
|
18
|
+
"typeEntriesOf",
|
|
19
|
+
"TypeEntry",
|
|
20
|
+
"SurfaceDiff",
|
|
21
|
+
"Withdrawal",
|
|
22
|
+
"Unannounced"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"kind": "added",
|
|
27
|
+
"summary": "Cache what the registry said for five minutes, so a page asking about twenty packages asks once each",
|
|
28
|
+
"symbols": [
|
|
29
|
+
"forgetRegistry",
|
|
30
|
+
"packageVersions"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"kind": "fixed",
|
|
35
|
+
"summary": "Tell a package that has never been published from a registry that will not answer, so a first release is not refused forever",
|
|
36
|
+
"symbols": [
|
|
37
|
+
"packageVersions"
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"date": "2026-09-10",
|
|
42
|
+
"version": "0.5.0"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"changes": [
|
|
46
|
+
{
|
|
47
|
+
"kind": "added",
|
|
48
|
+
"summary": "A tsconfig with comments is read and wired without being reformatted"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"kind": "fixed",
|
|
52
|
+
"summary": "Rollout scripts: an empty --version, a JSONC tsconfig, and a repository that could not be read no longer pass in silence"
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
"date": "2026-09-10",
|
|
56
|
+
"version": "0.4.0"
|
|
57
|
+
},
|
|
5
58
|
{
|
|
6
59
|
"changes": [
|
|
7
60
|
{
|
package/dist/cli.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
// src/cli.ts
|
|
5
5
|
import { existsSync } from "fs";
|
|
6
|
+
import ts2 from "typescript";
|
|
6
7
|
import { mkdir, readFile as readFile3, rm, writeFile as writeFile2 } from "fs/promises";
|
|
7
8
|
import { join as join2 } from "path";
|
|
8
9
|
|
|
@@ -437,33 +438,55 @@ var surfaceOfArchive = (archive) => {
|
|
|
437
438
|
};
|
|
438
439
|
var surfaceOfFiles = (files, entries) => {
|
|
439
440
|
const surface = new Map;
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
441
|
+
const offered = entries.flatMap((entry) => [...exportsOf(files, entry.path, new Set)].map(([name, declaration]) => ({ declaration, name, specifier: entry.specifier })));
|
|
442
|
+
for (const { declaration, name, specifier } of offered) {
|
|
443
|
+
const known = surface.get(name);
|
|
444
|
+
if (known === undefined)
|
|
445
|
+
surface.set(name, { declaration, entries: [specifier] });
|
|
446
|
+
else if (!known.entries.includes(specifier))
|
|
447
|
+
known.entries.push(specifier);
|
|
448
|
+
}
|
|
449
|
+
for (const value of surface.values())
|
|
450
|
+
value.entries.sort();
|
|
444
451
|
return surface;
|
|
445
452
|
};
|
|
446
|
-
var
|
|
453
|
+
var exportEntries = (value, packageName, specifier) => {
|
|
454
|
+
const here = specifier ?? packageName;
|
|
447
455
|
if (typeof value === "string")
|
|
448
|
-
return [value];
|
|
456
|
+
return [{ specifier: here, value }];
|
|
449
457
|
if (typeof value !== "object" || value === null)
|
|
450
458
|
return [];
|
|
451
|
-
return Object.entries(value).flatMap(([key, nested]) => key
|
|
459
|
+
return Object.entries(value).flatMap(([key, nested]) => key.startsWith(".") ? exportEntries(nested, packageName, key === "." ? packageName : `${packageName}${key.slice(1)}`) : exportEntries(nested, packageName, here));
|
|
452
460
|
};
|
|
453
461
|
var typeEntriesOf = (manifest, files) => {
|
|
454
462
|
const record = typeof manifest === "object" && manifest !== null ? new Map(Object.entries(manifest)) : new Map;
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
463
|
+
const named = record.get("name");
|
|
464
|
+
const packageName = typeof named === "string" ? named : "";
|
|
465
|
+
const resolved = (value, specifier) => {
|
|
466
|
+
if (typeof value !== "string")
|
|
467
|
+
return [];
|
|
468
|
+
const cleaned = value.replace(/^\.\//u, "");
|
|
469
|
+
const candidates = DECLARATION.test(cleaned) ? [cleaned] : [
|
|
470
|
+
cleaned.replace(/\.[cm]?js$/u, ".d.ts"),
|
|
471
|
+
cleaned.replace(/\.[cm]?js$/u, ".d.mts"),
|
|
472
|
+
cleaned.replace(/\.[cm]?js$/u, ".d.cts")
|
|
473
|
+
];
|
|
474
|
+
return candidates.filter((candidate) => files.has(candidate)).map((resolvedPath) => ({ path: resolvedPath, specifier }));
|
|
475
|
+
};
|
|
476
|
+
const found = [
|
|
477
|
+
...resolved(record.get("types"), packageName),
|
|
478
|
+
...resolved(record.get("typings"), packageName),
|
|
479
|
+
...exportEntries(record.get("exports"), packageName).flatMap((entry) => resolved(entry.value, entry.specifier))
|
|
459
480
|
];
|
|
460
|
-
const
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
481
|
+
const fallback = found.length === 0 && files.has("dist/index.d.ts") ? [{ path: "dist/index.d.ts", specifier: packageName }] : [];
|
|
482
|
+
const seen = new Set;
|
|
483
|
+
return [...found, ...fallback].filter((entry) => {
|
|
484
|
+
const key = `${entry.specifier}\x00${entry.path}`;
|
|
485
|
+
if (seen.has(key))
|
|
486
|
+
return false;
|
|
487
|
+
seen.add(key);
|
|
488
|
+
return true;
|
|
489
|
+
});
|
|
467
490
|
};
|
|
468
491
|
var SHAPE = {
|
|
469
492
|
members: "members",
|
|
@@ -518,23 +541,33 @@ var changeShape = (change) => {
|
|
|
518
541
|
};
|
|
519
542
|
var diffSurface = (before, after) => {
|
|
520
543
|
const removed = [];
|
|
544
|
+
const withdrawn = [];
|
|
521
545
|
const changed = [];
|
|
522
|
-
for (const [name,
|
|
546
|
+
for (const [name, was] of before) {
|
|
523
547
|
const now = after.get(name);
|
|
524
548
|
if (now === undefined)
|
|
525
549
|
removed.push(name);
|
|
526
|
-
|
|
550
|
+
if (now === undefined)
|
|
551
|
+
continue;
|
|
552
|
+
const lost = was.entries.filter((entry) => !now.entries.includes(entry));
|
|
553
|
+
if (lost.length > 0)
|
|
554
|
+
withdrawn.push({ from: lost, name });
|
|
555
|
+
if (now.declaration !== was.declaration)
|
|
527
556
|
changed.push({
|
|
528
|
-
after: now,
|
|
529
|
-
before: declaration,
|
|
557
|
+
after: now.declaration,
|
|
558
|
+
before: was.declaration,
|
|
530
559
|
name,
|
|
531
|
-
shape: changeShape({
|
|
560
|
+
shape: changeShape({
|
|
561
|
+
after: now.declaration,
|
|
562
|
+
before: was.declaration
|
|
563
|
+
})
|
|
532
564
|
});
|
|
533
565
|
}
|
|
534
566
|
return {
|
|
535
567
|
added: [...after.keys()].filter((name) => !before.has(name)),
|
|
536
568
|
changed,
|
|
537
|
-
removed
|
|
569
|
+
removed,
|
|
570
|
+
withdrawn
|
|
538
571
|
};
|
|
539
572
|
};
|
|
540
573
|
var notable = (diff) => diff.changed.filter((change) => change.shape === "parameters" || change.shape === "members");
|
|
@@ -670,10 +703,11 @@ var describeReconciliation = (found) => {
|
|
|
670
703
|
members: "changed the members of its type",
|
|
671
704
|
parameters: "changed what it is called with",
|
|
672
705
|
removed: "is no longer exported",
|
|
673
|
-
types: "changed its declaration"
|
|
706
|
+
types: "changed its declaration",
|
|
707
|
+
withdrawn: "is no longer exported from"
|
|
674
708
|
};
|
|
675
709
|
for (const entry of found.unannounced)
|
|
676
|
-
lines.push(`\`${entry.name}\` ${moved[entry.kind]} and no entry says so`);
|
|
710
|
+
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`);
|
|
677
711
|
for (const name of found.unknownSymbols)
|
|
678
712
|
lines.push(`an entry names \`${name}\`, which this package does not export`);
|
|
679
713
|
lines.push(...found.wrongMigrations);
|
|
@@ -684,6 +718,7 @@ var reconcile = (input) => {
|
|
|
684
718
|
const mentioned = new Set(input.changes.flatMap((change) => [...change.symbols ?? []]));
|
|
685
719
|
const unannounced = [
|
|
686
720
|
...input.diff.removed.filter((name) => !disruptive.has(name)).map((name) => ({ kind: "removed", name })),
|
|
721
|
+
...input.diff.withdrawn.filter((entry) => !disruptive.has(entry.name)).map((entry) => ({ from: entry.from, kind: "withdrawn", name: entry.name })),
|
|
687
722
|
...input.diff.changed.filter((change) => !mentioned.has(change.name) && change.shape !== "unknown").map((change) => ({ kind: change.shape, name: change.name }))
|
|
688
723
|
];
|
|
689
724
|
const unknownSymbols = [
|
|
@@ -709,6 +744,8 @@ var MAX_ARCHIVE_BYTES = MAX_ARCHIVE_MEBIBYTES * MEBIBYTE2;
|
|
|
709
744
|
var MAX_DOWNLOAD_MEBIBYTES = 32;
|
|
710
745
|
var MAX_DOWNLOAD_BYTES = MAX_DOWNLOAD_MEBIBYTES * MEBIBYTE2;
|
|
711
746
|
var TIMEOUT_MS = 20000;
|
|
747
|
+
var NOT_FOUND = 404;
|
|
748
|
+
var NOTHING_PUBLISHED = { published: [], tarballs: {} };
|
|
712
749
|
var PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u;
|
|
713
750
|
var MAX_NAME_LENGTH = 214;
|
|
714
751
|
var isPackageName = (name) => name.length <= MAX_NAME_LENGTH && PACKAGE_NAME.test(name);
|
|
@@ -752,9 +789,25 @@ var packageArchive = async (name, version, options = {}) => {
|
|
|
752
789
|
return;
|
|
753
790
|
}
|
|
754
791
|
};
|
|
792
|
+
var packuments = new Map;
|
|
793
|
+
var SECONDS_PER_MINUTE = 60;
|
|
794
|
+
var MILLISECONDS_PER_SECOND = 1000;
|
|
795
|
+
var PACKUMENT_MINUTES = 5;
|
|
796
|
+
var PACKUMENT_TTL_MS = PACKUMENT_MINUTES * SECONDS_PER_MINUTE * MILLISECONDS_PER_SECOND;
|
|
797
|
+
var forgetRegistry = () => packuments.clear();
|
|
755
798
|
var packageVersions = async (name, options = {}) => {
|
|
756
799
|
if (!isPackageName(name))
|
|
757
800
|
return;
|
|
801
|
+
const cacheable = options.fetch === undefined && options.registry === undefined;
|
|
802
|
+
const known = cacheable ? packuments.get(name) : undefined;
|
|
803
|
+
if (known !== undefined && Date.now() - known.asked < PACKUMENT_TTL_MS)
|
|
804
|
+
return known.value;
|
|
805
|
+
const value = askRegistry(name, options);
|
|
806
|
+
if (cacheable)
|
|
807
|
+
packuments.set(name, { asked: Date.now(), value });
|
|
808
|
+
return value;
|
|
809
|
+
};
|
|
810
|
+
var askRegistry = async (name, options) => {
|
|
758
811
|
const call = options.fetch ?? fetch;
|
|
759
812
|
const url = `${options.registry ?? DEFAULT_REGISTRY}/${name.replace("/", "%2f")}`;
|
|
760
813
|
try {
|
|
@@ -763,9 +816,9 @@ var packageVersions = async (name, options = {}) => {
|
|
|
763
816
|
signal: AbortSignal.timeout(TIMEOUT_MS)
|
|
764
817
|
});
|
|
765
818
|
const response = abbreviated.ok ? abbreviated : await call(url, { signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
766
|
-
if (
|
|
767
|
-
return;
|
|
768
|
-
return
|
|
819
|
+
if (response.ok)
|
|
820
|
+
return readPackument(await response.json());
|
|
821
|
+
return response.status === NOT_FOUND ? NOTHING_PUBLISHED : undefined;
|
|
769
822
|
} catch {
|
|
770
823
|
return;
|
|
771
824
|
}
|
|
@@ -1090,8 +1143,8 @@ var check = async (root, flags) => {
|
|
|
1090
1143
|
});
|
|
1091
1144
|
if (actual !== expected)
|
|
1092
1145
|
failures.push(`${CHANGELOG_MARKDOWN} is not what the entries say it should be \u2014 it is generated; run \`absolute-changelog release\` or restore it`);
|
|
1093
|
-
const tsconfig = await
|
|
1094
|
-
const include =
|
|
1146
|
+
const tsconfig = await readTsconfig(join2(root, "tsconfig.json"));
|
|
1147
|
+
const include = includeOf(tsconfig?.config);
|
|
1095
1148
|
if (Array.isArray(include) && !coversEntries(include))
|
|
1096
1149
|
failures.push('tsconfig.json does not compile changelog/, so nothing is checking the entries \u2014 add "changelog/**/*" to its include list');
|
|
1097
1150
|
for (const stray of await strayEntries(root))
|
|
@@ -1110,9 +1163,11 @@ var check = async (root, flags) => {
|
|
|
1110
1163
|
const versions = offline ? undefined : await packageVersions(manifest.name ?? "");
|
|
1111
1164
|
if (!offline && versions === undefined)
|
|
1112
1165
|
failures.push(`the registry did not answer for ${manifest.name}, so nothing was compared \u2014 pass --offline to skip the comparison deliberately`);
|
|
1166
|
+
if (versions !== undefined && versions.published.length === 0)
|
|
1167
|
+
console.warn(` (${manifest.name} has never been published, so there was nothing to compare)`);
|
|
1113
1168
|
const baseline = (versions?.published ?? []).filter((version) => compareVersions(version, manifest.version) <= 0).sort(compareVersions).at(LAST2);
|
|
1114
|
-
if (versions !== undefined && baseline === undefined && !offline)
|
|
1115
|
-
console.warn(` (nothing published at or below ${manifest.version}
|
|
1169
|
+
if (versions !== undefined && versions.published.length > 0 && baseline === undefined && !offline)
|
|
1170
|
+
console.warn(` (nothing published at or below ${manifest.version}, so there was nothing to compare)`);
|
|
1116
1171
|
if (built !== undefined && baseline !== undefined) {
|
|
1117
1172
|
const published = await publishedSurface(manifest.name ?? "", baseline);
|
|
1118
1173
|
if (published === undefined)
|
|
@@ -1142,18 +1197,35 @@ var subpathsOf = (manifest) => {
|
|
|
1142
1197
|
return new Set(Object.keys(exports).map((key) => key === "." ? manifest.name ?? "" : `${manifest.name}${key.slice(1)}`));
|
|
1143
1198
|
};
|
|
1144
1199
|
var CHECKING_CONFIGS = ["tsconfig.json", "tsconfig.eslint.json"];
|
|
1200
|
+
var readTsconfig = async (path2) => {
|
|
1201
|
+
const source = await readFile3(path2, "utf8").catch(() => {
|
|
1202
|
+
return;
|
|
1203
|
+
});
|
|
1204
|
+
if (source === undefined)
|
|
1205
|
+
return;
|
|
1206
|
+
const read = ts2.parseConfigFileTextToJson(path2, source);
|
|
1207
|
+
if (read.error !== undefined)
|
|
1208
|
+
return;
|
|
1209
|
+
const { config } = read;
|
|
1210
|
+
const parsed = config;
|
|
1211
|
+
return { config: parsed, source };
|
|
1212
|
+
};
|
|
1145
1213
|
var coversEntries = (include) => Array.isArray(include) && include.some((entry) => typeof entry === "string" && entry.startsWith("changelog"));
|
|
1214
|
+
var includeOf = (config) => typeof config === "object" && config !== null ? new Map(Object.entries(config)).get("include") : undefined;
|
|
1146
1215
|
var includeEntriesIn = async (root, file) => {
|
|
1147
1216
|
const path2 = join2(root, file);
|
|
1148
|
-
const
|
|
1149
|
-
if (
|
|
1217
|
+
const read = await readTsconfig(path2);
|
|
1218
|
+
if (read === undefined)
|
|
1150
1219
|
return false;
|
|
1151
|
-
|
|
1220
|
+
const include = includeOf(read.config);
|
|
1221
|
+
if (!Array.isArray(include) || coversEntries(include))
|
|
1152
1222
|
return false;
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1223
|
+
const opened = /"include"\s*:\s*\[/u.exec(read.source);
|
|
1224
|
+
if (opened === null)
|
|
1225
|
+
return false;
|
|
1226
|
+
const opens = opened.index + opened[0].length;
|
|
1227
|
+
await writeFile2(path2, `${read.source.slice(0, opens)}
|
|
1228
|
+
"changelog/**/*",${read.source.slice(opens)}`);
|
|
1157
1229
|
return true;
|
|
1158
1230
|
};
|
|
1159
1231
|
var includeEntries = async (root) => {
|
|
@@ -1231,5 +1303,5 @@ await main().catch((error) => {
|
|
|
1231
1303
|
process.exit(1);
|
|
1232
1304
|
});
|
|
1233
1305
|
|
|
1234
|
-
//# debugId=
|
|
1306
|
+
//# debugId=220FF2C6ABB0241464756E2164756E21
|
|
1235
1307
|
//# sourceMappingURL=cli.js.map
|