@absolutejs/changelog 0.2.0 → 0.4.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/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
 
@@ -269,6 +270,10 @@ var migrationsBetween = (changelog, from, upto) => disruptiveBetween(changelog,
269
270
  });
270
271
  var symbolsDisturbedBetween = (changelog, from, upto) => new Set(disruptiveBetween(changelog, from, upto).flatMap(({ change }) => change.symbols));
271
272
 
273
+ // src/surface.ts
274
+ import path from "path";
275
+ import ts from "typescript";
276
+
272
277
  // src/tarball.ts
273
278
  var TAR_BLOCK = 512;
274
279
  var NAME_LENGTH = 100;
@@ -321,114 +326,185 @@ var readTar = (archive, keep, limits = {}) => {
321
326
  };
322
327
 
323
328
  // src/surface.ts
324
- var DECLARATION = /\bexport\s+(?:declare\s+)?(?:abstract\s+)?(?:async\s+)?(function|const|let|var|class|interface|type|enum|namespace)\s+([A-Za-z_$][\w$]*)/gu;
325
- var NAMED_EXPORT = /\bexport\s*\{([^}]*)\}/gu;
326
- var BODIED = new Set(["class", "interface", "enum", "namespace"]);
327
- var OPENS = new Set(["{", "(", "["]);
328
- var CLOSES = new Set(["}", ")", "]"]);
329
- var IDENTIFIER = /^[A-Za-z_$][\w$]*$/u;
330
- var LAST2 = -1;
331
- var NOWHERE = -1;
332
- var MEMBER = /([A-Za-z_$][\w$]*)\s*[?!]?\s*[:(<]/u;
333
- var isStub = (declaration) => declaration.startsWith("export {");
334
- var declarationEnd = (source, from, bodied) => {
335
- let depth = 0;
336
- for (let index = from;index < source.length; index += 1) {
337
- const character = source[index] ?? "";
338
- if (OPENS.has(character))
339
- depth += 1;
340
- else if (CLOSES.has(character))
341
- depth -= 1;
342
- else if (character === ";" && depth <= 0)
343
- return index + 1;
344
- if (CLOSES.has(character) && depth <= 0 && bodied)
345
- return index + 1;
346
- }
347
- return source.length;
348
- };
349
- var normalize = (declaration) => declaration.replace(/\s+/gu, " ").trim();
350
- var reExported = (body) => body.split(",").map((part) => part.trim().replace(/^type\s+/u, "")).filter((clause) => clause !== "").map((clause) => [
351
- clause.split(/\s+as\s+/u).at(LAST2)?.trim(),
352
- clause
353
- ]).filter((pair) => pair[0] !== undefined && IDENTIFIER.test(pair[0]));
354
- var exportedDeclarations = (source) => {
329
+ var DECLARATION = /\.d\.[cm]?ts$/u;
330
+ var parse = (source, name) => ts.createSourceFile(name, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
331
+ var normalize = (text) => text.replace(/\s+/gu, " ").trim();
332
+ var resolveFrom = (files, from, specifier) => {
333
+ if (!specifier.startsWith("."))
334
+ return;
335
+ const base = path.posix.join(path.posix.dirname(from), specifier);
336
+ const candidates = [
337
+ base,
338
+ `${base.replace(/\.js$/u, "")}.d.ts`,
339
+ `${base.replace(/\.js$/u, "")}.d.mts`,
340
+ `${base.replace(/\.js$/u, "")}.d.cts`,
341
+ `${base}/index.d.ts`,
342
+ `${base}/index.d.mts`,
343
+ `${base}/index.d.cts`
344
+ ];
345
+ return candidates.find((candidate) => files.has(candidate));
346
+ };
347
+ var declaredNameOf = (statement) => {
348
+ if (ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement) || ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement) || ts.isEnumDeclaration(statement) || ts.isModuleDeclaration(statement))
349
+ return statement.name !== undefined && ts.isIdentifier(statement.name) ? statement.name.text : undefined;
350
+ return;
351
+ };
352
+ var isExported = (statement) => ts.canHaveModifiers(statement) && ts.getModifiers(statement)?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) === true;
353
+ var variableNamesOf = (statement) => statement.declarationList.declarations.map((declaration) => declaration.name).filter((name) => ts.isIdentifier(name)).map((name) => name.text);
354
+ var declarationsIn = (file) => {
355
355
  const found = new Map;
356
- const add = (name, declaration) => {
357
- const existing = found.get(name);
358
- found.set(name, existing === undefined ? declaration : `${existing}
359
- ${declaration}`);
360
- };
361
- for (const match of source.matchAll(DECLARATION)) {
362
- const [, keyword, name] = match;
363
- if (name === undefined || match.index === undefined)
356
+ for (const statement of file.statements) {
357
+ const text = normalize(statement.getText(file));
358
+ const named = declaredNameOf(statement);
359
+ if (named !== undefined)
360
+ found.set(named, text);
361
+ if (!ts.isVariableStatement(statement))
364
362
  continue;
365
- add(name, normalize(source.slice(match.index, declarationEnd(source, match.index + match[0].length, BODIED.has(keyword ?? "")))));
363
+ for (const name of variableNamesOf(statement))
364
+ found.set(name, text);
366
365
  }
367
- for (const match of source.matchAll(NAMED_EXPORT))
368
- for (const [exported, clause] of reExported(match[1] ?? ""))
369
- add(exported, `export { ${clause} }`);
370
366
  return found;
371
367
  };
372
- var surfaceOfArchive = (archive) => surfaceOfFiles([...readTar(archive, (path) => path.endsWith(".d.ts")).values()].map(asText));
373
- var surfaceOfFiles = (files) => {
368
+ var declaresTypes = (manifest) => {
369
+ if (typeof manifest !== "object" || manifest === null)
370
+ return false;
371
+ const record = new Map(Object.entries(manifest));
372
+ if (typeof record.get("types") === "string")
373
+ return true;
374
+ if (typeof record.get("typings") === "string")
375
+ return true;
376
+ return JSON.stringify(record.get("exports") ?? {}).includes('"types"');
377
+ };
378
+ var contributionOf = (input) => {
379
+ const { file, files, from, local, seen, statement } = input;
380
+ const nothing = [];
381
+ if (ts.isExportAssignment(statement))
382
+ return [
383
+ [
384
+ statement.isExportEquals === true ? "export=" : "default",
385
+ normalize(statement.getText(file))
386
+ ]
387
+ ];
388
+ const named = declaredNameOf(statement);
389
+ if (named !== undefined && isExported(statement))
390
+ return [
391
+ [named, local.get(named) ?? normalize(statement.getText(file))]
392
+ ];
393
+ if (ts.isVariableStatement(statement) && isExported(statement)) {
394
+ const text = normalize(statement.getText(file));
395
+ return variableNamesOf(statement).map((name) => [name, text]);
396
+ }
397
+ if (!ts.isExportDeclaration(statement))
398
+ return nothing;
399
+ const specifier = statement.moduleSpecifier !== undefined && ts.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : undefined;
400
+ const target = specifier === undefined ? undefined : resolveFrom(files, from, specifier);
401
+ const theirs = target === undefined ? new Map : exportsOf(files, target, new Set(seen));
402
+ const clause = statement.exportClause;
403
+ if (clause === undefined)
404
+ return [...theirs].filter(([name]) => name !== "default" && name !== "export=");
405
+ if (ts.isNamespaceExport(clause))
406
+ return [[clause.name.text, `export * as ${clause.name.text}`]];
407
+ return clause.elements.map((element) => {
408
+ const sourceName = (element.propertyName ?? element.name).text;
409
+ const text = (specifier === undefined ? local.get(sourceName) : theirs.get(sourceName)) ?? `export { ${sourceName} }`;
410
+ return [element.name.text, text];
411
+ });
412
+ };
413
+ var exportsOf = (files, from, seen) => {
374
414
  const surface = new Map;
375
- for (const source of files)
376
- for (const [name, declaration] of exportedDeclarations(source)) {
377
- const known = surface.get(name);
378
- if (known === undefined || isStub(known) && !isStub(declaration))
379
- surface.set(name, declaration);
380
- }
415
+ const source = files.get(from);
416
+ if (source === undefined || seen.has(from))
417
+ return surface;
418
+ seen.add(from);
419
+ const file = parse(source, from);
420
+ const local = declarationsIn(file);
421
+ const contributed = file.statements.flatMap((statement) => contributionOf({ file, files, from, local, seen, statement }));
422
+ for (const [name, text] of contributed)
423
+ surface.set(name, text);
381
424
  return surface;
382
425
  };
426
+ var surfaceOfArchive = (archive) => {
427
+ const held = readTar(archive, (name) => DECLARATION.test(name) || name === "package.json");
428
+ const files = new Map([...held].map(([name, bytes]) => [name, asText(bytes)]));
429
+ const manifest = files.get("package.json");
430
+ let parsed;
431
+ try {
432
+ parsed = manifest === undefined ? undefined : JSON.parse(manifest);
433
+ } catch {
434
+ parsed = undefined;
435
+ }
436
+ const entries = typeEntriesOf(parsed, files);
437
+ return entries.length === 0 ? undefined : surfaceOfFiles(files, entries);
438
+ };
439
+ var surfaceOfFiles = (files, entries) => {
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);
445
+ return surface;
446
+ };
447
+ var typeCandidatesIn = (value) => {
448
+ if (typeof value === "string")
449
+ return [value];
450
+ if (typeof value !== "object" || value === null)
451
+ return [];
452
+ return Object.entries(value).flatMap(([key, nested]) => key === "types" || key === "typings" ? typeCandidatesIn(nested) : typeCandidatesIn(nested));
453
+ };
454
+ var typeEntriesOf = (manifest, files) => {
455
+ 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"))
460
+ ];
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])];
468
+ };
383
469
  var SHAPE = {
384
470
  members: "members",
385
471
  parameters: "parameters",
386
472
  types: "types",
387
473
  unknown: "unknown"
388
474
  };
