@heroiclands/package-build 25.6.2 → 25.7.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +19 -1
  3. package/bin/content-commands.mjs +64 -79
  4. package/bin/package-build.mjs +25 -10
  5. package/ci/wip-workflows.test.mjs +252 -0
  6. package/content-config.mjs +118 -8
  7. package/docs/api.md +237 -47
  8. package/docs/commands.md +99 -35
  9. package/docs/configuration.md +93 -44
  10. package/docs/guides/release-and-deploy.md +26 -14
  11. package/docs/guides/site.md +53 -0
  12. package/engine/cdn-publish.mjs +164 -6
  13. package/engine/content-index.mjs +71 -19
  14. package/engine/content-spans.mjs +18 -1
  15. package/engine/content-styles.mjs +85 -0
  16. package/engine/derived-sections.mjs +4 -3
  17. package/engine/event-views.mjs +6 -4
  18. package/engine/helpers.mjs +33 -9
  19. package/engine/index.mjs +1 -0
  20. package/engine/note-cache.mjs +1107 -0
  21. package/engine/note-page.mjs +424 -0
  22. package/engine/pack-config.mjs +47 -4
  23. package/engine/pathnames.mjs +22 -7
  24. package/engine/prepared-only.mjs +32 -0
  25. package/engine/preview-index.mjs +55 -13
  26. package/engine/site-build.mjs +34 -448
  27. package/engine/site-config.mjs +33 -4
  28. package/engine/site-maps.mjs +16 -24
  29. package/engine/site-origins.mjs +23 -0
  30. package/engine/site-page.mjs +648 -0
  31. package/engine/site-render.mjs +142 -10
  32. package/engine/site-serve.mjs +1 -1
  33. package/engine/sql-tables.mjs +12 -3
  34. package/package.json +6 -1
  35. package/site-theme/static/css/style.css +14 -0
  36. package/types/content-config.d.mts +34 -0
  37. package/types/engine/cdn-publish.d.mts +22 -0
  38. package/types/engine/content-index.d.mts +36 -2
  39. package/types/engine/content-styles.d.mts +20 -0
  40. package/types/engine/derived-sections.d.mts +3 -3
  41. package/types/engine/event-views.d.mts +4 -3
  42. package/types/engine/helpers.d.mts +18 -1
  43. package/types/engine/index.d.mts +1 -0
  44. package/types/engine/note-cache.d.mts +149 -0
  45. package/types/engine/note-page.d.mts +92 -0
  46. package/types/engine/pack-config.d.mts +30 -0
  47. package/types/engine/prepared-only.d.mts +14 -0
  48. package/types/engine/preview-index.d.mts +35 -0
  49. package/types/engine/site-build.d.mts +5 -100
  50. package/types/engine/site-config.d.mts +3 -2
  51. package/types/engine/site-maps.d.mts +7 -11
  52. package/types/engine/site-origins.d.mts +9 -0
  53. package/types/engine/site-page.d.mts +261 -0
  54. package/types/engine/site-render.d.mts +20 -1
  55. package/types/engine/sql-tables.d.mts +8 -4
package/docs/commands.md CHANGED
@@ -1324,13 +1324,17 @@ package leaves out as a named asset set.
1324
1324
  **SYNOPSIS**
1325
1325
 
1326
1326
  ```
1327
- package-build cdn publish --set <file> --name <display name> [--dry-run]
1327
+ package-build cdn publish [--dest internal|cloudflare] --set <file> --name <display name> [--dry-run]
1328
1328
  ```
1329
1329
 
1330
1330
  **DESCRIPTION**
1331
1331
 
1332
- The one writer to `<site.assets>/<contentPackage>/`. It is run by hand, or by
1333
- a manually dispatched workflow; no build or release runs it.
1332
+ Publication is manual and separate from site building and deployment. No build
1333
+ or release runs it. `--dest internal` is the default: pictures go to
1334
+ `atlas:/srv/preview/cdn`, served at `https://cdn.internal.heroiclands.org`.
1335
+ `--dest cloudflare` uses the existing R2 bucket served at configured `site.assets`
1336
+ (normally `https://cdn.heroiclands.org`). Each destination uses the same planner,
1337
+ transforms, object keys and publication rules.
1334
1338
 
1335
1339
  Every icon and image under the package's picture roots is uploaded flat, as
1336
1340
  `icons/<basename>` or `images/<basename>` — shipped and left-out pictures
