@heroiclands/package-build 24.0.4 → 24.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +8 -0
  3. package/assets/styles/content-base.css +25 -10
  4. package/bin/content-commands.mjs +183 -15
  5. package/bin/package-build.mjs +5 -170
  6. package/docs/authoring/links-and-markup.md +215 -179
  7. package/docs/commands.md +88 -2
  8. package/docs/configuration.md +6 -3
  9. package/docs/content-format.md +4 -3
  10. package/docs/diagnostics.md +57 -29
  11. package/docs/guides/book.md +1 -1
  12. package/docs/guides/module-project.md +2 -2
  13. package/docs/guides/site.md +2 -2
  14. package/docs/guides/writing-in-invented-scripts.md +175 -125
  15. package/docs/reference/format-details.md +53 -35
  16. package/docs/reference/note-types.md +10 -10
  17. package/docs/reference/theme-pages.md +18 -9
  18. package/engine/build-assets.mjs +179 -0
  19. package/engine/calendar-artifacts.mjs +7 -3
  20. package/engine/compendiums.mjs +16 -11
  21. package/engine/content-render.mjs +23 -2
  22. package/engine/content-scripts.mjs +348 -140
  23. package/engine/content-session.mjs +62 -14
  24. package/engine/content-spans.mjs +13 -1
  25. package/engine/content-timings.mjs +67 -0
  26. package/engine/content-verse.mjs +158 -373
  27. package/engine/note-vocabulary.mjs +5 -5
  28. package/engine/pdf-render.mjs +63 -44
  29. package/engine/run-rows.mjs +18 -20
  30. package/engine/script-font-delivery.mjs +22 -15
  31. package/engine/verse-metre.mjs +36 -8
  32. package/engine/written-in.mjs +9 -13
  33. package/package.json +1 -1
  34. package/site-theme/static/css/style.css +19 -10
  35. package/types/engine/build-assets.d.mts +23 -0
  36. package/types/engine/calendar-artifacts.d.mts +5 -1
  37. package/types/engine/content-scripts.d.mts +18 -7
  38. package/types/engine/content-session.d.mts +19 -1
  39. package/types/engine/content-timings.d.mts +14 -0
  40. package/types/engine/content-verse.d.mts +37 -163
  41. package/types/engine/run-rows.d.mts +16 -9
  42. package/types/engine/script-font-delivery.d.mts +8 -4
  43. package/types/engine/verse-metre.d.mts +4 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,51 @@
1
1
  # @heroiclands/package-build
2
2
 
3
+ ## 24.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ **Content builds**
8
+
9
+ - Build indexes, calendars, assets and packs with
10
+ `package-build content build --calendaria-version 1.4.2`, sharing preparation
11
+ across validation and compilation.
12
+
13
+ ## 24.1.0
14
+
15
+ ### Minor Changes
16
+
17
+ **Languages and scripts**
18
+
19
+ - Language-only text and linked names retain Roman spelling and surrounding
20
+ fonts; request native script with `.nativefont` and explicit `lang=` on the
21
+ same construct.
22
+ - Native fonts and maps require that opt-in; `map=` and `font=` override
23
+ defaults independently, with explicit `font=` needed when no font default
24
+ exists.
25
+ - Remove or escape bold and italic markup inside non-English spans.
26
+
27
+ **Verse**
28
+
29
+ - For `cols=N` above one, prefix each reading with its column number and spaces;
30
+ keep numbers nondecreasing within blank-separated logical rows, leaving
31
+ omitted columns empty.
32
+ - Repeat readings explicitly; each column’s first is foreground and later
33
+ readings grey, while numbering counts logical rows.
34
+ - Use leading periods for indentation and `***` for stanza breaks; leading
35
+ spaces do not indent.
36
+
37
+ ### Patch Changes
38
+
39
+ **Languages and scripts**
40
+
41
+ - Language-marked links preserve destinations, spoilers, draft cues and
42
+ surrounding spacing.
43
+
44
+ **Verse**
45
+
46
+ - Mixed marked and unmarked runs in multiple languages retain spelling, spacing
47
+ and each run’s language, font and mapping.
48
+
3
49
  ## 24.0.4
4
50
 
5
51
  ### Patch Changes
