@heroiclands/package-build 22.0.3 → 22.1.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 +51 -0
- package/CONTENT.md +34 -18
- package/README.md +1 -1
- package/bin/content-build.mjs +40 -22
- package/bin/package-build.mjs +3 -2
- package/config.mjs +52 -1
- package/content-config.mjs +235 -8
- package/docs/api.md +8 -7
- package/docs/commands.md +68 -32
- package/docs/configuration.md +254 -61
- package/docs/getting-started.md +15 -4
- package/docs/project-setup.md +45 -20
- package/engine/pack-config.mjs +21 -2
- package/engine/site-build.mjs +15 -52
- package/engine/site-config.mjs +462 -0
- package/manifest.mjs +12 -7
- package/package.json +2 -1
- package/stage.mjs +4 -3
- package/types/config.d.mts +24 -0
- package/types/content-config.d.mts +53 -0
- package/types/engine/site-build.d.mts +3 -25
- package/types/engine/site-config.d.mts +223 -0
- package/types/manifest.d.mts +4 -4
- package/types/stage.d.mts +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 22.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- b0ec919: **The site build generates the Hugo configuration.** `content-build site`
|
|
8
|
+
writes the whole Hugo source tree under `build/hugo/` — a generated
|
|
9
|
+
`hugo.toml`, the content mount at `build/hugo/content/`, Hugo's cache —
|
|
10
|
+
beside the deployment root `build/site/`, and the repository carries no Hugo
|
|
11
|
+
configuration of its own. Every value in the generated file has one source:
|
|
12
|
+
`package.json` (`homepage`, `description`, `author`),
|
|
13
|
+
`package-build.config.yaml` (`packageBuild.manifest.title`, `site.assets`,
|
|
14
|
+
`site.list`, `site.notfound`), the installed `@heroiclands/hugo-theme`, and
|
|
15
|
+
the organisation's brand links and navigation. `package.json`'s `homepage`
|
|
16
|
+
is checked on every site build: it must be
|
|
17
|
+
`https://www.heroiclands.org/<contentPackage>/`.
|
|
18
|
+
|
|
19
|
+
**`deps fetch` also fetches the navigation.** The header menu every site
|
|
20
|
+
renders comes from `https://www.heroiclands.org/nav.json`, cached under
|
|
21
|
+
`build/cache/navigation/` by `content-build deps fetch` and read from the
|
|
22
|
+
cache by the site build — a cold cache fails naming `deps fetch`, so a
|
|
23
|
+
package with no other dependency now runs `deps fetch` before `site` too.
|
|
24
|
+
|
|
25
|
+
**To migrate a repository:**
|
|
26
|
+
|
|
27
|
+
- Delete `site/` and its `.gitignore` entries (`site/content`, `site/public`,
|
|
28
|
+
`site/resources`), and any `packageBuild.clean.extra` entry naming them.
|
|
29
|
+
- Delete `site.out` from `package-build.config.yaml`; the location is fixed.
|
|
30
|
+
- Move the `[params.notfound]` block from the old `hugo.toml` to
|
|
31
|
+
`site.notfound` (`tagline`, `sitenoun`, optional `heroimage` and
|
|
32
|
+
`links[]` of `{title, url, text}`), `params.list.shortcodes` to
|
|
33
|
+
`site.list.shortcodes`, and `params.cdnBaseURL` to `site.assets` where it
|
|
34
|
+
was not already there. Anything else the file carried that the generator
|
|
35
|
+
does not write — `markup.goldmark.extensions.linkify`, say — goes under
|
|
36
|
+
`site.hugo`, which is deep-merged last; every key the generator writes is
|
|
37
|
+
refused there, naming its source.
|
|
38
|
+
- Point the npm scripts at the generated tree:
|
|
39
|
+
`hugo --source build/hugo --minify --gc --cleanDestinationDir` and
|
|
40
|
+
`hugo server --source build/hugo`.
|
|
41
|
+
- Make sure `package.json` declares `homepage`, `description` and `author`,
|
|
42
|
+
and that `@heroiclands/hugo-theme` is under `devDependencies`.
|
|
43
|
+
- a5e83fd: **A manifest's `description` comes from `package.json`.**
|
|
44
|
+
`packageBuild.manifest.description` is refused, the same way `id` and
|
|
45
|
+
`version` are — delete it; the manifest carries `package.json`'s own
|
|
46
|
+
`description`.
|
|
47
|
+
|
|
48
|
+
**`package.json`'s `homepage` and `author` are read and normalised.**
|
|
49
|
+
`homepage` names the address every package's site is served at — an
|
|
50
|
+
absolute URL ending `/<contentPackage>/` — and `author` is accepted in
|
|
51
|
+
either of npm's forms, a string or `{name, email, url}`, and normalised to
|
|
52
|
+
the object form.
|
|
53
|
+
|
|
3
54
|
## 22.0.3
|
|
4
55
|
|
|
5
56
|
### Patch Changes
|
package/CONTENT.md
CHANGED
|
@@ -234,8 +234,9 @@ publish:
|
|
|
234
234
|
site: content
|
|
235
235
|
address: { prefix: guide/ }
|
|
236
236
|
|
|
237
|
-
|
|
238
|
-
|
|
237
|
+
packageBuild:
|
|
238
|
+
manifest:
|
|
239
|
+
title: The Toolkit
|
|
239
240
|
|
|
240
241
|
pdf:
|
|
241
242
|
title: The Toolkit
|
|
@@ -2260,22 +2261,22 @@ address anyone links to. A package publishes its content index regardless — th
|
|
|
2260
2261
|
licensing constraint is against publishing _pages_, not against the artifact
|
|
2261
2262
|
existing — but nothing may declare it as a dependency.
|
|
2262
2263
|
|
|
2263
|
-
The homepage's file is written at the root of
|
|
2264
|
-
site root, one level above the
|
|
2265
|
-
`publish.address.prefix` puts everything else —
|
|
2266
|
-
it, `homepage-root.md`. As with every other page, the front matter's `url`
|
|
2264
|
+
The homepage's file is written at the root of the content mount,
|
|
2265
|
+
`build/hugo/content/` — the package's own site root, one level above the
|
|
2266
|
+
content mount, which is where `publish.address.prefix` puts everything else —
|
|
2267
|
+
under the name its address gives it, `homepage-root.md`. As with every other page, the front matter's `url`
|
|
2267
2268
|
decides where it publishes, and states it relative to the site root — `site.base`
|
|
2268
2269
|
does not reach it.
|
|
2269
2270
|
|
|
2270
2271
|
**What it does not do is decide addresses.** Those come from `publish.address`,
|
|
2271
2272
|
the same setting the content index reads, so a page and its index record cannot
|
|
2272
2273
|
disagree about where the page is. Everything under `site:` is _framing_ —
|
|
2273
|
-
|
|
2274
|
-
|
|
2274
|
+
what a section is called, which extra trees are published beside the content,
|
|
2275
|
+
and the residue of the generated Hugo configuration that is this repository's
|
|
2276
|
+
own:
|
|
2275
2277
|
|
|
2276
2278
|
```yaml
|
|
2277
2279
|
site:
|
|
2278
|
-
out: kb/content # required; wiped on every run
|
|
2279
2280
|
base: /sohl/ # default: /<contentPackage>/ — hrefs only, never a page's `url:`
|
|
2280
2281
|
packages: [sohl, thalorna] # default: just contentPackage
|
|
2281
2282
|
backfillSections: true
|
|
@@ -2294,11 +2295,16 @@ site:
|
|
|
2294
2295
|
description: Folk, animals and the things that walk the world.
|
|
2295
2296
|
readmeSections:
|
|
2296
2297
|
dev-docs: { title: Developer Documentation, banner: banners/dev-docs.webp }
|
|
2298
|
+
list: { shortcodes: true }
|
|
2299
|
+
notfound:
|
|
2300
|
+
tagline: Song of Heroic Lands has no page at
|
|
2301
|
+
sitenoun: site
|
|
2302
|
+
links:
|
|
2303
|
+
- { title: Knowledgebase, url: kb/, text: Every section, with the full content catalog. }
|
|
2297
2304
|
```
|
|
2298
2305
|
|
|
2299
2306
|
| Key | What it decides |
|
|
2300
2307
|
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2301
|
-
| `out` | The Hugo content root. **Required** in both modes, and wiped on every run — see below. |
|
|
2302
2308
|
| `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>/`. |
|
|
2303
2309
|
| `packages` | Which content packages this site renders. Defaults to its own. |
|
|
2304
2310
|
| `sections` | The Hugo sections this site declares, and what each says about itself — see below. |
|
|
@@ -2308,6 +2314,17 @@ site:
|
|
|
2308
2314
|
| `trees` | Extra source trees published beside the content, preserving their source layout below a section. |
|
|
2309
2315
|
| `pass` | A named bundle of this repository's own body rewrites. |
|
|
2310
2316
|
| `passOptions` | That bundle's options. |
|
|
2317
|
+
| `assets` | The host every package's imagery is served from; the generated `params.cdnBaseURL`. |
|
|
2318
|
+
| `list` | How a listing page renders; the generated `params.list`. |
|
|
2319
|
+
| `notfound` | The wording of the "page not found" page; the generated `params.notfound`. |
|
|
2320
|
+
| `hugo` | A mapping deep-merged over the generated Hugo configuration, last. Every key the generator writes is refused here — see `docs/configuration.md`. |
|
|
2321
|
+
|
|
2322
|
+
Where the tree is written is not among them. `content-build site` writes the
|
|
2323
|
+
whole Hugo source tree under `build/hugo/` — the generated `hugo.toml`, the
|
|
2324
|
+
content mount at `build/hugo/content/`, Hugo's cache — and the consumer's
|
|
2325
|
+
script runs `hugo --source build/hugo` over it. The generated file's every
|
|
2326
|
+
value has a source the repository already states; `docs/configuration.md`
|
|
2327
|
+
lists them.
|
|
2311
2328
|
|
|
2312
2329
|
### What a section may declare
|
|
2313
2330
|
|
|
@@ -2409,15 +2426,14 @@ The **writers** name no keys: a section's `_index.md` is whatever the entry
|
|
|
2409
2426
|
resolved to, `title` first. So extending the vocabulary is a change to the
|
|
2410
2427
|
schema alone, and the two can no longer drift apart.
|
|
2411
2428
|
|
|
2412
|
-
### Why
|
|
2429
|
+
### Why the output location is fixed
|
|
2413
2430
|
|
|
2414
|
-
The
|
|
2415
|
-
page whose note was renamed cannot linger and keep publishing.
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
the repository root.
|
|
2431
|
+
The content mount is a build artifact and is **deleted on every run**, so that a
|
|
2432
|
+
page whose note was renamed cannot linger and keep publishing. A configurable
|
|
2433
|
+
location is a location that can be unset — and an unset one resolves to the
|
|
2434
|
+
repository root, where the wipe deletes the working tree. A fixed one under
|
|
2435
|
+
`build/` can point nowhere else, so `site.out` is refused by name and the wipe
|
|
2436
|
+
needs no guard.
|
|
2421
2437
|
|
|
2422
2438
|
### Consumer passes are named, not imported
|
|
2423
2439
|
|
package/README.md
CHANGED
package/bin/content-build.mjs
CHANGED
|
@@ -79,6 +79,7 @@ import {
|
|
|
79
79
|
unaddressableForeignPackages,
|
|
80
80
|
formatUnaddressableFinding,
|
|
81
81
|
} from "../engine/metadata-index.mjs";
|
|
82
|
+
import { fetchNavigation, generateHugoConfig, writeHugoConfig } from "../engine/site-config.mjs";
|
|
82
83
|
import { renderItemFieldReference } from "../engine/field-reference.mjs";
|
|
83
84
|
import { lintContentTree } from "../engine/content-lint.mjs";
|
|
84
85
|
import { lintContentCharset } from "../engine/content-charset.mjs";
|
|
@@ -1484,7 +1485,14 @@ function pdfCommand() {
|
|
|
1484
1485
|
* compiled into packs. Everything a consumer would otherwise write for itself —
|
|
1485
1486
|
* the walk, the address derivation, the address index, table expansion,
|
|
1486
1487
|
* wikilink resolution, code-fence protection, the foreign-manifest merge and
|
|
1487
|
-
* the section-landing backfill — happens here, from configuration.
|
|
1488
|
+
* the section-landing backfill — happens here, from configuration. So does the
|
|
1489
|
+
* Hugo configuration: the whole source tree Hugo reads lands under
|
|
1490
|
+
* `build/hugo/`, and the consumer's script runs Hugo over it.
|
|
1491
|
+
*
|
|
1492
|
+
* **The Hugo configuration is generated before anything is written.** Its
|
|
1493
|
+
* sources — `package.json`'s `homepage`, the cached navigation, the installed
|
|
1494
|
+
* theme — are each a way the build can fail, and failing before the output
|
|
1495
|
+
* tree is cleared leaves the last good site in place to be looked at.
|
|
1488
1496
|
*
|
|
1489
1497
|
* **Each gate is reported and the run stops at the first that fires.** They are
|
|
1490
1498
|
* ordered so the report names the cause rather than its symptoms: an unusable
|
|
@@ -1497,20 +1505,17 @@ function pdfCommand() {
|
|
|
1497
1505
|
function siteCommand() {
|
|
1498
1506
|
return {
|
|
1499
1507
|
command: "site",
|
|
1500
|
-
describe: "Build
|
|
1501
|
-
builder: (
|
|
1502
|
-
|
|
1503
|
-
describe: "Write the mount here instead of the configured `site.out`.",
|
|
1504
|
-
type: "string",
|
|
1505
|
-
});
|
|
1506
|
-
},
|
|
1507
|
-
handler: async (argv) => {
|
|
1508
|
+
describe: "Build the Hugo source tree from the content tree",
|
|
1509
|
+
builder: () => {},
|
|
1510
|
+
handler: async () => {
|
|
1508
1511
|
try {
|
|
1512
|
+
const config = loadPackConfig();
|
|
1513
|
+
const hugo = generateHugoConfig(config);
|
|
1509
1514
|
const result = buildSite({
|
|
1510
|
-
|
|
1511
|
-
|
|
1515
|
+
config,
|
|
1516
|
+
sqlTables: await prepareTreeSqlTables(config.paths.content, {
|
|
1517
|
+
skipDirectories: config.skipDirectories,
|
|
1512
1518
|
}),
|
|
1513
|
-
...(argv.out ? { outRoot: argv.out } : {}),
|
|
1514
1519
|
});
|
|
1515
1520
|
const { gates } = result;
|
|
1516
1521
|
|
|
@@ -1623,6 +1628,8 @@ function siteCommand() {
|
|
|
1623
1628
|
`${s.tree ?? 0} tree page(s) + ${s.landings} ` +
|
|
1624
1629
|
`landing(s) to ${path.relative(process.cwd(), s.out)}`,
|
|
1625
1630
|
);
|
|
1631
|
+
const { file } = writeHugoConfig(config, hugo);
|
|
1632
|
+
log.info(`wrote ${path.relative(process.cwd(), file)}`);
|
|
1626
1633
|
} catch (err) {
|
|
1627
1634
|
reportFailure(err);
|
|
1628
1635
|
process.exitCode = 1;
|
|
@@ -1782,16 +1789,19 @@ async function fetchFromLocalArtifact(config, argv) {
|
|
|
1782
1789
|
|
|
1783
1790
|
/**
|
|
1784
1791
|
* `deps fetch` — fill the caches this build resolves other packages through:
|
|
1785
|
-
* the **content index** of every declared dependency,
|
|
1786
|
-
*
|
|
1787
|
-
*
|
|
1788
|
-
*
|
|
1789
|
-
*
|
|
1790
|
-
*
|
|
1791
|
-
*
|
|
1792
|
-
*
|
|
1793
|
-
*
|
|
1794
|
-
*
|
|
1792
|
+
* the **content index** of every declared dependency, the **item catalogue**
|
|
1793
|
+
* of those additionally declaring `itemCatalog: true`, and the **site
|
|
1794
|
+
* navigation** heroiclands.org publishes, which the site build writes its
|
|
1795
|
+
* menu from.
|
|
1796
|
+
*
|
|
1797
|
+
* The two dependency sets differ deliberately. Citing another package's
|
|
1798
|
+
* *addresses* and embedding its *items* are separate edges, and a package may
|
|
1799
|
+
* have either without the other — `harn-ensemble` cites no foreign address and
|
|
1800
|
+
* embeds 324,016 item references. The navigation is fetched for every package,
|
|
1801
|
+
* because every package publishes a site.
|
|
1802
|
+
*
|
|
1803
|
+
* Its own command rather than a step of `package compile` or `site`, so that
|
|
1804
|
+
* neither reaches the network. A build that downloads silently is not
|
|
1795
1805
|
* reproducible, breaks offline, and hides a dependency's version change behind
|
|
1796
1806
|
* a passing run.
|
|
1797
1807
|
*
|
|
@@ -1838,6 +1848,14 @@ function depsCommand() {
|
|
|
1838
1848
|
await fetchFromLocalArtifact(config, argv);
|
|
1839
1849
|
return;
|
|
1840
1850
|
}
|
|
1851
|
+
// First, because every package needs it and it depends on
|
|
1852
|
+
// nothing a repository declares: a dependency whose release
|
|
1853
|
+
// cannot be read stops the run after the navigation is cached,
|
|
1854
|
+
// not before.
|
|
1855
|
+
const navigation = await fetchNavigation(config);
|
|
1856
|
+
log.info(
|
|
1857
|
+
`Fetched the site navigation to ${path.relative(process.cwd(), navigation)}.`,
|
|
1858
|
+
);
|
|
1841
1859
|
const indexes = await fetchAllMetadata(config);
|
|
1842
1860
|
if (indexes) log.info(`Fetched ${indexes} dependency content index(es).`);
|
|
1843
1861
|
const count = await fetchAllCatalogs(config);
|
package/bin/package-build.mjs
CHANGED
|
@@ -79,6 +79,7 @@ import { hideBin } from "yargs/helpers";
|
|
|
79
79
|
|
|
80
80
|
import { loadPackageBuildConfig } from "../config.mjs";
|
|
81
81
|
import { writeSiteRoot } from "../engine/site-root.mjs";
|
|
82
|
+
import { DEPLOY_ROOT } from "../engine/site-config.mjs";
|
|
82
83
|
import { compilesFoundryDocuments } from "../content-config.mjs";
|
|
83
84
|
import { loadPackConfig, packConfigPath } from "../engine/pack-config.mjs";
|
|
84
85
|
import { cleanBuildArtifacts, stageAssets } from "../stage.mjs";
|
|
@@ -483,12 +484,12 @@ function siteRootCommand() {
|
|
|
483
484
|
builder: (y) =>
|
|
484
485
|
y.option("out", {
|
|
485
486
|
type: "string",
|
|
486
|
-
describe:
|
|
487
|
+
describe: `The directory that is deployed (default: ${DEPLOY_ROOT})`,
|
|
487
488
|
}),
|
|
488
489
|
handler: handler(async (argv) => {
|
|
489
490
|
const config = loadPackageBuildConfig();
|
|
490
491
|
const shared = loadPackConfig();
|
|
491
|
-
const out = path.resolve(config.rootDir, argv.out ??
|
|
492
|
+
const out = path.resolve(config.rootDir, argv.out ?? DEPLOY_ROOT);
|
|
492
493
|
|
|
493
494
|
const { files } = writeSiteRoot({ pkg: shared.contentPackage, out });
|
|
494
495
|
for (const file of files) {
|
package/config.mjs
CHANGED
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
* assetTransform: ./utils/svg-theme.mjs
|
|
47
47
|
* stageDir: build/stage
|
|
48
48
|
* clean:
|
|
49
|
-
* extra: [
|
|
49
|
+
* extra: [coverage]
|
|
50
50
|
* lang:
|
|
51
51
|
* sources: lang/*.json
|
|
52
52
|
* deploy:
|
|
@@ -91,6 +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: "package.json `description`",
|
|
94
95
|
url: "package.json `repository`",
|
|
95
96
|
bugs: "package.json `repository`",
|
|
96
97
|
manifest: "package.json `repository` and the release tag",
|
|
@@ -599,6 +600,56 @@ export function resolveAssetTransform(declared, rootDir) {
|
|
|
599
600
|
return path.resolve(rootDir, declared);
|
|
600
601
|
}
|
|
601
602
|
|
|
603
|
+
/**
|
|
604
|
+
* `package.json`'s `homepage`, checked against the address it must be.
|
|
605
|
+
*
|
|
606
|
+
* A package's Foundry manifest already derives its own `url` from
|
|
607
|
+
* `contentPackage` (`packageHomepage` in `manifest.mjs`); `homepage` states
|
|
608
|
+
* the same address a second time, in `package.json`, for the generated Hugo
|
|
609
|
+
* configuration to read a `baseURL` from without knowing where each
|
|
610
|
+
* repository keeps its own site configuration.
|
|
611
|
+
*
|
|
612
|
+
* Required unconditionally: every package publishes a site, so there is no
|
|
613
|
+
* package this does not apply to.
|
|
614
|
+
*
|
|
615
|
+
* **Not called by {@link resolvePackageBuildConfig}.** Every packaging
|
|
616
|
+
* command — `clean`, `deploy`, `manifest` and the rest — resolves through it,
|
|
617
|
+
* and none of them reads `homepage`: the Foundry manifest's own `url` is
|
|
618
|
+
* `packageHomepage(contentPackage)`, independent of it. The right caller is
|
|
619
|
+
* whatever reads `homepage` to write a site's `baseURL`.
|
|
620
|
+
*
|
|
621
|
+
* @param {string|null} homepage - The resolved `package.json` `homepage`, or
|
|
622
|
+
* `null` when none is declared.
|
|
623
|
+
* @param {string} contentPackage - The resolved `contentPackage`.
|
|
624
|
+
* @returns {void}
|
|
625
|
+
*/
|
|
626
|
+
export function checkHomepage(homepage, contentPackage) {
|
|
627
|
+
if (homepage === null) {
|
|
628
|
+
fail(
|
|
629
|
+
"homepage",
|
|
630
|
+
"is not declared in `package.json`, and every package needs one " +
|
|
631
|
+
"to build its site's `baseURL` from. Add " +
|
|
632
|
+
`\`https://www.heroiclands.org/${contentPackage}/\``,
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const suffix = `/${contentPackage}/`;
|
|
637
|
+
let pathname;
|
|
638
|
+
try {
|
|
639
|
+
pathname = new URL(homepage).pathname;
|
|
640
|
+
} catch {
|
|
641
|
+
fail("homepage", `is \`${homepage}\`, which is not an absolute URL`);
|
|
642
|
+
}
|
|
643
|
+
if (!pathname.endsWith(suffix)) {
|
|
644
|
+
fail(
|
|
645
|
+
"homepage",
|
|
646
|
+
`is \`${homepage}\`, but \`contentPackage\` is \`${contentPackage}\` — ` +
|
|
647
|
+
`a package's site is served at \`https://www.heroiclands.org${suffix}\`, ` +
|
|
648
|
+
`so \`package.json\`'s \`homepage\` must end \`${suffix}\``,
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
602
653
|
/**
|
|
603
654
|
* Resolve a package-build configuration from an already-loaded shared one.
|
|
604
655
|
*
|