@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.
- package/CHANGELOG.md +46 -0
- package/README.md +8 -0
- package/assets/styles/content-base.css +25 -10
- package/bin/content-commands.mjs +183 -15
- package/bin/package-build.mjs +5 -170
- package/docs/authoring/links-and-markup.md +215 -179
- package/docs/commands.md +88 -2
- package/docs/configuration.md +6 -3
- package/docs/content-format.md +4 -3
- package/docs/diagnostics.md +57 -29
- package/docs/guides/book.md +1 -1
- package/docs/guides/module-project.md +2 -2
- package/docs/guides/site.md +2 -2
- package/docs/guides/writing-in-invented-scripts.md +175 -125
- package/docs/reference/format-details.md +53 -35
- package/docs/reference/note-types.md +10 -10
- package/docs/reference/theme-pages.md +18 -9
- package/engine/build-assets.mjs +179 -0
- package/engine/calendar-artifacts.mjs +7 -3
- package/engine/compendiums.mjs +16 -11
- package/engine/content-render.mjs +23 -2
- package/engine/content-scripts.mjs +348 -140
- package/engine/content-session.mjs +62 -14
- package/engine/content-spans.mjs +13 -1
- package/engine/content-timings.mjs +67 -0
- package/engine/content-verse.mjs +158 -373
- package/engine/note-vocabulary.mjs +5 -5
- package/engine/pdf-render.mjs +63 -44
- package/engine/run-rows.mjs +18 -20
- package/engine/script-font-delivery.mjs +22 -15
- package/engine/verse-metre.mjs +36 -8
- package/engine/written-in.mjs +9 -13
- package/package.json +1 -1
- package/site-theme/static/css/style.css +19 -10
- package/types/engine/build-assets.d.mts +23 -0
- package/types/engine/calendar-artifacts.d.mts +5 -1
- package/types/engine/content-scripts.d.mts +18 -7
- package/types/engine/content-session.d.mts +19 -1
- package/types/engine/content-timings.d.mts +14 -0
- package/types/engine/content-verse.d.mts +37 -163
- package/types/engine/run-rows.d.mts +16 -9
- package/types/engine/script-font-delivery.d.mts +8 -4
- 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
|
-
/*
|
|
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"] {
|
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
|
|