@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.
@@ -73,6 +73,7 @@ export namespace DEFAULT_PATHS {
73
73
  let unpack: "build/tmp/packs";
74
74
  let foreignCache: "build/cache/foreign";
75
75
  let metadataCache: "build/cache/metadata";
76
+ let navigationCache: "build/cache/navigation";
76
77
  }
77
78
  /**
78
79
  * The Foundry document types a compendium pack may hold. This is the set the
@@ -175,6 +176,20 @@ export const SITE_MODES: readonly ["homepage", "content"];
175
176
  * @type {symbol}
176
177
  */
177
178
  export const DERIVED_SYSTEM_VERSION: symbol;
179
+ /**
180
+ * Hugo keys a repository may **not** declare under `site.hugo`, because the
181
+ * site build generates them and would only overwrite what was written.
182
+ *
183
+ * The same rule `DERIVED_MANIFEST_KEYS` states for the manifest, for the same
184
+ * reason: an authored `baseURL` would look authoritative, sit there unread,
185
+ * and disagree with the site forever. Each key names where its value comes
186
+ * from. A dotted key names a nested one, and covers everything beneath it —
187
+ * `params.brand` refuses `params.brand.logo` too — so `site.hugo` reaches only
188
+ * what the generator does not write.
189
+ *
190
+ * @type {Readonly<Record<string, string>>}
191
+ */
192
+ export const DERIVED_HUGO_KEYS: Readonly<Record<string, string>>;
178
193
  /**
179
194
  * How much of a package reaches the web.
180
195
  *
@@ -339,6 +354,11 @@ export type PathsInput = {
339
354
  * only those supplying a catalogue.
340
355
  */
341
356
  metadataCache?: string | undefined;
357
+ /**
358
+ * Where the site navigation is fetched
359
+ * to, for the generated Hugo menu.
360
+ */
361
+ navigationCache?: string | undefined;
342
362
  };
343
363
  /**
344
364
  * {@link PathsInput}, resolved to absolute paths against `rootDir`.
@@ -352,6 +372,7 @@ export type ResolvedPaths = {
352
372
  unpack: string;
353
373
  foreignCache: string;
354
374
  metadataCache: string;
375
+ navigationCache: string;
355
376
  };
356
377
  /**
357
378
  * The identity every compiled document's `_stats` block carries.
@@ -590,6 +611,22 @@ export type ContentBuildConfigInput = {
590
611
  * package.
591
612
  */
592
613
  foundryPackage?: string | undefined;
614
+ /**
615
+ * `package.json`'s own `homepage` —
616
+ * the site build's `baseURL`. Checked
617
+ * by `checkHomepage` in
618
+ * `config.mjs`.
619
+ */
620
+ homepage?: string | undefined;
621
+ /**
622
+ * `package.json`'s own `author`, in
623
+ * either of npm's forms.
624
+ */
625
+ author?: string | {
626
+ name: string;
627
+ email?: string;
628
+ url?: string;
629
+ } | undefined;
593
630
  /**
594
631
  * Whether the package is a system, a
595
632
  * module, or documentation — the kind
@@ -683,6 +720,22 @@ export type ContentBuildConfig = {
683
720
  * package, which ships no Foundry package.
684
721
  */
685
722
  foundryPackage: string | null;
723
+ /**
724
+ * `package.json`'s own `homepage`,
725
+ * checked by `checkHomepage` in
726
+ * `config.mjs`.
727
+ */
728
+ homepage: string | null;
729
+ /**
730
+ * `package.json`'s own `author`, normalised
731
+ * from either of npm's forms; `null` when
732
+ * the package declares none.
733
+ */
734
+ author: Readonly<{
735
+ name: string;
736
+ email?: string;
737
+ url?: string;
738
+ }> | null;
686
739
  packageKind: PackageKind;
