@absolutejs/changelog 0.1.2 → 0.3.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
@@ -235,8 +235,8 @@ var parseChangelog = (value) => {
235
235
  return { problems: ["not an object"] };
236
236
  if (!isText(value.name))
237
237
  problems.push("needs a package name");
238
- if (value.contract !== CHANGELOG_CONTRACT)
239
- problems.push(`contract ${JSON.stringify(value.contract)} is not ${CHANGELOG_CONTRACT}`);
238
+ if (typeof value.contract !== "number" || value.contract > CHANGELOG_CONTRACT)
239
+ problems.push(`contract ${JSON.stringify(value.contract)} is newer than ${CHANGELOG_CONTRACT}, which this reader understands`);
240
240
  const raw = value.releases;
241
241
  if (!Array.isArray(raw))
242
242
  problems.push("releases is not a list");
@@ -269,6 +269,10 @@ var migrationsBetween = (changelog, from, upto) => disruptiveBetween(changelog,
269
269
  });
270
270
  var symbolsDisturbedBetween = (changelog, from, upto) => new Set(disruptiveBetween(changelog, from, upto).flatMap(({ change }) => change.symbols));
271
271
 
272
+ // src/surface.ts
273
+ import path from "path";
274
+ import ts from "typescript";
275
+
272
276
  // src/tarball.ts
273
277
  var TAR_BLOCK = 512;
274
278
  var NAME_LENGTH = 100;
@@ -321,114 +325,185 @@ var readTar = (archive, keep, limits = {}) => {
321
325
  };
322
326
 
323
327
  // 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) => {
328
+ var DECLARATION = /\.d\.[cm]?ts$/u;
329
+ var parse = (source, name) => ts.createSourceFile(name, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
330
+ var normalize = (text) => text.replace(/\s+/gu, " ").trim();
331
+ var resolveFrom = (files, from, specifier) => {
332
+ if (!specifier.startsWith("."))
333
+ return;
334
+ const base = path.posix.join(path.posix.dirname(from), specifier);
335
+ const candidates = [
336
+ base,
337
+ `${base.replace(/\.js$/u, "")}.d.ts`,
338
+ `${base.replace(/\.js$/u, "")}.d.mts`,
339
+ `${base.replace(/\.js$/u, "")}.d.cts`,
340
+ `${base}/index.d.ts`,
341
+ `${base}/index.d.mts`,
342
+ `${base}/index.d.cts`
343
+ ];
344
+ return candidates.find((candidate) => files.has(candidate));
345
+ };
346
+ var declaredNameOf = (statement) => {
347
+ if (ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement) || ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement) || ts.isEnumDeclaration(statement) || ts.isModuleDeclaration(statement))
348
+ return statement.name !== undefined && ts.isIdentifier(statement.name) ? statement.name.text : undefined;
349
+ return;
350
+ };
351
+ var isExported = (statement) => ts.canHaveModifiers(statement) && ts.getModifiers(statement)?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) === true;
352
+ var variableNamesOf = (statement) => statement.declarationList.declarations.map((declaration) => declaration.name).filter((name) => ts.isIdentifier(name)).map((name) => name.text);
353
+ var declarationsIn = (file) => {
355
354
  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)
355
+ for (const statement of file.statements) {
356
+ const text = normalize(statement.getText(file));
357
+ const named = declaredNameOf(statement);
358
+ if (named !== undefined)
359
+ found.set(named, text);
360
+ if (!ts.isVariableStatement(statement))
364
361
  continue;
365
- add(name, normalize(source.slice(match.index, declarationEnd(source, match.index + match[0].length, BODIED.has(keyword ?? "")))));
362
+ for (const name of variableNamesOf(statement))
363
+ found.set(name, text);
366
364
  }
367
- for (const match of source.matchAll(NAMED_EXPORT))
368
- for (const [exported, clause] of reExported(match[1] ?? ""))
369
- add(exported, `export { ${clause} }`);
370
365
  return found;
371
366
  };
