@heroiclands/package-build 24.1.0 → 25.0.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 +32 -0
- package/README.md +2 -0
- package/bin/content-commands.mjs +183 -15
- package/bin/package-build.mjs +5 -170
- package/docs/api.md +23 -11
- package/docs/authoring/links-and-markup.md +8 -0
- package/docs/commands.md +91 -0
- package/docs/guides/writing-in-invented-scripts.md +23 -15
- package/docs/reference/format-details.md +25 -0
- package/docs/reference/note-types.md +1 -0
- package/engine/build-assets.mjs +179 -0
- package/engine/calendar-artifacts.mjs +7 -3
- package/engine/compendiums.mjs +16 -11
- package/engine/content-charset.mjs +12 -4
- package/engine/content-format.yaml +1 -0
- package/engine/content-scripts.mjs +94 -14
- package/engine/content-session.mjs +62 -14
- package/engine/content-timings.mjs +67 -0
- package/engine/index.mjs +3 -0
- package/engine/language-data.mjs +4 -1
- package/engine/language-terms.mjs +10 -0
- package/engine/map-lexicon.mjs +12 -1
- package/engine/roman-characters.mjs +113 -0
- package/engine/script-font-delivery.mjs +22 -15
- package/package.json +1 -1
- package/types/engine/build-assets.d.mts +23 -0
- package/types/engine/calendar-artifacts.d.mts +5 -1
- package/types/engine/content-charset.d.mts +8 -0
- package/types/engine/content-scripts.d.mts +5 -0
- package/types/engine/content-session.d.mts +19 -1
- package/types/engine/content-timings.d.mts +14 -0
- package/types/engine/index.d.mts +1 -0
- package/types/engine/roman-characters.d.mts +45 -0
- package/types/engine/script-font-delivery.d.mts +8 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 25.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
**Languages and scripts**
|
|
8
|
+
|
|
9
|
+
- Add `data.roman` to every configured language note: an NFC string listing each
|
|
10
|
+
permitted Roman character once, with capitals, accents, digits and punctuation
|
|
11
|
+
explicit.
|
|
12
|
+
- Use `""` for no written characters; omit spaces and line breaks, and declare
|
|
13
|
+
inventories without ranges, automatic case expansion or inheritance. Maps and
|
|
14
|
+
fonts grant no extra Roman characters.
|
|
15
|
+
- Language-tagged source and visible link labels are checked before mapping;
|
|
16
|
+
configured names and their BCP 47 tags select the same inventory.
|
|
17
|
+
- Invalid characters and non-NFC spellings are reported at their original
|
|
18
|
+
positions; link destinations, attributes and verse structure are excluded.
|
|
19
|
+
- Only period maps to Semaron's pause; recognized native signs require explicit
|
|
20
|
+
native presentation and a face that draws them, while mappings never broaden
|
|
21
|
+
Roman inventories.
|
|
22
|
+
- Unassociated external tags such as `en` and `zxx` retain ordinary behavior;
|
|
23
|
+
standalone `package-build markdown` does not load language inventories.
|
|
24
|
+
|
|
25
|
+
## 24.1.1
|
|
26
|
+
|
|
27
|
+
### Patch Changes
|
|
28
|
+
|
|
29
|
+
**Content builds**
|
|
30
|
+
|
|
31
|
+
- Build indexes, calendars, assets and packs with
|
|
32
|
+
`package-build content build --calendaria-version 1.4.2`, sharing preparation
|
|
33
|
+
across validation and compilation.
|
|
34
|
+
|
|
3
35
|
## 24.1.0
|
|
4
36
|
|
|
5
37
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -16,6 +16,8 @@ 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
|
|
|
21
23
|
Language annotations keep authored spelling: `[Zaron]{lang=vyl}` stays Roman.
|
package/bin/content-commands.mjs
CHANGED
|
@@ -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(
|
|
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
|
|
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
|
}
|
package/bin/package-build.mjs
CHANGED
|
@@ -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 {
|
|
91
|
-
import { emitDiagnostic, formatDiagnostic
|
|
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
|
|
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(
|
|
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
|
|
package/docs/api.md
CHANGED
|
@@ -1384,17 +1384,29 @@ Linting a content tree's **addresses** — the rules every package's notes are a
|
|
|
1384
1384
|
|
|
1385
1385
|
The charset authored content is held to, so a book can choose its face. The packs and the website render in whatever font the reader's browser or Foundry supplies, and a glyph nobody has is somebody else's problem. **A book is not that.** A PDF embeds the faces it sets, so every character in the corpus is a claim on the book's typeface — and the claim is silent, which is what makes it expensive.
|
|
1386
1386
|
|
|
1387
|
-
| Export
|
|
1388
|
-
|
|
|
1389
|
-
| `isLetterTier`
|
|
1390
|
-
| `TYPOGRAPHY`
|
|
1391
|
-
| `NOTATION`
|
|
1392
|
-
| `
|
|
1393
|
-
| `
|
|
1394
|
-
| `
|
|
1395
|
-
| `
|
|
1396
|
-
| `
|
|
1397
|
-
| `
|
|
1387
|
+
| Export | Signature | Returns | Use it when |
|
|
1388
|
+
| -------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- |
|
|
1389
|
+
| `isLetterTier` | `function isLetterTier(cp)` | {boolean} Whether Tier 1 admits it. | Tier 1 — the letters, and the two whitespace characters a file is made of. |
|
|
1390
|
+
| `TYPOGRAPHY` | `const TYPOGRAPHY` | — | Tier 2 — typography, enumerated one codepoint at a time. |
|
|
1391
|
+
| `NOTATION` | `const NOTATION` | — | Tier 3 — the notation the rules and price tables are written in. |
|
|
1392
|
+
| `isSettingScriptCodePoint` | `isSettingScriptCodePoint(cp)` | `boolean` | recognizing native setting-script input before the selected face checks the individual glyph |
|
|
1393
|
+
| `isAllowedCodePoint` | `function isAllowedCodePoint(cp)` | {boolean} Whether it is allowed outside a code fence. | Whether the charset admits a code point anywhere in a note. |
|
|
1394
|
+
| `isDiagramCodePoint` | `function isDiagramCodePoint(cp)` | {boolean} Whether a fence may carry it. | Whether a code point is diagram furniture, admitted inside a fence only. |
|
|
1395
|
+
| `refusalFor` | `function refusalFor(cp)` | {string} A clause naming what it is and what to do instead. | The reason a code point is refused. |
|
|
1396
|
+
| `decomposedRuns` | `function decomposedRuns(text)` | {Array<{sequence: string, composed: string, index: number}>} Each offending run, in the order it appears. | Every non-NFC run in a string, with the composed form it should have been. |
|
|
1397
|
+
| `checkText` | `function checkText(text, file)` | {Array<{file: string, line: number, column: number, severity: "warning", message: string}>} What is wrong, in file order. | Check one file's text against the charset and the normalization rule. |
|
|
1398
|
+
| `lintContentCharset` | `function lintContentCharset(contentBase,` | `{{findings: Array<{file: string, line: number, column: number, severity: "warning", message: string}>, files: number}}` The findings, and how many files produced them. | Walk a content tree and check every authored file in it. |
|
|
1399
|
+
|
|
1400
|
+
### `engine.romanCharacters`
|
|
1401
|
+
|
|
1402
|
+
Exact permitted Roman source characters for configured languages, before script
|
|
1403
|
+
mapping. Inventories come from `data.roman` on each language's own note.
|
|
1404
|
+
|
|
1405
|
+
| Export | Signature | Returns | Use it when |
|
|
1406
|
+
| ------------------------ | --------------------------------------------- | ----------------------------- | --------------------------------------------------------------------------------------------- |
|
|
1407
|
+
| `romanInventoryProblems` | `romanInventoryProblems(value)` | `string[]` | checking NFC, repeated characters and forbidden whitespace or controls in a literal inventory |
|
|
1408
|
+
| `romanInventoriesOf` | `romanInventoriesOf(records, languages)` | `Map<string, RomanInventory>` | looking up a configured language's own inventory by name or BCP 47 tag |
|
|
1409
|
+
| `romanTextProblems` | `romanTextProblems(text, inventory, native?)` | `{offset, message}[]` | validating visible unescaped source with exact offsets and an optional native-sign predicate |
|
|
1398
1410
|
|
|
1399
1411
|
### `engine.pathCharset`
|
|
1400
1412
|
|
|
@@ -644,6 +644,14 @@ has an error is set exactly as written.
|
|
|
644
644
|
|
|
645
645
|
## Marking language, font and mapping
|
|
646
646
|
|
|
647
|
+
Each configured language note declares `data.roman`, a literal NFC string of
|
|
648
|
+
permitted source characters. Include capitals, accents, digits and punctuation
|
|
649
|
+
explicitly; an empty string declares no written characters. Spaces and line
|
|
650
|
+
breaks are structural. `package-build lint` checks Roman source before mapping,
|
|
651
|
+
including language-only runs and visible link labels. The configured name and
|
|
652
|
+
its BCP 47 tag select the same inventory; external tags without a configured
|
|
653
|
+
language note keep their ordinary behavior.
|
|
654
|
+
|
|
647
655
|
`lang=` identifies the language. It preserves the authored spelling and the
|
|
648
656
|
surrounding prose font. Add **`.nativefont`** to request native script:
|
|
649
657
|
|
package/docs/commands.md
CHANGED
|
@@ -1556,6 +1556,89 @@ package-build: This repository declares no end-to-end suite to `run`. Name one u
|
|
|
1556
1556
|
|
|
1557
1557
|
## Content operations
|
|
1558
1558
|
|
|
1559
|
+
### `package-build content build`
|
|
1560
|
+
|
|
1561
|
+
**NAME**
|
|
1562
|
+
|
|
1563
|
+
Build indexes, calendars, assets and packs from one captured content tree.
|
|
1564
|
+
|
|
1565
|
+
**SYNOPSIS**
|
|
1566
|
+
|
|
1567
|
+
```text
|
|
1568
|
+
package-build content build --calendaria-version <version>
|
|
1569
|
+
```
|
|
1570
|
+
|
|
1571
|
+
**DESCRIPTION**
|
|
1572
|
+
|
|
1573
|
+
The complete content build runs these existing operations in order:
|
|
1574
|
+
|
|
1575
|
+
1. Capture authored sources and prepare and publish fresh metadata, dictionary
|
|
1576
|
+
and terms indexes.
|
|
1577
|
+
2. Emit calendar definitions and Calendaria imports from the published index.
|
|
1578
|
+
3. Stage the configured assets, including calendars, provenance-filtered
|
|
1579
|
+
pictures, base styles and fonts.
|
|
1580
|
+
4. Run content lint, link checks and pack compilation with the same live session.
|
|
1581
|
+
|
|
1582
|
+
Finish dependency fetching and source generation before invoking the command.
|
|
1583
|
+
Public and complete SQL preparations remain separate and each is reused by
|
|
1584
|
+
operations needing that audience. No resolved index or source cache survives
|
|
1585
|
+
into another invocation. Standalone commands and `content run` remain available.
|
|
1586
|
+
|
|
1587
|
+
The command clears its index directory, generated calendar directory, pack JSON
|
|
1588
|
+
and configured compiled pack directories before preparing the new build. Other
|
|
1589
|
+
stage files, including a manifest written by a parallel packaging task, remain.
|
|
1590
|
+
Use `package-build stage reset` before starting the complete packaging workflow
|
|
1591
|
+
when the entire stage must start empty. Build output paths must be outside the
|
|
1592
|
+
captured content and picture roots. A phase that changes those inputs fails the
|
|
1593
|
+
build, invalidates publication readiness and stops further use of the capture;
|
|
1594
|
+
source generation belongs before this command.
|
|
1595
|
+
|
|
1596
|
+
After all three index files are published successfully, the command writes
|
|
1597
|
+
`<paths.contentIndex>/.publication-ready` (normally
|
|
1598
|
+
`build/content-index/.publication-ready`). It removes that marker at startup.
|
|
1599
|
+
The marker means this invocation published a complete fresh index, even when
|
|
1600
|
+
calendar emission, asset staging, lint, link checks or compilation later fails.
|
|
1601
|
+
A subsequent index consumer can check it to report independent findings after a
|
|
1602
|
+
build failure. Clear the marker before a surrounding workflow starts as well,
|
|
1603
|
+
so failures before the command starts cannot expose an earlier run's index.
|
|
1604
|
+
Never restore the marker or resolved indexes from a cache.
|
|
1605
|
+
|
|
1606
|
+
Preparation failure blocks publication and compilation but preserves useful
|
|
1607
|
+
independent lint and link findings. Calendar or asset failure blocks compilation;
|
|
1608
|
+
ordinary lint or link findings do not block an otherwise valid compile. Pack
|
|
1609
|
+
generation still refuses to compile incomplete output. An advisory warning does
|
|
1610
|
+
not fail the command.
|
|
1611
|
+
|
|
1612
|
+
The final timing summary includes eager capture and index derivation, SQL by
|
|
1613
|
+
audience, publication, calendars, assets, validation, rendering and native pack
|
|
1614
|
+
compilation. Stage times are exclusive: a nested SQL preparation is charged to
|
|
1615
|
+
SQL rather than its calling stage. Counts, preparation reuse and peak process
|
|
1616
|
+
RSS accompany the timings; importing the CLI and validating its arguments happen
|
|
1617
|
+
before the measured build interval.
|
|
1618
|
+
|
|
1619
|
+
**OPTIONS**
|
|
1620
|
+
|
|
1621
|
+
| Option | Type | Default | Description |
|
|
1622
|
+
| ---------------------- | ------ | -------- | -------------------------------------- |
|
|
1623
|
+
| `--calendaria-version` | string | required | Calendaria import version, e.g. 1.4.2. |
|
|
1624
|
+
|
|
1625
|
+
**EXIT STATUS**
|
|
1626
|
+
|
|
1627
|
+
0 when every operation succeeds. 1 when arguments, preparation or any build
|
|
1628
|
+
operation fails. Publication readiness alone does not mean the build passed.
|
|
1629
|
+
|
|
1630
|
+
**EXAMPLES**
|
|
1631
|
+
|
|
1632
|
+
```bash
|
|
1633
|
+
package-build deps fetch
|
|
1634
|
+
package-build content build --calendaria-version 1.4.2
|
|
1635
|
+
```
|
|
1636
|
+
|
|
1637
|
+
**SEE ALSO**
|
|
1638
|
+
|
|
1639
|
+
`package-build content run`, `package-build content-index`, `package-build calendars`,
|
|
1640
|
+
`package-build assets`, `package-build package compile`.
|
|
1641
|
+
|
|
1559
1642
|
### `package-build content run <targets..>`
|
|
1560
1643
|
|
|
1561
1644
|
**NAME**
|
|
@@ -2132,6 +2215,14 @@ dependencies; exact Item UUID validation remains part of compilation. Reads
|
|
|
2132
2215
|
the content tree named by `root`, defaulting to
|
|
2133
2216
|
`paths.content`; writes nothing.
|
|
2134
2217
|
|
|
2218
|
+
Every configured language note declares `data.roman`, its exact NFC Roman
|
|
2219
|
+
source characters. Lint checks language-tagged text before mapping, using the
|
|
2220
|
+
same inventory for a configured name and its BCP 47 tag. Visible linked labels
|
|
2221
|
+
are checked; destinations, attributes and verse structure are excluded. An
|
|
2222
|
+
explicit empty inventory declares no written characters. External tags without
|
|
2223
|
+
a language-note association retain their ordinary behavior. Standalone
|
|
2224
|
+
`package-build markdown` does not load these inventories.
|
|
2225
|
+
|
|
2135
2226
|
Name marking is advisory: lint checks explicit `.name` spans and authored link
|
|
2136
2227
|
labels against the [name-marking
|
|
2137
2228
|
conventions](authoring/links-and-markup.md#marking-names-in-prose). Plain prose
|