@@ -1339,11 +1343,10 @@ site build gives it. `packageBuild.assetTransform` runs on each file exactly
1339
1343
  as `package-build assets` runs it, so a picture on the CDN holds the bytes the
1340
1344
  staged package holds. SVG is served as `image/svg+xml`.
1341
1345
 
1342
- The bucket is served at `site.assets`, so an object's key is its path below
1343
- that address: `thalorna/images/thorn.webp` is
1346
+ An object's key is its path below the selected CDN origin. For Cloudflare, `thalorna/images/thorn.webp` is
1344
1347
  `https://cdn.heroiclands.org/thalorna/images/thorn.webp`.
1345
1348
 
1346
- `--set` names the set file, written at `<site.assets>/<contentPackage>/<file>`.
1349
+ `--set` names the set file, written at `<selected CDN origin>/<contentPackage>/<file>`.
1347
1350
  It lists only the pictures the package leaves out — an address a set does not
1348
1351
  list resolves to the shipped copy — chosen by the same rule the stage applies,
1349
1352
  `packageBuild.shippingLicenses` included. Its shape is the shipped set's, with
@@ -1380,9 +1383,32 @@ does not have, and a note naming left-out art that is not on the CDN when the
1380
1383
  run ends, are warnings at the note's line and column: an art slot under
1381
1384
  `data:`, an embed, or a body image.
1382
1385
 
1383
- **Credentials.** The store is an S3-compatible bucket — Cloudflare R2 for
1384
- `cdn.heroiclands.org` — signed with AWS Signature Version 4. Its settings are
1385
- read from the environment, after `.env.local` and `.env` are loaded:
1386
+ **Internal access.** Install `rsync` and OpenSSH on the publishing machine.
1387
+ The default uses your ordinary `atlas` SSH account, which must have write access
1388
+ to `/srv/preview/cdn`. Keys come from your SSH configuration or agent; the command
1389
+ uses batch mode and requires an already verified host key with
1390
+ `StrictHostKeyChecking=yes`. It uses only rsync's receiver protocol, keeps keys out
1391
+ of arguments and logs, and never deletes existing CDN objects. Transfer failures
1392
+ report the exit code and a bounded, sanitized receiver diagnostic; local staging
1393
+ failures name the operation and filesystem error code. Commands, key material and
1394
+ full child-process errors are omitted.
1395
+
1396
+ For an existing restricted publisher credential, configure an SSH alias with its
1397
+ `User preview-publish`, `IdentityFile` and verified `UserKnownHostsFile`. Then set
1398
+ `CDN_INTERNAL_DESTINATION=your-alias:cdn` in the environment or `.env.local`.
1399
+ The deployed forced command `rrsync -wo -munge /srv/preview` accepts that relative
1400
+ destination; do not repeat `/srv/preview` for this account. The override accepts
1401
+ only an SSH host alias followed by `:cdn` or `:/srv/preview/cdn`. It changes the
1402
+ connection, not the public origin. This command neither installs credentials nor
1403
+ changes server permissions.
1404
+
1405
+ Internal skip checks and set comparisons read the selected internal CDN over
1406
+ HTTPS. An internal dry run uses only those reads: no SSH connection, temporary
1407
+ staging or R2 credentials. The CDN must be reachable from the LAN or VPN.
1408
+
1409
+ **Cloudflare credentials.** With `--dest cloudflare`, the store is the existing
1410
+ S3-compatible bucket, signed with AWS Signature Version 4. Its settings are read
1411
+ from the environment, after `.env.local` and `.env` are loaded:
1386
1412
 
1387
1413
  | Variable | What it is |
1388
1414
  | ----------------------- | ------------------------------------------------------------------------- |
@@ -1391,46 +1417,35 @@ read from the environment, after `.env.local` and `.env` are loaded:
1391
1417
  | `CDN_ACCESS_KEY_ID` | The access key id of a token that can write the bucket. |
1392
1418
  | `CDN_SECRET_ACCESS_KEY` | That token's secret. Never printed. |
1393
1419
 
