@heroiclands/package-build 22.1.1 → 22.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/CONTENT.md +27 -32
- package/bin/content-build.mjs +0 -3
- package/bin/package-build.mjs +9 -26
- package/config.mjs +7 -2
- package/content-config.mjs +73 -41
- package/docs/api.md +26 -25
- package/docs/commands.md +22 -22
- package/docs/configuration.md +78 -46
- package/docs/content-format.md +36 -6
- package/engine/foundry-entries.mjs +12 -1
- package/engine/frontmatter-lint.mjs +2 -3
- package/engine/helpers.mjs +4 -1
- package/engine/ids.mjs +12 -0
- package/engine/note-claims.mjs +21 -0
- package/engine/pack-config.mjs +14 -1
- package/engine/pack-router.mjs +82 -5
- package/engine/pdf-build.mjs +2 -2
- package/engine/retired-fields.mjs +2 -3
- package/engine/schema-check.mjs +6 -6
- package/engine/site-build.mjs +59 -218
- package/engine/site-config.mjs +22 -22
- package/engine/site-index.mjs +18 -62
- package/engine/web-wikilinks.mjs +2 -16
- package/engine/wikilinks.mjs +35 -14
- package/manifest.mjs +16 -10
- package/package.json +1 -1
- package/release.mjs +41 -8
- package/sohl/kb-passes.mjs +14 -85
- package/types/config.d.mts +17 -0
- package/types/content-config.d.mts +6 -6
- package/types/engine/ids.d.mts +11 -0
- package/types/engine/pack-router.d.mts +20 -2
- package/types/engine/schema-check.d.mts +5 -5
- package/types/engine/site-build.d.mts +12 -62
- package/types/engine/site-config.d.mts +6 -8
- package/types/engine/site-index.d.mts +3 -28
- package/types/engine/web-wikilinks.d.mts +1 -1
- package/types/engine/wikilinks.d.mts +6 -3
- package/types/manifest.d.mts +5 -3
- package/types/release.d.mts +7 -6
- package/types/sohl/kb-passes.d.mts +5 -36
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 22.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- a79b33a: **A note may declare `pack: none`.** The universal `pack` key accepts one more value: `none` says the note publishes to the site and compiles into no Foundry document. Such a note is walked, published as a page, present in the content index with an address and no UUID, and addressable by wikilink like any other — on the web the link reaches the page, and in a compiled journal the reader gets the link's text with no document to open. Every pack compiler passes over it without a finding. It is accepted only on a type whose sole document is the JournalEntry its prose becomes (`doc`, `place`, `lore`, `scenario`) and refused by name on a type that compiles an Item, an Actor, a Macro, a Scene or an Adventure, naming the document it would drop. It is read per system like any other `pack:`, and a configured pack may not be called `none`.
|
|
8
|
+
|
|
9
|
+
**`site.trees` and `site.readmeSections` are refused.** A page is a note in the content tree, and there is no second mechanism for mounting a directory of markdown beside it. A configuration declaring either key fails with a message saying so. A package that mounted a directory of pages moves them into `assets/content/` as `doc` notes — each with a `shortcode` and `pack: none` — rewrites their relative links as wikilinks, and declares the section that lists them under `site.sections`. The `sohlKb` site pass no longer rewrites repository-relative links, and its `blob` option has no reader.
|
|
10
|
+
|
|
11
|
+
## 22.2.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- 241451e: **`schema.json` is a release asset, not a committed file.** `package-build schema` writes `build/schema.json` instead of the repository root, and `--check` is gone — there is no committed copy left to compare against. `package-build release` publishes `schema.json` beside the archive and the manifest, the way it already publishes the content index, whenever the staged tree carries one.
|
|
16
|
+
|
|
17
|
+
To keep publishing a schema: drop the committed `schema.json` and any lint step that runs `package-build schema --check`; add `{ from: build/schema.json, to: schema.json }` to `packageBuild.assets`, and run `package-build schema` before `package-build assets` in the build chain so the release never ships one older than the source it was cut from.
|
|
18
|
+
- 483b630: **`content-build site` refuses to generate a configuration with no `site.assets`.** The theme resolves every relative asset — the brand logo, the 404 hero, every CDN-resolved image — against `site.assets`, and there is no defensible default: a package that built a site with the key absent published every one of those as a broken relative path. Declare `site.assets` — the `https://` host every package's imagery is served from — in `package-build.config.yaml` for any package that publishes a site.
|
|
19
|
+
- 985f599: **A package now describes itself once for Foundry and once for the site.** Foundry's package browser wants a pitch — HTML, any length — and a site's `<meta name="description">` wants one plain sentence; deriving both from `package.json`'s `description` forced one string onto both. Declare `packageBuild.manifest.descriptionHtml` in `package-build.config.yaml` for the Foundry pitch (HTML allowed, emitted as the manifest's `description`) and `site.description` for the site's meta description (plain text, required for `content-build site`). `package.json`'s own `description` is read by neither any more — a warning names both keys when one is still declared — and the field can be deleted.
|
|
20
|
+
|
|
3
21
|
## 22.1.1
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/CONTENT.md
CHANGED
|
@@ -488,6 +488,17 @@ pack: mysteries
|
|
|
488
488
|
type one system maps and another does not stays silent for the system that
|
|
489
489
|
declines it.
|
|
490
490
|
|
|
491
|
+
- **`pack: none` compiles the note into no document, on purpose.** The note is
|
|
492
|
+
walked, published as a page, present in the content index with an address
|
|
493
|
+
and no UUID, and addressable by wikilink; every pass passes over it without
|
|
494
|
+
a finding, and the finding above says nothing about it. It is accepted only
|
|
495
|
+
on a type whose sole document is the JournalEntry its prose becomes — a
|
|
496
|
+
`doc`, say — and refused by name on a type that compiles an Item, an Actor,
|
|
497
|
+
a Macro, a Scene or an Adventure, where it would drop that document. A
|
|
498
|
+
configured pack may not be called `none`. This is how a page of developer
|
|
499
|
+
documentation lives in the content tree beside everything else: it is a
|
|
500
|
+
note like any other, and it reaches no compendium.
|
|
501
|
+
|
|
491
502
|
**The configuration is found by walking up from the working directory, and from
|
|
492
503
|
the installed package only if that finds nothing.** `engine/pack-config.mjs`
|
|
493
504
|
climbs from `process.cwd()` first, so a build reads the tree it was run in —
|
|
@@ -2256,10 +2267,10 @@ note-format knowledge against game-system knowledge, and a homepage carries no
|
|
|
2256
2267
|
|
|
2257
2268
|
`publish.site` then says how much _else_ is published:
|
|
2258
2269
|
|
|
2259
|
-
| Mode | What is published
|
|
2260
|
-
| ---------- |
|
|
2261
|
-
| `homepage` | The authored homepage, and no other page. **The default, and the floor.**
|
|
2262
|
-
| `content` | The homepage plus every page the content tree compiles to
|
|
2270
|
+
| Mode | What is published |
|
|
2271
|
+
| ---------- | ------------------------------------------------------------------------- |
|
|
2272
|
+
| `homepage` | The authored homepage, and no other page. **The default, and the floor.** |
|
|
2273
|
+
| `content` | The homepage plus every page the content tree compiles to. |
|
|
2263
2274
|
|
|
2264
2275
|
There is no value meaning "no web presence": every package publishes its
|
|
2265
2276
|
homepage. A boolean is refused, with a message naming the mode to write
|
|
@@ -2273,8 +2284,8 @@ content_ — journal text, artwork, item descriptions, compiled notes — and a
|
|
|
2273
2284
|
announcing the module discloses none of it. Because the failure mode is silent,
|
|
2274
2285
|
the mode **fences the content surfaces off** rather than trusting a
|
|
2275
2286
|
configuration to stay empty: in `homepage` mode the tree is never walked for
|
|
2276
|
-
pages, and `sections`, `
|
|
2277
|
-
|
|
2287
|
+
pages, and `sections`, `landing` and `backfillSections` emit nothing even when
|
|
2288
|
+
they are declared.
|
|
2278
2289
|
|
|
2279
2290
|
That is separate from the **dependency** edge, which such a module also
|
|
2280
2291
|
declines: being cited by another package is what would stop it being
|
|
@@ -2293,9 +2304,12 @@ does not reach it.
|
|
|
2293
2304
|
**What it does not do is decide addresses.** Those come from `publish.address`,
|
|
2294
2305
|
the same setting the content index reads, so a page and its index record cannot
|
|
2295
2306
|
disagree about where the page is. Everything under `site:` is _framing_ —
|
|
2296
|
-
what a section is called,
|
|
2297
|
-
|
|
2298
|
-
|
|
2307
|
+
what a section is called, and the residue of the generated Hugo configuration
|
|
2308
|
+
that is this repository's own. What the site publishes is the content tree and
|
|
2309
|
+
nothing beside it: a page of documentation is a note (`type: doc`, with
|
|
2310
|
+
`pack: none` where it compiles into no document), so there is no second
|
|
2311
|
+
mechanism for mounting a directory of markdown, and a `trees:` or
|
|
2312
|
+
`readmeSections:` key is refused with a message saying so.
|
|
2299
2313
|
|
|
2300
2314
|
```yaml
|
|
2301
2315
|
site:
|
|
@@ -2307,16 +2321,12 @@ site:
|
|
|
2307
2321
|
passOptions:
|
|
2308
2322
|
apiBase: /sohl/api/
|
|
2309
2323
|
symbolMap: kb/data/api-symbols.json
|
|
2310
|
-
blob: https://github.com/HeroicLands/…/blob/main/
|
|
2311
|
-
trees:
|
|
2312
|
-
- { from: kb/dev-docs, section: dev-docs }
|
|
2313
2324
|
sections:
|
|
2314
2325
|
being:
|
|
2315
2326
|
title: Beings
|
|
2316
2327
|
banner: banners/creature.webp
|
|
2317
2328
|
description: Folk, animals and the things that walk the world.
|
|
2318
|
-
|
|
2319
|
-
dev-docs: { title: Developer Documentation, banner: banners/dev-docs.webp }
|
|
2329
|
+
reference: { title: Reference, listType: doc, listSubType: reference }
|
|
2320
2330
|
list: { shortcodes: true }
|
|
2321
2331
|
notfound:
|
|
2322
2332
|
tagline: Song of Heroic Lands has no page at
|
|
@@ -2330,10 +2340,8 @@ site:
|
|
|
2330
2340
|
| `base` | Where the package is served: the prefix on every rendered `href`, and what a manifest `path` is measured against. It reaches no page's own `url:` — see [A page's URL is its address](#a-pages-url-is-its-address). Defaults to `/<contentPackage>/`. |
|
|
2331
2341
|
| `packages` | Which content packages this site renders. Defaults to its own. |
|
|
2332
2342
|
| `sections` | The Hugo sections this site declares, and what each says about itself — see below. |
|
|
2333
|
-
| `readmeSections` | The same, for a `trees` entry, whose landing comes from its own `README`. |
|
|
2334
2343
|
| `landing` | Frontmatter for the mount's own `_index.md`. Passed through — the vocabulary is the theme's. |
|
|
2335
2344
|
| `backfillSections` | Write a bare `_index.md` for any other directory directly under the mount. |
|
|
2336
|
-
| `trees` | Extra source trees published beside the content, preserving their source layout below a section. |
|
|
2337
2345
|
| `pass` | A named bundle of this repository's own body rewrites. |
|
|
2338
2346
|
| `passOptions` | That bundle's options. |
|
|
2339
2347
|
| `assets` | The host every package's imagery is served from; the generated `params.cdnBaseURL`. |
|
|
@@ -2377,12 +2385,6 @@ Whatever the entry may carry is the whole of what the section can say.
|
|
|
2377
2385
|
| `listType` | no | The content **type** whose pages this section lists. |
|
|
2378
2386
|
| `listSubType` | no | Narrows that to one **subType**. Only with a `listType`. |
|
|
2379
2387
|
|
|
2380
|
-
`readmeSections` takes the same keys, for a **`trees`** entry: those pages keep
|
|
2381
|
-
their source layout below a named section, so the tree's own `README.md` is that
|
|
2382
|
-
section's landing. What the section declares wins over what the `README` happens
|
|
2383
|
-
to carry — the landing has to match the card that links to it. No content note
|
|
2384
|
-
reaches this map any more.
|
|
2385
|
-
|
|
2386
2388
|
#### Saying what a section lists
|
|
2387
2389
|
|
|
2388
2390
|
A generic list layout has nothing to render, because the membership a section
|
|
@@ -2477,15 +2479,9 @@ a site publishes dead `{@link}` tags at exit 0. A map that is read reports
|
|
|
2477
2479
|
its symbol count at info level, which is the only way to tell a map that loaded
|
|
2478
2480
|
from one that loaded empty without reading the emitted HTML.
|
|
2479
2481
|
|
|
2480
|
-
A bundle supplies
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
1. `beforeLinks`, on every page, before wikilinks resolve — a `{@link}` tag may
|
|
2484
|
-
sit in prose a wikilink also touches.
|
|
2485
|
-
2. `afterLinks`, on pages from an extra tree only — repository-relative links are
|
|
2486
|
-
a property of how those pages are authored, not of content notes.
|
|
2487
|
-
|
|
2488
|
-
Both run inside code-fence protection, so neither can rewrite a fenced example.
|
|
2482
|
+
A bundle supplies one hook, `beforeLinks`, which runs on every page before
|
|
2483
|
+
wikilinks resolve — a `{@link}` tag may sit in prose a wikilink also touches.
|
|
2484
|
+
It runs inside code-fence protection, so it cannot rewrite a fenced example.
|
|
2489
2485
|
|
|
2490
2486
|
### The gates
|
|
2491
2487
|
|
|
@@ -2499,7 +2495,6 @@ after the links that failed because of it reads as a pile of broken notes.
|
|
|
2499
2495
|
| Addresses | A note with no shortcode to be addressed by, or no section to be filed under. |
|
|
2500
2496
|
| Unusable manifest | A vendored manifest this build cannot read. |
|
|
2501
2497
|
| Unaddressable manifest | One it can read but cannot look anything up in. |
|
|
2502
|
-
| Package conflicts | One address claimed by two packages. |
|
|
2503
2498
|
| Tables and wikilinks | A table directive that cannot be honoured, or a link that lands nowhere. |
|
|
2504
2499
|
|
|
2505
2500
|
None of them exits the process from inside the library; the command decides. That
|
package/bin/content-build.mjs
CHANGED
|
@@ -1561,9 +1561,6 @@ function siteCommand() {
|
|
|
1561
1561
|
for (const f of gates.unaddressable) {
|
|
1562
1562
|
console.error(formatUnaddressable(f, loadPackConfig()));
|
|
1563
1563
|
}
|
|
1564
|
-
for (const c of gates.conflicts) {
|
|
1565
|
-
log.error(`address ${c.key} is also published by ${c.package}`);
|
|
1566
|
-
}
|
|
1567
1564
|
if (gatesFailed(gates)) {
|
|
1568
1565
|
process.exitCode = 1;
|
|
1569
1566
|
return;
|
package/bin/package-build.mjs
CHANGED
|
@@ -334,9 +334,11 @@ async function formatGenerated(text, filepath) {
|
|
|
334
334
|
* so the system publishes the field sets as data — the same shape the link
|
|
335
335
|
* manifest already uses for addresses.
|
|
336
336
|
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
*
|
|
337
|
+
* Writes under `build/`, alongside every other generated artifact: a release
|
|
338
|
+
* publishes it beside the archive and the manifest (see `package-build
|
|
339
|
+
* release`), and `packageBuild.assets` carries it into the staged tree from
|
|
340
|
+
* there. There is nothing committed to compare it against, so there is no
|
|
341
|
+
* `--check`.
|
|
340
342
|
*
|
|
341
343
|
* @returns {object} The yargs command module.
|
|
342
344
|
*/
|
|
@@ -344,15 +346,8 @@ function schemaCommand() {
|
|
|
344
346
|
return {
|
|
345
347
|
command: "schema",
|
|
346
348
|
describe: "Publish this package's DataModel field sets as schema.json",
|
|
347
|
-
builder: (y) =>
|
|
348
|
-
|
|
349
|
-
type: "boolean",
|
|
350
|
-
default: false,
|
|
351
|
-
describe:
|
|
352
|
-
"Fail when the committed schema.json is out of date " +
|
|
353
|
-
"rather than rewriting it",
|
|
354
|
-
}),
|
|
355
|
-
handler: handler(async (argv) => {
|
|
349
|
+
builder: (y) => y,
|
|
350
|
+
handler: handler(async () => {
|
|
356
351
|
const config = loadPackageBuildConfig();
|
|
357
352
|
if (!config.schema.length) {
|
|
358
353
|
console.log(
|
|
@@ -369,25 +364,13 @@ function schemaCommand() {
|
|
|
369
364
|
version: pkg.version,
|
|
370
365
|
});
|
|
371
366
|
|
|
372
|
-
const out = path.join(config.rootDir, SCHEMA_ARTIFACT_FILE);
|
|
367
|
+
const out = path.join(config.rootDir, "build", SCHEMA_ARTIFACT_FILE);
|
|
373
368
|
const text = await formatGenerated(JSON.stringify(artifact), out);
|
|
374
369
|
const counts = Object.entries(artifact.documents)
|
|
375
370
|
.map(([kind, subtypes]) => `${Object.keys(subtypes).length} ${kind}`)
|
|
376
371
|
.join(", ");
|
|
377
372
|
|
|
378
|
-
|
|
379
|
-
const current = fs.existsSync(out) ? fs.readFileSync(out, "utf8") : null;
|
|
380
|
-
if (current !== text) {
|
|
381
|
-
die(
|
|
382
|
-
`${SCHEMA_ARTIFACT_FILE} does not match what this ` +
|
|
383
|
-
`package's data models would produce — regenerate ` +
|
|
384
|
-
`it with \`package-build schema\`.`,
|
|
385
|
-
);
|
|
386
|
-
}
|
|
387
|
-
console.log(`✅ ${SCHEMA_ARTIFACT_FILE} is up to date ` + `(${counts} subtypes).`);
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
|
|
373
|
+
fs.mkdirSync(path.dirname(out), { recursive: true });
|
|
391
374
|
fs.writeFileSync(out, text, "utf8");
|
|
392
375
|
console.log(
|
|
393
376
|
`✅ Wrote ${SCHEMA_ARTIFACT_FILE} for ${artifact.system} ` +
|
package/config.mjs
CHANGED
|
@@ -91,7 +91,7 @@ const SECTION_KEYS = [
|
|
|
91
91
|
export const DERIVED_MANIFEST_KEYS = Object.freeze({
|
|
92
92
|
id: "`foundryPackage`, itself derived from package.json `name`",
|
|
93
93
|
version: "package.json `version`",
|
|
94
|
-
description: "
|
|
94
|
+
description: "`packageBuild.manifest.descriptionHtml`",
|
|
95
95
|
url: "package.json `repository`",
|
|
96
96
|
bugs: "package.json `repository`",
|
|
97
97
|
manifest: "package.json `repository` and the release tag",
|
|
@@ -152,11 +152,16 @@ const ARTIFACT_OF_KIND = Object.freeze({
|
|
|
152
152
|
* message, so {@link loadPackageBuildConfig} — the half that knows which file
|
|
153
153
|
* was read — can resolve it to a line and column. This half stays pure.
|
|
154
154
|
*
|
|
155
|
+
* Exported so a sibling module composing a configuration value this module
|
|
156
|
+
* does not itself validate — {@link module:engine/site-config}'s
|
|
157
|
+
* `hugoConfig`, checking `site.assets` — reports through the one helper
|
|
158
|
+
* rather than a second copy.
|
|
159
|
+
*
|
|
155
160
|
* @param {string} where - Dotted path of the offending key.
|
|
156
161
|
* @param {string} problem - What is wrong with it.
|
|
157
162
|
* @returns {never}
|
|
158
163
|
*/
|
|
159
|
-
function fail(where, problem) {
|
|
164
|
+
export function fail(where, problem) {
|
|
160
165
|
throw Object.assign(new TypeError(`package-build config: \`${where}\` ${problem}.`), {
|
|
161
166
|
field: where,
|
|
162
167
|
});
|
package/content-config.mjs
CHANGED
|
@@ -73,7 +73,7 @@ import { ADDRESS_SEGMENT_PATTERN, isAddressSegment } from "./engine/address-char
|
|
|
73
73
|
import { ASSET_TYPE_NAMES } from "./engine/asset-types.mjs";
|
|
74
74
|
import { isReservedPackage } from "./engine/packages.mjs";
|
|
75
75
|
import { EMPTY_ICON_REGISTRY, checkIconRegistry } from "./engine/content-icons.mjs";
|
|
76
|
-
import { MAP_TYPES, PACK_BY_TYPE } from "./engine/ids.mjs";
|
|
76
|
+
import { MAP_TYPES, NO_PACK, PACK_BY_TYPE } from "./engine/ids.mjs";
|
|
77
77
|
import { ACTOR_TYPES } from "./engine/subtype-registry.mjs";
|
|
78
78
|
import { NOTE_VOCABULARY } from "./engine/note-vocabulary.mjs";
|
|
79
79
|
|
|
@@ -256,10 +256,10 @@ export const DEFAULT_ADDRESS_SCHEME = Object.freeze({
|
|
|
256
256
|
* and the default.
|
|
257
257
|
*
|
|
258
258
|
* - `homepage` — the authored homepage, and **no other page**. The content tree
|
|
259
|
-
* is not walked for pages, `site.sections` / `site.
|
|
260
|
-
*
|
|
259
|
+
* is not walked for pages, `site.sections` / `site.landing` emit nothing,
|
|
260
|
+
* and nothing serves a page for its addresses.
|
|
261
261
|
* - `content` — the homepage *plus* every page the content tree publishes: the
|
|
262
|
-
* knowledgebase
|
|
262
|
+
* knowledgebase and the section landings.
|
|
263
263
|
*
|
|
264
264
|
* **Homepage-only is a first-class mode, not an accommodation.**
|
|
265
265
|
* `sohl-kethira-basic` (unofficial Hârn fan material under Keléstia Productions'
|
|
@@ -759,11 +759,10 @@ const DOCS_KEYS = ["itemFields"];
|
|
|
759
759
|
const SITE_KEYS = [
|
|
760
760
|
"base",
|
|
761
761
|
"assets",
|
|
762
|
+
"description",
|
|
762
763
|
"packages",
|
|
763
764
|
"sections",
|
|
764
|
-
"readmeSections",
|
|
765
765
|
"landing",
|
|
766
|
-
"trees",
|
|
767
766
|
"pass",
|
|
768
767
|
"passOptions",
|
|
769
768
|
"backfillSections",
|
|
@@ -771,7 +770,6 @@ const SITE_KEYS = [
|
|
|
771
770
|
"notfound",
|
|
772
771
|
"hugo",
|
|
773
772
|
];
|
|
774
|
-
const SITE_TREE_KEYS = ["from", "section"];
|
|
775
773
|
const SITE_LIST_KEYS = ["shortcodes"];
|
|
776
774
|
const SITE_NOTFOUND_KEYS = ["tagline", "sitenoun", "heroimage", "links"];
|
|
777
775
|
const SITE_NOTFOUND_LINK_KEYS = ["title", "url", "text"];
|
|
@@ -1078,6 +1076,15 @@ function normalizePack(value, where, nested = false) {
|
|
|
1078
1076
|
rejectUnknownKeys(pack, PACK_KEYS, `${where}.`);
|
|
1079
1077
|
|
|
1080
1078
|
const name = requireNonEmptyString(pack.name, `${where}.name`);
|
|
1079
|
+
// A note writes `pack: none` to compile into no document, so a pack of
|
|
1080
|
+
// that name could never be addressed by one.
|
|
1081
|
+
if (name === NO_PACK) {
|
|
1082
|
+
fail(
|
|
1083
|
+
`${where}.name`,
|
|
1084
|
+
`may not be \`${NO_PACK}\` — a note declares \`pack: ${NO_PACK}\` to ` +
|
|
1085
|
+
`compile into no document, so no pack may answer to the name`,
|
|
1086
|
+
);
|
|
1087
|
+
}
|
|
1081
1088
|
const type = pack.type;
|
|
1082
1089
|
if (
|
|
1083
1090
|
typeof type !== "string" ||
|
|
@@ -1575,6 +1582,33 @@ function normalizeSiteAssets(value) {
|
|
|
1575
1582
|
return assets.replace(/\/+$/, "");
|
|
1576
1583
|
}
|
|
1577
1584
|
|
|
1585
|
+
/**
|
|
1586
|
+
* The site's `<meta name="description">`, plain text.
|
|
1587
|
+
*
|
|
1588
|
+
* Foundry's package browser wants a pitch — HTML, any length,
|
|
1589
|
+
* `packageBuild.manifest.descriptionHtml` — and a `<meta>` tag wants one
|
|
1590
|
+
* plain sentence. The two audiences are different enough that one string
|
|
1591
|
+
* cannot serve both, so this is the site's own, checked for markup rather
|
|
1592
|
+
* than trusted to carry none: a value with a `<` in it is refused, naming
|
|
1593
|
+
* `descriptionHtml` as where markup belongs.
|
|
1594
|
+
*
|
|
1595
|
+
* @param {unknown} value - The configured value, or `undefined`.
|
|
1596
|
+
* @returns {string} The description; `""` when unset.
|
|
1597
|
+
*/
|
|
1598
|
+
function normalizeSiteDescription(value) {
|
|
1599
|
+
if (value === undefined) return "";
|
|
1600
|
+
const description = requireNonEmptyString(value, "site.description");
|
|
1601
|
+
if (description.includes("<")) {
|
|
1602
|
+
fail(
|
|
1603
|
+
"site.description",
|
|
1604
|
+
"contains `<` — this is plain text for the site's " +
|
|
1605
|
+
'`<meta name="description">`; markup belongs in ' +
|
|
1606
|
+
"`packageBuild.manifest.descriptionHtml`",
|
|
1607
|
+
);
|
|
1608
|
+
}
|
|
1609
|
+
return description;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1578
1612
|
/**
|
|
1579
1613
|
* A map of section name → landing metadata.
|
|
1580
1614
|
*
|
|
@@ -1616,7 +1650,7 @@ export const DERIVED_HUGO_KEYS = Object.freeze({
|
|
|
1616
1650
|
disableKinds: "whether any note in the tree carries `tags:`, which the site walk discovers",
|
|
1617
1651
|
taxonomies: "whether any note in the tree carries `tags:`, which the site walk discovers",
|
|
1618
1652
|
outputs: "whether any note in the tree carries `tags:`, which the site walk discovers",
|
|
1619
|
-
"params.description": "
|
|
1653
|
+
"params.description": "`site.description`",
|
|
1620
1654
|
"params.author": "package.json `author`",
|
|
1621
1655
|
"params.cdnBaseURL": "`site.assets`",
|
|
1622
1656
|
"params.brand": "the organisation's brand links, in `engine/site-config.mjs`",
|
|
@@ -1722,15 +1756,22 @@ function normalizeSiteHugo(value) {
|
|
|
1722
1756
|
/**
|
|
1723
1757
|
* The `site` section — how this repository frames the website it publishes.
|
|
1724
1758
|
*
|
|
1725
|
-
* Everything here is *framing*: what a section is called, which
|
|
1726
|
-
*
|
|
1727
|
-
*
|
|
1728
|
-
*
|
|
1729
|
-
*
|
|
1730
|
-
*
|
|
1731
|
-
*
|
|
1732
|
-
*
|
|
1733
|
-
*
|
|
1759
|
+
* Everything here is *framing*: what a section is called, which named pass
|
|
1760
|
+
* bundle supplies the repository's own body rewrites, and the residue of the
|
|
1761
|
+
* generated Hugo configuration that is genuinely this repository's own. Where
|
|
1762
|
+
* the Hugo tree is written is not a choice: `content-build site` writes it
|
|
1763
|
+
* under `build/hugo/`, and a `site.out` is refused by name. How a page gets
|
|
1764
|
+
* its **address** is deliberately not here either — that is
|
|
1765
|
+
* `publish.address`, shared with the link manifest so the two cannot disagree
|
|
1766
|
+
* about where a page is.
|
|
1767
|
+
*
|
|
1768
|
+
* **What the site publishes is the content tree, and nothing beside it.** A
|
|
1769
|
+
* page of documentation is a note — `type: doc`, addressed by its shortcode,
|
|
1770
|
+
* compiling into no document where it says `pack: none` — so there is no
|
|
1771
|
+
* second mechanism for mounting a directory of markdown, and a configuration
|
|
1772
|
+
* that names one (`site.trees`, and the `site.readmeSections` that titled
|
|
1773
|
+
* such a tree's landing) is refused with a message saying where the page
|
|
1774
|
+
* goes instead.
|
|
1734
1775
|
*
|
|
1735
1776
|
* @param {unknown} value - The `site` block, or `undefined`.
|
|
1736
1777
|
* @returns {Readonly<object>} It, frozen, with every default filled.
|
|
@@ -1739,11 +1780,10 @@ function normalizeSite(value) {
|
|
|
1739
1780
|
const empty = Object.freeze({
|
|
1740
1781
|
base: "",
|
|
1741
1782
|
assets: "",
|
|
1783
|
+
description: "",
|
|
1742
1784
|
packages: Object.freeze([]),
|
|
1743
1785
|
sections: Object.freeze({}),
|
|
1744
|
-
readmeSections: Object.freeze({}),
|
|
1745
1786
|
landing: null,
|
|
1746
|
-
trees: Object.freeze([]),
|
|
1747
1787
|
pass: "",
|
|
1748
1788
|
passOptions: Object.freeze({}),
|
|
1749
1789
|
backfillSections: false,
|
|
@@ -1764,27 +1804,20 @@ function normalizeSite(value) {
|
|
|
1764
1804
|
"the location is not configurable. Remove the key",
|
|
1765
1805
|
);
|
|
1766
1806
|
}
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
if (
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
from: requireNonEmptyString(tree.from, `${where}.from`),
|
|
1780
|
-
section: requireNonEmptyString(tree.section, `${where}.section`),
|
|
1781
|
-
// The tree's own path, POSIX-separated — what a
|
|
1782
|
-
// repository-relative link inside it is resolved against.
|
|
1783
|
-
rel: String(tree.from).split(path.sep).join("/"),
|
|
1784
|
-
}),
|
|
1785
|
-
);
|
|
1786
|
-
});
|
|
1807
|
+
// Refused by name too, and ahead of the vocabulary check for the same
|
|
1808
|
+
// reason: a page is a note in the content tree, and the useful thing to
|
|
1809
|
+
// say is where it goes rather than that the key is unknown.
|
|
1810
|
+
for (const key of ["trees", "readmeSections"]) {
|
|
1811
|
+
if (input[key] === undefined) continue;
|
|
1812
|
+
fail(
|
|
1813
|
+
`site.${key}`,
|
|
1814
|
+
"is retired — a page is a note in the content tree. Give each page " +
|
|
1815
|
+
"`type: doc`, a `shortcode` and `pack: none`, file it under " +
|
|
1816
|
+
"`assets/content/`, and declare the section that lists it under " +
|
|
1817
|
+
"`site.sections`",
|
|
1818
|
+
);
|
|
1787
1819
|
}
|
|
1820
|
+
rejectUnknownKeys(input, SITE_KEYS, "site.");
|
|
1788
1821
|
|
|
1789
1822
|
let packages = [];
|
|
1790
1823
|
if (input.packages !== undefined) {
|
|
@@ -1807,11 +1840,10 @@ function normalizeSite(value) {
|
|
|
1807
1840
|
return Object.freeze({
|
|
1808
1841
|
base: input.base === undefined ? "" : requireNonEmptyString(input.base, "site.base"),
|
|
1809
1842
|
assets: normalizeSiteAssets(input.assets),
|
|
1843
|
+
description: normalizeSiteDescription(input.description),
|
|
1810
1844
|
packages: Object.freeze(packages),
|
|
1811
1845
|
sections: normalizeSectionMap(input.sections, "site.sections"),
|
|
1812
|
-
readmeSections: normalizeSectionMap(input.readmeSections, "site.readmeSections"),
|
|
1813
1846
|
landing,
|
|
1814
|
-
trees: Object.freeze(trees),
|
|
1815
1847
|
pass: input.pass === undefined ? "" : requireNonEmptyString(input.pass, "site.pass"),
|
|
1816
1848
|
passOptions:
|
|
1817
1849
|
input.passOptions === undefined ?
|
package/docs/api.md
CHANGED
|
@@ -103,6 +103,7 @@ Deterministic document ids, derived by hashing rather than stored, so compile pa
|
|
|
103
103
|
| `MAP_TYPES` | `const MAP_TYPES` | — | enumerating the content types that compile into a Foundry `Scene` |
|
|
104
104
|
| `MAP_SUBTYPES` | `const MAP_SUBTYPES` | — | reading the map subTypes, which differ only in derived canvas defaults |
|
|
105
105
|
| `JOURNAL_TYPES` | `const JOURNAL_TYPES` | — | enumerating content types whose whole document _is_ a JournalEntry |
|
|
106
|
+
| `NO_PACK` | `const NO_PACK` | — | spelling the `pack:` value (`none`) that routes a note's document into no compendium |
|
|
106
107
|
| `PACK_BY_TYPE` | `const PACK_BY_TYPE` | — | looking up the conventional pack name and document type for a content type |
|
|
107
108
|
| `RETIRED_TYPES` | `const RETIRED_TYPES` | — | looking up what a retired content type was replaced by |
|
|
108
109
|
| `assertTypeNotRetired` | `assertTypeNotRetired(type, where)` | throws | refusing a note whose type has been retired outright |
|
|
@@ -200,6 +201,8 @@ Which pack a note's document lands in, when a document type has more than one co
|
|
|
200
201
|
| ------------------ | -------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
201
202
|
| `PackRoutingError` | `class PackRoutingError extends Error` | — | catching a note that cannot be routed to any configured pack |
|
|
202
203
|
| `PACK_FIELD` | `const PACK_FIELD` | — | naming the frontmatter field a note declares its pack in |
|
|
204
|
+
| `NO_PACK` | `const NO_PACK` | — | spelling the `pack:` value (`none`) that routes a note's document into no compendium |
|
|
205
|
+
| `declaresNoPack` | `declaresNoPack(fm, system)` | `boolean` | asking, without a router, whether a note declares `pack: none` for one system's document |
|
|
203
206
|
| `createPackRouter` | `createPackRouter(packs)` | `{resolve, resolveOrNull}` | building a router for one configured pack list, purely, for testing without a config file on disk |
|
|
204
207
|
| `routerFor` | `routerFor(config)` | the router | getting the router for a resolved configuration, built once per configuration |
|
|
205
208
|
| `packRouter` | `packRouter()` | the router | asking, as every module that emits a UUID does, where a note's document lives |
|
|
@@ -605,25 +608,23 @@ The toolchain's own content index — the files it ships, addressed. Every other
|
|
|
605
608
|
|
|
606
609
|
Publishing a content tree as a website. Compiling a content tree into compendium packs is `content-build package compile`. Publishing the _same tree_ as a website was a script each consumer wrote for itself — 473 code lines in `sohl` and 462 in `sohl-thalorna`, 87 of them identical — and the copies drifted in ways neither repository could see. `sohl-thalorna` reimplemented four things this package already exported, not because it needed different behaviour but because its script predates the extraction. That is the failure a command removes: a consumer cannot accidentally reimplement one.
|
|
607
610
|
|
|
608
|
-
| Export | Signature | Returns | Use it when
|
|
609
|
-
| ---------------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
610
|
-
| `
|
|
611
|
-
| `
|
|
612
|
-
| `
|
|
613
|
-
| `
|
|
614
|
-
| `
|
|
615
|
-
| `
|
|
616
|
-
| `
|
|
617
|
-
| `
|
|
618
|
-
| `
|
|
619
|
-
| `
|
|
620
|
-
| `
|
|
621
|
-
| `
|
|
622
|
-
| `
|
|
623
|
-
| `
|
|
624
|
-
| `
|
|
625
|
-
| `resolveSitePass` | `function resolveSitePass(name, options)` | {{beforeLinks?: Function, afterLinks?: Function}} The bundle. | Resolves `site.pass` to its bundle. |
|
|
626
|
-
| `buildSite` | `buildSite({ config, sqlTables })` | {{gates: object, stats: object\|null, tableErrors: object[], wikiErrors: object[], manifests: object\|null}} | Builds a Hugo content tree from a content tree, and reports what it found. |
|
|
611
|
+
| Export | Signature | Returns | Use it when |
|
|
612
|
+
| ---------------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
|
|
613
|
+
| `collectContentPages` | `function collectContentPages(contentBase, ctx)` | {{pages: object[], addressFindings: object[], fmLinkFindings: object[]}} | The content tree's pages, and what could not be addressed. |
|
|
614
|
+
| `collectHomepages` | `function collectHomepages(contentBase, ctx)` | {{pages: object[], addressFindings: object[]}} The homepage notes, in walk order, and the ones among them that could not be addressed. | The package's homepage notes — the authored page at `/<contentPackage>/`. |
|
|
615
|
+
| `writeHomepages` | `function writeHomepages(outRoot, pages, config)` | {number} How many pages were written. | Writes each homepage at its address, below the package's own root. |
|
|
616
|
+
| `siteGates` | `function siteGates(pages, findings,` | {object} The gate results and, when they pass, the built index. | The integrity gates a site build runs before it writes anything. |
|
|
617
|
+
| `emptyGates` | `function emptyGates()` | {object} An all-clear gate result. | The gate result of a build that ran none of them. |
|
|
618
|
+
| `gatesFailed` | `function gatesFailed(gates)` | — | Whether any gate produced a finding. |
|
|
619
|
+
| `tableUniverse` | `function tableUniverse(pages)` | {Map<string, object[]>} Package → the notes it may tabulate. | The universe a generated table searches, grouped by package. |
|
|
620
|
+
| `sectionFrontmatter` | `function sectionFrontmatter(meta)` | {object} Its front matter, `title` first. | The front matter a section's landing states about itself. |
|
|
621
|
+
| `pageFrontmatter` | `function pageFrontmatter(page,` | {object} The frontmatter to write. | The frontmatter a page publishes with. |
|
|
622
|
+
| `pageDestination` | `function pageDestination(page)` | — | Where a page is written, relative to the output root. |
|
|
623
|
+
| `renderPages` | `function renderPages(pages, options)` | {{written: number, byKind: Record<string, number>, tableErrors: object[], wikiErrors: object[]}} | Renders and writes every page. |
|
|
624
|
+
| `writeSectionLandings` | `function writeSectionLandings(outRoot,` | {number} How many landings were written. | Writes the Hugo sections a published tree declares. |
|
|
625
|
+
| `pluralTitle` | `function pluralTitle(name)` | {string} The display title. | A section landing's title, from its directory name — `macro` → `Macros`. |
|
|
626
|
+
| `resolveSitePass` | `function resolveSitePass(name, options)` | {{beforeLinks?: Function}} The bundle. | Resolves `site.pass` to its bundle. |
|
|
627
|
+
| `buildSite` | `buildSite({ config, sqlTables })` | {{gates: object, stats: object\|null, tableErrors: object[], wikiErrors: object[], manifests: object\|null}} | Builds a Hugo content tree from a content tree, and reports what it found. |
|
|
627
628
|
|
|
628
629
|
### `engine.contentLint`
|
|
629
630
|
|
|
@@ -1138,13 +1139,12 @@ Which of SoHL's facts a note's summary panel carries, and how they group. Read o
|
|
|
1138
1139
|
|
|
1139
1140
|
### `sohl.kbPasses`
|
|
1140
1141
|
|
|
1141
|
-
The `sohl` knowledgebase's own body
|
|
1142
|
+
The `sohl` knowledgebase's own body pass: a rewrite driven by a TypeDoc symbol map only this package has, named from `site.passOptions` the same way an asset transform is named from configuration. The rewrite never fails a build — an unresolved `{@link}` degrades to a code span — but building the bundle from a misconfigured `symbolMap` fails loudly before any page renders.
|
|
1142
1143
|
|
|
1143
|
-
| Export
|
|
1144
|
-
|
|
|
1145
|
-
| `resolveApiLinks`
|
|
1146
|
-
| `
|
|
1147
|
-
| `sohlKbPass` | `sohlKbPass(options)` | `{beforeLinks: Function, afterLinks: Function}` | building the `sohl` knowledgebase pass bundle the site renderer calls around wikilink resolution |
|
|
1144
|
+
| Export | Signature | Returns | Use it when |
|
|
1145
|
+
| ----------------- | ----------------------------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
1146
|
+
| `resolveApiLinks` | `resolveApiLinks(body, symbols, apiBase)` | `string` — the body with every tag resolved | resolving inline TypeDoc `{@link}` / `{@linkcode}` / `{@linkplain}` tags in a markdown body against the API symbol map |
|
|
1147
|
+
| `sohlKbPass` | `sohlKbPass(options)` | `{beforeLinks: Function}` | building the `sohl` knowledgebase pass bundle the site renderer calls before wikilink resolution |
|
|
1148
1148
|
|
|
1149
1149
|
### Flat exports (not under a namespace)
|
|
1150
1150
|
|
|
@@ -1290,6 +1290,7 @@ console.log(config.stageDir);
|
|
|
1290
1290
|
| `resolvePackageBuildConfig` | `resolvePackageBuildConfig(shared)` | `Readonly<PackageBuildConfig>` | validating the `packageBuild:` section from an already-loaded shared configuration — the pure half, usable without touching disk |
|
|
1291
1291
|
| `loadPackageBuildConfig` | `loadPackageBuildConfig()` | `Readonly<PackageBuildConfig>` | reading and validating the repository's resolved package-build configuration from disk, read fresh on each call rather than cached at import |
|
|
1292
1292
|
| `checkHomepage` | `checkHomepage(homepage, contentPackage)` | `void` | validating a resolved `homepage` against `contentPackage` — called by `content-build site` before the generated `baseURL` is written, not by `resolvePackageBuildConfig` itself |
|
|
1293
|
+
| `fail` | `fail(where, problem)` | `never` | rejecting a configured value from a sibling module, naming the dotted key path it was written under, so the loader can resolve it to a line and column |
|
|
1293
1294
|
|
|
1294
1295
|
## `./prettier`
|
|
1295
1296
|
|