475
+ var isStub = (declaration) => declaration.startsWith("export {");
476
+ var signatureOf = (declaration) => {
477
+ const file = parse(`${declaration}
478
+ `, "declaration.d.ts");
479
+ const [statement] = file.statements;
480
+ if (statement === undefined)
481
+ return;
482
+ return statement;
483
+ };
389
484
  var parametersOf = (declaration) => {
390
- let depth = 0;
391
- let start = NOWHERE;
392
- for (const [index, character] of [...declaration].entries()) {
393
- if (character === "{" && depth === 0)
394
- return "";
395
- depth += character === "(" ? 1 : 0;
396
- if (character === "(" && depth === 1)
397
- start = index;
398
- depth -= character === ")" ? 1 : 0;
399
- if (character === ")" && depth === 0 && start > NOWHERE)
400
- return declaration.slice(start, index + 1);
401
- }
402
- return "";
485
+ const statement = signatureOf(declaration);
486
+ if (statement === undefined)
487
+ return "";
488
+ const found = [];
489
+ const visit = (node) => {
490
+ if (ts.isFunctionTypeNode(node) || ts.isFunctionDeclaration(node) || ts.isMethodSignature(node) || ts.isMethodDeclaration(node) || ts.isConstructSignatureDeclaration(node) || ts.isConstructorDeclaration(node))
491
+ found.push(node.parameters.map((parameter) => normalize(parameter.getText())).join(", "));
492
+ ts.forEachChild(node, visit);
493
+ };
494
+ visit(statement);
495
+ return found.join(" | ");
403
496
  };