1394
- Setting some but not all of them is an error naming the missing ones. With
1395
- none set, `--dry-run` compares against the public CDN at `site.assets`
1396
- instead, read-only, and an upload is refused.
1397
-
1398
- A workflow calls the command the same way, passing the four values as named
1399
- secrets in the step's environment:
1400
-
1401
- ```yaml
1402
- - run: npx package-build cdn publish --set alt-art.json --name "Thalorna — illustrated"
1403
- env:
1404
- CDN_ENDPOINT: ${{ secrets.CDN_ENDPOINT }}
1405
- CDN_BUCKET: ${{ secrets.CDN_BUCKET }}
1406
- CDN_ACCESS_KEY_ID: ${{ secrets.CDN_ACCESS_KEY_ID }}
1407
- CDN_SECRET_ACCESS_KEY: ${{ secrets.CDN_SECRET_ACCESS_KEY }}
1408
- ```
1420
+ Setting some but not all of them is an error naming the missing ones. With none
1421
+ set, `--dest cloudflare --dry-run` compares against `site.assets` read-only, and an
1422
+ upload is refused. Internal publication ignores all four R2 settings.
1409
1423
 
1410
1424
  **OPTIONS**
1411
1425
 
1412
- | Option | Type | Default | Description |
1413
- | ----------- | ------- | ------- | --------------------------------------------------------------------------- |
1414
- | `--set` | string | — | Required. The set file's name under the package's CDN root, a `.json` file. |
1415
- | `--name` | string | — | Required. The set's display name. |
1416
- | `--dry-run` | boolean | `false` | List what would upload and what is skipped; write nothing. |
1426
+ | Option | Type | Default | Description |
1427
+ | ----------- | ------- | ---------- | --------------------------------------------------------------------------- |
1428
+ | `--dest` | string | `internal` | `internal` for atlas; `cloudflare` for the configured R2 bucket. |
1429
+ | `--set` | string | — | Required. The set file's name under the package's CDN root, a `.json` file. |
1430
+ | `--name` | string | — | Required. The set's display name. |
1431
+ | `--dry-run` | boolean | `false` | List what would upload and what is skipped; write nothing. |
1417
1432
 
1418
1433
  **EXIT STATUS**
1419
1434
 
1420
1435
  1 when a picture's file has no address, when a picture fails to upload, when
1421
- `--set` is `default.json` or a path, when `site.assets` is undeclared, and
1422
- when an upload has no store configured. Warnings about notes do not change
1436
+ `--set` is `default.json` or a path, when Cloudflare `site.assets` is undeclared, when an internal destination or
1437
+ planned relative key is unsafe, and when an upload lacks access to its store. Warnings about notes do not change
1423
1438
  it. Otherwise 0.
1424
1439
 
1425
1440
  **EXAMPLES**
1426
1441
 
1427
1442
  ```
1428
1443
  $ package-build cdn publish --set alt-art.json --name "Thalorna — illustrated" --dry-run
1429
- Dry run: comparing 582 pictures against https://cdn.heroiclands.org, read-only.
1444
+ Dry run: comparing 582 pictures against https://cdn.internal.heroiclands.org, read-only.
1430
1445
  would upload thalorna/icons/anvilbdg.svg (969 bytes)
1431
1446
  …
1432
1447
  would upload thalorna/alt-art.json (582 assets)
1433
- Would upload 582, skipped 0, failed 0; https://cdn.heroiclands.org/thalorna/alt-art.json lists 582 left-out pictures (would change).
1448
+ Would upload 582, skipped 0, failed 0; https://cdn.internal.heroiclands.org/thalorna/alt-art.json lists 582 left-out pictures (would change).
1434
1449
 
1435
1450
  $ package-build cdn publish --set default.json --name Shipped
1436
1451
  package-build: `--set default.json` is refused: `default.json` is the shipped set's name, the fallback every other set is read against. Name the set for its art
@@ -2685,7 +2700,7 @@ it for search.
2685
2700
  **SYNOPSIS**
2686
2701
 
2687
2702
  ```
