@absolutejs/changelog 0.7.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 +7 -0
- package/changelog.json +14 -0
- package/dist/cli.js +58 -26
- package/dist/cli.js.map +3 -3
- package/dist/index.js +99 -67
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,13 @@ 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
|
+
|
|
9
16
|
## 0.7.0 — 2026-09-10
|
|
10
17
|
|
|
11
18
|
### Added
|
package/changelog.json
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
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
|
+
},
|
|
5
19
|
{
|
|
6
20
|
"changes": [
|
|
7
21
|
{
|
package/dist/cli.js
CHANGED
|
@@ -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) =>
|
|
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) =>
|
|
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 (
|
|
616
|
+
var loadEntry = async (path) => {
|
|
585
617
|
try {
|
|
586
|
-
const module = await import(pathToFileURL(
|
|
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 };
|
|
@@ -601,10 +633,10 @@ var OUTPUT_DIRECTORIES = ["dist", "build", "lib", "types", "out"];
|
|
|
601
633
|
var newestUnder = async (root) => {
|
|
602
634
|
const found = await readdir(root, { withFileTypes: true }).catch(() => []);
|
|
603
635
|
const times = await Promise.all(found.map(async (entry) => {
|
|
604
|
-
const
|
|
636
|
+
const path = join(root, entry.name);
|
|
605
637
|
if (entry.isDirectory())
|
|
606
|
-
return newestUnder(
|
|
607
|
-
return stat(
|
|
638
|
+
return newestUnder(path);
|
|
639
|
+
return stat(path).then((stats) => stats.mtimeMs, () => 0);
|
|
608
640
|
}));
|
|
609
641
|
return Math.max(0, ...times);
|
|
610
642
|
};
|
|
@@ -660,8 +692,8 @@ var loadUnreleased = async (packageRoot) => {
|
|
|
660
692
|
}
|
|
661
693
|
return { entries, problems };
|
|
662
694
|
};
|
|
663
|
-
var readJsonFile = async (
|
|
664
|
-
const source = await readFile(
|
|
695
|
+
var readJsonFile = async (path) => {
|
|
696
|
+
const source = await readFile(path, "utf8").catch(() => {
|
|
665
697
|
return;
|
|
666
698
|
});
|
|
667
699
|
if (source === undefined)
|
|
@@ -684,9 +716,9 @@ var indentOf = (source) => {
|
|
|
684
716
|
const found = /\n([\t ]+)\S/u.exec(source)?.[1];
|
|
685
717
|
return found === undefined || found === "" ? "\t" : found;
|
|
686
718
|
};
|
|
687
|
-
var writeJsonLike = async (
|
|
688
|
-
const source = await readFile2(
|
|
689
|
-
await writeFile(
|
|
719
|
+
var writeJsonLike = async (path, document) => {
|
|
720
|
+
const source = await readFile2(path, "utf8").catch(() => "");
|
|
721
|
+
await writeFile(path, `${JSON.stringify(document, null, indentOf(source))}
|
|
690
722
|
`);
|
|
691
723
|
};
|
|
692
724
|
|
|
@@ -852,7 +884,7 @@ var publishedChangelog = async (name, version, options = {}) => {
|
|
|
852
884
|
const archive = await packageArchive(name, version, options);
|
|
853
885
|
if (!archive)
|
|
854
886
|
return;
|
|
855
|
-
const file = readTar(archive, (
|
|
887
|
+
const file = readTar(archive, (path) => path === CHANGELOG_FILE).get(CHANGELOG_FILE);
|
|
856
888
|
if (!file)
|
|
857
889
|
return;
|
|
858
890
|
try {
|
|
@@ -1192,7 +1224,7 @@ var check = async (root, flags) => {
|
|
|
1192
1224
|
failures.push(`the registry did not answer for ${manifest.name}, so nothing was compared \u2014 pass --offline to skip the comparison deliberately`);
|
|
1193
1225
|
if (versions !== undefined && versions.published.length === 0)
|
|
1194
1226
|
console.warn(` (${manifest.name} has never been published, so there was nothing to compare)`);
|
|
1195
|
-
const baseline = (versions?.published ?? []).filter((version) => compareVersions(version, manifest.version) <= 0).sort(compareVersions).at(
|
|
1227
|
+
const baseline = (versions?.published ?? []).filter((version) => compareVersions(version, manifest.version) <= 0).sort(compareVersions).at(LAST3);
|
|
1196
1228
|
if (versions !== undefined && versions.published.length > 0 && baseline === undefined && !offline)
|
|
1197
1229
|
console.warn(` (nothing published at or below ${manifest.version}, so there was nothing to compare)`);
|
|
1198
1230
|
if (built !== undefined && baseline !== undefined) {
|
|
@@ -1216,7 +1248,7 @@ var check = async (root, flags) => {
|
|
|
1216
1248
|
`));
|
|
1217
1249
|
console.log(`${manifest.name}@${manifest.version}: changelog holds (${entries.length} pending, ${current.releases.length} released)`);
|
|
1218
1250
|
};
|
|
1219
|
-
var
|
|
1251
|
+
var LAST3 = -1;
|
|
1220
1252
|
var subpathsOf = (manifest) => {
|
|
1221
1253
|
const { exports } = manifest;
|
|
1222
1254
|
if (typeof exports !== "object" || exports === null)
|
|
@@ -1224,13 +1256,13 @@ var subpathsOf = (manifest) => {
|
|
|
1224
1256
|
return new Set(Object.keys(exports).map((key) => key === "." ? manifest.name ?? "" : `${manifest.name}${key.slice(1)}`));
|
|
1225
1257
|
};
|
|
1226
1258
|
var CHECKING_CONFIGS = ["tsconfig.json", "tsconfig.eslint.json"];
|
|
1227
|
-
var readTsconfig = async (
|
|
1228
|
-
const source = await readFile3(
|
|
1259
|
+
var readTsconfig = async (path) => {
|
|
1260
|
+
const source = await readFile3(path, "utf8").catch(() => {
|
|
1229
1261
|
return;
|
|
1230
1262
|
});
|
|
1231
1263
|
if (source === undefined)
|
|
1232
1264
|
return;
|
|
1233
|
-
const read = ts2.parseConfigFileTextToJson(
|
|
1265
|
+
const read = ts2.parseConfigFileTextToJson(path, source);
|
|
1234
1266
|
if (read.error !== undefined)
|
|
1235
1267
|
return;
|
|
1236
1268
|
const { config } = read;
|
|
@@ -1240,8 +1272,8 @@ var readTsconfig = async (path2) => {
|
|
|
1240
1272
|
var coversEntries = (include) => Array.isArray(include) && include.some((entry) => typeof entry === "string" && entry.startsWith("changelog"));
|
|
1241
1273
|
var includeOf = (config) => typeof config === "object" && config !== null ? new Map(Object.entries(config)).get("include") : undefined;
|
|
1242
1274
|
var includeEntriesIn = async (root, file) => {
|
|
1243
|
-
const
|
|
1244
|
-
const read = await readTsconfig(
|
|
1275
|
+
const path = join2(root, file);
|
|
1276
|
+
const read = await readTsconfig(path);
|
|
1245
1277
|
if (read === undefined)
|
|
1246
1278
|
return false;
|
|
1247
1279
|
const include = includeOf(read.config);
|
|
@@ -1251,7 +1283,7 @@ var includeEntriesIn = async (root, file) => {
|
|
|
1251
1283
|
if (opened === null)
|
|
1252
1284
|
return false;
|
|
1253
1285
|
const opens = opened.index + opened[0].length;
|
|
1254
|
-
await writeFile2(
|
|
1286
|
+
await writeFile2(path, `${read.source.slice(0, opens)}
|
|
1255
1287
|
"changelog/**/*",${read.source.slice(opens)}`);
|
|
1256
1288
|
return true;
|
|
1257
1289
|
};
|
|
@@ -1260,8 +1292,8 @@ var includeEntries = async (root) => {
|
|
|
1260
1292
|
return wired.some(Boolean) ? undefined : "no tsconfig include list to wire \u2014 make sure changelog/ is compiled by something";
|
|
1261
1293
|
};
|
|
1262
1294
|
var ignoreGenerated = async (root) => {
|
|
1263
|
-
const
|
|
1264
|
-
const existing = await readFile3(
|
|
1295
|
+
const path = join2(root, ".prettierignore");
|
|
1296
|
+
const existing = await readFile3(path, "utf8").catch(() => "");
|
|
1265
1297
|
const wanted = [CHANGELOG_FILE, CHANGELOG_MARKDOWN].filter((name) => !existing.split(`
|
|
1266
1298
|
`).includes(name));
|
|
1267
1299
|
if (wanted.length === 0)
|
|
@@ -1269,7 +1301,7 @@ var ignoreGenerated = async (root) => {
|
|
|
1269
1301
|
const head = existing.trim() === "" ? `# Generated by absolute-changelog. Edit the entries, not these.
|
|
1270
1302
|
` : `${existing.trimEnd()}
|
|
1271
1303
|
`;
|
|
1272
|
-
await writeFile2(
|
|
1304
|
+
await writeFile2(path, `${head}${wanted.join(`
|
|
1273
1305
|
`)}
|
|
1274
1306
|
`);
|
|
1275
1307
|
};
|
|
@@ -1333,5 +1365,5 @@ await main().catch((error) => {
|
|
|
1333
1365
|
process.exit(1);
|
|
1334
1366
|
});
|
|
1335
1367
|
|
|
1336
|
-
//# debugId=
|
|
1368
|
+
//# debugId=D76B4516336E7D1B64756E2164756E21
|
|
1337
1369
|
//# sourceMappingURL=cli.js.map
|