package/README.md CHANGED
@@ -16,8 +16,16 @@ npm install -D @heroiclands/package-build
16
16
 
17
17
  The generated project includes a homepage, website and book targets, npm scripts, and a local `.gitignore`. [Getting started](docs/getting-started.md) routes you to a [system](docs/guides/system-project.md), [module](docs/guides/module-project.md), or [content authoring](docs/authoring/first-note.md) path. The [content-language-server](https://github.com/HeroicLands/content-language-server) provides editor navigation and completion.
18
18
 
19
+ Build indexes, calendars, staged assets and packs together with `package-build content build --calendaria-version 1.4.2`. The [complete content build](docs/commands.md#package-build-content-build) shares one source capture across publication, lint, links and compilation and reports each build stage's time.
20
+
19
21
  Run selected content operations together with `package-build content run index lint links compile`. The [shared content command](docs/commands.md#package-build-content-run-targets) captures one source tree for the selected checks and outputs; standalone commands remain available.
20
22
 
23
+ Language annotations keep authored spelling: `[Zaron]{lang=vyl}` stays Roman.
24
+ Add `.nativefont` with an explicit `lang=` to use native script defaults;
25
+ see [writing in invented scripts](docs/guides/writing-in-invented-scripts.md).
26
+ Parallel verse sets `cols=N`, then prefixes each reading with its column
27
+ number; blank lines divide logical rows.
28
+
21
29
  ## Working on this repository
22
30
 
23
31
  Run `npm ci` in each fresh clone or worktree. Its `prepare` step installs the
@@ -124,7 +124,7 @@
124
124
  * the two markers are also the editor preview's stylesheet, with the content
125
125
  * roots replaced by the preview's own. */
126
126
 
127
- /* Each verse line breaks in the markup; each paragraph is a stanza. */
127
+ /* Verse groups are paragraphs; explicit stanza separators add their own gap. */
128
128
  :is(.journal-entry-page, .journal-entry-content, .editor-content, .prosemirror) .verse {
129
129
  margin: 1.5rem 0;
130
130
  line-height: 1.7;
@@ -144,31 +144,36 @@
144
144
  white-space: pre-wrap;
145
145
  }
146
146
 
147
- .verse .verse-line.i1 {
147
+ .verse :is(.verse-line, .verse-part).i1 {
148
148
  --verse-indent: 1.25em;
149
149
  }
150
- .verse .verse-line.i2 {
150
+ .verse :is(.verse-line, .verse-part).i2 {
151
151
  --verse-indent: 2.5em;
152
152
  }
153
- .verse .verse-line.i3 {
153
+ .verse :is(.verse-line, .verse-part).i3 {
154
154
  --verse-indent: 3.75em;
155
155
  }
156
- .verse .verse-line.i4 {
156
+ .verse :is(.verse-line, .verse-part).i4 {
157
157
  --verse-indent: 5em;
158
158
  }
159
- .verse .verse-line.i5 {
159
+ .verse :is(.verse-line, .verse-part).i5 {
160
160
  --verse-indent: 6.25em;
161
161
  }
162
- .verse .verse-line.i6 {
162
+ .verse :is(.verse-line, .verse-part).i6 {
163
163
  --verse-indent: 7.5em;
164
164
  }
165
- .verse .verse-line.i7 {
165
+ .verse :is(.verse-line, .verse-part).i7 {
166
166
  --verse-indent: 8.75em;
167
167
  }
168
- .verse .verse-line.i8 {
168
+ .verse :is(.verse-line, .verse-part).i8 {
169
169
  --verse-indent: 10em;
170
170
  }
171
171
 
172
+ .verse .verse-stanza-break {
173
+ display: block;
174
+ height: 0.5em;
175
+ }
176
+
172
177
  /* Numbered verse carries its numbers in a margin. A verse in columns is one
173
178
  * grid per verse line: its number, then each column's parts stacked, the
174
179
  * first in the normal tone and the rest muted, every part in the serif unless
@@ -212,6 +217,8 @@
212
217
  display: grid;
213
218
  grid-auto-flow: column;
214
219
  grid-auto-columns: 1fr;
220
+ direction: ltr;
221
+ grid-template-columns: repeat(var(--verse-columns, 2), minmax(0, 1fr));
215
222
  column-gap: 1.5rem;
216
223
  margin-bottom: 0.5em;
217
224
  }
@@ -219,7 +226,7 @@
219
226
  :is(.journal-entry-page, .journal-entry-content, .editor-content, .prosemirror)
220
227
  .verse-numbered.verse-columns
221
228
  .verse-row {
222
- grid-template-columns: 2.5em;
229
+ grid-template-columns: 2.5em repeat(var(--verse-columns, 2), minmax(0, 1fr));
223
230
  }
224
231
 
225
232
  :is(.journal-entry-page, .journal-entry-content, .editor-content, .prosemirror)
@@ -240,6 +247,7 @@
240
247
  .verse-columns
241
248
  .verse-part {
242
249
  font-family: serif;
250
+ padding-inline-start: var(--verse-indent, 0em);
243
251
  }
244
252
 
245
253
  :is(.journal-entry-page, .journal-entry-content, .editor-content, .prosemirror)
@@ -248,6 +256,13 @@
248
256
  color: color-mix(in srgb, currentColor 60%, transparent);
249
257
  }
250
258
 
259
+ :is(.journal-entry-page, .journal-entry-content, .editor-content, .prosemirror)
260
+ .verse-columns
261
+ .verse-secondary
262
+ * {
263
+ color: inherit;
264
+ }
265
+
251
266
  :is(.journal-entry-page, .journal-entry-content, .editor-content, .prosemirror)
252
267
  .verse-columns
253
268
  .verse-part[dir="rtl"] {
@@ -135,6 +135,10 @@ import { serveSite } from "../engine/site-serve.mjs";
135
135
  import { writeSiteRoot } from "../engine/site-root.mjs";
136
136
  import { auditLinks, buildLinkIndex, walkReachability } from "../engine/content-links.mjs";
137
137
  import { prepareLinkSqlTables, prepareTreeSqlTables } from "../engine/sql-tables.mjs";
138
+ import { createContentTimings } from "../engine/content-timings.mjs";
139
+ import { emitCalendarArtifacts } from "../engine/calendar-artifacts.mjs";
140
+ import { stagePackageBuildAssets } from "../engine/build-assets.mjs";
141
+ import { DEPLOYED_ASSET_TYPES } from "../engine/asset-types.mjs";
138
142
  import { createContentSession, CONTENT_TARGETS } from "../engine/content-session.mjs";
139
143
  import { createFirstUseChecker } from "../engine/first-use.mjs";
140
144
  import { createNameCatalog, createNamesReport, namesReportFinding } from "../engine/names.mjs";
@@ -297,7 +301,7 @@ export function registerContentCommands(cli) {
297
301
  .command(proseCommand())
298
302
  .command(contentFormatCommand())
299
303
  .command(withIndexPreflight(linksCommand()))
300
- .command(withIndexPreflight(contentCommand()))
304
+ .command(contentCommand())
301
305
  .command(formatCommand())
302
306
  .command(markdownCommand())
303
307
  .command(
@@ -1494,25 +1498,187 @@ function markdownCommand() {
1494
1498
  };
1495
1499
  }
1496
1500
 
1497
- /**
1498
- * `package-build links` — check that every link in a content tree lands.
1499
- *
1500
- * Reports a dead `#anchor`, a dead qualified address, and a wikilink authored
1501
- * in frontmatter, plus a vendored manifest that has drifted out of reach. All
1502
- * of it is package-agnostic, so a consumer needs no script of its own: the
1503
- * manifest directory is the only thing it might name, and that comes from its
1504
- * configuration.
1505
- *
1506
- * @returns {object} The yargs command module.
1507
- */
1508
- // eslint-disable-next-line
1501
+ /** Content operations with a shared invocation-local capture. */
1509
1502
  function contentCommand() {
1510
1503
  return {
1511
- command: "content <action> <targets..>",
1504
+ command: "content",
1505
+ describe: "Run content operations in one in-memory session",
1506
+ builder: (yargs) =>
1507
+ yargs
1508
+ .command(withIndexPreflight(contentRunCommand()))
1509
+ .command(contentBuildCommand())
1510
+ .demandCommand(1, "Choose content run or content build."),
1511
+ handler: () => {},
1512
+ };
1513
+ }
1514
+
1515
+ /** The complete build writes only outside the captured input roots. */
1516
+ function contentBuildOutputs(config, packaging) {
1517
+ const roots = [
1518
+ config.paths.content,
1519
+ ...DEPLOYED_ASSET_TYPES.map((entry) => path.resolve(config.paths.assets, entry.root)),
1520
+ ];
1521
+ const stage = path.resolve(packaging.rootDir, packaging.stageDir);
1522
+ const outputs = [
1523
+ config.paths.contentIndex,
1524
+ config.paths.packJson,
1525
+ config.paths.stage,
1526
+ stage,
1527
+ path.join(config.rootDir, "build/calendars"),
1528
+ ];
1529
+ const contains = (parent, child) => {
1530
+ const relative = path.relative(path.resolve(parent), path.resolve(child));
1531
+ return (
1532
+ relative === "" ||
1533
+ (!relative.startsWith(`..${path.sep}`) &&
1534
+ relative !== ".." &&
1535
+ !path.isAbsolute(relative))
1536
+ );
1537
+ };
1538
+ for (const output of [...outputs, ...packaging.assets.map(({ to }) => path.resolve(stage, to))])
1539
+ for (const root of roots)
1540
+ if (contains(root, output) || contains(output, root))
1541
+ throw new Error(
1542
+ `content build output ${output} overlaps captured input ${root}; keep build outputs outside source roots`,
1543
+ );
1544
+ return [
1545
+ ...new Set([
1546
+ config.paths.contentIndex,
1547
+ config.paths.packJson,
1548
+ path.join(config.rootDir, "build/calendars"),
1549
+ ...config.packDirectories.map((name) => path.join(config.paths.stage, name)),
1550
+ ]),
1551
+ ];
1552
+ }
1553
+
1554
+ /** Publish, generate calendars, stage assets and check/compile one live session. */
1555
+ function contentBuildCommand() {
1556
+ return {
1557
+ command: "build",
1558
+ describe:
1559
+ "Publish fresh indexes, emit calendars, stage assets, lint, check links and compile",
1560
+ builder: (yargs) =>
1561
+ yargs
1562
+ .option("calendaria-version", {
1563
+ type: "string",
1564
+ demandOption: true,
1565
+ describe: "Calendaria import format version",
1566
+ })
1567
+ .check((argv) => {
1568
+ if (!/^\d+\.\d+\.\d+$/.test(argv.calendariaVersion))
1569
+ throw new Error("--calendaria-version requires a version such as 1.4.2");
1570
+ return true;
1571
+ }),
1572
+ handler: async (argv) => {
1573
+ const timings = createContentTimings();
1574
+ let session;
1575
+ const shared = {
1576
+ ...argv,
1577
+ references: true,
1578
+ [PREPARATION_REPORTED]: true,
1579
+ [REPORTED_FAILURES]: new Set(),
1580
+ };
1581
+ const fail = (error) => {
1582
+ reportContentFailure(error, shared);
1583
+ process.exitCode = 1;
1584
+ };
1585
+ try {
1586
+ const config = loadPackConfig();
1587
+ const marker = path.join(config.paths.contentIndex, ".publication-ready");
1588
+ fs.rmSync(marker, { force: true });
1589
+ const packaging = loadPackageBuildConfig();
1590
+ const outputs = contentBuildOutputs(config, packaging);
1591
+ for (const output of outputs) fs.rmSync(output, { recursive: true, force: true });
1592
+ cachedMetadataIndexes(config);
1593
+ session = await createContentSession({ config, timings, trackChanges: true });
1594
+ shared[CONTENT_SESSION] = session;
1595
+ let prepared = false;
1596
+ const reportedProblems = new Set();
1597
+ try {
1598
+ assertCompendiumOperation(config, "compile");
1599
+ await session.prepare(["index"]);
1600
+ prepared = true;
1601
+ } catch (error) {
1602
+ if (error.findings) {
1603
+ for (const finding of error.findings) {
1604
+ emitDiagnostic(finding);
1605
+ reportedProblems.add(finding);
1606
+ }
1607
+ shared[REPORTED_FAILURES].add(error);
1608
+ process.exitCode = 1;
1609
+ } else fail(error);
1610
+ }
1611
+ for (const problem of session.problems) {
1612
+ if (!reportedProblems.has(problem)) emitDiagnostic(problem);
1613
+ process.exitCode = 1;
1614
+ }
1615
+ let staged = false;
1616
+ if (
1617
+ prepared &&
1618
+ (await timings.measure("index publication", () =>
1619
+ contentIndexCommand().handler(shared),
1620
+ ))
1621
+ ) {
1622
+ fs.writeFileSync(marker, `${config.contentPackage}\n`);
1623
+ try {
1624
+ await timings.measure("calendars", () => {
1625
+ const result = emitCalendarArtifacts({
1626
+ config,
1627
+ calendariaVersion: argv.calendariaVersion,
1628
+ sources: session.sources,
1629
+ });
1630
+ log.info(
1631
+ `${result.calendars} calendars emitted (${result.files} files).`,
1632
+ );
1633
+ });
1634
+ await timings.measure("assets", () =>
1635
+ stagePackageBuildAssets({
1636
+ config: packaging,
1637
+ packConfig: config,
1638
+ session,
1639
+ }),
1640
+ );
1641
+ staged = true;
1642
+ } catch (error) {
1643
+ fail(error);
1644
+ }
1645
+ try {
1646
+ session.assertUnchanged();
1647
+ } catch (error) {
1648
+ fs.rmSync(marker, { force: true });
1649
+ throw error;
1650
+ }
1651
+ }
1652
+ await timings.measure("validation", () => lintCommand().handler(shared));
1653
+ await timings.measure("links", () => linksCommand().handler(shared));
1654
+ if (staged)
1655
+ await timings.measure("compile", () =>
1656
+ packageCommand().handler({ ...shared, action: "compile" }),
1657
+ );
1658
+ } catch (error) {
1659
+ fail(error);
1660
+ } finally {
1661
+ const summary = timings.snapshot();
1662
+ log.info(
1663
+ `Content timings (exclusive): ${Object.entries(summary.stages)
1664
+ .map(([name, ms]) => `${name} ${Number(ms).toFixed(1)} ms`)
1665
+ .join("; ")}; total ${summary.totalMs.toFixed(1)} ms.`,
1666
+ );
1667
+ log.info(
1668
+ `Content work: ${session?.sources.size ?? 0} sources; ${session?.parsed.size ?? 0} parsed notes; ${session?.records.length ?? 0} records; reuse ${JSON.stringify(summary.hits)}; peak RSS ${(process.resourceUsage().maxRSS / 1024).toFixed(1)} MiB.`,
1669
+ );
1670
+ session?.dispose();
1671
+ }
1672
+ },
1673
+ };
1674
+ }
1675
+
1676
+ function contentRunCommand() {
1677
+ return {
1678
+ command: "run <targets..>",
1512
1679
  describe: "Run selected content operations with one in-memory source capture",
1513
1680
  builder: (yargs) =>
1514
1681
  yargs
1515
- .positional("action", { type: "string", choices: ["run"] })
1516
1682
  .positional("targets", { type: "string", array: true, choices: CONTENT_TARGETS })
1517
1683
  .option("references", {
1518
1684
  type: "boolean",
@@ -1801,9 +1967,11 @@ function contentIndexCommand() {
1801
1967
  `${config.contentPackage} → ${path.relative(process.cwd(), written.file)} ` +
1802
1968
  `(${written.rows} ${relation} rows)`,
1803
1969
  );
1970
+ return true;
1804
1971
  } catch (err) {
1805
1972
  reportContentFailure(err, argv);
1806
1973
  process.exitCode = 1;
1974
+ return false;
1807
1975
  } finally {
1808
1976
  if (!argv[CONTENT_SESSION]) session?.dispose();
1809
1977
  }
@@ -87,20 +87,13 @@ import yargs from "yargs";
87
87
  import { hideBin } from "yargs/helpers";
88
88
 
89
89
  import { loadPackageBuildConfig } from "../config.mjs";
90
- import { checkForeignAssetBindings } from "../engine/asset-bindings.mjs";
91
- import { emitDiagnostic, formatDiagnostic, positionOfYamlPath } from "../engine/diagnostics.mjs";
90
+ import { loadAssetTransform, stagePackageBuildAssets } from "../engine/build-assets.mjs";
91
+ import { emitDiagnostic, formatDiagnostic } from "../engine/diagnostics.mjs";
92
92
  import { writeSiteRoot } from "../engine/site-root.mjs";
93
93
  import { DEPLOY_ROOT } from "../engine/site-config.mjs";
94
94
  import { compilesFoundryDocuments, siteBase } from "../content-config.mjs";
95
95
  import { loadPackConfig, packConfigPath, resolveConfigFile } from "../engine/pack-config.mjs";
96
- import { cleanBuildArtifacts, resetStage, stageAssets } from "../stage.mjs";
97
- import { rowsStagingPictureRoots, stagePackageAssets } from "../engine/asset-shipping.mjs";
98
- import { baseStyleStageEntry } from "../engine/base-styles.mjs";
99
- import {
100
- checkScriptFonts,
101
- copyScriptFonts,
102
- STAGED_FONTS_DIR,
103
- } from "../engine/script-font-delivery.mjs";
96
+ import { cleanBuildArtifacts, resetStage } from "../stage.mjs";
104
97
  import {
105
98
  CDN_ENV,
106
99
  cdnCredentials,
@@ -438,171 +431,13 @@ function cleanCommand() {
438
431
  };
439
432
  }
440
433
 
441
- /**
442
- * A finding for each `packageBuild.assets` row that would stage a picture root
443
- * itself, located at the row's `from` when the configuration is YAML.
444
- *
445
- * @param {readonly {from: string, to: string}[]} rows - The configured rows.
446
- * @param {{rootDir: string, assetsBase: string}} opts - Where they resolve.
447
- * @returns {object[]} The findings.
448
- */
449
- function pictureRootRowFindings(rows, { rootDir, assetsBase }) {
450
- const offending = rowsStagingPictureRoots(rows, { rootDir, assetsBase });
451
- if (!offending.length) return [];
452
- const file = packConfigPath();
453
- const text =
454
- file.endsWith(".yaml") || file.endsWith(".yml") ? fs.readFileSync(file, "utf8") : "";
455
- return offending.map(({ index, from, root }) => ({
456
- file,
457
- ...(text ? positionOfYamlPath(text, ["packageBuild", "assets", index, "from"]) : {}),
458
- severity: "error",
459
- message:
460
- `\`packageBuild.assets[${index}]\` copies \`${from}\`, which reaches ${root}/ — ` +
461
- "the stage copies the icon and image roots itself, flat and filtered by " +
462
- "each file's provenance, so a row copying them would ship every file " +
463
- "unfiltered. Remove the row",
464
- }));
465
- }
466
-
467
- /**
468
- * The repository's `packageBuild.assetTransform`, loaded.
469
- *
470
- * One loader for every command that writes a picture, so the stage and the CDN
471
- * hold the same bytes for the same file.
472
- *
473
- * @param {object} config - The resolved package-build configuration.
474
- * @returns {Promise<((sourcePath: string) => string|null|undefined)|undefined>}
475
- * The transform, or `undefined` when the repository names none.
476
- */
477
- async function loadAssetTransform(config) {
478
- if (!config.assetTransform) return undefined;
479
- const module = await import(`file://${config.assetTransform}`).catch((err) =>
480
- die(
481
- `cannot load \`packageBuild.assetTransform\` ` +
482
- `(${config.assetTransform}): ${err.message}`,
483
- ),
484
- );
485
- const transform = module.transform;
486
- if (typeof transform !== "function") {
487
- die(
488
- `\`packageBuild.assetTransform\` ` +
489
- `(${config.assetTransform}) exports no \`transform\` ` +
490
- `function. It must export ` +
491
- `\`transform(sourcePath) -> string | null\`.`,
492
- );
493
- }
494
- return transform;
495
- }
496
-
497
- /**
498
- * `assets` — stage the repository's static files into the package root.
499
- *
500
- * The table is data (`packageBuild.assets`). A repository that has to *change*
501
- * a file on the way — SoHL rewrites each SVG's hard-coded fill so icons follow
502
- * the Foundry theme — names a module in `packageBuild.assetTransform`, whose
503
- * `transform(sourcePath)` returns replacement text or `null` to copy verbatim.
504
- * That is the one genuine piece of code in the job, and it stays the
505
- * repository's.
506
- *
507
- * @returns {object} The yargs command module.
508
- */
434
+ /** Stage the configured package assets. */
509
435
  function assetsCommand() {
510
436
  return {
511
437
  command: "assets",
512
438
  describe: "Stage static assets into the package root",
513
439
  builder: (y) => y,
514
- handler: handler(async () => {
515
- const config = loadPackageBuildConfig();
516
- const packConfig = loadPackConfig();
517
- const assetsBase = packConfig.paths.assets;
518
- const findings = [
519
- ...pictureRootRowFindings(config.assets, {
520
- rootDir: config.rootDir,
521
- assetsBase,
522
- }),
523
- ...checkForeignAssetBindings(packConfig, { assets: config.assets }),
524
- ];
525
- if (findings.length) {
526
- const error = new Error(findings.map(formatDiagnostic).join("\n"));
527
- error.located = true;
528
- die(error);
529
- }
530
-
531
- const transform = await loadAssetTransform(config);
532
-
533
- // `to:` is relative to the staged package root, so a
534
- // repository's table reads `lang`, not `build/stage/lang`.
535
- const entries = config.assets.map(({ from, to }) => [
536
- from,
537
- path.join(config.stageDir, to),
538
- ]);
539
- let { entries: count, files } = stageAssets(entries, {
540
- cwd: config.rootDir,
541
- transform,
542
- });
543
-
544
- // The picture roots, staged by the toolchain rather than by a row:
545
- // flat, filtered by each file's provenance, and listed in the
546
- // package's asset set.
547
- const pictures = stagePackageAssets({
548
- assetsBase,
549
- contentPackage: packConfig.contentPackage,
550
- stageDir: path.resolve(config.rootDir, config.stageDir),
551
- // The manifest's title names the set; a package with none is
552
- // named by its content package.
553
- title:
554
- typeof config.manifest?.title === "string" && config.manifest.title.trim() ?
555
- config.manifest.title
556
- : packConfig.contentPackage,
557
- transform,
558
- shippingLicenses: config.shippingLicenses,
559
- });
560
- if (pictures.findings.length) {
561
- const error = new Error(pictures.findings.map(formatDiagnostic).join("\n"));
562
- error.located = true;
563
- die(error);
564
- }
565
- files += pictures.staged.length;
566
- if (pictures.staged.length || pictures.excluded.length) {
567
- console.log(
568
- `Pictures: ${pictures.staged.length} staged, ` +
569
- `${pictures.excluded.length} left out by provenance.`,
570
- );
571
- }
572
-
573
- // Staged in its own pass, without the repository's transform: the
574
- // sheet is this toolchain's file rather than one of the
575
- // repository's assets, and a `transform` written to retheme a
576
- // package's own SVGs has no business being handed it.
577
- if (config.baseStyles) {
578
- const staged = stageAssets([baseStyleStageEntry(config.stageDir)], {
579
- cwd: config.rootDir,
580
- });
581
- count += staged.entries;
582
- files += staged.files;
583
- }
584
-
585
- // The installed fonts, when the package has installed the fonts
586
- // package and a note sets text in one of its faces: the
587
- // stylesheet, the WOFF2 files and the licence, which the
588
- // manifest's `styles` names.
589
- const fontFindings = checkScriptFonts(packConfig);
590
- if (fontFindings.length) {
591
- const error = new Error(fontFindings.map(formatDiagnostic).join("\n"));
592
- error.located = true;
593
- die(error);
594
- }
595
- const fonts = copyScriptFonts(
596
- packConfig,
597
- path.resolve(config.rootDir, config.stageDir, STAGED_FONTS_DIR),
598
- ["woff2"],
599
- );
600
- if (fonts) {
601
- count += 1;
602
- files += fonts;
603
- }
604
- console.log(`✅ Static assets staged (${count} entries, ${files} files).`);
605
- }),
440
+ handler: handler(() => stagePackageBuildAssets()),
606
441
  };
607
442
  }
608
443