2688
- package-build site build [--out <dir>]
2703
+ package-build site build [--root <domain>] [--out <dir>] [--base-url <url>] [--assets <url>]
2689
2704
  ```
2690
2705
 
2691
2706
  **DESCRIPTION**
@@ -2748,6 +2763,11 @@ out in the [site guide](guides/site.md#search).
2748
2763
 
2749
2764
  The site's address, title and menu are read before the tree is, so a missing
2750
2765
  `homepage` or a cold navigation cache fails with the last good site in place.
2766
+ With [`site.inputs`](configuration.md#local-site-inputs), a local module supplies
2767
+ the selected navigation and text artifacts instead of reading the navigation
2768
+ cache. It receives the effective configuration, runs before content rendering,
2769
+ and may reject canonical navigation drift. Its files are validated before
2770
+ output replacement and written after the renderer clears the site.
2751
2771
  Every gate is then checked and reported, and the run stops at the first that
2752
2772
  fires, ordered so the report names the cause rather than its symptoms — an
2753
2773
  unusable dependency manifest, reported after the links that failed because of
@@ -2760,9 +2780,44 @@ run fails before the search index is built.
2760
2780
 
2761
2781
  `--out <dir>` — the directory that is deployed. Defaults to `build/site`.
2762
2782
 
2783
+ `--root <domain>` — domain for the build's addresses, defaulting to
2784
+ `internal.heroiclands.org`. The site uses `https://www.<domain>` with the
2785
+ package's existing homepage path. An explicit root also selects
2786
+ `https://cdn.<domain>` over configured `site.assets`; omitting this flag keeps
2787
+ configured assets, or derives the CDN when none are configured. Supply a plain
2788
+ DNS domain without a scheme, path, port, credentials, query or fragment.
2789
+
2790
+ `--base-url <url>` — the absolute HTTP(S) address of this build. Its pathname
2791
+ becomes the effective `site.base`, and must end with `/`; `/` serves the site
2792
+ at the origin's root. Pages, local links, theme files, script fonts and search
2793
+ all use that prefix. Output lands under `<out>/<pathname>/`.
2794
+
2795
+ `--assets <url>` — the absolute HTTP(S) asset root for this build. It may
2796
+ include a path; trailing slashes are trimmed.
2797
+
2798
+ Explicit `--base-url` overrides the root-derived site URL, including its path;
2799
+ explicit `--assets` overrides both root-derived and configured assets. These
2800
+ low-level flags are independent and may be combined with `--root`. Cached
2801
+ navigation links on the canonical Heroic Lands www origin and the brand's
2802
+ license link use the effective site origin. External links and authored content
2803
+ links keep their addresses. Asset references on the exact canonical
2804
+ `https://cdn.heroiclands.org` origin use the effective asset root, preserving
2805
+ the path, query and fragment. Other image origins stay unchanged.
2806
+ The build only writes local output; it neither
2807
+ uploads nor fetches missing dependencies or assets.
2808
+
2809
+ Overrides apply in memory
2810
+ before preflight and rendering; they never rewrite `package.json` or
2811
+ `package-build.config.yaml`. The authored homepage must still pass its usual
2812
+ validation. Both URLs require plain paths without percent escapes, dot
2813
+ segments (`.` or `..`), or doubled slashes, and reject credentials, queries,
2814
+ fragments, whitespace and backslashes.
2815
+ Invalid overrides fail before output is replaced. A site declaring
2816
+ `site.sitemaps` must still be served at `/`.
2817
+
2763
2818
  **EXIT STATUS**
2764
2819
 
2765
- 1 if `package.json` declares no `homepage`, or one that does not end
2820
+ 1 if a runtime URL override is invalid. 1 if `package.json` declares no `homepage`, or one that does not end
2766
2821
  `/<contentPackage>/`; if the site declares no title, description or `assets`
2767
2822
  host; or if the navigation has not been fetched (`package-build deps fetch`
2768
2823
  fills the cache and is named in the message). 1 if any gate fires — no
@@ -2774,6 +2829,15 @@ findings of severity `warning` included.
2774
2829
 
2775
2830
  **EXAMPLES**
2776
2831
 