687
740
  /**
688
741
  * Derived, and **conditional**: the served
@@ -105,8 +105,8 @@ export function collectHomepages(contentBase: string, ctx: object): {
105
105
  * has. Nothing is written at `/<package>/` itself: that becomes a redirect the
106
106
  * package's own repository authors, which is a routing fact rather than a page.
107
107
  *
108
- * @param {string} outRoot - The package's site root — the configured `site.out`,
109
- * one level above the content mount.
108
+ * @param {string} outRoot - The package's site root — the content mount's
109
+ * root, `build/hugo/content`, one level above the mount itself.
110
110
  * @param {readonly object[]} pages - From {@link collectHomepages}.
111
111
  * @param {object} config - The resolved configuration, for the package name and
112
112
  * the default title.
@@ -379,26 +379,6 @@ export function resolveSitePass(name: string | undefined, options: object): {
379
379
  beforeLinks?: Function;
380
380
  afterLinks?: Function;
381
381
  };
382
- /**
383
- * The output root, having established that it is safe to delete.
384
- *
385
- * The whole tree is a build artifact and is wiped on every run, so this
386
- * resolution is the difference between clearing a build directory and clearing
387
- * the repository. An unset `site.out` resolves to `rootDir` itself, and the
388
- * wipe then deletes the working tree — which is not a hypothetical: it happened
389
- * while this module was being written, on a configuration that simply had no
390
- * `site` section yet.
391
- *
392
- * So the path is refused unless it is **strictly inside** the repository root.
393
- * Both failing shapes are ordinary rather than exotic — an absent setting, and a
394
- * `..` that climbs out — and neither should be recoverable by being careful.
395
- *
396
- * @param {string} rootDir - The repository root.
397
- * @param {string} out - The configured `site.out`.
398
- * @returns {string} The absolute output root.
399
- * @throws {Error} When it is unset, or is not below `rootDir`.
400
- */
401
- export function resolveOutputRoot(rootDir: string, out: string): string;
402
382
  /**
403
383
  * Builds a Hugo content tree from a content tree, and reports what it found.
404
384
  *
@@ -410,7 +390,6 @@ export function resolveOutputRoot(rootDir: string, out: string): string;
410
390
  * @param {object} [options] - Options.
411
391
  * @param {object} [options.config] - A resolved configuration; loaded when
412
392
  * omitted.
413
- * @param {string} [options.outRoot] - Override the configured output mount.
414
393
  * @param {Map<string, object[]>} [options.sqlTables] - Prepared `sql` results,
415
394
  * keyed by the note's absolute file, from
416
395
  * {@link module:engine/sql-tables.prepareSqlTables}. A page authoring an
@@ -419,9 +398,8 @@ export function resolveOutputRoot(rootDir: string, out: string): string;
419
398
  * @returns {{gates: object, stats: object|null, tableErrors: object[],
420
399
  * wikiErrors: object[], imageErrors: object[], manifests: object|null}}
421
400
  */
