@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/CHANGELOG.md +46 -0
- package/README.md +41 -4
- package/changelog.json +194 -40
- package/dist/apply.d.ts +3 -5
- package/dist/cli.js +337 -140
- package/dist/cli.js.map +11 -11
- package/dist/entries.d.ts +24 -5
- package/dist/index.d.ts +2 -2
- package/dist/index.js +513 -230
- package/dist/index.js.map +11 -11
- package/dist/read.d.ts +1 -1
- package/dist/reconcile.d.ts +14 -3
- package/dist/registry.d.ts +1 -0
- package/dist/surface.d.ts +21 -22
- package/dist/types.d.ts +41 -9
- package/dist/write.d.ts +9 -0
- package/package.json +74 -70
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
|
|
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 =
|
|
325
|
-
var
|
|
326
|
-
var
|
|
327
|
-
var
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
var
|
|
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
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
-
|
|
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
|
|
373
|
-
|
|
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
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
|
406
|
-
if (opened < 0)
|
|
407
|
-
return new Set;
|
|
497
|
+
const statement = signatureOf(declaration);
|
|
408
498
|
const members = new Set;
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
const
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
-
|
|
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 (
|
|
543
|
+
var loadEntry = async (path2) => {
|
|
469
544
|
try {
|
|
470
|
-
const module = await import(pathToFileURL(
|
|
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
|
|
484
|
-
|
|
485
|
-
|
|
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, {
|
|
564
|
+
const found = await readdir(join(packageRoot, directory), {
|
|
565
|
+
withFileTypes: true
|
|
566
|
+
}).catch(() => []);
|
|
488
567
|
await Promise.all(found.map(async (entry) => {
|
|
489
|
-
const
|
|
568
|
+
const relative = `${directory}/${entry.name}`;
|
|
490
569
|
if (entry.isDirectory())
|
|
491
|
-
return walk(
|
|
492
|
-
if (!entry.name
|
|
570
|
+
return walk(relative);
|
|
571
|
+
if (!DECLARATION2.test(entry.name))
|
|
493
572
|
return;
|
|
494
|
-
|
|
573
|
+
files.set(relative, await readFile(join(packageRoot, relative), "utf8").catch(() => ""));
|
|
495
574
|
return;
|
|
496
575
|
}));
|
|
497
576
|
};
|
|
498
|
-
await walk(
|
|
499
|
-
|
|
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 (
|
|
526
|
-
const source = await readFile(
|
|
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 (
|
|
546
|
-
const source = await readFile2(
|
|
547
|
-
await writeFile(
|
|
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(
|
|
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
|
|
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
|
-
...
|
|
565
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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, {
|
|
619
|
-
|
|
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, (
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
945
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
963
|
-
const document = await readJsonFile(
|
|
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(
|
|
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
|
|
980
|
-
const existing = await readFile3(
|
|
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(
|
|
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
|
-
|
|
998
|
-
|
|
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
|
-
|
|
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=
|
|
1234
|
+
//# debugId=0E6F1175A8DDD6A464756E2164756E21
|
|
1038
1235
|
//# sourceMappingURL=cli.js.map
|