2832
+ ```bash
2833
+ package-build site build \
2834
+ --base-url https://www.internal.heroiclands.org/thalorna/ \
2835
+ --assets https://cdn.internal.heroiclands.org
2836
+ package-build site build \
2837
+ --base-url https://www.internal.heroiclands.org/pr/thalorna/123/thalorna/ \
2838
+ --assets https://cdn.internal.heroiclands.org
2839
+ ```
2840
+
2777
2841
  ```
2778
2842
  $ package-build site build
2779
2843
  […] indexing content
@@ -974,23 +974,24 @@ Any other key under `docs.itemFields` is refused:
974
974
 
975
975
  **Type:** object · **Optional** · every key defaults to nothing published:
976
976
 
977
- | Key | Type | Default |
978
- | ------------------- | -------- | -------------------------------------------- |
979
- | `site.base` | string | `""` |
980
- | `site.title` | string | `""`, required when no manifest title exists |
981
- | `site.assets` | string | `""`, but required for `site build` |
982
- | `site.description` | string | `""`, but required for `site build` |
983
- | `site.packages` | string[] | `[]` |
984
- | `site.pass` | string | `""` |
985
- | `site.passOptions` | object | `{}` |
986
- | `site.notfound` | object | `null` |
987
- | `site.search` | boolean | `true` |
988
- | `site.maps` | boolean | `true` |
989
- | `site.sections` | object | `{}` |
990
- | `site.feed` | string | `""` |
991
- | `site.tags` | boolean | `false` |
992
- | `site.sitemaps` | string[] | `[]`, and only at the root of an origin |
993
- | `site.mergeIndexes` | string[] | `[]` |
977
+ | Key | Type | Default |
978
+ | ------------------- | -------- | --------------------------------------------- |
979
+ | `site.base` | string | `""` |
980
+ | `site.title` | string | `""`, required when no manifest title exists |
981
+ | `site.assets` | string | `""`; `site build` derives a CDN when omitted |
982
+ | `site.description` | string | `""`, but required for `site build` |
983
+ | `site.packages` | string[] | `[]` |
984
+ | `site.pass` | string | `""` |
985
+ | `site.passOptions` | object | `{}` |
986
+ | `site.notfound` | object | `null` |
987
+ | `site.search` | boolean | `true` |
988
+ | `site.maps` | boolean | `true` |
989
+ | `site.sections` | object | `{}` |
990
+ | `site.feed` | string | `""` |
991
+ | `site.tags` | boolean | `false` |
992
+ | `site.sitemaps` | string[] | `[]`, and only at the root of an origin |
993
+ | `site.mergeIndexes` | string[] | `[]` |
994
+ | `site.inputs` | string | `""`; optional local input-provider module |
994
995
 
995
996
  The authored content tree determines which pages reach the web. `site` is
996
997
  framing: which named pass bundle
@@ -1016,7 +1017,7 @@ renders it under the deployment root `build/site/`, at
1016
1017
 
1017
1018
  > ``package-build config: `site` must be a mapping.``
1018
1019
 
1019
- > ``package-build config: `site.<key>` is not a recognized option (expected one of: base, title, assets, description, packages, pass, passOptions, notfound, search, maps, sections, feed, tags, sitemaps, mergeIndexes).``
1020
+ > ``package-build config: `site.<key>` is not a recognized option (expected one of: base, title, assets, description, packages, pass, passOptions, notfound, search, maps, sections, feed, tags, sitemaps, mergeIndexes, inputs).``
1020
1021
 
1021
1022
  `site.assets` is the host every package's imagery is served from, and it is
1022
1023
  the one address in this file that is not this repository's own. A note names
@@ -1029,17 +1030,18 @@ Absolute, and the trailing slash is trimmed:
1029
1030
 
1030
1031
  > ``package-build config: `site.assets` must be an absolute `http://` or `https://` address — it is the host every package's imagery is served from, and a relative value resolves against whichever page happens to carry the image.``
1031
1032
 
1032
- `package-build site build` refuses a configuration with no `site.assets` at
1033
- all — there is no defensible default, because the theme
1034
- resolves every relative asset against it:
1033
+ `package-build site build` resolves its effective asset host from `--assets`,
1034
+ an explicit `--root`, or configured `site.assets`, in that order. If none is
1035
+ provided, it derives `https://cdn.internal.heroiclands.org` from the default
1036
+ root. Every relative asset resolves against that effective host.
1035
1037
 
1036
- > ``package-build config: `site.assets` is not declared, and a site build needs one — it is the host every package's imagery is served from, and the theme resolves every relative asset against it.``
1037
-
1038
- `package-build cdn publish` uploads the package's pictures under the same
1039
- host, at `<site.assets>/<contentPackage>/icons/<basename>` and
1040
- `…/images/<basename>`, which is the address the website gives each one. The
1041
- bucket behind the CDN is served at `site.assets`, so an object's key is its
1042
- path below this value.
1038
+ `package-build cdn publish --dest cloudflare` manually uploads pictures under
1039
+ this host, at `<site.assets>/<contentPackage>/icons/<basename>` and
1040
+ `…/images/<basename>`. The R2 bucket is served at `site.assets`, so an object's
1041
+ key is its path below this value. The default `--dest internal` instead publishes
1042
+ the same keys to atlas, served at `https://cdn.internal.heroiclands.org`; its set
1043
+ sources use that internal origin regardless of configured `site.assets`.
1044
+ Publication is separate from site building and deployment.
1043
1045
 
1044
1046
  The site settings carry the same host as
1045
1047
  `params.cdnBaseURL`, which the theme resolves a relative asset path against.