372
- var surfaceOfArchive = (archive) => surfaceOfFiles([...readTar(archive, (path) => path.endsWith(".d.ts")).values()].map(asText));
373
- var surfaceOfFiles = (files) => {
367
+ var declaresTypes = (manifest) => {
368
+ if (typeof manifest !== "object" || manifest === null)
369
+ return false;
370
+ const record = new Map(Object.entries(manifest));
371
+ if (typeof record.get("types") === "string")
372
+ return true;
373
+ if (typeof record.get("typings") === "string")
374
+ return true;
375
+ return JSON.stringify(record.get("exports") ?? {}).includes('"types"');
376
+ };
377
+ var contributionOf = (input) => {
378
+ const { file, files, from, local, seen, statement } = input;
379
+ const nothing = [];
380
+ if (ts.isExportAssignment(statement))
381
+ return [
382
+ [
383
+ statement.isExportEquals === true ? "export=" : "default",
384
+ normalize(statement.getText(file))
385
+ ]
386
+ ];
387
+ const named = declaredNameOf(statement);
388
+ if (named !== undefined && isExported(statement))
389
+ return [
390
+ [named, local.get(named) ?? normalize(statement.getText(file))]
391
+ ];
392
+ if (ts.isVariableStatement(statement) && isExported(statement)) {
393
+ const text = normalize(statement.getText(file));
394
+ return variableNamesOf(statement).map((name) => [name, text]);
395
+ }
396
+ if (!ts.isExportDeclaration(statement))
397
+ return nothing;
398
+ const specifier = statement.moduleSpecifier !== undefined && ts.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : undefined;
399
+ const target = specifier === undefined ? undefined : resolveFrom(files, from, specifier);
400
+ const theirs = target === undefined ? new Map : exportsOf(files, target, new Set(seen));
401
+ const clause = statement.exportClause;
402
+ if (clause === undefined)
403
+ return [...theirs].filter(([name]) => name !== "default" && name !== "export=");
404
+ if (ts.isNamespaceExport(clause))
405
+ return [[clause.name.text, `export * as ${clause.name.text}`]];
406
+ return clause.elements.map((element) => {
407
+ const sourceName = (element.propertyName ?? element.name).text;
408
+ const text = (specifier === undefined ? local.get(sourceName) : theirs.get(sourceName)) ?? `export { ${sourceName} }`;
409
+ return [element.name.text, text];
410
+ });
411
+ };
412
+ var exportsOf = (files, from, seen) => {
374
413
  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
- }
414
+ const source = files.get(from);
415
+ if (source === undefined || seen.has(from))
416
+ return surface;
417
+ seen.add(from);
418
+ const file = parse(source, from);
419
+ const local = declarationsIn(file);
420
+ const contributed = file.statements.flatMap((statement) => contributionOf({ file, files, from, local, seen, statement }));
421
+ for (const [name, text] of contributed)
422
+ surface.set(name, text);
423
+ return surface;
424
+ };
425
+ var surfaceOfArchive = (archive) => {
426
+ const held = readTar(archive, (name) => DECLARATION.test(name) || name === "package.json");
427
+ const files = new Map([...held].map(([name, bytes]) => [name, asText(bytes)]));
428
+ const manifest = files.get("package.json");
429
+ let parsed;
430
+ try {
431
+ parsed = manifest === undefined ? undefined : JSON.parse(manifest);
432
+ } catch {
433
+ parsed = undefined;
434
+ }
435
+ const entries = typeEntriesOf(parsed, files);
436
+ return entries.length === 0 ? undefined : surfaceOfFiles(files, entries);
437
+ };
438
+ var surfaceOfFiles = (files, entries) => {
439
+ const surface = new Map;
440
+ for (const entry of entries)
441
+ for (const [name, text] of exportsOf(files, entry, new Set))
442
+ if (!surface.has(name))
443
+ surface.set(name, text);
381
444
  return surface;
382
445
  };
446
+ var typeCandidatesIn = (value) => {
447
+ if (typeof value === "string")
448
+ return [value];
449
+ if (typeof value !== "object" || value === null)
450
+ return [];
451
+ return Object.entries(value).flatMap(([key, nested]) => key === "types" || key === "typings" ? typeCandidatesIn(nested) : typeCandidatesIn(nested));
452
+ };
453
+ var typeEntriesOf = (manifest, files) => {
454
+ const record = typeof manifest === "object" && manifest !== null ? new Map(Object.entries(manifest)) : new Map;
455
+ const claimed = [
456
+ record.get("types"),
457
+ record.get("typings"),
458
+ ...typeCandidatesIn(record.get("exports"))
459
+ ];
460
+ const entries = claimed.filter((value) => typeof value === "string").map((value) => value.replace(/^\.\//u, "")).flatMap((cleaned) => DECLARATION.test(cleaned) ? [cleaned] : [
461
+ cleaned.replace(/\.[cm]?js$/u, ".d.ts"),
462
+ cleaned.replace(/\.[cm]?js$/u, ".d.mts"),
463
+ cleaned.replace(/\.[cm]?js$/u, ".d.cts")
464
+ ]).filter((candidate) => files.has(candidate));
465
+ const fallback = entries.length === 0 && files.has("dist/index.d.ts") ? ["dist/index.d.ts"] : [];
466
+ return [...new Set([...entries, ...fallback])];
467
+ };
383
468
  var SHAPE = {
384
469
  members: "members",
385
470
  parameters: "parameters",
386
471
  types: "types",
387
472
  unknown: "unknown"
388
473
  };
