@absolutejs/changelog 0.6.0 → 0.7.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 CHANGED
@@ -6,6 +6,19 @@ 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.7.1 — 2026-10-07
10
+
11
+ ### Fixed
12
+
13
+ - **Quote style, comments and line wrapping no longer read as API changes** (`diffSurface`)
14
+ Two builds of the same API can print a declaration differently: a formatter swaps 'single' for "double" quotes, wraps a parameter list and adds a trailing comma, or a doc comment is reworded. The release gate and the upgrade advisor reported each of those as a changed export. Declarations are now compared token by token, with string literals in one form and comments, trailing commas and spacing ignored; a different string, type or parameter is still a change. The declarations shown in a report keep their own text.
15
+
16
+ ## 0.7.0 — 2026-09-10
17
+
18
+ ### Added
19
+
20
+ - **Say when the build is older than the source it was built from, because the gate compares what is on disk** (`buildIsStale`)
21
+
9
22
  ## 0.6.0 — 2026-09-10
10
23
 
11
24
  ### Added
package/changelog.json CHANGED
@@ -2,6 +2,33 @@
2
2
  "contract": 1,
3
3
  "name": "@absolutejs/changelog",
4
4
  "releases": [
5
+ {
6
+ "changes": [
7
+ {
8
+ "detail": "Two builds of the same API can print a declaration differently: a formatter swaps 'single' for \"double\" quotes, wraps a parameter list and adds a trailing comma, or a doc comment is reworded. The release gate and the upgrade advisor reported each of those as a changed export. Declarations are now compared token by token, with string literals in one form and comments, trailing commas and spacing ignored; a different string, type or parameter is still a change. The declarations shown in a report keep their own text.",
9
+ "kind": "fixed",
10
+ "summary": "Quote style, comments and line wrapping no longer read as API changes",
11
+ "symbols": [
12
+ "diffSurface"
13
+ ]
14
+ }
15
+ ],
16
+ "date": "2026-10-07",
17
+ "version": "0.7.1"
18
+ },
19
+ {
20
+ "changes": [
21
+ {
22
+ "kind": "added",
23
+ "summary": "Say when the build is older than the source it was built from, because the gate compares what is on disk",
24
+ "symbols": [
25
+ "buildIsStale"
26
+ ]
27
+ }
28
+ ],
29
+ "date": "2026-09-10",
30
+ "version": "0.7.0"
31
+ },
5
32
  {
6
33
  "changes": [
7
34
  {
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import { mkdir, readFile as readFile3, rm, writeFile as writeFile2 } from "fs/pr
8
8
  import { join as join2 } from "path";
9
9
 
10
10
  // src/entries.ts
11
- import { readdir, readFile } from "fs/promises";
11
+ import { readdir, readFile, stat } from "fs/promises";
12
12
  import { join } from "path";
13
13
  import { pathToFileURL } from "url";
14
14
 
@@ -337,6 +337,38 @@ var readTar = (archive, keep, limits = {}) => {
337
337
  var DECLARATION = /\.d\.[cm]?ts$/u;
338
338
  var parse = (source, name) => ts.createSourceFile(name, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
339
339
  var normalize = (text) => text.replace(/\s+/gu, " ").trim();
340
+ var CLOSERS = new Set(["}", ")", "]", ">"]);
341
+ var wordy = (token) => /^[\w$#@]/u.test(token) || /[\w$]$/u.test(token);
342
+ var LAST2 = -1;
343
+ var tokenText = (scanner, scanned, state) => {
344
+ const closesTemplate = scanned === ts.SyntaxKind.CloseBraceToken && state.templates.at(LAST2) === state.depth;
345
+ const token = closesTemplate ? scanner.reScanTemplateToken(false) : scanned;
346
+ if (closesTemplate && token === ts.SyntaxKind.TemplateTail)
347
+ state.templates.pop();
348
+ if (closesTemplate)
349
+ return scanner.getTokenText();
350
+ if (token === ts.SyntaxKind.StringLiteral)
351
+ return JSON.stringify(scanner.getTokenValue());
352
+ if (token === ts.SyntaxKind.TemplateHead)
353
+ state.templates.push(state.depth);
354
+ if (token === ts.SyntaxKind.OpenBraceToken)
355
+ state.depth += 1;
356
+ if (token === ts.SyntaxKind.CloseBraceToken)
357
+ state.depth -= 1;
358
+ return scanner.getTokenText();
359
+ };
360
+ var canonical = (text) => {
361
+ const scanner = ts.createScanner(ts.ScriptTarget.Latest, true, ts.LanguageVariant.Standard, text);
362
+ const state = { depth: 0, templates: [] };
363
+ const tokens = [];
364
+ for (let token = scanner.scan();token !== ts.SyntaxKind.EndOfFileToken; token = scanner.scan()) {
365
+ const value = tokenText(scanner, token, state);
366
+ if (CLOSERS.has(value) && tokens.at(LAST2) === ",")
367
+ tokens.pop();
368
+ tokens.push(value);
369
+ }
370
+ return tokens.reduce((joined, token, index) => index > 0 && wordy(tokens[index - 1] ?? "") && wordy(token) ? `${joined} ${token}` : `${joined}${token}`, "");
371
+ };
340
372
  var resolveFrom = (files, from, specifier) => {
341
373
  if (!specifier.startsWith("."))
342
374
  return;
@@ -502,7 +534,7 @@ var SHAPE = {
502
534
  types: "types",
503
535
  unknown: "unknown"
504
536
  };
505
- var isStub = (declaration) => declaration.startsWith("export {");
537
+ var isStub = (declaration) => /^export\s*\{/u.test(declaration);
506
538
  var signatureOf = (declaration) => {
507
539
  const file = parse(`${declaration}
508
540
  `, "declaration.d.ts");
@@ -518,7 +550,7 @@ var parametersOf = (declaration) => {
518
550
  const found = [];
519
551
  const visit = (node) => {
520
552
  if (ts.isFunctionTypeNode(node) || ts.isFunctionDeclaration(node) || ts.isMethodSignature(node) || ts.isMethodDeclaration(node) || ts.isConstructSignatureDeclaration(node) || ts.isConstructorDeclaration(node))
521
- found.push(node.parameters.map((parameter) => normalize(parameter.getText())).join(", "));
553
+ found.push(node.parameters.map((parameter) => canonical(parameter.getText())).join(", "));
522
554
  ts.forEachChild(node, visit);
523
555
  };
524
556
  visit(statement);
@@ -560,7 +592,7 @@ var diffSurface = (before, after) => {
560
592
  const lost = was.entries.filter((entry) => !now.entries.includes(entry));
561
593
  if (lost.length > 0)
562
594
  withdrawn.push({ from: lost, name });
563
- if (now.declaration !== was.declaration)
595
+ if (canonical(now.declaration) !== canonical(was.declaration))
564
596
  changed.push({
565
597
  after: now.declaration,
566
598
  before: was.declaration,
@@ -581,9 +613,9 @@ var diffSurface = (before, after) => {
581
613
  var notable = (diff) => diff.changed.filter((change) => change.shape === "parameters" || change.shape === "members");
582
614
 
583
615
  // src/entries.ts
584
- var loadEntry = async (path2) => {
616
+ var loadEntry = async (path) => {
585
617
  try {
586
- const module = await import(pathToFileURL(path2).href);
618
+ const module = await import(pathToFileURL(path).href);
587
619
  const named = typeof module === "object" && module !== null && "change" in module ? module.change : undefined;
588
620
  const exported = named ?? (typeof module === "object" && module !== null && "default" in module ? module.default : undefined);
589
621
  const read = { exported };
@@ -598,6 +630,23 @@ var loadEntry = async (path2) => {
598
630
  var ENTRY = /\.[cm]?ts$/u;
599
631
  var DECLARATION2 = /\.d\.[cm]?ts$/u;
600
632
  var OUTPUT_DIRECTORIES = ["dist", "build", "lib", "types", "out"];
633
+ var newestUnder = async (root) => {
634
+ const found = await readdir(root, { withFileTypes: true }).catch(() => []);
635
+ const times = await Promise.all(found.map(async (entry) => {
636
+ const path = join(root, entry.name);
637
+ if (entry.isDirectory())
638
+ return newestUnder(path);
639
+ return stat(path).then((stats) => stats.mtimeMs, () => 0);
640
+ }));
641
+ return Math.max(0, ...times);
642
+ };
643
+ var buildIsStale = async (packageRoot) => {
644
+ const [source, built] = await Promise.all([
645
+ newestUnder(join(packageRoot, "src")),
646
+ Promise.all(OUTPUT_DIRECTORIES.map((directory) => newestUnder(join(packageRoot, directory)))).then((times) => Math.max(0, ...times))
647
+ ]);
648
+ return source > 0 && built > 0 && source > built;
649
+ };
601
650
  var builtSurface = async (packageRoot) => {
602
651
  const manifest = await readJsonFile(join(packageRoot, "package.json"));
603
652
  const files = new Map;
@@ -643,8 +692,8 @@ var loadUnreleased = async (packageRoot) => {
643
692
  }
644
693
  return { entries, problems };
645
694
  };
646
- var readJsonFile = async (path2) => {
647
- const source = await readFile(path2, "utf8").catch(() => {
695
+ var readJsonFile = async (path) => {
696
+ const source = await readFile(path, "utf8").catch(() => {
648
697
  return;
649
698
  });
650
699
  if (source === undefined)
@@ -667,9 +716,9 @@ var indentOf = (source) => {
667
716
  const found = /\n([\t ]+)\S/u.exec(source)?.[1];
668
717
  return found === undefined || found === "" ? "\t" : found;
669
718
  };
670
- var writeJsonLike = async (path2, document) => {
671
- const source = await readFile2(path2, "utf8").catch(() => "");
672
- await writeFile(path2, `${JSON.stringify(document, null, indentOf(source))}
719
+ var writeJsonLike = async (path, document) => {
720
+ const source = await readFile2(path, "utf8").catch(() => "");
721
+ await writeFile(path, `${JSON.stringify(document, null, indentOf(source))}
673
722
  `);
674
723
  };
675
724
 
@@ -835,7 +884,7 @@ var publishedChangelog = async (name, version, options = {}) => {
835
884
  const archive = await packageArchive(name, version, options);
836
885
  if (!archive)
837
886
  return;
838
- const file = readTar(archive, (path2) => path2 === CHANGELOG_FILE).get(CHANGELOG_FILE);
887
+ const file = readTar(archive, (path) => path === CHANGELOG_FILE).get(CHANGELOG_FILE);
839
888
  if (!file)
840
889
  return;
841
890
  try {
@@ -1164,6 +1213,8 @@ var check = async (root, flags) => {
1164
1213
  const offline = flags.offline === true;
1165
1214
  const built = await builtSurface(root);
1166
1215
  const shipsTypes = declaresTypes(await readJsonFile(join2(root, "package.json")));
1216
+ if (await buildIsStale(root))
1217
+ console.warn(" (the build is older than the source it was built from, so this compared yesterday's package \u2014 run the build first)");
1167
1218
  if (built === undefined && shipsTypes)
1168
1219
  failures.push("there are no declaration files where package.json says its types are \u2014 build before the gate, or nothing compares them");
1169
1220
  if (!shipsTypes)
@@ -1173,7 +1224,7 @@ var check = async (root, flags) => {
1173
1224
  failures.push(`the registry did not answer for ${manifest.name}, so nothing was compared \u2014 pass --offline to skip the comparison deliberately`);
1174
1225
  if (versions !== undefined && versions.published.length === 0)
1175
1226
  console.warn(` (${manifest.name} has never been published, so there was nothing to compare)`);
1176
- const baseline = (versions?.published ?? []).filter((version) => compareVersions(version, manifest.version) <= 0).sort(compareVersions).at(LAST2);
1227
+ const baseline = (versions?.published ?? []).filter((version) => compareVersions(version, manifest.version) <= 0).sort(compareVersions).at(LAST3);
1177
1228
  if (versions !== undefined && versions.published.length > 0 && baseline === undefined && !offline)
1178
1229
  console.warn(` (nothing published at or below ${manifest.version}, so there was nothing to compare)`);
1179
1230
  if (built !== undefined && baseline !== undefined) {
@@ -1197,7 +1248,7 @@ var check = async (root, flags) => {
1197
1248
  `));
1198
1249
  console.log(`${manifest.name}@${manifest.version}: changelog holds (${entries.length} pending, ${current.releases.length} released)`);
1199
1250
  };
1200
- var LAST2 = -1;
1251
+ var LAST3 = -1;
1201
1252
  var subpathsOf = (manifest) => {
1202
1253
  const { exports } = manifest;
1203
1254
  if (typeof exports !== "object" || exports === null)
@@ -1205,13 +1256,13 @@ var subpathsOf = (manifest) => {
1205
1256
  return new Set(Object.keys(exports).map((key) => key === "." ? manifest.name ?? "" : `${manifest.name}${key.slice(1)}`));
1206
1257
  };
1207
1258
  var CHECKING_CONFIGS = ["tsconfig.json", "tsconfig.eslint.json"];
1208
- var readTsconfig = async (path2) => {
1209
- const source = await readFile3(path2, "utf8").catch(() => {
1259
+ var readTsconfig = async (path) => {
1260
+ const source = await readFile3(path, "utf8").catch(() => {
1210
1261
  return;
1211
1262
  });
1212
1263
  if (source === undefined)
1213
1264
  return;
1214
- const read = ts2.parseConfigFileTextToJson(path2, source);
1265
+ const read = ts2.parseConfigFileTextToJson(path, source);
1215
1266
  if (read.error !== undefined)
1216
1267
  return;
1217
1268
  const { config } = read;
@@ -1221,8 +1272,8 @@ var readTsconfig = async (path2) => {
1221
1272
  var coversEntries = (include) => Array.isArray(include) && include.some((entry) => typeof entry === "string" && entry.startsWith("changelog"));
1222
1273
  var includeOf = (config) => typeof config === "object" && config !== null ? new Map(Object.entries(config)).get("include") : undefined;
1223
1274
  var includeEntriesIn = async (root, file) => {
1224
- const path2 = join2(root, file);
1225
- const read = await readTsconfig(path2);
1275
+ const path = join2(root, file);
1276
+ const read = await readTsconfig(path);
1226
1277
  if (read === undefined)
1227
1278
  return false;
1228
1279
  const include = includeOf(read.config);
@@ -1232,7 +1283,7 @@ var includeEntriesIn = async (root, file) => {
1232
1283
  if (opened === null)
1233
1284
  return false;
1234
1285
  const opens = opened.index + opened[0].length;
1235
- await writeFile2(path2, `${read.source.slice(0, opens)}
1286
+ await writeFile2(path, `${read.source.slice(0, opens)}
1236
1287
  "changelog/**/*",${read.source.slice(opens)}`);
1237
1288
  return true;
1238
1289
  };
@@ -1241,8 +1292,8 @@ var includeEntries = async (root) => {
1241
1292
  return wired.some(Boolean) ? undefined : "no tsconfig include list to wire \u2014 make sure changelog/ is compiled by something";
1242
1293
  };
1243
1294
  var ignoreGenerated = async (root) => {
1244
- const path2 = join2(root, ".prettierignore");
1245
- const existing = await readFile3(path2, "utf8").catch(() => "");
1295
+ const path = join2(root, ".prettierignore");
1296
+ const existing = await readFile3(path, "utf8").catch(() => "");
1246
1297
  const wanted = [CHANGELOG_FILE, CHANGELOG_MARKDOWN].filter((name) => !existing.split(`
1247
1298
  `).includes(name));
1248
1299
  if (wanted.length === 0)
@@ -1250,7 +1301,7 @@ var ignoreGenerated = async (root) => {
1250
1301
  const head = existing.trim() === "" ? `# Generated by absolute-changelog. Edit the entries, not these.
1251
1302
  ` : `${existing.trimEnd()}
1252
1303
  `;
1253
- await writeFile2(path2, `${head}${wanted.join(`
1304
+ await writeFile2(path, `${head}${wanted.join(`
1254
1305
  `)}
1255
1306
  `);
1256
1307
  };
@@ -1314,5 +1365,5 @@ await main().catch((error) => {
1314
1365
  process.exit(1);
1315
1366
  });
1316
1367
 
1317
- //# debugId=A14460FB75D13A7E64756E2164756E21
1368
+ //# debugId=D76B4516336E7D1B64756E2164756E21
1318
1369
  //# sourceMappingURL=cli.js.map