@@ -1284,6 +1286,55 @@ site:
1284
1286
  mergeIndexes: [sohl, thalorna, kethira, harnensemble]
1285
1287
  ```
1286
1288
 
1289
+ [`site build --root <domain>`](commands.md#package-build-site-build) selects
1290
+ `https://www.<domain>` while preserving the homepage path. Its default domain
1291
+ is `internal.heroiclands.org`. Explicit `--root` replaces configured `site.assets`
1292
+ with `https://cdn.<domain>`; without the flag the configured asset host stays,
1293
+ or the derived CDN is used if no asset host is configured. Explicit `--base-url`
1294
+ and `--assets` independently take precedence over these derived values.
1295
+ The effective configuration exposes the chosen address as `homepage`, its path
1296
+ as `site.base`, and the asset host as `site.assets`. The authored configuration
1297
+ stays unchanged and must be valid. A root-website integration uses these resolved
1298
+ inputs rather than deriving hosts itself; canonical source-navigation drift
1299
+ checks remain against the original navigation before projecting its URLs.
1300
+ `readCachedNavigation(effectiveConfig)` projects only canonical Heroic Lands
1301
+ www links onto the effective homepage origin; `siteSettings` does the same for
1302
+ the brand license link. Neither function writes the cache or changes external
1303
+ attribution links.
1304
+
1305
+ ### Local site inputs
1306
+
1307
+ `site.inputs` optionally names a local module relative to `rootDir`, such as
1308
+ `utils/site-inputs.mjs`. Use a plain relative path without dot segments,
1309
+ backslashes or symlinks. Its default export is a function taking `{config}`,
1310
+ the immutable effective configuration after root and URL overrides. The function
1311
+ may return a promise and must return `{navigation, files}`:
1312
+
1313
+ ```js
1314
+ export default async function ({ config }) {
1315
+ const navigation = [{ name: "Home", url: new URL("/", config.homepage).href }];
1316
+ return { navigation, files: { "nav.json": JSON.stringify(navigation) + "\n" } };
1317
+ }
1318
+ ```
1319
+
1320
+ The navigation uses the existing `{name, url, children?}` shape. A provider
1321
+ supplies selected URLs itself; the example reads the effective homepage instead
1322
+ of deriving hosts. A root website can compare its committed canonical navigation
1323
+ against its canonical roster before returning selected-origin navigation. A
1324
+ thrown drift finding stops the build before output is replaced. Without this
1325
+ key the builder continues to require the navigation cache from `deps fetch`.
1326
+
1327
+ `files` maps relative output paths to UTF-8 strings. Paths use plain ASCII
1328
+ letters, numbers, dots, underscores and hyphens within segments, with each
1329
+ segment beginning with a letter or number. Absolute paths, dot segments,
1330
+ symlinks, case collisions and file/directory collisions are refused. A provider
1331
+ cannot claim HTML, theme/static assets, fonts, search/tag trees, page paths,
1332
+ feeds, sitemap files, robots or deployment headers. All files are validated
1333
+ before output replacement, then written inside the package site's output after
1334
+ the renderer clears it. Their paths are included in `written.files`.
1335
+ This prepares build inputs and text artifacts; it is not a shell command or a
1336
+ publication hook.
1337
+
1287
1338
  ### What the site build reads
1288
1339
 
1289
1340
  `package-build site build` composes the site's settings from sources the
@@ -1291,17 +1342,17 @@ repository already states, a few constants of the organisation, and the
1291
1342
  navigation `deps fetch` cached. Every value has one source, and that source is
1292
1343
  where it is edited:
1293
1344
 