474
+ var isStub = (declaration) => declaration.startsWith("export {");
475
+ var signatureOf = (declaration) => {
476
+ const file = parse(`${declaration}
477
+ `, "declaration.d.ts");
478
+ const [statement] = file.statements;
479
+ if (statement === undefined)
480
+ return;
481
+ return statement;
482
+ };
389
483
  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 "";
484
+ const statement = signatureOf(declaration);
485
+ if (statement === undefined)
486
+ return "";
487
+ const found = [];
488
+ const visit = (node) => {
489
+ if (ts.isFunctionTypeNode(node) || ts.isFunctionDeclaration(node) || ts.isMethodSignature(node) || ts.isMethodDeclaration(node) || ts.isConstructSignatureDeclaration(node) || ts.isConstructorDeclaration(node))
490
+ found.push(node.parameters.map((parameter) => normalize(parameter.getText())).join(", "));
491
+ ts.forEachChild(node, visit);
492
+ };
493
+ visit(statement);
494
+ return found.join(" | ");
403
495
  };
404
496
  var membersOf = (declaration) => {
405
- const opened = declaration.indexOf("{");
406
- if (opened < 0)
407
- return new Set;
497
+ const statement = signatureOf(declaration);
408
498
  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 = "";
499
+ if (statement === undefined)
500
+ return members;
501
+ const visit = (node) => {
502
+ 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)))
503
+ members.add(node.name.text);
504
+ ts.forEachChild(node, visit);
416
505
  };
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();
506
+ visit(statement);
432
507
  return members;
433
508
  };
434
509
  var sameNames = (left, right) => left.size === right.size && [...left].every((name) => right.has(name));
