@heroiclands/package-build 20.4.0 → 20.6.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.
Files changed (78) hide show
  1. package/CHANGELOG.md +288 -0
  2. package/CONTENT.md +213 -20
  3. package/README.md +19 -1
  4. package/bin/content-build.mjs +135 -32
  5. package/bin/package-build.mjs +46 -13
  6. package/content-config.mjs +345 -101
  7. package/docs/api.md +1352 -0
  8. package/docs/commands.md +1609 -0
  9. package/docs/configuration.md +1432 -0
  10. package/docs/content-format.md +16 -6
  11. package/docs/diagnostics.md +356 -0
  12. package/docs/getting-started.md +813 -0
  13. package/docs/project-setup.md +469 -0
  14. package/engine/actor-compiler.mjs +30 -27
  15. package/engine/address-diff.mjs +45 -41
  16. package/engine/base-compiler.mjs +6 -0
  17. package/engine/bundles.mjs +9 -0
  18. package/engine/content-address.mjs +9 -9
  19. package/engine/content-index.mjs +44 -23
  20. package/engine/content-links.mjs +44 -11
  21. package/engine/content-lint.mjs +44 -10
  22. package/engine/content-tables.mjs +32 -27
  23. package/engine/folder-notes.mjs +4 -2
  24. package/engine/frontmatter-lint.mjs +35 -38
  25. package/engine/generate.mjs +5 -0
  26. package/engine/helpers.mjs +86 -32
  27. package/engine/index.mjs +12 -2
  28. package/engine/journals.mjs +9 -0
  29. package/engine/note-claims.mjs +18 -10
  30. package/engine/note-schemas.mjs +0 -5
  31. package/engine/note-vocabulary.mjs +32 -31
  32. package/engine/pack-config.mjs +26 -12
  33. package/engine/pack-router.mjs +0 -0
  34. package/engine/pdf-build.mjs +464 -0
  35. package/engine/pdf-fonts.mjs +420 -0
  36. package/engine/pdf-render.mjs +876 -0
  37. package/engine/pdf-toc.mjs +525 -0
  38. package/engine/scenes.mjs +14 -5
  39. package/engine/schema-check.mjs +1 -1
  40. package/engine/site-build.mjs +21 -3
  41. package/engine/web-wikilinks.mjs +6 -3
  42. package/engine/wikilinks.mjs +2 -4
  43. package/hm3/actors.mjs +8 -0
  44. package/hm3/items.mjs +8 -0
  45. package/package.json +1 -1
  46. package/release.mjs +63 -3
  47. package/sohl/actors.mjs +8 -0
  48. package/sohl/items.mjs +8 -0
  49. package/sohl/note-schemas.mjs +5 -5
  50. package/types/content-config.d.mts +66 -15
  51. package/types/engine/actor-compiler.d.mts +34 -30
  52. package/types/engine/address-diff.d.mts +57 -3
  53. package/types/engine/base-compiler.d.mts +10 -2
  54. package/types/engine/bundles.d.mts +9 -0
  55. package/types/engine/content-address.d.mts +9 -9
  56. package/types/engine/content-index.d.mts +57 -13
  57. package/types/engine/content-lint.d.mts +6 -4
  58. package/types/engine/content-tables.d.mts +49 -18
  59. package/types/engine/frontmatter-lint.d.mts +3 -2
  60. package/types/engine/helpers.d.mts +105 -31
  61. package/types/engine/index.d.mts +4 -0
  62. package/types/engine/journals.d.mts +9 -0
  63. package/types/engine/note-claims.d.mts +17 -10
  64. package/types/engine/note-vocabulary.d.mts +23 -196
  65. package/types/engine/pack-config.d.mts +4 -4
  66. package/types/engine/pdf-build.d.mts +42 -0
  67. package/types/engine/pdf-fonts.d.mts +30 -0
  68. package/types/engine/pdf-render.d.mts +156 -0
  69. package/types/engine/pdf-toc.d.mts +114 -0
  70. package/types/engine/scenes.d.mts +10 -1
  71. package/types/engine/schema-check.d.mts +2 -2
  72. package/types/engine/site-build.d.mts +34 -6
  73. package/types/engine/wikilinks.d.mts +2 -3
  74. package/types/hm3/actors.d.mts +8 -0
  75. package/types/hm3/items.d.mts +8 -0
  76. package/types/release.d.mts +15 -4
  77. package/types/sohl/actors.d.mts +10 -2
  78. package/types/sohl/items.d.mts +8 -0
@@ -63,7 +63,9 @@ import prefix from "loglevel-plugin-prefix";
63
63
  import yargs from "yargs";
64
64
  import { hideBin } from "yargs/helpers";
65
65
  import { compilePacks, cleanPacks, unpackPacks } from "../engine/compendiums.mjs";