1294
- | Setting | Derived from |
1295
- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
1296
- | base URL | `package.json` `homepage`, checked by `checkHomepage` — an absolute URL ending `/<contentPackage>/` |
1297
- | title | `site.title` when declared, otherwise `packageBuild.manifest.title` |
1298
- | `params.description` | `site.description`, which is required |
1299
- | `params.author` | the first entry of `authors`, its `name`; absent when `authors` is empty |
1300
- | `params.cdnBaseURL` | `site.assets`, which is required |
1301
- | `params.brand` | the organisation's brand links — `logo`, `licenseURL`, `discordURL` — in `engine/site-config.mjs` |
1302
- | `params.notfound` | `site.notfound`; absent when undeclared |
1303
- | `params.scriptFonts` | the installed script fonts' stylesheet, present only when the package delivers fonts |
1304
- | menu | the navigation `package-build deps fetch` caches from `https://www.heroiclands.org/nav.json`, entry for entry, a dropdown's entries as its `children` |
1345
+ | Setting | Derived from |
1346
+ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
1347
+ | base URL | Effective `homepage` from `siteBuildConfig`; authored `package.json` `homepage` first passes `checkHomepage` |
1348
+ | title | `site.title` when declared, otherwise `packageBuild.manifest.title` |
1349
+ | `params.description` | `site.description`, which is required |
1350
+ | `params.author` | the first entry of `authors`, its `name`; absent when `authors` is empty |
1351
+ | `params.cdnBaseURL` | `site.assets`, which is required |
1352
+ | `params.brand` | the organisation's brand links — `logo`, `licenseURL`, `discordURL` — in `engine/site-config.mjs` |
1353
+ | `params.notfound` | `site.notfound`; absent when undeclared |
1354
+ | `params.scriptFonts` | the installed script fonts' stylesheet, present only when the package delivers fonts |
1355
+ | menu | the navigation `package-build deps fetch` caches from `https://www.heroiclands.org/nav.json`, with canonical owned URLs projected onto the effective homepage origin, a dropdown's entries as its `children` |
1305
1356
 
1306
1357
  The site build reads the navigation from the cache only. A cold cache is an
1307
1358
  error naming the command that fills it:
@@ -1317,11 +1368,9 @@ the same way when it is absent:
1317
1368
 
1318
1369
  > ``package-build config: `site.description` is not declared, and the site's `<meta name="description">` reads from it.``
1319
1370
 
1320
- `params.cdnBaseURL` reads from `site.assets`, and the theme resolves every
1321
- relative asset against it, so a configuration declaring none fails the site
1322
- build the same way:
1323
-
1324
- > ``package-build config: `site.assets` is not declared, and a site build needs one — it is the host every package's imagery is served from, and the theme resolves every relative asset against it.``
1371
+ `params.cdnBaseURL` reads the effective `site.assets` chosen by the build
1372
+ resolver, and the theme resolves every relative asset against it. Direct
1373
+ `siteSettings` callers must supply that effective asset host.
1325
1374
 
1326
1375
  Nothing else is configurable. A site is its homepage and its pages: the
1327
1376
  homepage is the package root and every note is a page. A tag is a field a
@@ -133,23 +133,34 @@ the other actions and environment variables.
133
133
 
134
134
  ## Publish pictures to the CDN
135
135
 
136
- The website draws every picture a package has from
137
- `<site.assets>/<contentPackage>/`, and the art a Foundry package leaves out
138
- reaches a world from there as a named asset set. `package-build cdn publish`
139
- is the one command that writes there:
136
+ Run `package-build cdn publish` manually after pictures or their provenance change.
137
+ It uploads every icon and image flat, applies the stage's transformations, skips
138
+ pictures already present at the same size, and writes a named set listing the
139
+ pictures the Foundry package leaves out. Site builds and releases do not publish
140
+ pictures.
141
+
142
+ The default destination is atlas at `https://cdn.internal.heroiclands.org`:
140
143
 
141
144
  ```bash
142
145
  package-build cdn publish --set alt-art.json --name "Thalorna — illustrated" --dry-run
143
146
  package-build cdn publish --set alt-art.json --name "Thalorna — illustrated"
144
147
  ```
145
148
 
146
- It uploads every icon and image flat, transformed as the stage transforms
147
- them, skips what the CDN already holds at the same size, and writes
148
- `<site.assets>/<contentPackage>/alt-art.json` listing the left-out pictures.
149
- Run it after the pictures or their provenance change; it is not part of a
150
- build or a release.
149
+ Connect to the LAN or VPN. Install rsync and use your verified `atlas` SSH account
150
+ with write access to `/srv/preview/cdn`. SSH configuration or your SSH agent supplies
151
+ the key; unknown host keys fail. For a configured forced-rrsync publisher alias,
152
+ set `CDN_INTERNAL_DESTINATION=your-alias:cdn`; its relative path is required by the
153
+ restricted receiver. The command does not provision keys or change permissions.
154
+ An internal dry run only reads HTTPS; it never opens SSH or uses R2 credentials.
155
+
156
+ To publish to the public CDN, select Cloudflare explicitly:
157
+
158
+ ```bash
159
+ package-build cdn publish --dest cloudflare --set alt-art.json --name "Thalorna — illustrated"
160
+ ```
151
161
 