@@ -465,9 +540,9 @@ var diffSurface = (before, after) => {
465
540
  var notable = (diff) => diff.changed.filter((change) => change.shape === "parameters" || change.shape === "members");
466
541
 
467
542
  // src/entries.ts
468
- var loadEntry = async (path) => {
543
+ var loadEntry = async (path2) => {
469
544
  try {
470
- const module = await import(pathToFileURL(path).href);
545
+ const module = await import(pathToFileURL(path2).href);
471
546
  const named = typeof module === "object" && module !== null && "change" in module ? module.change : undefined;
472
547
  const exported = named ?? (typeof module === "object" && module !== null && "default" in module ? module.default : undefined);
473
548
  const read = { exported };
@@ -480,23 +555,28 @@ var loadEntry = async (path) => {
480
555
  }
481
556
  };
482
557
  var ENTRY = /\.[cm]?ts$/u;
483
- var builtSurface = async (packageRoot, out = "dist") => {
484
- const root = join(packageRoot, out);
485
- const sources = [];
558
+ var DECLARATION2 = /\.d\.[cm]?ts$/u;
559
+ var OUTPUT_DIRECTORIES = ["dist", "build", "lib", "types", "out"];
560
+ var builtSurface = async (packageRoot) => {
561
+ const manifest = await readJsonFile(join(packageRoot, "package.json"));
562
+ const files = new Map;
486
563
  const walk = async (directory) => {
487
- const found = await readdir(directory, { withFileTypes: true }).catch(() => []);
564
+ const found = await readdir(join(packageRoot, directory), {
565
+ withFileTypes: true
566
+ }).catch(() => []);
488
567
  await Promise.all(found.map(async (entry) => {
489
- const path = join(directory, entry.name);
568
+ const relative = `${directory}/${entry.name}`;
490
569
  if (entry.isDirectory())
491
- return walk(path);
492
- if (!entry.name.endsWith(".d.ts"))
570
+ return walk(relative);
571
+ if (!DECLARATION2.test(entry.name))
493
572
  return;
494
- sources.push(await readFile(path, "utf8").catch(() => ""));
573
+ files.set(relative, await readFile(join(packageRoot, relative), "utf8").catch(() => ""));
495
574
  return;
496
575
  }));
497
576
  };
498
- await walk(root);
499
- return sources.length === 0 ? undefined : surfaceOfFiles(sources);
577
+ await Promise.all(OUTPUT_DIRECTORIES.map((directory) => walk(directory)));
578
+ const entries = typeEntriesOf(manifest, files);
579
+ return entries.length === 0 ? undefined : surfaceOfFiles(files, entries);
500
580
  };
501
581
  var changelogPaths = (packageRoot) => ({
502
582
  history: join(packageRoot, "changelog", "history.md"),
@@ -522,8 +602,8 @@ var loadUnreleased = async (packageRoot) => {
522
602
  }
523
603
  return { entries, problems };
524
604
  };
525
- var readJsonFile = async (path) => {
526
- const source = await readFile(path, "utf8").catch(() => {
605
+ var readJsonFile = async (path2) => {
606
+ const source = await readFile(path2, "utf8").catch(() => {
527
607
  return;
528
608
  });
529
609
  if (source === undefined)
@@ -535,6 +615,10 @@ var readJsonFile = async (path) => {
535
615
  return;
536
616
  }
537
617
  };
618
+ var strayEntries = async (packageRoot) => {
619
+ const names = await readdir(join(packageRoot, UNRELEASED_DIRECTORY)).catch(() => []);
620
+ return names.filter((name) => !ENTRY.test(name) && name !== ".gitkeep" && !name.startsWith("."));
621
+ };
538
622
 
539
623
  // src/json.ts
540
624
  import { readFile as readFile2, writeFile } from "fs/promises";
@@ -542,40 +626,92 @@ var indentOf = (source) => {
542
626
  const found = /\n([\t ]+)\S/u.exec(source)?.[1];
543
627
  return found === undefined || found === "" ? "\t" : found;
544
628
  };
545
- var writeJsonLike = async (path, document) => {
546
- const source = await readFile2(path, "utf8").catch(() => "");
547
- await writeFile(path, `${JSON.stringify(document, null, indentOf(source))}
629
+ var writeJsonLike = async (path2, document) => {
630
+ const source = await readFile2(path2, "utf8").catch(() => "");
631
+ await writeFile(path2, `${JSON.stringify(document, null, indentOf(source))}
548
632
  `);
549
633
  };
550
634
 
551
635
  // src/reconcile.ts
636
+ var migrationHolds = (input) => {
637
+ const { after, change, subpaths } = input;
638
+ const wrong = [];
639
+ const { migration } = change;
640
+ if ("rename" in migration) {
641
+ const { from } = migration.rename;
642
+ const into = migration.rename.to;
643
+ if (!after.has(into))
644
+ wrong.push(`\`${change.summary}\` renames to \`${into}\`, which this version does not export`);
645
+ if (after.has(from))
646
+ wrong.push(`\`${change.summary}\` renames \`${from}\` away, but this version still exports it`);
647
+ if (!change.symbols.includes(from))
648
+ wrong.push(`\`${change.summary}\` renames \`${from}\`, which is not in its own symbols`);
649
+ }
650
+ if ("moved" in migration) {
651
+ const { symbol } = migration.moved;
652
+ const into = migration.moved.to;
653
+ if (!after.has(symbol))
654
+ wrong.push(`\`${change.summary}\` moves \`${symbol}\`, which this version does not export`);
655
+ if (subpaths.size > 0 && !subpaths.has(into))
656
+ wrong.push(`\`${change.summary}\` moves \`${symbol}\` to \`${into}\`, which is not an entry point of this package`);
657
+ }
658
+ if ("resubpath" in migration && subpaths.size > 0 && !subpaths.has(migration.resubpath.to))
659
+ wrong.push(`\`${change.summary}\` points at \`${migration.resubpath.to}\`, which is not an entry point of this package`);
660
+ if (change.kind === "removed") {
661
+ for (const symbol of change.symbols)
662
+ if (after.has(symbol))
663
+ wrong.push(`\`${change.summary}\` says \`${symbol}\` was removed, but this version still exports it`);
664
+ }
665
+ return wrong;
666
+ };
552
667
  var describeReconciliation = (found) => {
553
668
  const lines = [];
669
+ const moved = {
670
+ members: "changed the members of its type",
671
+ parameters: "changed what it is called with",
672
+ removed: "is no longer exported",
673
+ types: "changed its declaration"
674
+ };
554
675
  for (const entry of found.unannounced)
555
- 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`);
676
+ lines.push(`\`${entry.name}\` ${moved[entry.kind]} and no entry says so`);
556
677
  for (const name of found.unknownSymbols)
557
678
  lines.push(`an entry names \`${name}\`, which this package does not export`);
679
+ lines.push(...found.wrongMigrations);
558
680
  return lines;
559
681
  };
560
682
  var reconcile = (input) => {
561
- const announced = new Set(input.changes.filter(isDisruptive).flatMap((change) => [...change.symbols]));
683
+ const disruptive = new Set(input.changes.filter(isDisruptive).flatMap((change) => [...change.symbols]));
684
+ const mentioned = new Set(input.changes.flatMap((change) => [...change.symbols ?? []]));
562
685
  const unannounced = [
563
- ...input.diff.removed.map((name) => ({ kind: "removed", name })),
564
- ...notable(input.diff).map((change) => ({
565
- kind: change.shape === "parameters" ? "parameters" : "members",
566
- name: change.name
567
- }))
568
- ].filter((entry) => !announced.has(entry.name));
686
+ ...input.diff.removed.filter((name) => !disruptive.has(name)).map((name) => ({ kind: "removed", name })),
687
+ ...input.diff.changed.filter((change) => !mentioned.has(change.name) && change.shape !== "unknown").map((change) => ({ kind: change.shape, name: change.name }))
688
+ ];
569
689
  const unknownSymbols = [
570
690
  ...new Set(input.changes.flatMap((change) => [...change.symbols ?? []]).filter((name) => !input.known.has(name)))
571
691
  ];
572
- return { unannounced, unknownSymbols };
692
+ const { after } = input;
693
+ const wrongMigrations = after === undefined ? [] : input.changes.filter(isDisruptive).flatMap((change) => migrationHolds({
694
+ after,
695
+ change,
696
+ subpaths: input.subpaths ?? new Set
697
+ }));
698
+ return { unannounced, unknownSymbols, wrongMigrations };
573
699
  };
574
700
 
575
701
  // src/registry.ts
576
702
  import { gunzipSync } from "zlib";
577
703
  var ABBREVIATED = "application/vnd.npm.install-v1+json";
578
704
  var DEFAULT_REGISTRY = "https://registry.npmjs.org";
705
+ var KIBIBYTE2 = 1024;
706
+ var MEBIBYTE2 = KIBIBYTE2 * KIBIBYTE2;
707
+ var MAX_ARCHIVE_MEBIBYTES = 96;
708
+ var MAX_ARCHIVE_BYTES = MAX_ARCHIVE_MEBIBYTES * MEBIBYTE2;
709
+ var MAX_DOWNLOAD_MEBIBYTES = 32;
710
+ var MAX_DOWNLOAD_BYTES = MAX_DOWNLOAD_MEBIBYTES * MEBIBYTE2;
711
+ var TIMEOUT_MS = 20000;
712
+ var PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u;
713
+ var MAX_NAME_LENGTH = 214;
714
+ var isPackageName = (name) => name.length <= MAX_NAME_LENGTH && PACKAGE_NAME.test(name);
579
715
  var isRecord2 = (value) => typeof value === "object" && value !== null;
580
716
  var readPackument = (body) => {
581
717
  if (!isRecord2(body) || !isRecord2(body.versions))
@@ -603,20 +739,30 @@ var packageArchive = async (name, version, options = {}) => {
603
739
  if (url === undefined)
604
740
  return;
605
741
  try {
606
- const response = await call(url);
742
+ const response = await call(url, {
743
+ signal: AbortSignal.timeout(TIMEOUT_MS)
744
+ });
607
745
  if (!response.ok)
608
746
  return;
609
- return new Uint8Array(gunzipSync(new Uint8Array(await response.arrayBuffer())));
747
+ const compressed = new Uint8Array(await response.arrayBuffer());
748
+ if (compressed.byteLength > MAX_DOWNLOAD_BYTES)
749
+ return;
750
+ return new Uint8Array(gunzipSync(compressed, { maxOutputLength: MAX_ARCHIVE_BYTES }));
610
751
  } catch {
611
752
  return;
612
753
  }
613
754
  };
614
755
  var packageVersions = async (name, options = {}) => {
756
+ if (!isPackageName(name))
757
+ return;
615
758
  const call = options.fetch ?? fetch;
616
759
  const url = `${options.registry ?? DEFAULT_REGISTRY}/${name.replace("/", "%2f")}`;
617
760
  try {
618
- const abbreviated = await call(url, { headers: { accept: ABBREVIATED } });
619
- const response = abbreviated.ok ? abbreviated : await call(url);
761
+ const abbreviated = await call(url, {
762
+ headers: { accept: ABBREVIATED },
763
+ signal: AbortSignal.timeout(TIMEOUT_MS)
764
+ });
765
+ const response = abbreviated.ok ? abbreviated : await call(url, { signal: AbortSignal.timeout(TIMEOUT_MS) });
620
766
  if (!response.ok)
621
767
  return;
622
768
  return readPackument(await response.json());
@@ -628,7 +774,7 @@ var publishedChangelog = async (name, version, options = {}) => {
628
774
  const archive = await packageArchive(name, version, options);
629
775
  if (!archive)
630
776
  return;
631
- const file = readTar(archive, (path) => path === CHANGELOG_FILE).get(CHANGELOG_FILE);
777
+ const file = readTar(archive, (path2) => path2 === CHANGELOG_FILE).get(CHANGELOG_FILE);
632
778
  if (!file)
633
779
  return;
634
780
  try {
@@ -706,6 +852,7 @@ var renderRelease = (release) => {
706
852
  ].join(`
707
853
  `);
708
854
  };
855
+ var GENERATED_MARKER = "This file is generated by `absolute-changelog`";
709
856
  var renderChangelogJson = (changelog) => `${JSON.stringify({
710
857
  contract: CHANGELOG_CONTRACT,
711
858
  name: changelog.name,
@@ -731,7 +878,7 @@ var renderMarkdown = (changelog, options = {}) => {
731
878
  "",
732
879
  `All notable changes to \`${changelog.name}\`.`,
733
880
  "",
734
- "This file is generated by `absolute-changelog` from the entries in",
881
+ `${GENERATED_MARKER} from the entries in`,
735
882
  "`changelog/`. Edit an entry, not this file \u2014 and add new ones under",
736
883
  "`changelog/unreleased/`."
737
884
  ].join(`
@@ -820,6 +967,12 @@ var writeBoth = async (root, changelog) => {
820
967
  await writeFile2(paths.markdown, renderMarkdown(changelog, history === undefined ? {} : { history }));
821
968
  };
822
969
  var slug = (summary) => summary.toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-|-$/gu, "").slice(0, SLUG_MAX_LENGTH) || "change";
970
+ var sourceEntryOf = (root) => {
971
+ for (const candidate of ["src/index.ts", "src/index.tsx", "index.ts"])
972
+ if (existsSync(join2(root, candidate)))
973
+ return `../../${candidate.replace(/\.tsx?$/u, "")}`;
974
+ return;
975
+ };
823
976
  var add = async (root, flags) => {
824
977
  const kind = text(flags, "kind");
825
978
  const summary = text(flags, "summary");
@@ -843,10 +996,12 @@ var add = async (root, flags) => {
843
996
  ] : [
844
997
  ` migration: { instruction: ${JSON.stringify(instruction)}, manual: true },`
845
998
  ];
999
+ const api = sourceEntryOf(root);
846
1000
  const body = [
1001
+ ...api === undefined ? [] : [`import type * as Api from '${api}';`],
847
1002
  "import type { Change } from '@absolutejs/changelog';",
848
1003
  "",
849
- "export const change: Change = {",
1004
+ `export const change: Change${api === undefined ? "" : "<typeof Api>"} = {`,
850
1005
  ` kind: ${JSON.stringify(kind)},`,
851
1006
  ...disruptive ? migration : [],
852
1007
  ` summary: ${JSON.stringify(summary)}${symbols.length > 0 ? "," : ""}`,
@@ -933,22 +1088,45 @@ var check = async (root, flags) => {
933
1088
  const actual = await readFile3(changelogPaths(root).markdown, "utf8").catch(() => {
934
1089
  return;
935
1090
  });
936
- if (newest !== undefined && actual !== expected)
1091
+ if (actual !== expected)
937
1092
  failures.push(`${CHANGELOG_MARKDOWN} is not what the entries say it should be \u2014 it is generated; run \`absolute-changelog release\` or restore it`);
938
1093
  const tsconfig = await readJsonFile(join2(root, "tsconfig.json"));
939
1094
  const include = isPackage(tsconfig) ? tsconfig.include : undefined;
940
1095
  if (Array.isArray(include) && !coversEntries(include))
941
1096
  failures.push('tsconfig.json does not compile changelog/, so nothing is checking the entries \u2014 add "changelog/**/*" to its include list');
1097
+ for (const stray of await strayEntries(root))
1098
+ failures.push(`${UNRELEASED_DIRECTORY}/${stray} is not an entry, so no release will ever read it`);
1099
+ const shipped = Array.isArray(manifest.files) ? manifest.files : [];
1100
+ for (const file of [CHANGELOG_FILE, CHANGELOG_MARKDOWN])
1101
+ if (shipped.length > 0 && !shipped.includes(file))
1102
+ failures.push(`package.json does not ship ${file}`);
942
1103
  const offline = flags.offline === true;
943
1104
  const built = await builtSurface(root);
944
- if (!offline && built !== undefined && newest !== undefined) {
945
- const published = await publishedSurface(manifest.name ?? "", newest.version);
1105
+ const shipsTypes = declaresTypes(await readJsonFile(join2(root, "package.json")));
1106
+ if (built === undefined && shipsTypes)
1107
+ failures.push("there are no declaration files where package.json says its types are \u2014 build before the gate, or nothing compares them");
1108
+ if (!shipsTypes)
1109
+ console.warn(" (this package ships no types, so none were compared)");
1110
+ const versions = offline ? undefined : await packageVersions(manifest.name ?? "");
1111
+ if (!offline && versions === undefined)
1112
+ failures.push(`the registry did not answer for ${manifest.name}, so nothing was compared \u2014 pass --offline to skip the comparison deliberately`);
1113
+ const baseline = (versions?.published ?? []).filter((version) => compareVersions(version, manifest.version) <= 0).sort(compareVersions).at(LAST2);
1114
+ if (versions !== undefined && baseline === undefined && !offline)
1115
+ console.warn(` (nothing published at or below ${manifest.version}; this looks like a first release)`);
1116
+ if (built !== undefined && baseline !== undefined) {
1117
+ const published = await publishedSurface(manifest.name ?? "", baseline);
946
1118
  if (published === undefined)
947
- console.warn(` (could not read ${manifest.name}@${newest.version} from the registry; the types were not checked)`);
1119
+ failures.push(`could not read the published types of ${manifest.name}@${baseline}, so nothing was compared`);
1120
+ const speaking = [
1121
+ ...entries.map((entry) => entry.change),
1122
+ ...current.releases.filter((entry) => compareVersions(entry.version, baseline) > 0).flatMap((entry) => [...entry.changes])
1123
+ ];
948
1124
  failures.push(...published === undefined ? [] : describeReconciliation(reconcile({
949
- changes: entries.map((entry) => entry.change),
1125
+ after: new Set(built.keys()),
1126
+ changes: speaking,
950
1127
  diff: diffSurface(published, built),
951
- known: new Set([...published.keys(), ...built.keys()])
1128
+ known: new Set([...published.keys(), ...built.keys()]),
1129
+ subpaths: subpathsOf(manifest)
952
1130
  })));
953
1131
  }
954
1132
  if (failures.length > 0)
@@ -956,16 +1134,23 @@ var check = async (root, flags) => {
956
1134
  `));
957
1135
  console.log(`${manifest.name}@${manifest.version}: changelog holds (${entries.length} pending, ${current.releases.length} released)`);
958
1136
  };
1137
+ var LAST2 = -1;
1138
+ var subpathsOf = (manifest) => {
1139
+ const { exports } = manifest;
1140
+ if (typeof exports !== "object" || exports === null)
1141
+ return new Set;
1142
+ return new Set(Object.keys(exports).map((key) => key === "." ? manifest.name ?? "" : `${manifest.name}${key.slice(1)}`));
1143
+ };
959
1144
  var CHECKING_CONFIGS = ["tsconfig.json", "tsconfig.eslint.json"];
960
1145
  var coversEntries = (include) => Array.isArray(include) && include.some((entry) => typeof entry === "string" && entry.startsWith("changelog"));
961
1146
  var includeEntriesIn = async (root, file) => {
962
- const path = join2(root, file);
963
- const document = await readJsonFile(path);
1147
+ const path2 = join2(root, file);
1148
+ const document = await readJsonFile(path2);
964
1149
  if (!isPackage(document) || !Array.isArray(document.include))
965
1150
  return false;
966
1151
  if (coversEntries(document.include))
967
1152
  return false;
968
- await writeJsonLike(path, {
1153
+ await writeJsonLike(path2, {
969
1154
  ...document,
970
1155
  include: [...document.include, "changelog/**/*"]
971
1156
  });
@@ -976,8 +1161,8 @@ var includeEntries = async (root) => {
976
1161
  return wired.some(Boolean) ? undefined : "no tsconfig include list to wire \u2014 make sure changelog/ is compiled by something";
977
1162
  };
978
1163
  var ignoreGenerated = async (root) => {
979
- const path = join2(root, ".prettierignore");
980
- const existing = await readFile3(path, "utf8").catch(() => "");
1164
+ const path2 = join2(root, ".prettierignore");
1165
+ const existing = await readFile3(path2, "utf8").catch(() => "");
981
1166
  const wanted = [CHANGELOG_FILE, CHANGELOG_MARKDOWN].filter((name) => !existing.split(`
982
1167
  `).includes(name));
983
1168
  if (wanted.length === 0)
@@ -985,26 +1170,38 @@ var ignoreGenerated = async (root) => {
985
1170
  const head = existing.trim() === "" ? `# Generated by absolute-changelog. Edit the entries, not these.
986
1171
  ` : `${existing.trimEnd()}
987
1172
  `;
988
- await writeFile2(path, `${head}${wanted.join(`
1173
+ await writeFile2(path2, `${head}${wanted.join(`
989
1174
  `)}
990
1175
  `);
991
1176
  };
1177
+ var PUBLISH_GATE = "absolute-changelog check";
1178
+ var withPublishGate = (scripts) => {
1179
+ const existing = scripts.prepublishOnly;
1180
+ if (typeof existing === "string" && existing.includes(PUBLISH_GATE))
1181
+ return;
1182
+ return {
1183
+ ...scripts,
1184
+ prepublishOnly: typeof existing === "string" && existing.trim() !== "" ? `${existing} && ${PUBLISH_GATE}` : PUBLISH_GATE
1185
+ };
1186
+ };
992
1187
  var adopt = async (root) => {
993
1188
  const manifest = await readPackage(root);
994
1189
  const paths = changelogPaths(root);
995
1190
  await mkdir(paths.unreleased, { recursive: true });
996
1191
  await writeFile2(join2(paths.unreleased, ".gitkeep"), "# Entries wait here. One file per change; `absolute-changelog release` consumes them.\n");
997
- if (existsSync(paths.markdown) && !existsSync(paths.history)) {
998
- const previous = await readFile3(paths.markdown, "utf8");
1192
+ const previous = existsSync(paths.markdown) ? await readFile3(paths.markdown, "utf8") : undefined;
1193
+ if (previous !== undefined && !previous.includes(GENERATED_MARKER) && !existsSync(paths.history)) {
999
1194
  await mkdir(join2(root, "changelog"), { recursive: true });
1000
1195
  await writeFile2(paths.history, previous.replace(/^#\s+Changelog\s*/u, "").trimStart());
1001
1196
  await rm(paths.markdown, { force: true });
1002
1197
  }
1003
1198
  const files = manifest.files ?? [];
1004
1199
  const wanted = [CHANGELOG_FILE, CHANGELOG_MARKDOWN].filter((entry) => !files.includes(entry));
1005
- if (wanted.length > 0)
1200
+ const scripts = typeof manifest.scripts === "object" && manifest.scripts !== null ? withPublishGate(manifest.scripts) : undefined;
1201
+ if (wanted.length > 0 || scripts !== undefined)
1006
1202
  await writeJsonLike(join2(root, "package.json"), {
1007
1203
  ...manifest,
1204
+ ...scripts === undefined ? {} : { scripts },
1008
1205
  files: [...files, ...wanted].sort()
1009
1206
  });
1010
1207
  await ignoreGenerated(root);
@@ -1034,5 +1231,5 @@ await main().catch((error) => {
1034
1231
  process.exit(1);
1035
1232
  });
1036
1233
 
1037
- //# debugId=3B21DCC7DC15D28564756E2164756E21
1234
+ //# debugId=0E6F1175A8DDD6A464756E2164756E21
1038
1235
  //# sourceMappingURL=cli.js.map