422
- export function buildSite({ config, outRoot, sqlTables }?: {
401
+ export function buildSite({ config, sqlTables }?: {
423
402
  config?: object | undefined;
424
- outRoot?: string | undefined;
425
403
  sqlTables?: Map<string, object[]> | undefined;
426
404
  }): {
427
405
  gates: object;
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Check a navigation's shape, and return it.
3
+ *
4
+ * `[{name, url, children?: [{name, url}]}]`, every `url` absolute. Checked
5
+ * on fetch and again on read: a file that is not a navigation would otherwise
6
+ * reach the generated menu as `undefined` labels and links.
7
+ *
8
+ * @param {unknown} value - The parsed document.
9
+ * @param {string} where - What is being checked, for the error.
10
+ * @returns {NavigationEntry[]} The navigation.
11
+ * @throws {TypeError} When the shape is not a navigation.
12
+ */
13
+ export function checkNavigation(value: unknown, where?: string): NavigationEntry[];
14
+ /**
15
+ * The `[[menu.main]]` entries a navigation renders as.
16
+ *
17
+ * Entry for entry, in order; a dropdown is an entry with an `identifier` and
18
+ * its children are entries naming it as `parent`, which is how the theme's
19
+ * header partial draws one. Weights count from one within each level, so the
20
+ * order is the navigation's and not Hugo's alphabetical fallback.
21
+ *
22
+ * @param {readonly NavigationEntry[]} navigation - The navigation.
23
+ * @returns {MenuEntry[]} The menu entries.
24
+ */
25
+ export function menuEntries(navigation: readonly NavigationEntry[]): MenuEntry[];
26
+ /**
27
+ * Where the fetched navigation sits.
28
+ *
29
+ * @param {object} config - The resolved build configuration.
30
+ * @returns {string} The cache directory.
31
+ */
32
+ export function navigationCacheDir(config: object): string;
33
+ /**
34
+ * The cached navigation.
35
+ *
36
+ * **Reads the cache only.** A cold cache is an error naming the command that
37
+ * fills it, rather than a download nobody asked for: a site build that reaches
38
+ * the network is not reproducible and fails strangely offline. That is the
39
+ * content index's rule, and it holds here for the same reason. A half-finished
40
+ * fetch counts as cold.
41
+ *
42
+ * @param {object} config - The resolved build configuration.
43
+ * @returns {NavigationEntry[]} The navigation.
44
+ * @throws {Error} When it has not been fetched, or is not a navigation.
45
+ */
46
+ export function readCachedNavigation(config: object): NavigationEntry[];
47
+ /**
48
+ * Fetch the navigation into the cache, and stamp it complete.
49
+ *
50
+ * Rebuilt from empty on every call rather than kept when present: the
51
+ * navigation carries no version to key a cache on, and a package added to the
52
+ * roster reaches a site on its next `deps fetch`.
53
+ *
54
+ * @param {object} config - The resolved build configuration.
55
+ * @param {object} [options] - Options.
56
+ * @param {string} [options.url] - Where to fetch from. Defaults to
57
+ * {@link NAVIGATION_URL}.
58
+ * @param {typeof globalThis.fetch} [options.fetch] - The fetch to use.
59
+ * @returns {Promise<string>} The cached file.
60
+ * @throws {Error} When the download fails, or the document is not a navigation.
61
+ */
62
+ export function fetchNavigation(config: object, { url, fetch }?: {
63
+ url?: string | undefined;
64
+ fetch?: typeof globalThis.fetch | undefined;
65
+ }): Promise<string>;
66
+ /**
67
+ * The `themesDir` for a repository, as the path from `build/hugo/` to the
68
+ * directory holding the installed theme.
69
+ *
70
+ * Resolved the way Node resolves a package — `node_modules/` in the
71
+ * repository root, then in each parent — and written as a path rather than
72
+ * assumed, so a worktree that resolves its parent's install says so in the
73
+ * generated file.
74
+ *
75
+ * @param {string} rootDir - The repository root.
76
+ * @returns {string} The relative path, POSIX-separated.
77
+ * @throws {Error} When the theme is installed nowhere above the root.
78
+ */
79
+ export function resolveThemesDir(rootDir: string): string;
80
+ /**
81
+ * The Hugo configuration, as an object.
82
+ *
83
+ * Pure: every input is handed in, so a test can describe the generated shape
84
+ * without a repository on disk. {@link generateHugoConfig} is the same function
85
+ * with the reading put back.
86
+ *
87
+ * `checkHomepage` runs first, before any value is composed — a missing or
88
+ * mismatched `package.json` `homepage` is a finding on every site build.
89
+ *
90
+ * @param {object} options - The sources.
91
+ * @param {object} options.config - The resolved build configuration.
92
+ * @param {string} [options.description] - `package.json`'s `description`.
93
+ * @param {readonly NavigationEntry[]} options.navigation - The navigation.
94
+ * @param {string} options.themesDir - From {@link resolveThemesDir}.
95
+ * @returns {Record<string, any>} The configuration Hugo reads.
96
+ * @throws {TypeError} When `homepage` fails `checkHomepage`, or the
97
+ * configuration declares no `packageBuild.manifest.title`.
98
+ */
99
+ export function hugoConfig({ config, description, navigation, themesDir }: {
100
+ config: object;
101
+ description?: string | undefined;
102
+ navigation: readonly NavigationEntry[];
103
+ themesDir: string;
104
+ }): Record<string, any>;
105
+ /**
106
+ * The configuration as the TOML Hugo reads.
107
+ *
108
+ * @param {Record<string, unknown>} generated - From {@link hugoConfig}.
109
+ * @returns {string} The file's contents.
110
+ */
111
+ export function hugoToml(generated: Record<string, unknown>): string;
112
+ /**
113
+ * The Hugo configuration, every source read from the repository.
114
+ *
115
+ * Reads `package.json`, the cached navigation and the installed theme's
116
+ * location, and composes them with {@link hugoConfig}. Nothing is written, so
117
+ * a caller can run this before touching the output tree and fail with it
118
+ * intact.
119
+ *
120
+ * @param {object} config - The resolved build configuration.
121
+ * @returns {Record<string, any>} The configuration Hugo reads.
122
+ * @throws {Error} When any source is missing or wrong.
123
+ */
124
+ export function generateHugoConfig(config: object): Record<string, any>;
125
+ /**
126
+ * Write `build/hugo/hugo.toml`.
127
+ *
128
+ * @param {object} config - The resolved build configuration.
129
+ * @param {Record<string, unknown>} [generated] - The configuration to write,
130
+ * when the caller already generated it. Generated here otherwise.
131
+ * @returns {{file: string}} The file written.
132
+ */
133
+ export function writeHugoConfig(config: object, generated?: Record<string, unknown>): {
134
+ file: string;
135
+ };
136
+ /** The Hugo source directory, relative to the repository root. */
137
+ export const HUGO_SOURCE: "build/hugo";
138
+ /** The content mount `content-build site` writes, relative to the repository root. */
139
+ export const HUGO_CONTENT: "build/hugo/content";
140
+ /**
141
+ * The directory that is deployed, relative to the repository root.
142
+ *
143
+ * Hugo renders into `<DEPLOY_ROOT>/<contentPackage>/`; `package-build
144
+ * site-root` writes `_headers` and `_redirects` beside it.
145
+ */
146
+ export const DEPLOY_ROOT: "build/site";
147
+ /** The npm package the shared theme arrives as. */
148
+ export const THEME_PACKAGE: "@heroiclands/hugo-theme";
149
+ /** The theme's name under `themesDir`, which is the package's unscoped name. */
150
+ export const THEME: "hugo-theme";
151
+ /** The locale every site renders in. */
152
+ export const LOCALE: "en-us";
153
+ /**
154
+ * The brand chrome's own links, the same on every site.
155
+ *
156
+ * `logo` is resolved through the theme's `cdn-url.html`, so it is a path on
157
+ * the asset host rather than an address.
158
+ */
159
+ export const BRAND: Readonly<{
160
+ logo: "images/brand/sohl-icon-white.webp";
161
+ licenseURL: "https://www.heroiclands.org/license/";
162
+ discordURL: "https://discord.gg/EwMfkNd3az";
163
+ }>;
164
+ /**
165
+ * The kinds no site renders.
166
+ *
167
+ * A section exists only where `site.sections` declares one, so a tree holding
168
+ * only the homepage emits nothing beyond it; taxonomies and feeds would be
169
+ * empty shells on every site.
170
+ */
171
+ export const DISABLE_KINDS: readonly string[];
172
+ /**
173
+ * The markup settings the toolchain's own output requires.
174
+ *
175
+ * Pages are written with raw HTML in them — a `<figure>` for every image, a
176
+ * `<span>` marking an unresolved link — and Goldmark drops raw HTML unless
177
+ * told otherwise. A theme cannot supply this: Hugo does not merge a theme's
178
+ * `markup` block.
179
+ */
180
+ export const MARKUP: Readonly<{
181
+ goldmark: Readonly<{
182
+ renderer: Readonly<{
183
+ unsafe: true;
184
+ }>;
185
+ }>;
186
+ }>;
187
+ /** Where the navigation is published. */
188
+ export const NAVIGATION_URL: "https://www.heroiclands.org/nav.json";
189
+ /** The cached navigation's file name. */
190
+ export const NAVIGATION_FILE: "nav.json";
191
+ /**
192
+ * A navigation entry, as `nav.json` states one.
193
+ */
194
+ export type NavigationEntry = {
195
+ /**
196
+ * - The entry's label.
197
+ */
198
+ name: string;
199
+ /**
200
+ * - Where it links, absolute.
201
+ */
202
+ url: string;
203
+ /**
204
+ * - A dropdown's entries.
205
+ */
206
+ children?: NavigationEntry[] | undefined;
207
+ };
208
+ /**
209
+ * A Hugo menu entry, as `[[menu.main]]` states one.
210
+ */
211
+ export type MenuEntry = {
212
+ name: string;
213
+ url: string;
214
+ weight: number;
215
+ /**
216
+ * - Set on an entry that has children.
217
+ */
218
+ identifier?: string | undefined;
219
+ /**
220
+ * - Set on a child, naming its parent's identifier.
221
+ */
222
+ parent?: string | undefined;
223
+ };
@@ -188,10 +188,10 @@ export function publishedRelationships(relationships: Record<string, unknown>):
188
188
  *
189
189
  * - **Declared** — everything in `packageBuild.manifest`, emitted unchanged, so
190
190
  * a key Foundry adds later needs no release of this package.
191
- * - **Derived** — the identity, the release addresses, the version, the Foundry
192
- * and system compatibility ranges, and the pack list. These are refused if
193
- * also declared: an authored copy would be overwritten and the two would
194
- * disagree with nothing to say so.
191
+ * - **Derived** — the identity, the description, the release addresses, the
192
+ * version, the Foundry and system compatibility ranges, and the pack list.
193
+ * These are refused if also declared: an authored copy would be overwritten
194
+ * and the two would disagree with nothing to say so.
195
195
  * - **Computed** — namespaced `flags` a repository works out for itself, merged
196
196
  * over any it declared.
197
197
  *
package/types/stage.d.mts CHANGED
@@ -77,8 +77,9 @@ export function cleanBuildArtifacts(root: string, { extra, includeNodeModules }?
77
77
  /**
78
78
  * Directories every HeroicLands repository regenerates and none commits.
79
79
  *
80
- * A repository adds its own `sohl-thalorna` also clears the Hugo output
81
- * beneath `site/` — but these four are common to all of them because they come
82
- * from the shared toolchain rather than from any one package's layout.
80
+ * A repository adds its own through `packageBuild.clean.extra`, but these four
81
+ * are common to all of them because they come from the shared toolchain rather
82
+ * than from any one package's layout. Everything the site build writes — the
83
+ * Hugo source tree, Hugo's cache and the rendered site — is under `build/`.
83
84
  */
84
85
  export const BUILD_ARTIFACT_DIRS: readonly string[];