@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 +13 -0
- package/changelog.json +27 -0
- package/dist/cli.js +75 -24
- package/dist/cli.js.map +5 -5
- package/dist/entries.d.ts +12 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +115 -65
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
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) =>
|
|
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 };
|
|
@@ -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 (
|
|
647
|
-
const source = await readFile(
|
|
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 (
|
|
671
|
-
const source = await readFile2(
|
|
672
|
-
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))}
|
|
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, (
|
|
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(
|
|
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
|
|
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 (
|
|
1209
|
-
const source = await readFile3(
|
|
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(
|
|
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
|
|
1225
|
-
const read = await readTsconfig(
|
|
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(
|
|
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
|
|
1245
|
-
const existing = await readFile3(
|
|
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(
|
|
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=
|
|
1368
|
+
//# debugId=D76B4516336E7D1B64756E2164756E21
|
|
1318
1369
|
//# sourceMappingURL=cli.js.map
|