404
497
  var membersOf = (declaration) => {
405
- const opened = declaration.indexOf("{");
406
- if (opened < 0)
407
- return new Set;
498
+ const statement = signatureOf(declaration);
408
499
  const members = new Set;
409
- let depth = 0;
410
- let segment = "";
411
- const take = () => {
412
- const [, name] = MEMBER.exec(segment.trim()) ?? [];
413
- if (name !== undefined)
414
- members.add(name);
415
- segment = "";
500
+ if (statement === undefined)
501
+ return members;
502
+ const visit = (node) => {
503
+ if ((ts.isPropertySignature(node) || ts.isMethodSignature(node) || ts.isPropertyDeclaration(node) || ts.isMethodDeclaration(node) || ts.isEnumMember(node)) && node.name !== undefined && (ts.isIdentifier(node.name) || ts.isStringLiteral(node.name)))
504
+ members.add(node.name.text);
505
+ ts.forEachChild(node, visit);
416
506
  };
417
- for (const character of declaration.slice(opened)) {
418
- const opening = OPENS.has(character);
419
- const closing = CLOSES.has(character);
420
- depth += opening ? 1 : 0;
421
- if (opening && depth === 1)
422
- segment = "";
423
- depth -= closing ? 1 : 0;
424
- if (opening || closing || depth !== 1)
425
- continue;
426
- if (character === ";" || character === ",")
427
- take();
428
- else
429
- segment += character;
430
- }
431
- take();
507
+ visit(statement);
432
508
  return members;
433
509
  };
434
510
  var sameNames = (left, right) => left.size === right.size && [...left].every((name) => right.has(name));
@@ -465,9 +541,9 @@ var diffSurface = (before, after) => {
465
541
  var notable = (diff) => diff.changed.filter((change) => change.shape === "parameters" || change.shape === "members");
466
542
 
467
543
  // src/entries.ts
468
- var loadEntry = async (path) => {
544
+ var loadEntry = async (path2) => {
469
545
  try {
470
- const module = await import(pathToFileURL(path).href);
546
+ const module = await import(pathToFileURL(path2).href);
471
547
  const named = typeof module === "object" && module !== null && "change" in module ? module.change : undefined;
472
548
  const exported = named ?? (typeof module === "object" && module !== null && "default" in module ? module.default : undefined);
473
549
  const read = { exported };
@@ -480,23 +556,28 @@ var loadEntry = async (path) => {
480
556
  }
481
557
  };
482
558
  var ENTRY = /\.[cm]?ts$/u;
483
- var builtSurface = async (packageRoot, out = "dist") => {
484
- const root = join(packageRoot, out);
485
- const sources = [];
559
+ var DECLARATION2 = /\.d\.[cm]?ts$/u;
560
+ var OUTPUT_DIRECTORIES = ["dist", "build", "lib", "types", "out"];
561
+ var builtSurface = async (packageRoot) => {
562
+ const manifest = await readJsonFile(join(packageRoot, "package.json"));
563
+ const files = new Map;
486
564
  const walk = async (directory) => {
487
- const found = await readdir(directory, { withFileTypes: true }).catch(() => []);
565
+ const found = await readdir(join(packageRoot, directory), {
566
+ withFileTypes: true
567
+ }).catch(() => []);
488
568
  await Promise.all(found.map(async (entry) => {
489
- const path = join(directory, entry.name);
569
+ const relative = `${directory}/${entry.name}`;
490
570
  if (entry.isDirectory())
491
- return walk(path);
492
- if (!entry.name.endsWith(".d.ts"))
571
+ return walk(relative);
572
+ if (!DECLARATION2.test(entry.name))
493
573
  return;
494
- sources.push(await readFile(path, "utf8").catch(() => ""));
574
+ files.set(relative, await readFile(join(packageRoot, relative), "utf8").catch(() => ""));
495
575
  return;
496
576
  }));
497
577
  };
498
- await walk(root);
499
- return sources.length === 0 ? undefined : surfaceOfFiles(sources);
578
+ await Promise.all(OUTPUT_DIRECTORIES.map((directory) => walk(directory)));
579
+ const entries = typeEntriesOf(manifest, files);
580
+ return entries.length === 0 ? undefined : surfaceOfFiles(files, entries);
500
581
  };
501
582
  var changelogPaths = (packageRoot) => ({
502
583
  history: join(packageRoot, "changelog", "history.md"),
@@ -522,8 +603,8 @@ var loadUnreleased = async (packageRoot) => {
522
603
  }
523
604
  return { entries, problems };
524
605
  };
525
- var readJsonFile = async (path) => {
526
- const source = await readFile(path, "utf8").catch(() => {
606
+ var readJsonFile = async (path2) => {
607
+ const source = await readFile(path2, "utf8").catch(() => {
527
608
  return;
528
609
  });
529
610
  if (source === undefined)
@@ -546,9 +627,9 @@ var indentOf = (source) => {
546
627
  const found = /\n([\t ]+)\S/u.exec(source)?.[1];
547
628
  return found === undefined || found === "" ? "\t" : found;
548
629
  };
549
- var writeJsonLike = async (path, document) => {
550
- const source = await readFile2(path, "utf8").catch(() => "");
551
- await writeFile(path, `${JSON.stringify(document, null, indentOf(source))}
630
+ var writeJsonLike = async (path2, document) => {
631
+ const source = await readFile2(path2, "utf8").catch(() => "");
632
+ await writeFile(path2, `${JSON.stringify(document, null, indentOf(source))}
552
633
  `);
553
634
  };
554
635
 
@@ -586,8 +667,14 @@ var migrationHolds = (input) => {
586
667
  };
587
668
  var describeReconciliation = (found) => {
588
669
  const lines = [];
670
+ const moved = {
671
+ members: "changed the members of its type",
672
+ parameters: "changed what it is called with",
673
+ removed: "is no longer exported",
674
+ types: "changed its declaration"
675
+ };
589
676
  for (const entry of found.unannounced)
590
- lines.push(entry.kind === "removed" ? `\`${entry.name}\` is no longer exported and no entry says so` : `\`${entry.name}\` changed its ${entry.kind} and no entry says so`);
677
+ lines.push(`\`${entry.name}\` ${moved[entry.kind]} and no entry says so`);
591
678
  for (const name of found.unknownSymbols)
592
679
  lines.push(`an entry names \`${name}\`, which this package does not export`);
593
680
  lines.push(...found.wrongMigrations);
@@ -598,10 +685,7 @@ var reconcile = (input) => {
598
685
  const mentioned = new Set(input.changes.flatMap((change) => [...change.symbols ?? []]));
599
686
  const unannounced = [
600
687
  ...input.diff.removed.filter((name) => !disruptive.has(name)).map((name) => ({ kind: "removed", name })),
601
- ...notable(input.diff).filter((change) => !mentioned.has(change.name)).map((change) => ({
602
- kind: change.shape === "parameters" ? "parameters" : "members",
603
- name: change.name
604
- }))
688
+ ...input.diff.changed.filter((change) => !mentioned.has(change.name) && change.shape !== "unknown").map((change) => ({ kind: change.shape, name: change.name }))
605
689
  ];
606
690
  const unknownSymbols = [
607
691
  ...new Set(input.changes.flatMap((change) => [...change.symbols ?? []]).filter((name) => !input.known.has(name)))
@@ -619,6 +703,16 @@ var reconcile = (input) => {
619
703
  import { gunzipSync } from "zlib";
620
704
  var ABBREVIATED = "application/vnd.npm.install-v1+json";
621
705
  var DEFAULT_REGISTRY = "https://registry.npmjs.org";
706
+ var KIBIBYTE2 = 1024;
707
+ var MEBIBYTE2 = KIBIBYTE2 * KIBIBYTE2;
708
+ var MAX_ARCHIVE_MEBIBYTES = 96;
709
+ var MAX_ARCHIVE_BYTES = MAX_ARCHIVE_MEBIBYTES * MEBIBYTE2;
710
+ var MAX_DOWNLOAD_MEBIBYTES = 32;
711
+ var MAX_DOWNLOAD_BYTES = MAX_DOWNLOAD_MEBIBYTES * MEBIBYTE2;
712
+ var TIMEOUT_MS = 20000;
713
+ var PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u;
714
+ var MAX_NAME_LENGTH = 214;
715
+ var isPackageName = (name) => name.length <= MAX_NAME_LENGTH && PACKAGE_NAME.test(name);
622
716
  var isRecord2 = (value) => typeof value === "object" && value !== null;
623
717
  var readPackument = (body) => {
624
718
  if (!isRecord2(body) || !isRecord2(body.versions))
@@ -646,20 +740,30 @@ var packageArchive = async (name, version, options = {}) => {
646
740
  if (url === undefined)
647
741
  return;
648
742
  try {
649
- const response = await call(url);
743
+ const response = await call(url, {
744
+ signal: AbortSignal.timeout(TIMEOUT_MS)
745
+ });
650
746
  if (!response.ok)
651
747
  return;
652
- return new Uint8Array(gunzipSync(new Uint8Array(await response.arrayBuffer())));
748
+ const compressed = new Uint8Array(await response.arrayBuffer());
749
+ if (compressed.byteLength > MAX_DOWNLOAD_BYTES)
750
+ return;
751
+ return new Uint8Array(gunzipSync(compressed, { maxOutputLength: MAX_ARCHIVE_BYTES }));
653
752
  } catch {
654
753
  return;
655
754
  }
656
755
  };
657
756
  var packageVersions = async (name, options = {}) => {
757
+ if (!isPackageName(name))
758
+ return;
658
759
  const call = options.fetch ?? fetch;
659
760
  const url = `${options.registry ?? DEFAULT_REGISTRY}/${name.replace("/", "%2f")}`;
660
761
  try {
661
- const abbreviated = await call(url, { headers: { accept: ABBREVIATED } });
662
- const response = abbreviated.ok ? abbreviated : await call(url);
762
+ const abbreviated = await call(url, {
763
+ headers: { accept: ABBREVIATED },
764
+ signal: AbortSignal.timeout(TIMEOUT_MS)
765
+ });
766
+ const response = abbreviated.ok ? abbreviated : await call(url, { signal: AbortSignal.timeout(TIMEOUT_MS) });
663
767
  if (!response.ok)
664
768
  return;
665
769
  return readPackument(await response.json());
@@ -671,7 +775,7 @@ var publishedChangelog = async (name, version, options = {}) => {
671
775
  const archive = await packageArchive(name, version, options);
672
776
  if (!archive)
673
777
  return;
674
- const file = readTar(archive, (path) => path === CHANGELOG_FILE).get(CHANGELOG_FILE);
778
+ const file = readTar(archive, (path2) => path2 === CHANGELOG_FILE).get(CHANGELOG_FILE);
675
779
  if (!file)
676
780
  return;
677
781
  try {
@@ -749,6 +853,7 @@ var renderRelease = (release) => {
749
853
  ].join(`
750
854
  `);
751
855
  };
856
+ var GENERATED_MARKER = "This file is generated by `absolute-changelog`";
752
857
  var renderChangelogJson = (changelog) => `${JSON.stringify({
753
858
  contract: CHANGELOG_CONTRACT,
754
859
  name: changelog.name,
@@ -774,7 +879,7 @@ var renderMarkdown = (changelog, options = {}) => {
774
879
  "",
775
880
  `All notable changes to \`${changelog.name}\`.`,
776
881
  "",
777
- "This file is generated by `absolute-changelog` from the entries in",
882
+ `${GENERATED_MARKER} from the entries in`,
778
883
  "`changelog/`. Edit an entry, not this file \u2014 and add new ones under",
779
884
  "`changelog/unreleased/`."
780
885
  ].join(`
@@ -984,10 +1089,10 @@ var check = async (root, flags) => {
984
1089
  const actual = await readFile3(changelogPaths(root).markdown, "utf8").catch(() => {
985
1090
  return;
986
1091
  });
987
- if (newest !== undefined && actual !== expected)
1092
+ if (actual !== expected)
988
1093
  failures.push(`${CHANGELOG_MARKDOWN} is not what the entries say it should be \u2014 it is generated; run \`absolute-changelog release\` or restore it`);
989
- const tsconfig = await readJsonFile(join2(root, "tsconfig.json"));
990
- const include = isPackage(tsconfig) ? tsconfig.include : undefined;
1094
+ const tsconfig = await readTsconfig(join2(root, "tsconfig.json"));
1095
+ const include = includeOf(tsconfig?.config);
991
1096
  if (Array.isArray(include) && !coversEntries(include))
992
1097
  failures.push('tsconfig.json does not compile changelog/, so nothing is checking the entries \u2014 add "changelog/**/*" to its include list');
993
1098
  for (const stray of await strayEntries(root))
@@ -998,12 +1103,21 @@ var check = async (root, flags) => {
998
1103
  failures.push(`package.json does not ship ${file}`);
999
1104
  const offline = flags.offline === true;
1000
1105
  const built = await builtSurface(root);
1106
+ const shipsTypes = declaresTypes(await readJsonFile(join2(root, "package.json")));
1107
+ if (built === undefined && shipsTypes)
1108
+ failures.push("there are no declaration files where package.json says its types are \u2014 build before the gate, or nothing compares them");
1109
+ if (!shipsTypes)
1110
+ console.warn(" (this package ships no types, so none were compared)");
1001
1111
  const versions = offline ? undefined : await packageVersions(manifest.name ?? "");
1002
- const baseline = (versions?.published ?? []).filter((version) => compareVersions(version, manifest.version) <= 0).sort(compareVersions).at(LAST3);
1003
- if (!offline && built !== undefined && baseline !== undefined) {
1112
+ if (!offline && versions === undefined)
1113
+ failures.push(`the registry did not answer for ${manifest.name}, so nothing was compared \u2014 pass --offline to skip the comparison deliberately`);
1114
+ 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)`);
1117
+ if (built !== undefined && baseline !== undefined) {
1004
1118
  const published = await publishedSurface(manifest.name ?? "", baseline);
1005
1119
  if (published === undefined)
1006
- console.warn(` (could not read ${manifest.name}@${baseline} from the registry; the types were not checked)`);
1120
+ failures.push(`could not read the published types of ${manifest.name}@${baseline}, so nothing was compared`);
1007
1121
  const speaking = [
1008
1122
  ...entries.map((entry) => entry.change),
1009
1123
  ...current.releases.filter((entry) => compareVersions(entry.version, baseline) > 0).flatMap((entry) => [...entry.changes])
@@ -1016,14 +1130,12 @@ var check = async (root, flags) => {
1016
1130
  subpaths: subpathsOf(manifest)
1017
1131
  })));
1018
1132
  }
1019
- if (!offline && built === undefined)
1020
- console.warn(" (no dist to read, so the types were not checked \u2014 run the build before the gate)");
1021
1133
  if (failures.length > 0)
1022
1134
  throw new CliError(["This package\u2019s changelog does not hold:", ...failures].join(`
1023
1135
  `));
1024
1136
  console.log(`${manifest.name}@${manifest.version}: changelog holds (${entries.length} pending, ${current.releases.length} released)`);
1025
1137
  };
1026
- var LAST3 = -1;
1138
+ var LAST2 = -1;
1027
1139
  var subpathsOf = (manifest) => {
1028
1140
  const { exports } = manifest;
1029
1141
  if (typeof exports !== "object" || exports === null)
@@ -1031,18 +1143,35 @@ var subpathsOf = (manifest) => {
1031
1143
  return new Set(Object.keys(exports).map((key) => key === "." ? manifest.name ?? "" : `${manifest.name}${key.slice(1)}`));
1032
1144
  };
1033
1145
  var CHECKING_CONFIGS = ["tsconfig.json", "tsconfig.eslint.json"];
1146
+ var readTsconfig = async (path2) => {
1147
+ const source = await readFile3(path2, "utf8").catch(() => {
1148
+ return;
1149
+ });
1150
+ if (source === undefined)
1151
+ return;
1152
+ const read = ts2.parseConfigFileTextToJson(path2, source);
1153
+ if (read.error !== undefined)
1154
+ return;
1155
+ const { config } = read;
1156
+ const parsed = config;
1157
+ return { config: parsed, source };
1158
+ };
1034
1159
  var coversEntries = (include) => Array.isArray(include) && include.some((entry) => typeof entry === "string" && entry.startsWith("changelog"));
1160
+ var includeOf = (config) => typeof config === "object" && config !== null ? new Map(Object.entries(config)).get("include") : undefined;
1035
1161
  var includeEntriesIn = async (root, file) => {
1036
- const path = join2(root, file);
1037
- const document = await readJsonFile(path);
1038
- if (!isPackage(document) || !Array.isArray(document.include))
1162
+ const path2 = join2(root, file);
1163
+ const read = await readTsconfig(path2);
1164
+ if (read === undefined)
1039
1165
  return false;
1040
- if (coversEntries(document.include))
1166
+ const include = includeOf(read.config);
1167
+ if (!Array.isArray(include) || coversEntries(include))
1041
1168
  return false;
1042
- await writeJsonLike(path, {
1043
- ...document,
1044
- include: [...document.include, "changelog/**/*"]
1045
- });
1169
+ const opened = /"include"\s*:\s*\[/u.exec(read.source);
1170
+ if (opened === null)
1171
+ return false;
1172
+ const opens = opened.index + opened[0].length;
1173
+ await writeFile2(path2, `${read.source.slice(0, opens)}
1174
+ "changelog/**/*",${read.source.slice(opens)}`);
1046
1175
  return true;
1047
1176
  };
1048
1177
  var includeEntries = async (root) => {
@@ -1050,8 +1179,8 @@ var includeEntries = async (root) => {
1050
1179
  return wired.some(Boolean) ? undefined : "no tsconfig include list to wire \u2014 make sure changelog/ is compiled by something";
1051
1180
  };
1052
1181
  var ignoreGenerated = async (root) => {
1053
- const path = join2(root, ".prettierignore");
1054
- const existing = await readFile3(path, "utf8").catch(() => "");
1182
+ const path2 = join2(root, ".prettierignore");
1183
+ const existing = await readFile3(path2, "utf8").catch(() => "");
1055
1184
  const wanted = [CHANGELOG_FILE, CHANGELOG_MARKDOWN].filter((name) => !existing.split(`
1056
1185
  `).includes(name));
1057
1186
  if (wanted.length === 0)
@@ -1059,7 +1188,7 @@ var ignoreGenerated = async (root) => {
1059
1188
  const head = existing.trim() === "" ? `# Generated by absolute-changelog. Edit the entries, not these.
1060
1189
  ` : `${existing.trimEnd()}
1061
1190
  `;
1062
- await writeFile2(path, `${head}${wanted.join(`
1191
+ await writeFile2(path2, `${head}${wanted.join(`
1063
1192
  `)}
1064
1193
  `);
1065
1194
  };
@@ -1078,8 +1207,8 @@ var adopt = async (root) => {
1078
1207
  const paths = changelogPaths(root);
1079
1208
  await mkdir(paths.unreleased, { recursive: true });
1080
1209
  await writeFile2(join2(paths.unreleased, ".gitkeep"), "# Entries wait here. One file per change; `absolute-changelog release` consumes them.\n");
1081
- if (existsSync(paths.markdown) && !existsSync(paths.history)) {
1082
- const previous = await readFile3(paths.markdown, "utf8");
1210
+ const previous = existsSync(paths.markdown) ? await readFile3(paths.markdown, "utf8") : undefined;
1211
+ if (previous !== undefined && !previous.includes(GENERATED_MARKER) && !existsSync(paths.history)) {
1083
1212
  await mkdir(join2(root, "changelog"), { recursive: true });
1084
1213
  await writeFile2(paths.history, previous.replace(/^#\s+Changelog\s*/u, "").trimStart());
1085
1214
  await rm(paths.markdown, { force: true });
@@ -1120,5 +1249,5 @@ await main().catch((error) => {
1120
1249
  process.exit(1);
1121
1250
  });
1122
1251
 
1123
- //# debugId=7CE32A12E2B0BC0C64756E2164756E21
1252
+ //# debugId=4CD69EF0E551B35864756E2164756E21
1124
1253
  //# sourceMappingURL=cli.js.map