152
- The bucket's credentials go in `.env.local` beside the stage paths:
162
+ This retains the existing R2 transport and configured `site.assets` origin,
163
+ normally `https://cdn.heroiclands.org`. The bucket's credentials go in `.env.local`:
153
164
 
154
165
  ```dotenv
155
166
  CDN_ENDPOINT=https://<account id>.r2.cloudflarestorage.com
@@ -158,10 +169,11 @@ CDN_ACCESS_KEY_ID=<access key id>
158
169
  CDN_SECRET_ACCESS_KEY=<secret access key>
159
170
  ```
160
171
 
161
- A dry run with none of them set compares against the public CDN instead. A
162
- dispatched workflow passes the same four values as named secrets. See the
163
- [command reference](../commands.md#package-build-cdn-publish) for the set
164
- file's shape, the refusals and the exit status.
172
+ With none of those settings present, `--dest cloudflare --dry-run` compares against
173
+ `site.assets` read-only. Both destinations use the same keys and sets; neither
174
+ deletes existing shared CDN objects. See the
175
+ [command reference](../commands.md#package-build-cdn-publish) for SSH setup,
176
+ set shape, refusals and exit status.
165
177
 
166
178
  ## Publish a package-build npm version
167
179
 
@@ -306,6 +306,59 @@ The terminal reports indexing, rendering, writing and search indexing as they
306
306
  start, including page counts where available. Progress is on stdout;
307
307
  diagnostics retain their file, line and column on stderr.
308
308
 
309
+ Build locally for the internal site or a public release with the selected root:
310
+
311
+ ```bash
312
+ package-build site build --root internal.heroiclands.org
313
+ package-build site build --root heroiclands.org
314
+ ```
315
+
316
+ These select `https://www.internal.heroiclands.org` and
317
+ `https://cdn.internal.heroiclands.org`, or `https://www.heroiclands.org` and
318
+ `https://cdn.heroiclands.org`, retaining the package's path. Without `--root`,
319
+ the site defaults to the internal www origin and keeps configured `site.assets`
320
+ (or derives the internal CDN if none is configured). An explicit root overrides
321
+ the configured asset host. Build and publication are separate: this command
322
+ writes local output, reads cached dependencies, and never uploads or fetches
323
+ missing assets. Shared navigation and the brand license follow the selected
324
+ site origin; external attribution and authored content links retain their URLs.
325
+ Asset references to the exact canonical `https://cdn.heroiclands.org` origin
326
+ also use the effective asset root, retaining their path, query and fragment;
327
+ external image hosts keep their addresses. This happens during normal asset
328
+ resolution and leaves authored files unchanged.
329
+
330
+ A site that generates its own navigation can declare
331
+ [`site.inputs`](../configuration.md#local-site-inputs). Its local module receives
332
+ the selected addresses in the effective configuration and returns its menu plus
333
+ text files such as `nav.json`. The builder renders that menu and writes those
334
+ files together, so one build command prepares the complete site. A canonical
335
+ roster check belongs in that module before it returns deployment-specific URLs;
336
+ a drift finding leaves the last built site in place.
337
+
338
+ For a pull request preview, include the whole prefix:
339
+
340
+ ```bash
341
+ package-build site build \
342
+ --base-url https://www.internal.heroiclands.org/pr/thalorna/123/thalorna/ \
343
+ --assets https://cdn.internal.heroiclands.org
344
+ ```
345
+
346
+ This build writes to `build/site/pr/thalorna/123/thalorna/`. Its local
347
+ links, theme files, script fonts and search stay under that prefix; its images
348
+ use the supplied asset root. `--out` changes the deployment directory above the
349
+ prefix. Explicit `--base-url` takes precedence over the root-derived site
350
+ address, and explicit `--assets` takes precedence over both the root-derived and
351
+ configured asset host. Both URL options are optional and independent, and require absolute
352
+ HTTP(S) URLs. The base URL's path ends with `/`; the asset root may include a
353
+ path and has trailing slashes trimmed. Use plain paths without percent escapes,
354
+ dot segments (`.` or `..`), or doubled slashes, credentials, queries or fragments.
355
+
356
+ The options apply to this build only. Authored `package.json` and YAML stay
357
+ unchanged, and the authored homepage still has to match the configured site.
358
+ Invalid overrides fail before replacing output. A site with `site.sitemaps`
359
+ requires a base URL at the origin's root. See the
360
+ [command reference](../commands.md#package-build-site-build) for URL validation.
361
+
309
362
  ```bash
310
363
  package-build site serve
311
364
  ```