66
+ import { compilesFoundryDocuments } from "../content-config.mjs";
66
67
  import { loadPackConfig } from "../engine/pack-config.mjs";
68
+ import { buildPdf } from "../engine/pdf-build.mjs";
67
69
  import {
68
70
  fetchAllCatalogs,
69
71
  fetchCatalogFromPath,
@@ -245,6 +247,7 @@ const argv = yargs(hideBin(process.argv))
245
247
  .command(markdownCommand())
246
248
  .command(contentIndexCommand())
247
249
  .command(siteCommand())
250
+ .command(pdfCommand())
248
251
  .command(reachabilityCommand())
249
252
  .command(addressesCommand())
250
253
  .version(ownVersion())
@@ -349,13 +352,19 @@ function docsCommand() {
349
352
  if (current !== page) {
350
353
  // Staleness belongs to the whole file, so no line is
351
354
  // named — the diagnostics contract drops a field it
352
- // cannot supply rather than guessing one.
353
- log.error(
354
- `${relative}: error: out of date with the ` +
355
- `item-field declarationsrun ` +
356
- `\`content-build docs item-fields\` and commit ` +
357
- `the regenerated file`,
358
- );
355
+ // cannot supply rather than guessing one. Routed
356
+ // through `emitDiagnostic`, not `log.error`, so the
357
+ // path starts the line unprefixed by loglevel's
358
+ // `[timestamp] [ERROR]:` banner the same reason
359
+ // every other located failure in this file uses it.
360
+ emitDiagnostic({
361
+ file: relative,
362
+ severity: "error",
363
+ message:
364
+ "out of date with the item-field declarations " +
365
+ "— run `content-build docs item-fields` and " +
366
+ "commit the regenerated file",
367
+ });
359
368
  process.exitCode = 1;
360
369
  return;
361
370
  }
@@ -1363,6 +1372,87 @@ function contentIndexCommand() {
1363
1372
  };
1364
1373
  }
1365
1374
 
1375
+ /**
1376
+ * `content-build pdf` — build the book the content tree publishes as.
1377
+ *
1378
+ * The third surface, beside `package compile` and `site`. It takes the same
1379
+ * `--out` override the site command does, and reports what it found in the same
1380
+ * `file:line:column: severity: message` shape every other pass here uses — so
1381
+ * one editor, one CI annotator and one `grep` read all three.
1382
+ *
1383
+ * **Not building is a normal outcome, and it exits 0.** A package publishing
1384
+ * only a homepage, a package with no `pdf:` block and a package with no content
1385
+ * tree have each said they publish no book; saying so on stderr and failing the
1386
+ * command would break the release of every package that is not a book.
1387
+ *
1388
+ * @returns {object} The yargs command module.
1389
+ */
1390
+ function pdfCommand() {
1391
+ return {
1392
+ command: "pdf",
1393
+ describe: "Build the content tree as a PDF",
1394
+ builder: (yargs) => {
1395
+ yargs
1396
+ .option("out", {
1397
+ describe: "Write the book here instead of the configured `pdf.out`.",
1398
+ type: "string",
1399
+ })
1400
+ .option("book-version", {
1401
+ describe: "Stamp this version on the title page and in the file name.",
1402
+ type: "string",
1403
+ })
1404
+ // Declared positively so yargs derives `--no-compile` from it;
1405
+ // an option literally named `no-compile` is rejected as unknown
1406
+ // when it is actually passed.
1407
+ .option("compile", {
1408
+ describe: "Run the Typst compiler. `--no-compile` emits the source and stops.",
1409
+ type: "boolean",
1410
+ default: true,
1411
+ });
1412
+ },
1413
+ handler: async (argv) => {
1414
+ try {
1415
+ const result = await buildPdf({
1416
+ ...(argv.out ? { out: argv.out } : {}),
1417
+ ...(argv.bookVersion ? { version: String(argv.bookVersion) } : {}),
1418
+ compile: argv.compile !== false,
1419
+ });
1420
+
1421
+ for (const finding of result.findings) emitDiagnostic(finding);
1422
+
1423
+ if (!result.built) {
1424
+ // A reason is a deliberate no-op — the fence, an absent
1425
+ // block, an absent tree. No reason means the findings above
1426
+ // say what went wrong, and those decide the exit code.
1427
+ if (result.reason) {
1428
+ log.info(`No book built: ${result.reason}`);
1429
+ return;
1430
+ }
1431
+ process.exitCode = 1;
1432
+ return;
1433
+ }
1434
+
1435
+ const { stats } = result;
1436
+ log.info(
1437
+ `${stats.notes} entr(ies) and ${stats.prose} prose file(s) across ` +
1438
+ `${stats.sections} section(s)` +
1439
+ (stats.repeated ?
1440
+ `, ${stats.repeated} of them printed more than once`
1441
+ : ""),
1442
+ );
1443
+ log.info(`Typst source: ${result.typ}`);
1444
+ if (result.pdf) log.info(`Book: ${result.pdf}`);
1445
+ // Findings are reported, never fatal — a filter that selected
1446
+ // nothing is worth fixing and is not worth refusing to publish
1447
+ // the other two thousand entries over.
1448
+ } catch (err) {
1449
+ reportFailure(err);
1450
+ process.exitCode = 1;
1451
+ }
1452
+ },
1453
+ };
1454
+ }
1455
+
1366
1456
  /**
1367
1457
  * `content-build site` — publish the content tree as a website.
1368
1458
  *
@@ -1611,30 +1701,6 @@ function reachabilityCommand() {
1611
1701
  }
1612
1702
 
1613
1703
  // eslint-disable-next-line
1614
- /**
1615
- * `deps fetch` — fill the caches this build resolves other packages through:
1616
- * the **content index** of every declared dependency, and the **item
1617
- * catalogue** of those additionally declaring `itemCatalog: true`.
1618
- *
1619
- * The two sets differ deliberately. Citing another package's *addresses* and
1620
- * embedding its *items* are separate edges, and a package may have either
1621
- * without the other — `harn-ensemble` cites no foreign address and embeds
1622
- * 324,016 item references.
1623
- *
1624
- * Its own command rather than a step of `package compile`, so that a compile
1625
- * never reaches the network. A build that downloads silently is not
1626
- * reproducible, breaks offline, and hides a dependency's version change behind
1627
- * a passing run.
1628
- *
1629
- * `--from` fills the cache from a locally built artifact instead of a release,
1630
- * which is what makes iterating across packages possible: change the system,
1631
- * build it, and see the effect on every consumer **before** any of it ships.
1632
- * Otherwise testing a dependency change against its consumers costs a release
1633
- * round-trip, which makes releasing a debugging tool rather than a publishing
1634
- * decision.
1635
- *
1636
- * @returns {object} The yargs command module.
1637
- */
1638
1704
  /**
1639
1705
  * Resolve which declared dependency `--from` supplies, and cache it.
1640
1706
  *
@@ -1668,6 +1734,30 @@ async function fetchFromLocalArtifact(config, argv) {
1668
1734
  }
1669
1735
  }
1670
1736
 
1737
+ /**
1738
+ * `deps fetch` — fill the caches this build resolves other packages through:
1739
+ * the **content index** of every declared dependency, and the **item
1740
+ * catalogue** of those additionally declaring `itemCatalog: true`.
1741
+ *
1742
+ * The two sets differ deliberately. Citing another package's *addresses* and
1743
+ * embedding its *items* are separate edges, and a package may have either
1744
+ * without the other — `harn-ensemble` cites no foreign address and embeds
1745
+ * 324,016 item references.
1746
+ *
1747
+ * Its own command rather than a step of `package compile`, so that a compile
1748
+ * never reaches the network. A build that downloads silently is not
1749
+ * reproducible, breaks offline, and hides a dependency's version change behind
1750
+ * a passing run.
1751
+ *
1752
+ * `--from` fills the cache from a locally built artifact instead of a release,
1753
+ * which is what makes iterating across packages possible: change the system,
1754
+ * build it, and see the effect on every consumer **before** any of it ships.
1755
+ * Otherwise testing a dependency change against its consumers costs a release
1756
+ * round-trip, which makes releasing a debugging tool rather than a publishing
1757
+ * decision.
1758
+ *
1759
+ * @returns {object} The yargs command module.
1760
+ */
1671
1761
  function depsCommand() {
1672
1762
  return {
1673
1763
  command: "deps <action>",
@@ -1906,11 +1996,24 @@ function packageCommand() {
1906
1996
  // an unhandled-rejection stack trace. Report the message and set a
1907
1997
  // failing exit code, so a build guard reads as a build failure.
1908
1998
  try {
1999
+ const config = loadPackConfig();
2000
+ // The compile passes do not run for a package that declares no
2001
+ // packs by rule. Refused rather than exiting 0 having compiled
2002
+ // nothing: a build that succeeds and produces no documents is
2003
+ // the quiet failure this toolchain reports everywhere else.
2004
+ if (!compilesFoundryDocuments(config)) {
2005
+ throw new Error(
2006
+ `\`packageKind: ${config.packageKind}\` compiles no ` +
2007
+ `compendium, so there is nothing to ${action}. Build ` +
2008
+ `its site with \`content-build site\` and its book ` +
2009
+ `with \`content-build pdf\`.`,
2010
+ );
2011
+ }
1909
2012
  // The one directory the pipeline creates rather than expects:
1910
2013
  // `unpack` writes the extracted JSON there and `compile` reads
1911
2014
  // it back. Created here rather than at module scope so that
1912
2015
  // asking the CLI its version needs no configuration (#2).
1913
- fs.mkdirSync(loadPackConfig().paths.unpack, {
2016
+ fs.mkdirSync(config.paths.unpack, {
1914
2017
  recursive: true,
1915
2018
  });
1916
2019
  switch (action) {
@@ -78,6 +78,7 @@ import yargs from "yargs";
78
78
  import { hideBin } from "yargs/helpers";
79
79
 
80
80
  import { loadPackageBuildConfig } from "../config.mjs";
81
+ import { compilesFoundryDocuments } from "../content-config.mjs";
81
82
  import { loadPackConfig, packConfigPath } from "../engine/pack-config.mjs";
82
83
  import { cleanBuildArtifacts, stageAssets } from "../stage.mjs";
83
84
  import { buildSchemaArtifact } from "../engine/schema-extract.mjs";
@@ -413,6 +414,18 @@ function manifestCommand() {
413
414
  handler: handler(async () => {
414
415
  const config = loadPackageBuildConfig();
415
416
  const shared = loadPackConfig();
417
+ // Refused rather than written: a manifest is the file Foundry reads
418
+ // to install a package, and a documentation package is not one. An
419
+ // emitted `module.json` would advertise an installable package with
420
+ // no packs, no compatibility range and no id — the plausible lie
421
+ // this toolchain refuses everywhere else.
422
+ if (!compilesFoundryDocuments(shared)) {
423
+ die(
424
+ `\`packageKind: ${shared.packageKind}\` ships no Foundry ` +
425
+ `package, so there is no manifest to generate. The site ` +
426
+ `and the book are built by \`content-build\`.`,
427
+ );
428
+ }
416
429
  const packageJson = readPackageJson(config);
417
430
 
418
431
  let flags;
@@ -704,16 +717,6 @@ function langHardcoded(config) {
704
717
  );
705
718
  }
706
719
 
707
- /**
708
- * `lang <action>` — the three localization guards.
709
- *
710
- * They are three questions about one subject, and each is blind to what the
711
- * others see: `check` asks whether the file will load at all, `coverage`
712
- * whether the keys and the code agree, `hardcoded` whether the markup ever
713
- * asks for a key in the first place.
714
- *
715
- * @returns {object} The yargs command module.
716
- */
717
720
  /**
718
721
  * `package-build yaml` — lint note frontmatter and every YAML file.
719
722
  *
@@ -826,6 +829,16 @@ function labelsCheck(args) {
826
829
  console.log(`package-build: registry and §3 agree (${count} labels).`);
827
830
  }
828
831
 
832
+ /**
833
+ * `lang <action>` — the three localization guards.
834
+ *
835
+ * They are three questions about one subject, and each is blind to what the
836
+ * others see: `check` asks whether the file will load at all, `coverage`
837
+ * whether the keys and the code agree, `hardcoded` whether the markup ever
838
+ * asks for a key in the first place.
839
+ *
840
+ * @returns {object} The yargs command module.
841
+ */
829
842
  function langCommand() {
830
843
  return {
831
844
  command: "lang <action>",
@@ -931,17 +944,37 @@ function releaseCommand() {
931
944
  return {
932
945
  command: "release",
933
946
  describe: "Package the staged build for release",
934
- builder: (y) => y,
935
- handler: handler(async () => {
947
+ builder: (y) =>
948
+ y.option("no-pdf", {
949
+ describe:
950
+ "Skip the content-tree book. A package that publishes no content " +
951
+ "already builds none; this is for a release that has a tree and " +
952
+ "does not want the book.",
953
+ type: "boolean",
954
+ }),
955
+ handler: handler(async (argv) => {
936
956
  const config = loadPackageBuildConfig();
937
- const { zip, version, bytes } = await packRelease({
957
+ const result = await packRelease({
938
958
  artifact: config.artifact,
959
+ pdf: argv.pdf !== false,
939
960
  });
961
+ const { zip, version, bytes } = result;
940
962
  console.log(
941
963
  `✅ Packaged ${version} for release: ` +
942
964
  `${path.relative(config.rootDir, zip)} ` +
943
965
  `(${(bytes / 1024 / 1024).toFixed(1)} MB)`,
944
966
  );
967
+ // Reported, never fatal: the archive above is the release, and a
968
+ // book that would not set is a problem to fix rather than a reason
969
+ // to have published nothing.
970
+ for (const finding of result.pdfFindings ?? []) {
971
+ console.error(`${finding.file ?? "book"}: ${finding.severity}: ${finding.message}`);
972
+ }
973
+ if (result.pdf) {
974
+ console.log(`📕 Book: ${path.relative(config.rootDir, result.pdf)}`);
975
+ } else if (result.pdfSkipped) {
976
+ console.log(` No book: ${result.pdfSkipped}`);
977
+ }
945
978
  }),
946
979
  };
947
980
  }