@heroiclands/package-build 15.0.0 → 16.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.
- package/CHANGELOG.md +143 -0
- package/CONTENT.md +203 -0
- package/bin/content-build.mjs +54 -0
- package/content-config.mjs +10 -0
- package/engine/content-address.mjs +21 -36
- package/engine/content-index.mjs +439 -0
- package/engine/frontmatter-lint.mjs +86 -1
- package/engine/index.mjs +3 -0
- package/package.json +1 -1
- package/types/content-config.d.mts +9 -0
- package/types/engine/content-address.d.mts +22 -34
- package/types/engine/content-index.d.mts +194 -0
- package/types/engine/index.d.mts +1 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `{#slug}` anchors a note's body declares, with where each one sits.
|
|
3
|
+
*
|
|
4
|
+
* Only headings carrying an explicit anchor are collected. A bare `#` heading
|
|
5
|
+
* also starts a journal page, but it declares no slug, so nothing can address
|
|
6
|
+
* it with `#…` — listing it would offer a link that cannot be written.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} body - The note's markdown body, frontmatter already removed.
|
|
9
|
+
* @param {number} [bodyLine] - The 1-based file line the body starts on, from
|
|
10
|
+
* `parseMarkdownFile`. Anchors are reported at their position in the **file**,
|
|
11
|
+
* so an editor can jump straight to one; passing nothing numbers from the body.
|
|
12
|
+
* @returns {Array<{slug: string, name: string, level: number, line: number}>}
|
|
13
|
+
* In document order.
|
|
14
|
+
*/
|
|
15
|
+
export function collectAnchors(body: string, bodyLine?: number): Array<{
|
|
16
|
+
slug: string;
|
|
17
|
+
name: string;
|
|
18
|
+
level: number;
|
|
19
|
+
line: number;
|
|
20
|
+
}>;
|
|
21
|
+
/**
|
|
22
|
+
* The address a wikilink writes to reach a note, or `null` when it has none.
|
|
23
|
+
*
|
|
24
|
+
* A wikilink target is an address: `being-aurochs` locally, or
|
|
25
|
+
* `sohl-being-aurochs` from another package (`readQualifier` also accepts
|
|
26
|
+
* `being/aurochs`, the same two fields with a different separator). Both forms
|
|
27
|
+
* are already derivable from `type` and `shortcode`, which every record
|
|
28
|
+
* carries — so this field adds no information. What it adds is the *rule*:
|
|
29
|
+
* the lowercasing and the hyphen join live in one place, and a consumer that
|
|
30
|
+
* reimplements them slightly differently gets a lookup that matches nothing and
|
|
31
|
+
* says nothing about why. That is a real failure, not a hypothetical one — it
|
|
32
|
+
* is precisely how a resolver keyed on a bare `type/shortcode` silently misses
|
|
33
|
+
* every canonical `pkg-type-shortcode` entry.
|
|
34
|
+
*
|
|
35
|
+
* Derived by the same functions the link manifest and the site build use, so an
|
|
36
|
+
* index cannot disagree with either about where a note lives.
|
|
37
|
+
*
|
|
38
|
+
* @param {Record<string, any>} frontmatter - The note's parsed frontmatter.
|
|
39
|
+
* @param {string} contentPackage - The package the tree compiles as.
|
|
40
|
+
* @returns {{slug: string, canonical: string}|null} `slug` is what goes inside
|
|
41
|
+
* `[[…]]` within this package; `canonical` is the package-qualified key the
|
|
42
|
+
* manifest files the note under. `null` for a note with no type or no
|
|
43
|
+
* shortcode, which has no address at all and is stated as such rather than
|
|
44
|
+
* left for every reader to rediscover.
|
|
45
|
+
*/
|
|
46
|
+
export function noteAddress(frontmatter: Record<string, any>, contentPackage: string): {
|
|
47
|
+
slug: string;
|
|
48
|
+
canonical: string;
|
|
49
|
+
} | null;
|
|
50
|
+
/**
|
|
51
|
+
* Recursively sort an object's keys, so serialization is order-independent.
|
|
52
|
+
*
|
|
53
|
+
* Arrays keep their order — it is authored — but every object inside one is
|
|
54
|
+
* sorted too. Anything that is not a plain object is returned as it is.
|
|
55
|
+
*
|
|
56
|
+
* @param {unknown} value - The value to normalize.
|
|
57
|
+
* @returns {unknown} The value with every plain object's keys in sorted order.
|
|
58
|
+
*/
|
|
59
|
+
export function sortKeysDeep(value: unknown): unknown;
|
|
60
|
+
/**
|
|
61
|
+
* A note's display name reduced to printable 7-bit ASCII.
|
|
62
|
+
*
|
|
63
|
+
* Content names carry the setting's orthography — `Kûrbúl Helm`, `Hârn`,
|
|
64
|
+
* `Kèthîra` — and nobody types them. A reader searching the index, or an editor
|
|
65
|
+
* completing a wikilink, needs a form that matches what a keyboard produces, so
|
|
66
|
+
* the record states one rather than leaving every consumer to invent it (and to
|
|
67
|
+
* invent a *different* one, which is how two searches over the same data come
|
|
68
|
+
* to disagree).
|
|
69
|
+
*
|
|
70
|
+
* **Transliterated, not stripped.** `unidecode` — the same table
|
|
71
|
+
* {@link slugify} already runs, so an ASCII name and a slug can never disagree
|
|
72
|
+
* about a character — carries a letter across rather than deleting it:
|
|
73
|
+
* diacritics fold (`â`→`a`, `è`→`e`), ligatures expand (`æ`→`ae`, `Œ`→`OE`,
|
|
74
|
+
* `ß`→`ss`), the runic letters spell out (`þ`→`th`, `Þ`→`Th`, `ð`→`d`), and
|
|
75
|
+
* even a vulgar fraction becomes readable (`¾`→`3/4`). Deleting them instead
|
|
76
|
+
* would collapse `Kûrbúl` to `Krbl`, which is worse than the original.
|
|
77
|
+
*
|
|
78
|
+
* Anything still outside printable ASCII after that becomes a space, and runs
|
|
79
|
+
* of whitespace collapse — a space rather than nothing, so a character that
|
|
80
|
+
* transliterates away cannot silently weld two words together.
|
|
81
|
+
*
|
|
82
|
+
* The value is emitted even when it equals the name, so a consumer matching on
|
|
83
|
+
* it never has to branch on whether the name happened to be ASCII already.
|
|
84
|
+
*
|
|
85
|
+
* @param {unknown} name - The note's `name.full`.
|
|
86
|
+
* @returns {string|null} The ASCII form, or `null` when there is no name, or
|
|
87
|
+
* nothing printable survives.
|
|
88
|
+
*/
|
|
89
|
+
export function asciiName(name: unknown): string | null;
|
|
90
|
+
/**
|
|
91
|
+
* A note's `name.aliases` reduced to printable 7-bit ASCII, in order.
|
|
92
|
+
*
|
|
93
|
+
* An alias is the name a reader is at least as likely to reach for as the
|
|
94
|
+
* canonical one — `Killer Whale` for an orca, `Ice Bear` for a polar bear,
|
|
95
|
+
* `Ix'balam` for a jaguar — so anything searching or completing over the index
|
|
96
|
+
* has to match them too, and needs the same keyboard-typeable form
|
|
97
|
+
* {@link asciiName} gives the primary name.
|
|
98
|
+
*
|
|
99
|
+
* Order is the authored order, so a caller can pair an entry with the alias it
|
|
100
|
+
* came from. An alias that is not a non-empty string, or that leaves nothing
|
|
101
|
+
* printable behind, is dropped rather than left as a hole — the array is a set
|
|
102
|
+
* of names to match, and a null in it is not one.
|
|
103
|
+
*
|
|
104
|
+
* @param {unknown} aliases - The note's `name.aliases`; may be absent or null.
|
|
105
|
+
* @returns {Array<string>} Possibly empty, never null: a note with no aliases
|
|
106
|
+
* has an empty set of them, which is a fact rather than a missing value, and
|
|
107
|
+
* a consumer iterating it should not have to check first.
|
|
108
|
+
*/
|
|
109
|
+
export function asciiAliases(aliases: unknown): Array<string>;
|
|
110
|
+
/**
|
|
111
|
+
* Build one index record from a note's frontmatter and its place in the tree.
|
|
112
|
+
*
|
|
113
|
+
* @param {object} options - Options.
|
|
114
|
+
* @param {Record<string, any>} options.frontmatter - The note's parsed frontmatter.
|
|
115
|
+
* @param {string} options.relPath - Its path below the content root, POSIX-separated.
|
|
116
|
+
* @param {string} options.contentPackage - The package the tree compiles as.
|
|
117
|
+
* @param {string} [options.body] - The note's markdown body, for its anchors.
|
|
118
|
+
* @param {number} [options.bodyLine] - The 1-based file line the body starts on.
|
|
119
|
+
* @returns {Record<string, any>} The record, keys sorted at every depth.
|
|
120
|
+
* @throws {Error} When the note carries a key this module derives, which would
|
|
121
|
+
* otherwise be overwritten without a word.
|
|
122
|
+
*/
|
|
123
|
+
export function buildIndexRecord({ frontmatter, relPath, contentPackage, body, bodyLine }: {
|
|
124
|
+
frontmatter: Record<string, any>;
|
|
125
|
+
relPath: string;
|
|
126
|
+
contentPackage: string;
|
|
127
|
+
body?: string | undefined;
|
|
128
|
+
bodyLine?: number | undefined;
|
|
129
|
+
}): Record<string, any>;
|
|
130
|
+
/**
|
|
131
|
+
* Read a content tree into index records, in the order they will be written.
|
|
132
|
+
*
|
|
133
|
+
* @param {string} contentBase - The content tree to walk.
|
|
134
|
+
* @param {object} options - Options.
|
|
135
|
+
* @param {string} options.contentPackage - The package the tree compiles as.
|
|
136
|
+
* @param {Array<string>} [options.skipDirectories] - Directory names to skip.
|
|
137
|
+
* @returns {Array<Record<string, any>>} The records, in a total order that does
|
|
138
|
+
* not depend on directory-read order.
|
|
139
|
+
*/
|
|
140
|
+
export function collectContentIndex(contentBase: string, { contentPackage, skipDirectories }: {
|
|
141
|
+
contentPackage: string;
|
|
142
|
+
skipDirectories?: string[] | undefined;
|
|
143
|
+
}): Array<Record<string, any>>;
|
|
144
|
+
/**
|
|
145
|
+
* Serialize records as JSON Lines.
|
|
146
|
+
*
|
|
147
|
+
* @param {Array<Record<string, any>>} records - From {@link collectContentIndex}.
|
|
148
|
+
* @returns {string} One compact JSON object per line, newline-terminated. An
|
|
149
|
+
* empty set serializes to the empty string rather than to a lone newline, so
|
|
150
|
+
* the file is exactly the lines it holds.
|
|
151
|
+
*/
|
|
152
|
+
export function serializeContentIndex(records: Array<Record<string, any>>): string;
|
|
153
|
+
/**
|
|
154
|
+
* Emit this package's content index.
|
|
155
|
+
*
|
|
156
|
+
* @param {object} [options] - Options.
|
|
157
|
+
* @param {string} [options.contentBase] - The content tree; defaults to the
|
|
158
|
+
* configured `paths.content`.
|
|
159
|
+
* @param {string} [options.outDir] - Where to write; defaults to the configured
|
|
160
|
+
* `paths.contentIndex`.
|
|
161
|
+
* @param {object} [options.config] - A resolved configuration; loaded when omitted.
|
|
162
|
+
* @returns {{file: string, notes: number, bytes: number}} Where it was written,
|
|
163
|
+
* how many notes it holds, and its size.
|
|
164
|
+
* @throws {Error} When the content tree is absent, or when it yields no note at
|
|
165
|
+
* all — an empty index is indistinguishable from a mis-pointed tree, and a
|
|
166
|
+
* reader would take it as the authoritative statement that this package has
|
|
167
|
+
* no content.
|
|
168
|
+
*/
|
|
169
|
+
export function emitContentIndex({ contentBase, outDir, config }?: {
|
|
170
|
+
contentBase?: string | undefined;
|
|
171
|
+
outDir?: string | undefined;
|
|
172
|
+
config?: object | undefined;
|
|
173
|
+
}): {
|
|
174
|
+
file: string;
|
|
175
|
+
notes: number;
|
|
176
|
+
bytes: number;
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* The keys this module adds to a record, which a note therefore may not carry
|
|
180
|
+
* itself.
|
|
181
|
+
*
|
|
182
|
+
* `package` is the note's distribution unit — the configured `contentPackage`,
|
|
183
|
+
* since a note declaring its own is a hard error (package-build#56) — and it
|
|
184
|
+
* matches what the content-table expander puts on the same field, so a query
|
|
185
|
+
* reads the same value from either. `file` namespaces the note's place in the
|
|
186
|
+
* tree, again matching the expander's `file.*`.
|
|
187
|
+
*
|
|
188
|
+
* Both are checked rather than assumed: `folder` is real frontmatter on most
|
|
189
|
+
* notes, so the neighbouring names are close enough to a real key that a silent
|
|
190
|
+
* overwrite is a plausible future rather than a hypothetical one.
|
|
191
|
+
*
|
|
192
|
+
* @type {ReadonlyArray<string>}
|
|
193
|
+
*/
|
|
194
|
+
export const DERIVED_KEYS: ReadonlyArray<string>;
|
package/types/engine/index.d.mts
CHANGED
|
@@ -17,6 +17,7 @@ export * as contentAddress from "./content-address.mjs";
|
|
|
17
17
|
export * as foreignManifests from "./foreign-manifests.mjs";
|
|
18
18
|
export * as kbManifest from "./kb-manifest.mjs";
|
|
19
19
|
export * as manifestEmit from "./manifest-emit.mjs";
|
|
20
|
+
export * as contentIndex from "./content-index.mjs";
|
|
20
21
|
export * as siteBuild from "./site-build.mjs";
|
|
21
22
|
export * as contentLint from "./content-lint.mjs";
|
|
22
23
|
export * as contentLinks from "./content-links.mjs";
|