@heroiclands/package-build 11.1.0 → 13.0.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.
@@ -29,22 +29,9 @@ export function matchesKind(value: unknown, kind: string): boolean;
29
29
  * @param {Readonly<Record<string, {known?: readonly string[], fieldVocabulary?: boolean}>>} [opts.systems]
30
30
  * The system blocks to check, and what each accepts. See
31
31
  * {@link DEFAULT_SYSTEM_BLOCKS}.
32
- * @param {string} [opts.landing] - The repository's landing rule, from
33
- * `publish.address.landing`. It decides which note addresses a whole section,
34
- * and so whether a `subType` is a genre or an address (#197).
35
- * @param {readonly string[]} [opts.types] - The content types the format
36
- * declares — the sections that exist by construction (#200). Read from
37
- * `docs/content-format.md` rather than from `schemas`, because the two answer
38
- * different questions: whether an address is real, and whether this build can
39
- * check a note's fields. A type the specification declares and no schema
40
- * covers is a real section, and its notes are reported on their own account.
41
- * @param {readonly string[]} [opts.sections] - The sections the repository
42
- * configures, from `declaredSections`. Supplied by the caller for the same
43
- * reason `vocabulary` is: this module checks a note against what it is
44
- * handed.
45
32
  * @returns {object[]} Findings, each with a locator where one is obtainable.
46
33
  */
47
- export function lintNote(note: object, { schemas, index, vocabulary, systems, landing, types, sections, }: {
34
+ export function lintNote(note: object, { schemas, index, vocabulary, systems }: {
48
35
  schemas: Record<string, readonly object[]>;
49
36
  index?: object | undefined;
50
37
  vocabulary?: Record<string, object> | undefined;
@@ -52,9 +39,6 @@ export function lintNote(note: object, { schemas, index, vocabulary, systems, la
52
39
  known?: readonly string[];
53
40
  fieldVocabulary?: boolean;
54
41
  }>> | undefined;
55
- landing?: string | undefined;
56
- types?: readonly string[] | undefined;
57
- sections?: readonly string[] | undefined;
58
42
  }): object[];
59
43
  /**
60
44
  * Check every note in a built index against its type's schema.
@@ -67,16 +51,10 @@ export function lintNote(note: object, { schemas, index, vocabulary, systems, la
67
51
  * @param {boolean} [opts.references=true] - Whether to check references.
68
52
  * @param {Readonly<Record<string, {known?: readonly string[], fieldVocabulary?: boolean}>>} [opts.systems]
69
53
  * The system blocks to check. See {@link DEFAULT_SYSTEM_BLOCKS}.
70
- * @param {string} [opts.landing] - The repository's landing rule; see
71
- * {@link lintNote}.
72
- * @param {readonly string[]} [opts.types] - The content types the format
73
- * declares; see {@link lintNote}.
74
- * @param {readonly string[]} [opts.sections] - The sections the repository
75
- * configures; see {@link lintNote}.
76
54
  * @returns {{findings: object[], notes: number}} The findings, and how many
77
55
  * notes were inspected.
78
56
  */
79
- export function lintFrontmatter(index: object, { schemas, vocabulary, references, systems, landing, types, sections }: {
57
+ export function lintFrontmatter(index: object, { schemas, vocabulary, references, systems }: {
80
58
  schemas: Record<string, readonly object[]>;
81
59
  vocabulary?: Record<string, object> | undefined;
82
60
  references?: boolean | undefined;
@@ -84,9 +62,6 @@ export function lintFrontmatter(index: object, { schemas, vocabulary, references
84
62
  known?: readonly string[];
85
63
  fieldVocabulary?: boolean;
86
64
  }>> | undefined;
87
- landing?: string | undefined;
88
- types?: readonly string[] | undefined;
89
- sections?: readonly string[] | undefined;
90
65
  }): {
91
66
  findings: object[];
92
67
  notes: number;
@@ -35,6 +35,57 @@ export function hasTag(fm: object | null | undefined, tag: string): boolean;
35
35
  * @returns {boolean} Whether the note carries the `draft` tag.
36
36
  */
37
37
  export function isDraftNote(fm: object | null | undefined): boolean;
38
+ /**
39
+ * What to write in place of a retired subType value, if it is one.
40
+ *
41
+ * @param {string} type - The note's `type`.
42
+ * @param {string} value - The authored `subType`.
43
+ * @param {Readonly<Record<string, Readonly<Record<string, string>>>>} [retired]
44
+ * The map to read, defaulting to {@link RETIRED_SUBTYPES}.
45
+ * @returns {string|undefined} The current spelling, or `undefined` when the
46
+ * value is not a retired one — which is not the same as it being valid.
47
+ */
48
+ export function retiredSubType(type: string, value: string, retired?: Readonly<Record<string, Readonly<Record<string, string>>>>): string | undefined;
49
+ /**
50
+ * What a note carrying a retired subType is told.
51
+ *
52
+ * One message, so the lint and any later refusal cannot describe the same
53
+ * retirement differently.
54
+ *
55
+ * @param {string} type - The note's `type`.
56
+ * @param {string} value - The retired spelling the note carries.
57
+ * @param {string} replacement - What to write instead.
58
+ * @returns {string} The message.
59
+ */
60
+ export function retiredSubTypeMessage(type: string, value: string, replacement: string): string;
61
+ /**
62
+ * What a note carrying a subType outside the address charset is told.
63
+ *
64
+ * @param {string} value - The authored `subType`.
65
+ * @returns {string} The message.
66
+ */
67
+ export function subTypeCharsetMessage(value: string): string;
68
+ /**
69
+ * What a note carrying a type outside the address charset is told.
70
+ *
71
+ * @param {string} type - The authored `type`.
72
+ * @returns {string} The message.
73
+ */
74
+ export function typeCharsetMessage(type: string): string;
75
+ /**
76
+ * Refuse a vocabulary that declares a type or subType outside the charset.
77
+ *
78
+ * Run over {@link NOTE_VOCABULARY} as this module loads, so a declaration that
79
+ * breaks the rule cannot be imported. That is stricter than a lint on purpose:
80
+ * a note's bad value is one author's mistake and belongs in a report, while a
81
+ * bad *declaration* would tell every author to write something unaddressable.
82
+ *
83
+ * @param {Readonly<Record<string, TypeVocabulary>>} vocabulary - The registry.
84
+ * @param {string} [where] - What declares it, for the message.
85
+ * @throws {Error} Naming every offending type and subType at once, rather than
86
+ * stopping at the first — a reader fixing a list wants the whole list.
87
+ */
88
+ export function assertVocabularyCharset(vocabulary: Readonly<Record<string, TypeVocabulary>>, where?: string): void;
38
89
  /**
39
90
  * The `data:` keys a note type may carry.
40
91
  *
@@ -237,6 +288,31 @@ export const NOTE_VOCABULARY: Readonly<{
237
288
  })[];
238
289
  }>;
239
290
  }>;
291
+ /**
292
+ * The retired spelling of a subType a type declares → what to write now (#206).
293
+ *
294
+ * Keyed by type, because a retirement is a statement about *that type's*
295
+ * vocabulary: `user-guide` on a `doc` is the old spelling of `userguide`, while
296
+ * the same string on any other type is nothing but a charset violation, and
297
+ * saying "did you mean userguide" there would be a guess dressed as a fact.
298
+ *
299
+ * **Recorded here rather than left in `subTypes`** so the declared list stays
300
+ * the list of values a note *should* write. A retired value is accepted, not
301
+ * declared — the difference is exactly what makes the finding possible.
302
+ *
303
+ * **Deliberately not the shape of a type rename** ({@link
304
+ * import("./ids.mjs").RETIRED_TYPES}), which is an error: a retired type routes
305
+ * a note to the wrong pack, whereas a retired subType still compiles to the
306
+ * correct page. The sweep is the consumer's, and the ordering is the reverse of
307
+ * the usual — the acceptance ships *first*, because declaring only the new
308
+ * spelling while 43 `sohl` notes still author the old one would invalidate all
309
+ * 43 with a release they had no chance to sweep ahead of. A later change
310
+ * removes this map, and the old spelling then falls through to the ordinary
311
+ * undeclared-value error with no code left to remove.
312
+ *
313
+ * @type {Readonly<Record<string, Readonly<Record<string, string>>>>}
314
+ */
315
+ export const RETIRED_SUBTYPES: Readonly<Record<string, Readonly<Record<string, string>>>>;
240
316
  /**
241
317
  * One `data:` key a note type may carry.
242
318
  *
@@ -106,6 +106,53 @@ export function assertNoAliasesField(fm: object | null | undefined, { file, absP
106
106
  * @returns {boolean} Whether the retired field is declared.
107
107
  */
108
108
  export function declaresRetiredAliasesField(fm: object | null | undefined): boolean;
109
+ /**
110
+ * What a note declaring `section:` is told, in one place.
111
+ *
112
+ * Shared by the compile-time refusal and the frontmatter lint, because an
113
+ * author meets whichever of the two runs first and they should read the same.
114
+ * It names what lands a section now rather than a value to correct: no value
115
+ * makes declaring the field right.
116
+ *
117
+ * **What it did (#202).** It named the section a `collection` note headed,
118
+ * under the `collection` landing rule — the only reader it ever had, in the
119
+ * second branch of `landingOf` (`engine/content-address.mjs`). That rule went
120
+ * first, and the whole mechanism went with it (#204): a section is a Hugo
121
+ * directory the note format does not carry, so no note lands one and a page
122
+ * that introduces a type is an ordinary note addressed `doc-<type>`. Nothing
123
+ * else read the field, and no schema or vocabulary declared it, so left in
124
+ * place it would be ignored in silence — the note saying one thing and the
125
+ * build doing another.
126
+ *
127
+ * @param {string} [file] - The note's path, named in the message. Omit it where
128
+ * the caller emits through a diagnostic, whose locator already starts the
129
+ * line — repeating it prints the path twice.
130
+ * @returns {string} The message, unpunctuated at the end as a finding is.
131
+ */
132
+ export function sectionRetiredMessage(file?: string): string;
133
+ /**
134
+ * Refuse a note that declares `section:` at all.
135
+ *
136
+ * Presence is the whole test, as it is for `draft:` and `aliases:`: an empty
137
+ * value reads as "this note heads a section and names none", a statement about
138
+ * a rule that no longer exists.
139
+ *
140
+ * @param {object|null|undefined} fm - Parsed frontmatter, or nothing when it
141
+ * could not be parsed.
142
+ * @param {object} [options] - Options.
143
+ * @param {string} [options.file] - The note's path, named in the message. Omit
144
+ * it where the caller emits through a diagnostic, which puts the locator at
145
+ * the start of the line already — repeating it prints the path twice.
146
+ * @param {string} [options.absPath] - The note's file on disk, read only on the
147
+ * failing path to locate the offending line and column. The position rides on
148
+ * the thrown error as `position`, for a caller that emits a diagnostic.
149
+ * @returns {void}
150
+ * @throws {Error} When the note declares the field.
151
+ */
152
+ export function assertNoSectionField(fm: object | null | undefined, { file, absPath }?: {
153
+ file?: string | undefined;
154
+ absPath?: string | undefined;
155
+ }): void;
109
156
  /**
110
157
  * A frontmatter key's position in a note's file, or nothing.
111
158
  *
@@ -196,12 +196,10 @@ export function sectionFrontmatter(meta: object): object;
196
196
  * redirects of its own.
197
197
  *
198
198
  * A content page states its own **`url`**, which is its address rather than its
199
- * path (#181). Hugo would otherwise publish it where the file sits under the
200
- * mount, inside its section directory and the file sits there for a reason:
201
- * Hugo derives a page's section from its directory, which is what gives the
202
- * section its landing page, `.CurrentSection` and its per-section layout
203
- * lookup. So the directory stays and the address is stated, and the two are
204
- * free to differ.
199
+ * path (#181). It is written flat under the content mount (#204), so Hugo would
200
+ * otherwise publish it at `<mount><type>-<shortcode>/` rather than at the
201
+ * package-wide address the link manifest records the same address, one
202
+ * segment too deep. So the address is stated and the mount does not reach it.
205
203
  *
206
204
  * A content page carries the package the build **derived** (#65). No note
207
205
  * declares one — `package:` is retired (#56) — so the note's frontmatter alone
@@ -213,24 +211,34 @@ export function sectionFrontmatter(meta: object): object;
213
211
  * self-describing and makes sweeping the field out of a content tree
214
212
  * output-preserving for a site as it already is for the packs.
215
213
  *
214
+ * A **tree** page is the one that still reads `readmeSections`: a `trees` entry
215
+ * keeps its source layout below a named section, so its own `README` is that
216
+ * section's landing and takes the title and hero the section declares.
217
+ *
216
218
  * @param {object} page - The page.
217
- * @param {object} options - `{ sections, readmeSections, decorate }`.
219
+ * @param {object} options - `{ readmeSections, decorate }`.
218
220
  * @returns {object} The frontmatter to write.
219
221
  */
220
222
  export function pageFrontmatter(page: object, { readmeSections, decorate }: object): object;
221
223
  /**
222
224
  * Where a page is written, relative to the output root.
223
225
  *
224
- * **Into its section directory, which is not where it publishes** (#181). A
225
- * content page's URL is its address — `/<package>/<type>-<shortcode>/` — and it
226
- * is stated in the front matter; the file still goes to `<section>/`, because
227
- * Hugo reads a page's section from its path and nothing else. Flattening the
228
- * tree to match the URL would take the section landings, `.CurrentSection` and
229
- * every per-section layout with it.
230
- *
231
- * The filename is the address rather than the section-relative half of it, so
232
- * two sections cannot fight over one file: a `doc` note routes by its `subType`,
233
- * which may be spelled the same as another note's `type`.
226
+ * **Flat, under the mount, named by its address** (#204). A content page's URL
227
+ * is its address — `/<package>/<type>-<shortcode>/` — and the file is now named
228
+ * the same way, so the two agree. It used to be filed into `<section>/` so that
229
+ * Hugo would read a section off its path; a section appears in no address, and
230
+ * a directory chosen only to satisfy a rendering engine's idea of what a
231
+ * section is has no business in the note format.
232
+ *
233
+ * The name is the *whole* address rather than a section-relative half of it, so
234
+ * two types cannot fight over one file: a `doc` note's `subType` may be spelled
235
+ * the same as another note's `type`, and `doc-gear.md` and `weapongear-gear.md`
236
+ * are distinct whatever the sections used to be.
237
+ *
238
+ * **A `trees` entry is the exception, and always was.** Those pages preserve
239
+ * their source layout below a named section — they are a book with chapters,
240
+ * addressed by their path — so a `README` there is still its directory's
241
+ * `_index.md`.
234
242
  */
235
243
  export function pageDestination(page: any): string;
236
244
  /**
@@ -259,26 +267,43 @@ export function renderPages(pages: object[], options: object): {
259
267
  wikiErrors: object[];
260
268
  };
261
269
  /**
262
- * Writes the section landings a published tree needs but no note supplies.
263
- *
264
- * Two separate jobs, and both exist because of how Hugo decides what a section
265
- * is:
266
- *
270
+ * Writes the Hugo sections a published tree declares.
271
+ *
272
+ * **This is where a section lives now, and the only place** (#204). A content
273
+ * note carries none: it is addressed by `(type, shortcode)` and emitted flat
274
+ * under the mount, so nothing a page does creates a directory. A site that wants
275
+ * `/<package>/<prefix><section>/` to answer — with a title, a hero, and whatever
276
+ * listing its layout builds — says so here, in configuration, and this writes
277
+ * the `_index.md` that makes Hugo agree it is a section.
278
+ *
279
+ * Three jobs, all of them Hugo's directory semantics rather than the note
280
+ * format's:
281
+ *
282
+ * - **The mount's own landing**, so `/<package>/<prefix>` is a page rather than
283
+ * a directory listing. It carries a `type` of its own: Hugo's template lookup
284
+ * walks up a page's path, so an untyped landing template at the mount would
285
+ * also serve every section below it that has none.
267
286
  * - **Declared sections** get a titled `_index.md` with their hero, so a landing
268
287
  * matches the card that links to it instead of showing Hugo's auto-humanised
269
- * directory name. The body is empty, which lets the theme list the section's
270
- * children or say it is empty, for a section whose content has not shipped.
271
- * - **Every other section directly under the mount** gets a bare `_index.md`,
288
+ * directory name. The body is empty, which lets the theme decide what to list.
289
+ * - **Every other directory directly under the mount** gets a bare `_index.md`,
272
290
  * or its own address publishes nothing. Hugo generates a section page
273
291
  * automatically only for a *top-level* content directory; below that, a
274
- * directory without an `_index.md` is not a section, so its URL 404s while
275
- * its children publish normally. Mounting a tree one level down demotes every
276
- * section it holds, and the ones with no landing of their own quietly stop
277
- * existing while every page inside them keeps working.
292
+ * directory without an `_index.md` is not a section, so its URL 404s while its
293
+ * children publish normally. With content pages flat, what that reaches is a
294
+ * `trees` entry's directory the one thing left below the mount that a note
295
+ * creates.
296
+ *
297
+ * **A section listing is not a page listing any more.** A layout that reads
298
+ * `.Pages` off a section it declares here will find nothing, because no file is
299
+ * filed into it; one that queries `site.RegularPages` by `Params.type` — which
300
+ * is how `sohl`'s eleven catalog layouts already work — is unaffected. That is a
301
+ * consumer's layout to choose, and it is stated here because the choice is no
302
+ * longer free.
278
303
  *
279
304
  * Scoped to one level on purpose. A directory further down was not a section
280
- * before the move either, and giving it one here would silently re-scope the
281
- * prev/next navigation of every page inside it.
305
+ * before either, and giving it one here would silently re-scope the prev/next
306
+ * navigation of every page inside it.
282
307
  *
283
308
  * @param {string} outRoot - The mount directory.
284
309
  * @param {object} options - `{ sections, landing, sectionTitle }`.
@@ -73,9 +73,13 @@ export type SiteEntry = {
73
73
  */
74
74
  slug: string;
75
75
  /**
76
- * Section the page is filed under.
76
+ * The Hugo section a **tree** page is filed under, and
77
+ * the first segment of the `<sec>/<slug>` address it
78
+ * is reachable by. A content page has none: it is
79
+ * addressed by `(type, shortcode)` and emitted flat
80
+ * (#204).
77
81
  */
78
- sec: string;
82
+ sec?: string | undefined;
79
83
  /**
80
84
  * Source file's basename, e.g. `Climbing.md`.
81
85
  */
@@ -85,9 +89,10 @@ export type SiteEntry = {
85
89
  */
86
90
  url: string;
87
91
  /**
88
- * Whether the page is its section's landing.
92
+ * Whether a tree page is its directory's
93
+ * landing.
89
94
  */
90
- isReadme: boolean;
95
+ isReadme?: boolean | undefined;
91
96
  };
92
97
  /**
93
98
  * The resolved index and everything a wikilink resolver reads beside it.