@heroiclands/package-build 22.3.1 → 22.4.1
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 +65 -0
- package/CONTENT.md +189 -306
- package/bin/content-build.mjs +15 -20
- package/bin/package-build.mjs +87 -3
- package/content-config.mjs +70 -179
- package/docs/api.md +27 -32
- package/docs/commands.md +98 -19
- package/docs/configuration.md +90 -126
- package/docs/content-format.md +25 -15
- package/docs/getting-started.md +5 -4
- package/docs/project-setup.md +1 -1
- package/engine/actor-compiler.mjs +53 -14
- package/engine/changelog-lint.mjs +629 -0
- package/engine/content-links.mjs +25 -60
- package/engine/diagnostics.mjs +2 -2
- package/engine/field-reference.mjs +7 -1
- 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/githooks/pre-commit +53 -2
- package/package.json +1 -1
- package/types/content-config.d.mts +6 -8
- package/types/engine/actor-compiler.d.mts +42 -9
- package/types/engine/changelog-lint.d.mts +50 -0
- package/types/engine/content-links.d.mts +12 -23
- package/types/engine/diagnostics.d.mts +2 -2
- 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/githooks/pre-commit
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env sh
|
|
2
2
|
#
|
|
3
|
-
# Refuse to commit on a protected branch
|
|
4
|
-
#
|
|
3
|
+
# Refuse to commit on a protected branch, and refuse a staged changeset that
|
|
4
|
+
# reads like a pull-request description.
|
|
5
|
+
#
|
|
6
|
+
# See protected-branch.sh for the branch rule and its opt-outs;
|
|
7
|
+
# pre-merge-commit is that guard's counterpart for merges. The changeset check
|
|
8
|
+
# below has no merge-commit counterpart — a merge commit stages no new
|
|
9
|
+
# changeset of its own.
|
|
5
10
|
#
|
|
6
11
|
# Installed for everyone via the package.json "prepare" script, which points git
|
|
7
12
|
# at this directory (`git config core.hooksPath .githooks`) on `npm install`.
|
|
@@ -9,3 +14,49 @@
|
|
|
9
14
|
. "$(dirname "$0")/protected-branch.sh"
|
|
10
15
|
|
|
11
16
|
guard_protected_branch
|
|
17
|
+
|
|
18
|
+
# On unless refused: `changelog check` reads only the files this commit is
|
|
19
|
+
# about to stage and runs in-process, so it costs a fraction of what the
|
|
20
|
+
# pre-push container check does and stays on by the same default as the
|
|
21
|
+
# no-attribution guard.
|
|
22
|
+
. "$(dirname "$0")/hook-enabled.sh"
|
|
23
|
+
|
|
24
|
+
hook_enabled changelogCheck true || exit 0
|
|
25
|
+
|
|
26
|
+
# Pending changesets this commit actually stages — added, copied or modified.
|
|
27
|
+
# A deleted changeset has nothing left to lint, and a changeset already on
|
|
28
|
+
# `main` was linted by the commit that staged it. `README.md` documents the
|
|
29
|
+
# changeset format; it is not one.
|
|
30
|
+
staged=""
|
|
31
|
+
while IFS= read -r file; do
|
|
32
|
+
[ -z "$file" ] && continue
|
|
33
|
+
case "$file" in
|
|
34
|
+
*/README.md | README.md) continue ;;
|
|
35
|
+
esac
|
|
36
|
+
staged="$staged $file"
|
|
37
|
+
done <<EOF
|
|
38
|
+
$(git diff --cached --name-only --diff-filter=ACM -- '.changeset/*.md')
|
|
39
|
+
EOF
|
|
40
|
+
|
|
41
|
+
if [ -z "$staged" ]; then
|
|
42
|
+
exit 0
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# The binary lives beside this hook inside the package. If it is not there,
|
|
46
|
+
# this hook is being used from somewhere else — a global `core.hooksPath`, a
|
|
47
|
+
# copy — in a repository that does not install the package, and there is
|
|
48
|
+
# nothing to run.
|
|
49
|
+
runner="$(dirname "$0")/../bin/package-build.mjs"
|
|
50
|
+
if [ ! -f "$runner" ]; then
|
|
51
|
+
exit 0
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
# shellcheck disable=SC2086
|
|
55
|
+
node "$runner" changelog check $staged
|
|
56
|
+
status=$?
|
|
57
|
+
|
|
58
|
+
if [ "$status" -ne 0 ]; then
|
|
59
|
+
echo ""
|
|
60
|
+
echo "pre-commit: fix the changeset above, or skip this check with 'git commit --no-verify'."
|
|
61
|
+
exit 1
|
|
62
|
+
fi
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "22.
|
|
3
|
+
"version": "22.4.1",
|
|
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'
|
|
@@ -90,13 +90,33 @@ export function packagedItemAddress(pkg: string, subType: string, shortcode: str
|
|
|
90
90
|
* @returns {string} The catalogue key.
|
|
91
91
|
*/
|
|
92
92
|
export function catalogueKey(subType: string, shortcode: string, pkg?: string): string;
|
|
93
|
+
/**
|
|
94
|
+
* An item's own shortcode, read off its compiled document.
|
|
95
|
+
*
|
|
96
|
+
* **`system.shortcode`**, where a system's data model declares such a field.
|
|
97
|
+
* Where it does not — HM3's has no such field — the handle instead lives in
|
|
98
|
+
* that system's own flag namespace, `flags.<systemId>.shortcode`: a system
|
|
99
|
+
* writes its per-document handle into its own flags and never another
|
|
100
|
+
* system's, so a document extracted from one system's catalogue is read
|
|
101
|
+
* through that system's namespace and no other. `system.shortcode` wins where
|
|
102
|
+
* both are present.
|
|
103
|
+
*
|
|
104
|
+
* @param {object} doc - A compiled Item document, or an embedded item merged
|
|
105
|
+
* from one.
|
|
106
|
+
* @param {string|null} [systemId] - The system whose catalogue `doc` was read
|
|
107
|
+
* from. Omitted or `null`, only `system.shortcode` is read.
|
|
108
|
+
* @returns {string|undefined} The shortcode, or `undefined` when the document
|
|
109
|
+
* states neither.
|
|
110
|
+
*/
|
|
111
|
+
export function shortcodeOf(doc: object, systemId?: string | null): string | undefined;
|
|
93
112
|
/**
|
|
94
113
|
* What identifies one embedded item on its actor.
|
|
95
114
|
*
|
|
96
|
-
* **Its own
|
|
97
|
-
* merely *selects* the catalogue template the
|
|
98
|
-
* never written to the document. Two daggers
|
|
99
|
-
* embodiments and each must declare its
|
|
115
|
+
* **Its own shortcode** — read by {@link shortcodeOf} — not the entry's
|
|
116
|
+
* top-level `shortcode`, which merely *selects* the catalogue template the
|
|
117
|
+
* entry is written from and is never written to the document. Two daggers
|
|
118
|
+
* may share a selector; they are two embodiments and each must declare its
|
|
119
|
+
* own.
|
|
100
120
|
*
|
|
101
121
|
* The name is a last resort, for a **stand-alone** entry that names no template
|
|
102
122
|
* and states no shortcode. It is a poor identity — presentation, and free to be
|
|
@@ -105,9 +125,12 @@ export function catalogueKey(subType: string, shortcode: string, pkg?: string):
|
|
|
105
125
|
* message says to state a `system.shortcode`.
|
|
106
126
|
*
|
|
107
127
|
* @param {object} item - The merged embedded item.
|
|
128
|
+
* @param {string|null} [systemId] - The system this item's document was
|
|
129
|
+
* compiled or extracted for, so a document whose own data model carries no
|
|
130
|
+
* `system.shortcode` field is still read by its own flag namespace.
|
|
108
131
|
* @returns {string} The identity, for {@link embeddedItemId}.
|
|
109
132
|
*/
|
|
110
|
-
export function embeddedIdentity(item: object): string;
|
|
133
|
+
export function embeddedIdentity(item: object, systemId?: string | null): string;
|
|
111
134
|
/**
|
|
112
135
|
* The `_id` of one item embedded on an actor.
|
|
113
136
|
*
|
|
@@ -135,9 +158,9 @@ export function embeddedItemId(actorId: string, subType: string, identity: strin
|
|
|
135
158
|
/**
|
|
136
159
|
* Load every JSON file under each of `itemsSourceDirs`, returning one Map keyed
|
|
137
160
|
* by {@link itemAddress} — the compiled document's **subtype** and its
|
|
138
|
-
*
|
|
139
|
-
* The `_key` field is stripped from each entry — it is
|
|
140
|
-
* data model.
|
|
161
|
+
* shortcode, read by {@link shortcodeOf}. Folder docs and entries without a
|
|
162
|
+
* shortcode are skipped. The `_key` field is stripped from each entry — it is
|
|
163
|
+
* not part of the item data model.
|
|
141
164
|
*
|
|
142
165
|
* The directories are read as one address space, because an actor names an item
|
|
143
166
|
* by `(type, shortcode)` and never by the pack it happens to ship in. Two local
|
|
@@ -151,12 +174,22 @@ export function embeddedItemId(actorId: string, subType: string, identity: strin
|
|
|
151
174
|
* colliding with it. Local directories are therefore read first, and anything
|
|
152
175
|
* already claimed is left alone.
|
|
153
176
|
*
|
|
177
|
+
* **Each foreign directory reads its own flag namespace.** A foreign entry's
|
|
178
|
+
* `package` is the system whose catalogue it was extracted from, and that is
|
|
179
|
+
* the only namespace {@link shortcodeOf} is asked to fall back to for it — a
|
|
180
|
+
* document carrying another system's flag, sitting in this system's catalogue,
|
|
181
|
+
* is exactly the defect a system writing outside its own namespace produces,
|
|
182
|
+
* and is silently skipped rather than resolved.
|
|
183
|
+
*
|
|
154
184
|
* @param {readonly string[]} itemsSourceDirs - Every local Item pack's JSON tree.
|
|
155
185
|
* @param {readonly string[]} [foreignSourceDirs] - Extracted dependency
|
|
156
186
|
* catalogues, consulted only for addresses no local pack defines.
|
|
187
|
+
* @param {string|null} [system] - The system `itemsSourceDirs` were compiled
|
|
188
|
+
* for, so a local document whose data model carries no `system.shortcode`
|
|
189
|
+
* field is still read by its own flag namespace.
|
|
157
190
|
* @returns {Map<string, object>} The predefined items, by address.
|
|
158
191
|
*/
|
|
159
|
-
export function loadItemsMap(itemsSourceDirs: readonly string[], foreignSourceDirs?: readonly string[]): Map<string, object>;
|
|
192
|
+
export function loadItemsMap(itemsSourceDirs: readonly string[], foreignSourceDirs?: readonly string[], system?: string | null): Map<string, object>;
|
|
160
193
|
/**
|
|
161
194
|
* The Actor compile pass of one game system.
|
|
162
195
|
*
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lint one pending changeset (`.changeset/*.md`).
|
|
3
|
+
*
|
|
4
|
+
* @param {string} text - The file's full contents, frontmatter included.
|
|
5
|
+
* @returns {{findings: Array<{line: number, column?: number,
|
|
6
|
+
* severity: "error"|"warning", message: string}>}}
|
|
7
|
+
*/
|
|
8
|
+
export function lintChangesetText(text: string): {
|
|
9
|
+
findings: Array<{
|
|
10
|
+
line: number;
|
|
11
|
+
column?: number;
|
|
12
|
+
severity: "error" | "warning";
|
|
13
|
+
message: string;
|
|
14
|
+
}>;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Lint the first `## <version>` release section of a `CHANGELOG.md`.
|
|
18
|
+
*
|
|
19
|
+
* @param {string} text - The changelog's full contents.
|
|
20
|
+
* @returns {{findings: Array<{line?: number, column?: number,
|
|
21
|
+
* severity: "error"|"warning", message: string}>}}
|
|
22
|
+
*/
|
|
23
|
+
export function lintReleaseText(text: string): {
|
|
24
|
+
findings: Array<{
|
|
25
|
+
line?: number;
|
|
26
|
+
column?: number;
|
|
27
|
+
severity: "error" | "warning";
|
|
28
|
+
message: string;
|
|
29
|
+
}>;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* The finding one rule reports, before its line is mapped into the caller's
|
|
33
|
+
* file.
|
|
34
|
+
*/
|
|
35
|
+
export type RelativeFinding = {
|
|
36
|
+
/**
|
|
37
|
+
* - 1-based line within the section text.
|
|
38
|
+
*/
|
|
39
|
+
line: number;
|
|
40
|
+
/**
|
|
41
|
+
* - 1-based column, dropped when not meaningful.
|
|
42
|
+
*/
|
|
43
|
+
column?: number | undefined;
|
|
44
|
+
severity: "error" | "warning";
|
|
45
|
+
/**
|
|
46
|
+
* - Prefixed `changelog-check/<class> `, so a
|
|
47
|
+
* finding names the rule it tripped as well as what to write instead.
|
|
48
|
+
*/
|
|
49
|
+
message: string;
|
|
50
|
+
};
|
|
@@ -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.
|