@absolutejs/changelog 0.4.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 CHANGED
@@ -6,6 +6,21 @@ 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
+
9
24
  ## 0.4.0 — 2026-09-10
10
25
 
11
26
  ### Added
package/changelog.json CHANGED
@@ -2,6 +2,45 @@
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
+ },
5
44
  {
6
45
  "changes": [
7
46
  {
package/dist/cli.js CHANGED
@@ -438,33 +438,55 @@ var surfaceOfArchive = (archive) => {
438
438
  };
439
439
  var surfaceOfFiles = (files, entries) => {
440
440
  const surface = new Map;
441
- for (const entry of entries)
442
- for (const [name, text] of exportsOf(files, entry, new Set))
443
- if (!surface.has(name))
444
- surface.set(name, text);
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();
445
451
  return surface;
446
452
  };
447
- var typeCandidatesIn = (value) => {
453
+ var exportEntries = (value, packageName, specifier) => {
454
+ const here = specifier ?? packageName;
448
455
  if (typeof value === "string")
449
- return [value];
456
+ return [{ specifier: here, value }];
450
457
  if (typeof value !== "object" || value === null)
451
458
  return [];
452
- return Object.entries(value).flatMap(([key, nested]) => key === "types" || key === "typings" ? typeCandidatesIn(nested) : typeCandidatesIn(nested));
459
+ return Object.entries(value).flatMap(([key, nested]) => key.startsWith(".") ? exportEntries(nested, packageName, key === "." ? packageName : `${packageName}${key.slice(1)}`) : exportEntries(nested, packageName, here));
453
460
  };
454
461
  var typeEntriesOf = (manifest, files) => {
455
462
  const record = typeof manifest === "object" && manifest !== null ? new Map(Object.entries(manifest)) : new Map;
456
- const claimed = [
457
- record.get("types"),
458
- record.get("typings"),
459
- ...typeCandidatesIn(record.get("exports"))
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))
460
480
  ];
461
- const entries = claimed.filter((value) => typeof value === "string").map((value) => value.replace(/^\.\//u, "")).flatMap((cleaned) => DECLARATION.test(cleaned) ? [cleaned] : [
462
- cleaned.replace(/\.[cm]?js$/u, ".d.ts"),
463
- cleaned.replace(/\.[cm]?js$/u, ".d.mts"),
464
- cleaned.replace(/\.[cm]?js$/u, ".d.cts")
465
- ]).filter((candidate) => files.has(candidate));
466
- const fallback = entries.length === 0 && files.has("dist/index.d.ts") ? ["dist/index.d.ts"] : [];
467
- return [...new Set([...entries, ...fallback])];
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
+ });
468
490
  };
469
491
  var SHAPE = {
470
492
  members: "members",
@@ -519,23 +541,33 @@ var changeShape = (change) => {
519
541
  };
520
542
  var diffSurface = (before, after) => {
521
543
  const removed = [];
544
+ const withdrawn = [];
522
545
  const changed = [];
523
- for (const [name, declaration] of before) {
546
+ for (const [name, was] of before) {
524
547
  const now = after.get(name);
525
548
  if (now === undefined)
526
549
  removed.push(name);
527
- else if (now !== declaration)
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)
528
556
  changed.push({
529
- after: now,
530
- before: declaration,
557
+ after: now.declaration,
558
+ before: was.declaration,
531
559
  name,
532
- shape: changeShape({ after: now, before: declaration })
560
+ shape: changeShape({
561
+ after: now.declaration,
562
+ before: was.declaration
563
+ })
533
564
  });
534
565
  }
535
566
  return {
536
567
  added: [...after.keys()].filter((name) => !before.has(name)),
537
568
  changed,
538
- removed
569
+ removed,
570
+ withdrawn
539
571
  };
540
572
  };
541
573
  var notable = (diff) => diff.changed.filter((change) => change.shape === "parameters" || change.shape === "members");
@@ -671,10 +703,11 @@ var describeReconciliation = (found) => {
671
703
  members: "changed the members of its type",
672
704
  parameters: "changed what it is called with",
673
705
  removed: "is no longer exported",
674
- types: "changed its declaration"
706
+ types: "changed its declaration",
707
+ withdrawn: "is no longer exported from"
675
708
  };
676
709
  for (const entry of found.unannounced)
677
- 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`);
678
711
  for (const name of found.unknownSymbols)
679
712
  lines.push(`an entry names \`${name}\`, which this package does not export`);
680
713
  lines.push(...found.wrongMigrations);
@@ -685,6 +718,7 @@ var reconcile = (input) => {
685
718
  const mentioned = new Set(input.changes.flatMap((change) => [...change.symbols ?? []]));
686
719
  const unannounced = [
687
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 })),
688
722
  ...input.diff.changed.filter((change) => !mentioned.has(change.name) && change.shape !== "unknown").map((change) => ({ kind: change.shape, name: change.name }))
689
723
  ];
690
724
  const unknownSymbols = [
@@ -710,6 +744,8 @@ var MAX_ARCHIVE_BYTES = MAX_ARCHIVE_MEBIBYTES * MEBIBYTE2;
710
744
  var MAX_DOWNLOAD_MEBIBYTES = 32;
711
745
  var MAX_DOWNLOAD_BYTES = MAX_DOWNLOAD_MEBIBYTES * MEBIBYTE2;
712
746
  var TIMEOUT_MS = 20000;
747
+ var NOT_FOUND = 404;
748
+ var NOTHING_PUBLISHED = { published: [], tarballs: {} };
713
749
  var PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u;
714
750
  var MAX_NAME_LENGTH = 214;
715
751
  var isPackageName = (name) => name.length <= MAX_NAME_LENGTH && PACKAGE_NAME.test(name);
@@ -753,9 +789,25 @@ var packageArchive = async (name, version, options = {}) => {
753
789
  return;
754
790
  }
755
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();
756
798
  var packageVersions = async (name, options = {}) => {
757
799
  if (!isPackageName(name))
758
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) => {
759
811
  const call = options.fetch ?? fetch;
760
812
  const url = `${options.registry ?? DEFAULT_REGISTRY}/${name.replace("/", "%2f")}`;
761
813
  try {
@@ -764,9 +816,9 @@ var packageVersions = async (name, options = {}) => {
764
816
  signal: AbortSignal.timeout(TIMEOUT_MS)
765
817
  });
766
818
  const response = abbreviated.ok ? abbreviated : await call(url, { signal: AbortSignal.timeout(TIMEOUT_MS) });
767
- if (!response.ok)
768
- return;
769
- return readPackument(await response.json());
819
+ if (response.ok)
820
+ return readPackument(await response.json());
821
+ return response.status === NOT_FOUND ? NOTHING_PUBLISHED : undefined;
770
822
  } catch {
771
823
  return;
772
824
  }
@@ -1111,9 +1163,11 @@ var check = async (root, flags) => {
1111
1163
  const versions = offline ? undefined : await packageVersions(manifest.name ?? "");
1112
1164
  if (!offline && versions === undefined)
1113
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)`);
1114
1168
  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}; this looks like a first release)`);
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)`);
1117
1171
  if (built !== undefined && baseline !== undefined) {
1118
1172
  const published = await publishedSurface(manifest.name ?? "", baseline);
1119
1173
  if (published === undefined)
@@ -1249,5 +1303,5 @@ await main().catch((error) => {
1249
1303
  process.exit(1);
1250
1304
  });
1251
1305
 
1252
- //# debugId=4CD69EF0E551B35864756E2164756E21
1306
+ //# debugId=220FF2C6ABB0241464756E2164756E21
1253
1307
  //# sourceMappingURL=cli.js.map