@heroiclands/package-build 22.3.0 → 22.4.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 +59 -0
- package/CONTENT.md +182 -306
- package/bin/content-build.mjs +40 -23
- package/bin/package-build.mjs +4 -3
- package/content-config.mjs +82 -180
- package/docs/api.md +27 -32
- package/docs/commands.md +54 -19
- package/docs/configuration.md +102 -132
- package/docs/content-format.md +25 -15
- package/docs/getting-started.md +5 -4
- package/docs/project-setup.md +1 -1
- package/engine/content-links.mjs +25 -60
- package/engine/diagnostics.mjs +2 -2
- package/engine/field-reference.mjs +141 -0
- package/engine/homepage.mjs +91 -172
- package/engine/metadata-index.mjs +7 -3
- package/engine/note-vocabulary.mjs +0 -20
- package/engine/pdf-build.mjs +2 -2
- package/engine/site-build.mjs +64 -225
- package/engine/site-config.mjs +15 -53
- package/engine/site-root.mjs +26 -70
- package/package.json +1 -1
- package/types/content-config.d.mts +14 -8
- package/types/engine/content-links.d.mts +12 -23
- package/types/engine/diagnostics.d.mts +2 -2
- package/types/engine/field-reference.d.mts +78 -0
- package/types/engine/homepage.d.mts +51 -91
- package/types/engine/note-vocabulary.d.mts +0 -12
- package/types/engine/site-build.d.mts +28 -129
- package/types/engine/site-config.d.mts +10 -19
- package/types/engine/site-root.d.mts +11 -36
package/engine/site-root.mjs
CHANGED
|
@@ -15,12 +15,16 @@
|
|
|
15
15
|
* inside the prefix is published as a text file and never applied. Hugo owns
|
|
16
16
|
* everything under the prefix; this owns what sits beside it.
|
|
17
17
|
*
|
|
18
|
-
* **One
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
18
|
+
* **One file, `_headers`.** The prefix root is the homepage — the site build
|
|
19
|
+
* writes it as the mount's `_index.md` — so nothing redirects, and no
|
|
20
|
+
* `_redirects` is written. One left beside the site by an earlier build is
|
|
21
|
+
* removed rather than left to send every reader somewhere nothing publishes.
|
|
22
|
+
*
|
|
23
|
+
* **One implementation, because it is one policy.** What is indexable is a
|
|
24
|
+
* decision about the hosting rather than about any one package. Held in each
|
|
25
|
+
* consumer it is the same file with one constant changed, which is a file that
|
|
26
|
+
* drifts — and the drift is invisible, because nobody reads all of the copies
|
|
27
|
+
* at once.
|
|
24
28
|
*
|
|
25
29
|
* @module
|
|
26
30
|
*/
|
|
@@ -39,19 +43,6 @@ import path from "node:path";
|
|
|
39
43
|
*/
|
|
40
44
|
export const ORIGIN_SUFFIX = "pkg.heroiclands.org";
|
|
41
45
|
|
|
42
|
-
/**
|
|
43
|
-
* Where a package's landing is served, now that it is an addressed page.
|
|
44
|
-
*
|
|
45
|
-
* The site build emits the homepage at its own address rather than as the
|
|
46
|
-
* site root's `_index.md`, so the prefix root is a redirect to it.
|
|
47
|
-
*
|
|
48
|
-
* @param {string} pkg - The content package name.
|
|
49
|
-
* @returns {string} The landing's path.
|
|
50
|
-
*/
|
|
51
|
-
export function landingPath(pkg) {
|
|
52
|
-
return `/${pkg}/homepage-root/`;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
46
|
/**
|
|
56
47
|
* Suppress indexing of every address a deployment answers on but nobody
|
|
57
48
|
* advertises.
|
|
@@ -85,54 +76,25 @@ export function noindexHeaders() {
|
|
|
85
76
|
}
|
|
86
77
|
|
|
87
78
|
/**
|
|
88
|
-
* The
|
|
79
|
+
* The `_headers` file's contents: the `noindex` rules, and nothing else.
|
|
89
80
|
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* keeps the 301's canonical signal without the permanence.
|
|
81
|
+
* No `Cache-Control` is pinned on the prefix root. It is the homepage, and a
|
|
82
|
+
* lifetime on it would hold a stale copy at the most-linked address after a
|
|
83
|
+
* deploy; Pages' own defaults for a page apply.
|
|
94
84
|
*
|
|
95
|
-
* @param {string} pkg - The content package name.
|
|
96
|
-
* @returns {string[]} The header block's lines.
|
|
97
|
-
*/
|
|
98
|
-
export function cacheHeaders(pkg) {
|
|
99
|
-
return [
|
|
100
|
-
`/${pkg}/`,
|
|
101
|
-
" Cache-Control: max-age=3600",
|
|
102
|
-
"",
|
|
103
|
-
`/${pkg}`,
|
|
104
|
-
" Cache-Control: max-age=3600",
|
|
105
|
-
"",
|
|
106
|
-
];
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Both forms of the prefix root, because Pages matches the raw path.
|
|
111
|
-
*
|
|
112
|
-
* Redirect matching runs before any trailing-slash or `index.html` handling, so
|
|
113
|
-
* `/<pkg>` and `/<pkg>/` are distinct keys and a rule on one does not catch the
|
|
114
|
-
* other.
|
|
115
|
-
*
|
|
116
|
-
* @param {string} pkg - The content package name.
|
|
117
|
-
* @returns {string} The `_redirects` file's contents.
|
|
118
|
-
*/
|
|
119
|
-
export function redirects(pkg) {
|
|
120
|
-
const to = landingPath(pkg);
|
|
121
|
-
return [`/${pkg}/ ${to} 301`, `/${pkg} ${to} 301`, ""].join("\n");
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* The `_headers` file's contents.
|
|
126
|
-
*
|
|
127
|
-
* @param {string} pkg - The content package name.
|
|
128
85
|
* @returns {string} The file's contents.
|
|
129
86
|
*/
|
|
130
|
-
export function headers(
|
|
131
|
-
return
|
|
87
|
+
export function headers() {
|
|
88
|
+
return noindexHeaders().join("\n");
|
|
132
89
|
}
|
|
133
90
|
|
|
134
91
|
/**
|
|
135
|
-
* Write `_headers`
|
|
92
|
+
* Write `_headers` beside the rendered site, and remove any `_redirects`.
|
|
93
|
+
*
|
|
94
|
+
* The removal is part of owning the root: a `_redirects` this build did not
|
|
95
|
+
* write is one an earlier build left, and Cloudflare Pages applies whatever
|
|
96
|
+
* sits there. Left in place it would redirect the prefix root — the homepage —
|
|
97
|
+
* to an address nothing publishes.
|
|
136
98
|
*
|
|
137
99
|
* @param {object} options - Options.
|
|
138
100
|
* @param {string} options.pkg - The content package name, which is also the
|
|
@@ -152,14 +114,8 @@ export function writeSiteRoot({ pkg, out }) {
|
|
|
152
114
|
);
|
|
153
115
|
}
|
|
154
116
|
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
]) {
|
|
160
|
-
const file = path.join(root, name);
|
|
161
|
-
fs.writeFileSync(file, body);
|
|
162
|
-
written.push(file);
|
|
163
|
-
}
|
|
164
|
-
return { files: written };
|
|
117
|
+
const file = path.join(root, "_headers");
|
|
118
|
+
fs.writeFileSync(file, headers());
|
|
119
|
+
fs.rmSync(path.join(root, "_redirects"), { force: true });
|
|
120
|
+
return { files: [file] };
|
|
165
121
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "22.
|
|
3
|
+
"version": "22.4.0",
|
|
4
4
|
"description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package — content compilation, manifest, localization, staging, bundle, release and deployment.",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -133,10 +133,9 @@ export const DEFAULT_ADDRESS_SCHEME: Readonly<{
|
|
|
133
133
|
* and the default.
|
|
134
134
|
*
|
|
135
135
|
* - `homepage` — the authored homepage, and **no other page**. The content tree
|
|
136
|
-
* is not walked for pages,
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
* knowledgebase and the section landings.
|
|
136
|
+
* is not walked for pages, and nothing serves a page for its addresses.
|
|
137
|
+
* - `content` — the homepage *plus* every page the content tree publishes, one
|
|
138
|
+
* per note.
|
|
140
139
|
*
|
|
141
140
|
* **Homepage-only is a first-class mode, not an accommodation.**
|
|
142
141
|
* `sohl-kethira-basic` (unofficial Hârn fan material under Keléstia Productions'
|
|
@@ -200,10 +199,9 @@ export const DERIVED_HUGO_KEYS: Readonly<Record<string, string>>;
|
|
|
200
199
|
* and the default.
|
|
201
200
|
*
|
|
202
201
|
* - `homepage` — the authored homepage, and **no other page**. The content tree
|
|
203
|
-
* is not walked for pages,
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
* knowledgebase and the section landings.
|
|
202
|
+
* is not walked for pages, and nothing serves a page for its addresses.
|
|
203
|
+
* - `content` — the homepage *plus* every page the content tree publishes, one
|
|
204
|
+
* per note.
|
|
207
205
|
*
|
|
208
206
|
* **Homepage-only is a first-class mode, not an accommodation.**
|
|
209
207
|
* `sohl-kethira-basic` (unofficial Hârn fan material under Keléstia Productions'
|
|
@@ -541,6 +539,14 @@ export type DocPageSpec = {
|
|
|
541
539
|
* first table. Markdown, emitted verbatim.
|
|
542
540
|
*/
|
|
543
541
|
preamble?: string[] | undefined;
|
|
542
|
+
/**
|
|
543
|
+
* Further note frontmatter,
|
|
544
|
+
* deep-merged over the generated envelope
|
|
545
|
+
* (`type: doc`, `subType: reference`,
|
|
546
|
+
* `shortcode`, `name.full`, `pack: none`)
|
|
547
|
+
* when `out` is under the content tree.
|
|
548
|
+
*/
|
|
549
|
+
frontmatter?: Record<string, unknown> | undefined;
|
|
544
550
|
};
|
|
545
551
|
/**
|
|
546
552
|
* The documentation pages this repository generates.
|
|
@@ -79,41 +79,30 @@ export function buildLinkIndex(contentBase: string, { config, skipDirectories, s
|
|
|
79
79
|
* not and cannot: it is published *verbatim* by every publishing mode, including
|
|
80
80
|
* the homepage-only mode two fan-licensed packages ship under, where the content
|
|
81
81
|
* tree is never walked and there is no index for a wikilink to resolve against.
|
|
82
|
-
* So a
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* `being`: two 404s on the package's front page, through every build.
|
|
82
|
+
* So a homepage addresses the web the way the web does — markdown links in its
|
|
83
|
+
* body — and this is what looks at those. A dead link on the page a reader
|
|
84
|
+
* arrives at is the one nothing else would report.
|
|
86
85
|
*
|
|
87
86
|
* **What is checkable, stated plainly.** Only an address into this site is, and
|
|
88
87
|
* only against facts this build already holds:
|
|
89
88
|
*
|
|
90
89
|
* - A **retired content type** in the path. The engine knows the retired names
|
|
91
|
-
* and what replaced it, so this is a fact rather than a guess
|
|
92
|
-
* exactly the SoHL defect.
|
|
90
|
+
* and what replaced it, so this is a fact rather than a guess.
|
|
93
91
|
* - A **hardcoded absolute URL** into this package's own prefix, or into one a
|
|
94
|
-
*
|
|
92
|
+
* fetched index names. Every one of them has a better form to write, which
|
|
95
93
|
* is why every one is reported — including a bare `/<package>/`, which names
|
|
96
|
-
* another package's
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* True, and beside the point: it does not need resolving. A landing's address
|
|
101
|
-
* *is* its package prefix, so `/<package>/` is the absolute URL with the host
|
|
102
|
-
* struck off — host-free, emitted verbatim, and needing no index, which is
|
|
103
|
-
* what lets it hold in homepage-only mode where the tree is never walked. The
|
|
104
|
-
* form was already accepted here; nothing had ever named it as the one to use.
|
|
105
|
-
* - A **root-relative `url:`**, which the theme's `relURL` prefixes a second
|
|
106
|
-
* time. `href:` means "already resolved, use verbatim", so the same leading
|
|
107
|
-
* slash is correct there and is not reported.
|
|
94
|
+
* another package's front page. A front page's address *is* its package
|
|
95
|
+
* prefix, so `/<package>/` is the absolute URL with the host struck off —
|
|
96
|
+
* host-free, emitted verbatim, and needing no index, which is what lets it
|
|
97
|
+
* hold in homepage-only mode where the tree is never walked.
|
|
108
98
|
* - A **wikilink**, which nothing on this page will ever resolve.
|
|
109
99
|
*
|
|
110
100
|
* **What is not checkable, and is not attempted.** Whether an external URL
|
|
111
101
|
* answers — there is no network at build time, and a build must not fail because
|
|
112
102
|
* a third party is down. And whether a live in-site address names a page that
|
|
113
|
-
* exists: several of the surfaces a
|
|
114
|
-
* tools entirely (generated API documentation,
|
|
115
|
-
*
|
|
116
|
-
* link as dead.
|
|
103
|
+
* exists: several of the surfaces a homepage routes to are produced by other
|
|
104
|
+
* tools entirely (generated API documentation, say), so this build does not
|
|
105
|
+
* hold the set of published pages and would report a working link as dead.
|
|
117
106
|
*
|
|
118
107
|
* @param {ReturnType<typeof buildLinkIndex>} index - The built index.
|
|
119
108
|
* @returns {Array<{note: object, field: string, url: string, text: string,
|
|
@@ -184,7 +184,7 @@ export function positionOfLiteral(text: string, needle: string, occurrence?: num
|
|
|
184
184
|
*
|
|
185
185
|
* **`key: true` addresses the declaration rather than the value.** A finding
|
|
186
186
|
* about a *value* — this pack name is not in `packs[]` — belongs on the value,
|
|
187
|
-
* which is the default. A finding that names a **field** — `\`site.
|
|
187
|
+
* which is the default. A finding that names a **field** — `\`site.notfound.x\`
|
|
188
188
|
* is not a recognized option` — sends the reader to look for that field, so the
|
|
189
189
|
* position should be the field's own, and in a flow mapping
|
|
190
190
|
* (`{ title: X, banner: Y }`) the two are different columns on one line. The
|
|
@@ -210,7 +210,7 @@ export function positionOfYamlPath(text: string, keyPath: ReadonlyArray<string |
|
|
|
210
210
|
* The YAML key path a **dotted field path** addresses.
|
|
211
211
|
*
|
|
212
212
|
* Configuration checks report the offending key as the path a reader would
|
|
213
|
-
* write it — `packs[1].name`, `site.
|
|
213
|
+
* write it — `packs[1].name`, `site.notfound.links[0].url` — because that
|
|
214
214
|
* is what the message has to say. {@link positionOfYamlPath} addresses a node
|
|
215
215
|
* by segments instead, so this is the one translation between them: `.`
|
|
216
216
|
* separates map keys, and a bracketed suffix is a sequence index.
|
|
@@ -18,3 +18,81 @@ export function renderItemFieldReference({ title, preamble, generatedBy, config,
|
|
|
18
18
|
generatedBy?: string | undefined;
|
|
19
19
|
config?: object | undefined;
|
|
20
20
|
}): string;
|
|
21
|
+
/**
|
|
22
|
+
* Whether a destination file sits under a content tree.
|
|
23
|
+
*
|
|
24
|
+
* The one question that decides whether `docs item-fields` writes a note
|
|
25
|
+
* envelope: `assets/content/` walks every file under it for its `type:`, so a
|
|
26
|
+
* generated page filed there needs one to publish at all, while a page filed
|
|
27
|
+
* anywhere else — a repository's own `docs/` — is read by nobody but Hugo's
|
|
28
|
+
* `--check` guard and the reader following a link, neither of which wants
|
|
29
|
+
* frontmatter.
|
|
30
|
+
*
|
|
31
|
+
* @param {string} destination - Absolute path of the file being written.
|
|
32
|
+
* @param {string} contentRoot - Absolute path of the content tree root
|
|
33
|
+
* (`config.paths.content`).
|
|
34
|
+
* @returns {boolean} Whether `destination` resolves inside `contentRoot`.
|
|
35
|
+
*/
|
|
36
|
+
export function isUnderContentTree(destination: string, contentRoot: string): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* A note's `shortcode`, derived from the basename of its destination file.
|
|
39
|
+
*
|
|
40
|
+
* Lowercase alphanumerics only — the address charset every other shortcode in
|
|
41
|
+
* the tree is held to — so `item-frontmatter.md` derives `itemfrontmatter`
|
|
42
|
+
* rather than carrying a hyphen no address segment permits.
|
|
43
|
+
*
|
|
44
|
+
* @param {string} destination - Where the note is written.
|
|
45
|
+
* @returns {string} The derived shortcode.
|
|
46
|
+
*/
|
|
47
|
+
export function shortcodeFromBasename(destination: string): string;
|
|
48
|
+
/**
|
|
49
|
+
* The note envelope `docs item-fields` writes when its page lives in the
|
|
50
|
+
* content tree.
|
|
51
|
+
*
|
|
52
|
+
* The universal keys every note in the format carries: `type: doc`,
|
|
53
|
+
* `subType: reference` — this page is out-of-world lookup material, the same
|
|
54
|
+
* genre as every other generated reference — a `shortcode`, `name.full` from
|
|
55
|
+
* the page's title, and `pack: none`, since the page publishes to the website
|
|
56
|
+
* and compiles into no compendium document. A consumer's own
|
|
57
|
+
* `docs.itemFields.frontmatter` is deep-merged over it, so it may add keys or
|
|
58
|
+
* override any of the derived ones, `shortcode` included.
|
|
59
|
+
*
|
|
60
|
+
* @param {object} options
|
|
61
|
+
* @param {string} options.title - The page's H1, and `name.full`'s default.
|
|
62
|
+
* @param {string} options.shortcode - The derived shortcode, from
|
|
63
|
+
* {@link shortcodeFromBasename}.
|
|
64
|
+
* @param {Record<string, unknown>} [options.frontmatter] - The consumer's
|
|
65
|
+
* declared `docs.itemFields.frontmatter`.
|
|
66
|
+
* @returns {Record<string, unknown>} The envelope, ready for `matter.stringify`.
|
|
67
|
+
*/
|
|
68
|
+
export function itemFieldsEnvelope({ title, shortcode, frontmatter }: {
|
|
69
|
+
title: string;
|
|
70
|
+
shortcode: string;
|
|
71
|
+
frontmatter?: Record<string, unknown> | undefined;
|
|
72
|
+
}): Record<string, unknown>;
|
|
73
|
+
/**
|
|
74
|
+
* Wrap the rendered item-fields page in the note envelope, when its
|
|
75
|
+
* destination is under the content tree.
|
|
76
|
+
*
|
|
77
|
+
* `--check` compares the **whole** file this returns, envelope included — a
|
|
78
|
+
* page committed with its old envelope by hand, or with none at all, is
|
|
79
|
+
* exactly the staleness the guard exists to catch.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} body - The page {@link renderItemFieldReference} rendered.
|
|
82
|
+
* @param {object} options
|
|
83
|
+
* @param {string} options.title - The page's H1, threaded through to
|
|
84
|
+
* `name.full`.
|
|
85
|
+
* @param {string} options.destination - Absolute path the page is written to.
|
|
86
|
+
* @param {string} options.contentRoot - Absolute path of the content tree
|
|
87
|
+
* root (`config.paths.content`).
|
|
88
|
+
* @param {Record<string, unknown>} [options.frontmatter] - The consumer's
|
|
89
|
+
* declared `docs.itemFields.frontmatter`.
|
|
90
|
+
* @returns {string} `body`, unchanged when `destination` is outside the
|
|
91
|
+
* content tree; otherwise `body` with the note envelope stringified above it.
|
|
92
|
+
*/
|
|
93
|
+
export function renderItemFieldsPage(body: string, { title, destination, contentRoot, frontmatter }: {
|
|
94
|
+
title: string;
|
|
95
|
+
destination: string;
|
|
96
|
+
contentRoot: string;
|
|
97
|
+
frontmatter?: Record<string, unknown> | undefined;
|
|
98
|
+
}): string;
|
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The file a homepage is written to, relative to the package's site root.
|
|
3
|
-
*
|
|
4
|
-
* Its **address**, flat at the package root, and stated in the page's own `url`
|
|
5
|
-
* — the same separation every other page has, where the directory
|
|
6
|
-
* decides the Hugo section and the front matter decides the URL. Flat rather
|
|
7
|
-
* than inside a `homepage/` section directory, because a homepage is not one of
|
|
8
|
-
* a kind: a section holding exactly one page would publish a landing at
|
|
9
|
-
* `/<package>/kb/homepage/` that nothing links to and nobody wrote.
|
|
10
|
-
*
|
|
11
|
-
* @param {object} fm - Parsed frontmatter.
|
|
12
|
-
* @returns {string} The destination filename, e.g. `homepage-root.md`.
|
|
13
|
-
* @throws {Error} When the note declares no shortcode, and so has no address.
|
|
14
|
-
*/
|
|
15
|
-
export function homepageDestination(fm: object): string;
|
|
16
1
|
/**
|
|
17
2
|
* Whether a note's frontmatter declares the homepage type.
|
|
18
3
|
*
|
|
@@ -24,7 +9,7 @@ export function isHomepage(fm: object | null | undefined): boolean;
|
|
|
24
9
|
* What the address rule says about one note's top-level fields.
|
|
25
10
|
*
|
|
26
11
|
* Two statements about the same thing, so they are made together: the field a
|
|
27
|
-
* homepage **owes** and the
|
|
12
|
+
* homepage **owes** and the fields it may **not** write.
|
|
28
13
|
*
|
|
29
14
|
* The missing `shortcode` comes first, and is located at `type:` rather than at
|
|
30
15
|
* a key that is not there — the `homepage` value is what makes the field
|
|
@@ -34,8 +19,9 @@ export function isHomepage(fm: object | null | undefined): boolean;
|
|
|
34
19
|
* diagnostic per finding walks down the file.
|
|
35
20
|
*
|
|
36
21
|
* Presence is the whole test for a refused field, and absence-or-blank for the
|
|
37
|
-
* required one: `shortcode:` authored empty is no address, and
|
|
38
|
-
* make `id` mean something on a page that compiles to no document
|
|
22
|
+
* required one: `shortcode:` authored empty is no address, and no value can
|
|
23
|
+
* make `id` or `landing` mean something on a page that compiles to no document
|
|
24
|
+
* and renders as its body.
|
|
39
25
|
*
|
|
40
26
|
* Each finding carries the `locator` key to position it at, because the two
|
|
41
27
|
* things that would resolve one — the raw note text and the position helper —
|
|
@@ -79,13 +65,11 @@ export function checkHomepageAddressFields(fm: object | null | undefined, { isAu
|
|
|
79
65
|
* this exists to prevent, and it is silent — the site build reports `wrote 0
|
|
80
66
|
* homepage(s)` and exits 0.
|
|
81
67
|
* - _Two_ and it serves a page nobody chose. **This is a cardinality rule, and
|
|
82
|
-
* only that.**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* name is a question nothing here can answer, and both being reachable is
|
|
88
|
-
* not an answer to it.
|
|
68
|
+
* only that.** Both are written to the mount's `_index.md`, so the second
|
|
69
|
+
* silently overwrites the first; the duplicate-address check catches only
|
|
70
|
+
* the pair that happen to share a shortcode, and says nothing at all about
|
|
71
|
+
* a `homepage-root` beside a `homepage-front`. Which of the two should be
|
|
72
|
+
* the front page is a question nothing here can answer.
|
|
89
73
|
*
|
|
90
74
|
* Neither has a safe default, so neither is a warning. A warning is the right
|
|
91
75
|
* severity for something a build can proceed past correctly, and a build that
|
|
@@ -149,24 +133,15 @@ export function homepageTitle(fm: object | null | undefined, config: object): st
|
|
|
149
133
|
/**
|
|
150
134
|
* The frontmatter a homepage publishes with.
|
|
151
135
|
*
|
|
152
|
-
* The note's own, plus the derived values every emitted page carries: the
|
|
153
|
-
* resolved `title`, the package the build derived — no note declares one
|
|
136
|
+
* The note's own, plus the two derived values every emitted page carries: the
|
|
137
|
+
* resolved `title`, and the package the build derived — no note declares one
|
|
154
138
|
* (`package:` is retired) and the theme's breadcrumb partial reads
|
|
155
|
-
* `.Params.package
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
* for one; it decides nothing while `url` is present, but a page carrying only
|
|
162
|
-
* `url` would report a slug Hugo had inferred from the filename.
|
|
163
|
-
*
|
|
164
|
-
* **Site-root relative, and so carrying no package base**, exactly as
|
|
165
|
-
* `pageFrontmatter` states a content page's: Hugo resolves a `url`
|
|
166
|
-
* against `baseURL`, whose path is already where the package is served, so a
|
|
167
|
-
* stated base was written twice and published the landing at
|
|
168
|
-
* `/<package>/<package>/homepage-root/`. Where the package is served is what
|
|
169
|
-
* every *href* is composed from and it reaches this page's address not at all.
|
|
139
|
+
* `.Params.package`.
|
|
140
|
+
*
|
|
141
|
+
* **No `url` and no `slug`.** Hugo publishes the `home` kind at `baseURL`,
|
|
142
|
+
* whose path is already where the package is served, so the page has no
|
|
143
|
+
* address to state; a content page states one because its file sits under the
|
|
144
|
+
* mount and its address does not.
|
|
170
145
|
*
|
|
171
146
|
* An authored `aliases` is dropped for the same reason it is on every other
|
|
172
147
|
* page: Hugo reads it as URL redirects, so passing it through would publish a
|
|
@@ -178,43 +153,33 @@ export function homepageTitle(fm: object | null | undefined, config: object): st
|
|
|
178
153
|
* @param {string} options.contentPackage - The package this build publishes.
|
|
179
154
|
* @param {string} options.title - The resolved title.
|
|
180
155
|
* @returns {object} The frontmatter to write.
|
|
181
|
-
* @throws {Error} When the note declares no shortcode, and so has no address.
|
|
182
156
|
*/
|
|
183
157
|
export function homepageFrontmatter(fm: object, { contentPackage, title }: {
|
|
184
158
|
contentPackage: string;
|
|
185
159
|
title: string;
|
|
186
160
|
}): object;
|
|
187
161
|
/**
|
|
188
|
-
* Every address a homepage carries
|
|
189
|
-
*
|
|
190
|
-
* **Both halves of the page are in scope, and that is the finding rather than
|
|
191
|
-
* the assumption.** Of the six homepages authored today, four carry every link
|
|
192
|
-
* in the body as ordinary markdown and two carry them in `landing:` — and the
|
|
193
|
-
* one whose dead links prompted the check has an *empty body*, so a body-only
|
|
194
|
-
* reading would have found nothing at all on it. A dead link in a card is
|
|
195
|
-
* exactly as broken as one in a paragraph.
|
|
162
|
+
* Every address a homepage carries: the markdown links in its body.
|
|
196
163
|
*
|
|
197
|
-
*
|
|
198
|
-
* the
|
|
164
|
+
* Nowhere else: the frontmatter holds no address, because a homepage is a
|
|
165
|
+
* page with a body and the card block that once carried links is refused.
|
|
166
|
+
* Top-level `title` and `description` are not walked — they are set as text,
|
|
167
|
+
* never rendered as markdown — and `banner:` is not an address: it is an image
|
|
168
|
+
* path resolved through the CDN base, and `banner: none` is a sentinel rather
|
|
169
|
+
* than a target.
|
|
199
170
|
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
* browser against the landing's own address, which *is* the package root, so
|
|
204
|
-
* a relative one means the same thing a `url` does.
|
|
171
|
+
* A body link is emitted as written and resolved by the browser against the
|
|
172
|
+
* homepage's own address, which *is* the package root, so a package-relative
|
|
173
|
+
* one (`kb/rules/`) lands where a reader expects.
|
|
205
174
|
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
* `title` and `description` are not walked either — they are set as text, never
|
|
209
|
-
* rendered as markdown.
|
|
175
|
+
* Links inside code are ignored, so an example in a fenced block is not
|
|
176
|
+
* reported as a dead address.
|
|
210
177
|
*
|
|
211
|
-
* @param {object|null|undefined} fm - The note's frontmatter.
|
|
212
178
|
* @param {string} [body] - The note's markdown body.
|
|
213
179
|
* @returns {Array<{field: string, url: string, kind: string}>} Every address,
|
|
214
|
-
*
|
|
215
|
-
* written at.
|
|
180
|
+
* in body order, each recorded at `field: "body"` with `kind: "body"`.
|
|
216
181
|
*/
|
|
217
|
-
export function homepageAddresses(
|
|
182
|
+
export function homepageAddresses(body?: string): Array<{
|
|
218
183
|
field: string;
|
|
219
184
|
url: string;
|
|
220
185
|
kind: string;
|
|
@@ -250,17 +215,28 @@ export const HOMEPAGE_FIELDS: readonly import("./field-spec.mjs").FieldSpec[];
|
|
|
250
215
|
*/
|
|
251
216
|
export const HOMEPAGE_SHORTCODE: string;
|
|
252
217
|
/**
|
|
253
|
-
* The
|
|
218
|
+
* The file a homepage is written to, relative to the package's site root.
|
|
254
219
|
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
* destination
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
* everywhere else and are permitted.
|
|
220
|
+
* The mount's own `_index.md`: Hugo renders it as the `home` kind, at
|
|
221
|
+
* `baseURL` — which is `/<package>/`, the package's own address. One fixed
|
|
222
|
+
* destination rather than one derived from the note's address, because the
|
|
223
|
+
* homepage's address *is* the package root; the note's `shortcode` names the
|
|
224
|
+
* page in links and decides no file.
|
|
261
225
|
*
|
|
262
|
-
*
|
|
263
|
-
|
|
226
|
+
* @type {string}
|
|
227
|
+
*/
|
|
228
|
+
export const HOMEPAGE_DESTINATION: string;
|
|
229
|
+
/**
|
|
230
|
+
* The top-level fields a homepage refuses, and what each would decide.
|
|
231
|
+
*
|
|
232
|
+
* `id` is the Foundry document id a compendium UUID is built from, and a
|
|
233
|
+
* homepage compiles into no document. `landing` is a card block; the homepage
|
|
234
|
+
* is a page with a body, rendered as one, so nothing reads it — and an index
|
|
235
|
+
* of what the package publishes is a `doc` note carrying a content table,
|
|
236
|
+
* authored where every other page is.
|
|
237
|
+
*
|
|
238
|
+
* `name` and `shortcode` are permitted: the shortcode is what a link is
|
|
239
|
+
* written with, and `name` titles the page like every other note's.
|
|
264
240
|
*
|
|
265
241
|
* **A named class, not an allow-list, and that boundary is the decision.** A
|
|
266
242
|
* homepage's frontmatter is *emitted into the published page*
|
|
@@ -276,19 +252,3 @@ export const HOMEPAGE_SHORTCODE: string;
|
|
|
276
252
|
* @type {ReadonlyMap<string, string>}
|
|
277
253
|
*/
|
|
278
254
|
export const HOMEPAGE_REFUSED_FIELDS: ReadonlyMap<string, string>;
|
|
279
|
-
/**
|
|
280
|
-
* The two frontmatter keys that hold an address, and what each one means.
|
|
281
|
-
*
|
|
282
|
-
* They are **not** interchangeable, and a check that treated them as one would
|
|
283
|
-
* be wrong about both. The theme resolves a `url` against the site with
|
|
284
|
-
* `relURL`, so a package writes `kb/rules/` and is served `/sohl/kb/rules/`
|
|
285
|
-
* without ever naming its own prefix. An `href` is an address that is *already*
|
|
286
|
-
* resolved and is used verbatim — which is what `cards.source: sections` fills
|
|
287
|
-
* in, since a section's permalink already carries the prefix.
|
|
288
|
-
*
|
|
289
|
-
* So a leading `/` is a defect in a `url` (it is prefixed a second time) and
|
|
290
|
-
* correct in an `href`.
|
|
291
|
-
*
|
|
292
|
-
* @type {ReadonlySet<string>}
|
|
293
|
-
*/
|
|
294
|
-
export const HOMEPAGE_ADDRESS_KEYS: ReadonlySet<string>;
|
|
@@ -50,18 +50,6 @@ export function exclusiveTagGroups(type: string, groups?: object): {
|
|
|
50
50
|
* @returns {boolean} Whether the note carries it.
|
|
51
51
|
*/
|
|
52
52
|
export function hasTag(fm: object | null | undefined, tag: string): boolean;
|
|
53
|
-
/**
|
|
54
|
-
* Whether a note carries any `tags:` at all, however authored.
|
|
55
|
-
*
|
|
56
|
-
* The one question the site build asks of tags in aggregate — whether the
|
|
57
|
-
* tree publishes taxonomy pages — rather than about a particular tag. Reads
|
|
58
|
-
* `tags` and `tag` exactly as {@link hasTag} does, and treats an empty list
|
|
59
|
-
* or a blank string as carrying none.
|
|
60
|
-
*
|
|
61
|
-
* @param {object|null|undefined} fm - Parsed frontmatter.
|
|
62
|
-
* @returns {boolean} Whether the note carries at least one tag.
|
|
63
|
-
*/
|
|
64
|
-
export function hasAnyTag(fm: object | null | undefined): boolean;
|
|
65
53
|
/**
|
|
66
54
|
* Whether a note is tagged as an unfinished **draft**.
|
|
67
55
|
*
|