@heroiclands/package-build 22.17.1 → 22.17.2
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 +14 -0
- package/bin/content-commands.mjs +21 -23
- package/docs/api.md +4 -14
- package/docs/commands.md +19 -18
- package/docs/reference/format-details.md +27 -45
- package/docs/reference/note-types.md +1 -1
- package/engine/actor-compiler.mjs +5 -5
- package/engine/address-diff.mjs +32 -279
- package/engine/art-fields.mjs +8 -5
- package/engine/content-format.yaml +4 -4
- package/engine/content-index.mjs +28 -3
- package/engine/content-lint.mjs +3 -182
- package/engine/frontmatter-lint.mjs +112 -59
- package/engine/helpers.mjs +15 -3
- package/engine/index.mjs +0 -3
- package/engine/map-graphviz.mjs +4 -1
- package/engine/note-frontmatter.mjs +1 -0
- package/engine/note-vocabulary.mjs +1 -13
- package/engine/subtype-registry.mjs +2 -6
- package/engine/systems.mjs +11 -13
- package/package.json +1 -1
- package/types/engine/actor-compiler.d.mts +5 -5
- package/types/engine/address-diff.d.mts +18 -107
- package/types/engine/art-fields.d.mts +4 -5
- package/types/engine/frontmatter-lint.d.mts +14 -0
- package/types/engine/helpers.d.mts +12 -1
- package/types/engine/index.d.mts +0 -1
- package/types/engine/note-vocabulary.d.mts +0 -9
- package/types/engine/subtype-registry.d.mts +2 -6
- package/types/engine/systems.d.mts +6 -6
- package/engine/note-renames.mjs +0 -134
- package/types/engine/note-renames.d.mts +0 -102
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The one spelling of an address in this space: `type:shortcode`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* shortcode is used **verbatim**, not lowercased: `readItemAddresses` reads it
|
|
8
|
-
* off the compiled document, where `Tabri` is stored as authored, and folding
|
|
9
|
-
* case here would join two addresses the packs keep apart.
|
|
4
|
+
* Used for addresses read from compiled documents. The shortcode is used
|
|
5
|
+
* **verbatim**, not lowercased: folding case would join two addresses the packs
|
|
6
|
+
* keep apart.
|
|
10
7
|
*
|
|
11
8
|
* @param {string} type - The Foundry document subtype, not the note type.
|
|
12
9
|
* @param {string} shortcode - The address's `system.shortcode`.
|
|
@@ -39,89 +36,24 @@ export function readItemAddresses(dirs: readonly string[]): Map<string, {
|
|
|
39
36
|
shortcode: string;
|
|
40
37
|
file: string;
|
|
41
38
|
}>;
|
|
42
|
-
/**
|
|
43
|
-
* Every rename the tree's notes **declare**, as old address → where it went.
|
|
44
|
-
*
|
|
45
|
-
* Read from the content tree rather than from compiled output, because a
|
|
46
|
-
* declaration is authored and the compiled document does not carry it: nothing
|
|
47
|
-
* downstream consumes `renamedFrom:`, so emitting it into every pack to let one
|
|
48
|
-
* diagnostic read it back would put a build-time note in shipped data forever.
|
|
49
|
-
* The tree is already read by this module for the same reason
|
|
50
|
-
* ({@link noteFilesById}) — to place a finding where its author can fix it.
|
|
51
|
-
*
|
|
52
|
-
* **A declaration is keyed by document subtype, not by note type.** The address
|
|
53
|
-
* space is the one consumers resolve against, and it is spelled in compiled
|
|
54
|
-
* documents: `hm3` compiles a `projectile` note into a `missilegear` item,
|
|
55
|
-
* so that is the address a rename of it moves. {@link referencedSubtype} is the
|
|
56
|
-
* function that already answers this for a being's embedded `(type, shortcode)`
|
|
57
|
-
* references, so both sides read the same rule rather than a second copy of it.
|
|
58
|
-
*
|
|
59
|
-
* **An entry is emitted for every system that maps the type**, whether or not
|
|
60
|
-
* the note declares that system's block. Over-emitting is inert — the diff uses
|
|
61
|
-
* an entry only when the baseline published the old address *and* this build
|
|
62
|
-
* publishes the new one, and a system the note does not compile for satisfies
|
|
63
|
-
* neither — while asking which blocks a note declares would put a second,
|
|
64
|
-
* subtly different answer to that question in a third place.
|
|
65
|
-
*
|
|
66
|
-
* **First claim wins on a collision.** Two notes naming one predecessor is a
|
|
67
|
-
* contradiction — an address has one successor — and it is reported as an error
|
|
68
|
-
* by `engine/content-lint.mjs`, where both notes are in hand and can both be
|
|
69
|
-
* named. Picking one here keeps this a map; it is not a resolution, and nothing
|
|
70
|
-
* rests on which one it picked.
|
|
71
|
-
*
|
|
72
|
-
* @param {string} contentBase - Root of the content tree.
|
|
73
|
-
* @param {object} opts
|
|
74
|
-
* @param {readonly string[]} [opts.skipDirectories] - The corpus scope. Stated
|
|
75
|
-
* by the caller, never defaulted — see {@link addressCorpus}.
|
|
76
|
-
* @param {readonly object[]} [opts.maps] - The document-subtype maps.
|
|
77
|
-
* @param {object} [opts.config] - The resolved build configuration.
|
|
78
|
-
* @param {readonly object[]} [opts.records] - Index records the caller already
|
|
79
|
-
* derived, shared with {@link noteFilesById} so one command reads one corpus.
|
|
80
|
-
* @param {object[]} [opts.problems] - Collects the notes the index cannot
|
|
81
|
-
* record, so one of them does not abort the diff before it reports.
|
|
82
|
-
* @returns {Map<string, {to: string, file: string, shortcode: string}>} Old
|
|
83
|
-
* address → the address the declaring note publishes at now, and that note.
|
|
84
|
-
*/
|
|
85
|
-
export function declaredPredecessors(contentBase: string, { skipDirectories, maps, config, records, problems }?: {
|
|
86
|
-
skipDirectories?: readonly string[] | undefined;
|
|
87
|
-
maps?: readonly object[] | undefined;
|
|
88
|
-
config?: object | undefined;
|
|
89
|
-
records?: readonly object[] | undefined;
|
|
90
|
-
problems?: object[] | undefined;
|
|
91
|
-
}): Map<string, {
|
|
92
|
-
to: string;
|
|
93
|
-
file: string;
|
|
94
|
-
shortcode: string;
|
|
95
|
-
}>;
|
|
96
39
|
/**
|
|
97
40
|
* Every address the baseline published that this build does not.
|
|
98
41
|
*
|
|
99
|
-
* An address that
|
|
100
|
-
*
|
|
101
|
-
*
|
|
42
|
+
* An address that arrived is not a finding. A departed address is a rename
|
|
43
|
+
* only when its document id appears at another current address; otherwise it
|
|
44
|
+
* is a withdrawal.
|
|
102
45
|
*
|
|
103
46
|
* @param {Map<string, object>} baseline - The released address space.
|
|
104
47
|
* @param {Map<string, object>} current - This build's address space.
|
|
105
48
|
* @param {object} opts
|
|
106
|
-
* @param {string} opts.baseline - What the baseline is, for the message
|
|
107
|
-
*
|
|
108
|
-
* @param {Map<string, {to: string, file: string}>} [opts.predecessors] - The
|
|
109
|
-
* declared renames, from {@link declaredPredecessors}. Omitted, the diff
|
|
110
|
-
* falls back to the id join alone and reports an unpinned rename as a
|
|
111
|
-
* withdrawal, which is what it did before.
|
|
112
|
-
* @returns {Array<object>} One finding per departed address, in address order
|
|
113
|
-
* so two runs read the same. `kind` is `"renamed"` (with `to`, and `declared`
|
|
114
|
-
* when it was the note's word rather than an id match) or `"withdrawn"`.
|
|
49
|
+
* @param {string} opts.baseline - What the baseline is, for the message.
|
|
50
|
+
* @returns {Array<object>} One finding per departed address, in address order.
|
|
115
51
|
*/
|
|
116
|
-
export function diffItemAddresses(baseline: Map<string, object>, current: Map<string, object>, { baseline: label
|
|
52
|
+
export function diffItemAddresses(baseline: Map<string, object>, current: Map<string, object>, { baseline: label }: {
|
|
117
53
|
baseline: string;
|
|
118
|
-
predecessors?: Map<string, {
|
|
119
|
-
to: string;
|
|
120
|
-
file: string;
|
|
121
|
-
}> | undefined;
|
|
122
54
|
}): Array<object>;
|
|
123
55
|
/**
|
|
124
|
-
* Every content note in a tree, indexed by the document id it
|
|
56
|
+
* Every content note in a tree, indexed by the document id it compiles under.
|
|
125
57
|
*
|
|
126
58
|
* The address space is read from compiled output because that is what actually
|
|
127
59
|
* ships; the tree is read only to place a finding somewhere a reader can open
|
|
@@ -135,7 +67,7 @@ export function diffItemAddresses(baseline: Map<string, object>, current: Map<st
|
|
|
135
67
|
* @param {object} [opts.config] - The resolved build configuration, which the
|
|
136
68
|
* id is derived against. See {@link addressCorpus} for why that matters.
|
|
137
69
|
* @param {readonly object[]} [opts.records] - Index records the caller already
|
|
138
|
-
* derived
|
|
70
|
+
* derived by the caller.
|
|
139
71
|
* @param {object[]} [opts.problems] - Collects the notes the index cannot
|
|
140
72
|
* record, so one of them does not abort the diff before it reports.
|
|
141
73
|
* @returns {Map<string, string>} Document id → the note's absolute path.
|
|
@@ -149,22 +81,13 @@ export function noteFilesById(contentBase: string, { skipDirectories, config, re
|
|
|
149
81
|
/**
|
|
150
82
|
* Where to send the reader for one finding.
|
|
151
83
|
*
|
|
152
|
-
* A
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
* the baseline document, which is the only artefact left that records the
|
|
156
|
-
* address existing. When neither is readable the position is **dropped**, never
|
|
157
|
-
* defaulted to `1:1`.
|
|
158
|
-
*
|
|
159
|
-
* A **declared** rename knows its note without any lookup — the declaration is
|
|
160
|
-
* how it was found — and is reported at the `renamedFrom:` line rather than the
|
|
161
|
-
* `shortcode:` line, because that is the line the finding is about and the one
|
|
162
|
-
* the author deletes once the declaration has done its work.
|
|
84
|
+
* A matched document is located at its current `shortcode:` line. A withdrawal
|
|
85
|
+
* is located in the baseline artifact, the remaining evidence for its address.
|
|
86
|
+
* When neither source has a usable position, the location is dropped.
|
|
163
87
|
*
|
|
164
88
|
* @param {object} finding - One finding from {@link diffItemAddresses}.
|
|
165
89
|
* @param {Map<string, string>} noteFiles - From {@link noteFilesById}.
|
|
166
|
-
* @returns {{file?: string, line?: number, column?: number}}
|
|
167
|
-
* position fields for {@link formatDiagnostic}.
|
|
90
|
+
* @returns {{file?: string, line?: number, column?: number}} Position fields.
|
|
168
91
|
*/
|
|
169
92
|
export function locateAddressFinding(finding: object, noteFiles: Map<string, string>): {
|
|
170
93
|
file?: string;
|
|
@@ -174,21 +97,9 @@ export function locateAddressFinding(finding: object, noteFiles: Map<string, str
|
|
|
174
97
|
/**
|
|
175
98
|
* What one finding says, without a locator or a severity.
|
|
176
99
|
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
* known — and says so, rather than leaving the reader to wonder whether one was
|
|
181
|
-
* looked for.
|
|
182
|
-
*
|
|
183
|
-
* **A declared rename says it is declared**. The two claims are not
|
|
184
|
-
* equally checkable: an id match is a fact in the artefacts, while a
|
|
185
|
-
* declaration is an author's word, and a reader deciding whether to trust the
|
|
186
|
-
* successor needs to know which one they have. Saying "the same document" of a
|
|
187
|
-
* declared rename would spend the id match's credibility on it.
|
|
188
|
-
*
|
|
189
|
-
* The consequence sentence is the same for both, because it is the same
|
|
190
|
-
* consequence: the old address stops resolving either way, and that is what
|
|
191
|
-
* the reader has to act on.
|
|
100
|
+
* A matched document names the address where it now appears. A withdrawal
|
|
101
|
+
* names no successor because the current build publishes no document with
|
|
102
|
+
* that identity.
|
|
192
103
|
*
|
|
193
104
|
* @param {object} finding - One finding from {@link diffItemAddresses}.
|
|
194
105
|
* @returns {string} The message.
|
|
@@ -142,14 +142,13 @@ export function artPathname(index: object, value: unknown, defaultType: string,
|
|
|
142
142
|
type?: string;
|
|
143
143
|
};
|
|
144
144
|
/**
|
|
145
|
-
* The default art address a being's
|
|
145
|
+
* The default art address a being's subtype chooses, or `null`.
|
|
146
146
|
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
* lookup rather than a precedence rule.
|
|
147
|
+
* An NPC and a character use person art; a creature uses creature art. The
|
|
148
|
+
* tag lookup preserves defaults for package trees that have not migrated.
|
|
150
149
|
*
|
|
151
150
|
* @param {object} fm - The note's frontmatter.
|
|
152
|
-
* @returns {string|null} The address, or `null`
|
|
151
|
+
* @returns {string|null} The address, or `null` when no subtype or legacy tag applies.
|
|
153
152
|
*/
|
|
154
153
|
export function beingDefaultArt(fm: object): string | null;
|
|
155
154
|
/**
|
|
@@ -12,6 +12,20 @@
|
|
|
12
12
|
* @returns {boolean} Whether it is acceptable.
|
|
13
13
|
*/
|
|
14
14
|
export function matchesKind(value: unknown, kind: string, context?: object): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Resolve system-block addresses declared by the system's own field schema.
|
|
17
|
+
* @param {object} note - A note from the link index.
|
|
18
|
+
* @param {object} opts
|
|
19
|
+
* @param {object} opts.index - The address index.
|
|
20
|
+
* @param {Record<string, readonly object[]>} [opts.schemas] - Note schemas.
|
|
21
|
+
* @param {Readonly<Record<string, object>>} [opts.systems] - Declared system blocks.
|
|
22
|
+
* @returns {object[]} Located findings for unresolved addresses.
|
|
23
|
+
*/
|
|
24
|
+
export function systemAddressFindings(note: object, { index, schemas, systems }?: {
|
|
25
|
+
index: object;
|
|
26
|
+
schemas?: Record<string, readonly object[]> | undefined;
|
|
27
|
+
systems?: Readonly<Record<string, object>> | undefined;
|
|
28
|
+
}): object[];
|
|
15
29
|
/**
|
|
16
30
|
* Check one note against its type's schema.
|
|
17
31
|
*
|
|
@@ -14,7 +14,8 @@ export function renderFoundryMarkdown(body: any, captions: any, footnoteNumbers:
|
|
|
14
14
|
* @param {string} filePath - Markdown file to read.
|
|
15
15
|
* @param {{addressContext?: object}} [opts] - Address defaults for decoded fields.
|
|
16
16
|
* @returns {{frontmatter: object|null, body: string, description: string,
|
|
17
|
-
* bodyLine?: number, bodyColumn?: number
|
|
17
|
+
* bodyLine?: number, bodyColumn?: number, parseError?: {message: string,
|
|
18
|
+
* line?: number, column?: number}}} Parsed note and body position.
|
|
18
19
|
*/
|
|
19
20
|
export function parseMarkdownFile(filePath: string, { addressContext }?: {
|
|
20
21
|
addressContext?: object;
|
|
@@ -24,6 +25,11 @@ export function parseMarkdownFile(filePath: string, { addressContext }?: {
|
|
|
24
25
|
description: string;
|
|
25
26
|
bodyLine?: number;
|
|
26
27
|
bodyColumn?: number;
|
|
28
|
+
parseError?: {
|
|
29
|
+
message: string;
|
|
30
|
+
line?: number;
|
|
31
|
+
column?: number;
|
|
32
|
+
};
|
|
27
33
|
};
|
|
28
34
|
/**
|
|
29
35
|
* Refuse a corpus read whose scope its caller did not state.
|
|
@@ -98,6 +104,11 @@ export function walkMarkdownTree(rootDir: string, { skipDirectories, addressCont
|
|
|
98
104
|
description: string;
|
|
99
105
|
bodyLine?: number;
|
|
100
106
|
bodyColumn?: number;
|
|
107
|
+
parseError?: {
|
|
108
|
+
message: string;
|
|
109
|
+
line?: number;
|
|
110
|
+
column?: number;
|
|
111
|
+
};
|
|
101
112
|
}, void, unknown>;
|
|
102
113
|
/**
|
|
103
114
|
* The template priority a note states, for a system that treats an unstated one
|
package/types/engine/index.d.mts
CHANGED
|
@@ -47,7 +47,6 @@ export * as infobox from "./infobox.mjs";
|
|
|
47
47
|
export * as infoboxRegistry from "./infobox-registry.mjs";
|
|
48
48
|
export * as infoboxRender from "./infobox-render.mjs";
|
|
49
49
|
export * as noteIds from "./note-ids.mjs";
|
|
50
|
-
export * as noteRenames from "./note-renames.mjs";
|
|
51
50
|
export * as metadataIndex from "./metadata-index.mjs";
|
|
52
51
|
export * as foundryEntries from "./foundry-entries.mjs";
|
|
53
52
|
export * as contentIndex from "./content-index.mjs";
|
|
@@ -233,15 +233,6 @@ export const DECLARED_TAGS: Readonly<{
|
|
|
233
233
|
types: string[];
|
|
234
234
|
tags: readonly string[];
|
|
235
235
|
}>;
|
|
236
|
-
/**
|
|
237
|
-
* What kind of being this is — a person, or one of the beasts and made
|
|
238
|
-
* things. A being is one or the other, so the group is a slot.
|
|
239
|
-
*/
|
|
240
|
-
beingKind: Readonly<{
|
|
241
|
-
types: string[];
|
|
242
|
-
exclusive: "kind";
|
|
243
|
-
tags: readonly string[];
|
|
244
|
-
}>;
|
|
245
236
|
/** A note's working state, which any note may carry. */
|
|
246
237
|
state: Readonly<{
|
|
247
238
|
types: null;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The map one system ships, by its id.
|
|
3
3
|
*
|
|
4
|
-
* @param {string|undefined} system - The system id
|
|
4
|
+
* @param {string|undefined} system - The system id.
|
|
5
5
|
* @returns {import("./document-subtypes.mjs").DocumentSubtypeMap|undefined} Its
|
|
6
6
|
* map, or `undefined` where this toolchain ships none for it.
|
|
7
7
|
*/
|
|
@@ -29,10 +29,6 @@ export function schemaSubtypeOf(system: string | undefined, type: string): strin
|
|
|
29
29
|
/**
|
|
30
30
|
* The note-type → document-subtype maps this toolchain ships.
|
|
31
31
|
*
|
|
32
|
-
* Two, since `hm3/` landed — and it joined this list rather than the
|
|
33
|
-
* claim table growing a second copy of the same fact, which is what the list
|
|
34
|
-
* was for.
|
|
35
|
-
*
|
|
36
32
|
* The union is what makes the vocabulary wider than any one repository's
|
|
37
33
|
* configuration: `armorlocation` is a real content type because HM3 maps it,
|
|
38
34
|
* however a given repository is configured, so a tree full of them is a
|
|
@@ -71,7 +67,7 @@ export const KNOWN_DOCUMENT_SUBTYPE_MAPS: readonly import("./document-subtypes.m
|
|
|
71
67
|
*/
|
|
72
68
|
export const DEFAULT_DOCUMENT_SUBTYPES: import("./document-subtypes.mjs").DocumentSubtypeMap;
|
|
73
69
|
/**
|
|
74
|
-
* Every system
|
|
70
|
+
* Every system identifier with declared system-specific document types.
|
|
75
71
|
*
|
|
76
72
|
* Derived from {@link KNOWN_DOCUMENT_SUBTYPE_MAPS}, so registering a map is the
|
|
77
73
|
* whole of adding a system to every reader that asks "which systems are there?"
|
|
@@ -28,8 +28,8 @@ export function isSystemSegment(value: unknown): boolean;
|
|
|
28
28
|
*
|
|
29
29
|
* The nullish case is called out by name because it is the likely mistake and
|
|
30
30
|
* the least legible failure: a YAML `null`, a `~`, or a key written with no
|
|
31
|
-
* value at all reaches here as `undefined`, and
|
|
32
|
-
*
|
|
31
|
+
* value at all reaches here as `undefined`, and a list of accepted values
|
|
32
|
+
* would leave a reader hunting for the value they cannot see.
|
|
33
33
|
*
|
|
34
34
|
* @param {unknown} value - The offending value.
|
|
35
35
|
* @param {string} [where] - What carried it, for the message — a note path, a
|
|
@@ -83,11 +83,11 @@ export const NO_SYSTEM: string;
|
|
|
83
83
|
/** The Address segment for a note's readable content. */
|
|
84
84
|
export const NOTE_SYSTEM: "note";
|
|
85
85
|
/**
|
|
86
|
-
* Every game system this toolchain
|
|
86
|
+
* Every game system identifier this toolchain recognizes.
|
|
87
87
|
*
|
|
88
|
-
* The registry, and the only place the list is written down.
|
|
89
|
-
*
|
|
90
|
-
*
|
|
88
|
+
* The registry, and the only place the list is written down. A recognized id
|
|
89
|
+
* can have no system-specific document types; adding one of those requires a
|
|
90
|
+
* subtype map and compiler as well.
|
|
91
91
|
*
|
|
92
92
|
* `none` is **not** a member: it marks the absence of a system, and a caller
|
|
93
93
|
* asking "is this a system" and a caller asking "is this a well-formed
|
package/engine/note-renames.mjs
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
|
|
3
|
-
* Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
|
|
4
|
-
*
|
|
5
|
-
* This work is licensed under the GNU General Public License v3.0 (GPLv3).
|
|
6
|
-
* You may copy, modify, and distribute it under the terms of that license.
|
|
7
|
-
*
|
|
8
|
-
* For full terms, see the LICENSE.md file in the project root or visit:
|
|
9
|
-
* https://www.gnu.org/licenses/gpl-3.0.html
|
|
10
|
-
*
|
|
11
|
-
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* The shortcodes a note declares it used to be published under.
|
|
16
|
-
*
|
|
17
|
-
* A package's `(type, shortcode)` addresses are a published interface, and
|
|
18
|
-
* `addresses diff` reports what a build stopped publishing — telling a
|
|
19
|
-
* **rename** from a **withdrawal** by matching document ids across two
|
|
20
|
-
* releases. The property that rested on is gone: an id is derived from
|
|
21
|
-
* the canonical address, which carries the shortcode, so renaming a shortcode
|
|
22
|
-
* moves the id too: both sides of the join move together, the match finds
|
|
23
|
-
* nothing, and a rename is reported as a withdrawal with no successor named.
|
|
24
|
-
*
|
|
25
|
-
* **The remaining lever needed foresight, which is the wrong thing to ask for.**
|
|
26
|
-
* An authored `id:` still wins, and pinning one does keep a document's identity
|
|
27
|
-
* across a rename — but it has to be written *before* the rename, by an author
|
|
28
|
-
* who does not yet know they will make one. An author who has just renamed a
|
|
29
|
-
* shortcode knows exactly what the old one was, and that is the only moment
|
|
30
|
-
* anyone does.
|
|
31
|
-
*
|
|
32
|
-
* So a note states it:
|
|
33
|
-
*
|
|
34
|
-
* ```yaml
|
|
35
|
-
* type: weapongear
|
|
36
|
-
* shortcode: Taburi
|
|
37
|
-
* renamedFrom: Tabri
|
|
38
|
-
* ```
|
|
39
|
-
*
|
|
40
|
-
* **A declaration is an assertion, not a match, and the diagnostic says which
|
|
41
|
-
* it had.** That distinction is the whole ethos of `address-diff.mjs`: it
|
|
42
|
-
* refuses to guess a successor from a similar-looking string, because a wrong
|
|
43
|
-
* one sends the reader to the wrong fix. An author's declaration is neither a
|
|
44
|
-
* guess nor an identity match — it is testimony from the one party that knows —
|
|
45
|
-
* so it is used, attributed, and never silently blended with an id match.
|
|
46
|
-
*
|
|
47
|
-
* **It is a list, because renames chain.** The diff is release-to-release and a
|
|
48
|
-
* shortcode may be renamed more than once between two releases; a baseline
|
|
49
|
-
* several releases back published a name two renames ago. A single value would
|
|
50
|
-
* report that as a withdrawal again, which is the bug this closes.
|
|
51
|
-
*
|
|
52
|
-
* **It is transient.** Once every baseline a build is compared against post-dates
|
|
53
|
-
* the rename, the declaration has nothing left to say and may be deleted. That
|
|
54
|
-
* is the difference from an `id:` pin, which is permanent, opaque, and a second
|
|
55
|
-
* identity for a thing that already has one.
|
|
56
|
-
*
|
|
57
|
-
* **It is one key per note, at the top level, however many systems the note
|
|
58
|
-
* compiles into.** A shortcode is the note's, not a system block's: a note
|
|
59
|
-
* carrying `sohl:` and `hm3:` blocks compiles into two documents that share one
|
|
60
|
-
* shortcode, so a rename moves both and one declaration covers both.
|
|
61
|
-
*
|
|
62
|
-
* This module reads the key and says nothing about whether the values are
|
|
63
|
-
* *sound* — that a value is a well-formed shortcode, is not the note's own, and
|
|
64
|
-
* does not name an address some live note still publishes are cross-note
|
|
65
|
-
* questions, and `engine/content-lint.mjs` owns them. Composing a value into an
|
|
66
|
-
* address key is likewise not here: the diff defines that space and spells the
|
|
67
|
-
* key once, in `engine/address-diff.mjs`.
|
|
68
|
-
*
|
|
69
|
-
* @module
|
|
70
|
-
*/
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* The `renamedFrom:` entries a note authors, exactly as authored.
|
|
74
|
-
*
|
|
75
|
-
* Scalar or list, because one predecessor is the overwhelmingly common case and
|
|
76
|
-
* requiring `- ` on it would be friction with nothing behind it; both normalize
|
|
77
|
-
* here so no reader has to ask which form it got.
|
|
78
|
-
*
|
|
79
|
-
* Entries are returned **unvalidated** — a number, a nested list, a blank
|
|
80
|
-
* string all come back as they were written. The lint needs to see them to
|
|
81
|
-
* report them, and a reader that quietly dropped them would report a correct
|
|
82
|
-
* tree clean while a typo silently did nothing.
|
|
83
|
-
*
|
|
84
|
-
* @param {object|null|undefined} fm - Parsed frontmatter.
|
|
85
|
-
* @returns {readonly unknown[]} The authored entries, in authored order; empty
|
|
86
|
-
* when the note declares none.
|
|
87
|
-
*/
|
|
88
|
-
export function renamedFromEntries(fm) {
|
|
89
|
-
if (!fm || typeof fm !== "object") return [];
|
|
90
|
-
const raw = /** @type {{renamedFrom?: unknown}} */ (fm).renamedFrom;
|
|
91
|
-
if (raw == null) return [];
|
|
92
|
-
return Array.isArray(raw) ? raw : [raw];
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* The well-formed shortcodes among a note's `renamedFrom:` entries.
|
|
97
|
-
*
|
|
98
|
-
* Trimmed, de-duplicated, and in authored order. Anything that is not a
|
|
99
|
-
* non-blank string is skipped rather than coerced: it is reported by the lint,
|
|
100
|
-
* and a diff that guessed at what a number meant would claim a rename nobody
|
|
101
|
-
* declared.
|
|
102
|
-
*
|
|
103
|
-
* De-duplicating here rather than leaving it to callers is not tidiness — the
|
|
104
|
-
* predecessor index is a map, so a repeated entry would otherwise be indexed
|
|
105
|
-
* twice and the second write would look like a conflicting claim.
|
|
106
|
-
*
|
|
107
|
-
* @param {object|null|undefined} fm - Parsed frontmatter.
|
|
108
|
-
* @returns {string[]} The declared predecessor shortcodes.
|
|
109
|
-
*/
|
|
110
|
-
export function renamedFrom(fm) {
|
|
111
|
-
const seen = new Set();
|
|
112
|
-
for (const entry of renamedFromEntries(fm)) {
|
|
113
|
-
if (typeof entry !== "string") continue;
|
|
114
|
-
const trimmed = entry.trim();
|
|
115
|
-
if (trimmed) seen.add(trimmed);
|
|
116
|
-
}
|
|
117
|
-
return [...seen];
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Whether a note declares the key at all, however malformed its value.
|
|
122
|
-
*
|
|
123
|
-
* Separate from {@link renamedFrom} returning nothing, because the two mean
|
|
124
|
-
* opposite things to a lint: a note declaring no key is silent and correct,
|
|
125
|
-
* while one declaring `renamedFrom: []` — or a single blank string — has said
|
|
126
|
-
* something that does nothing, which is worth a word.
|
|
127
|
-
*
|
|
128
|
-
* @param {object|null|undefined} fm - Parsed frontmatter.
|
|
129
|
-
* @returns {boolean} `true` when the key is present and not null.
|
|
130
|
-
*/
|
|
131
|
-
export function declaresRenamedFrom(fm) {
|
|
132
|
-
if (!fm || typeof fm !== "object") return false;
|
|
133
|
-
return /** @type {{renamedFrom?: unknown}} */ (fm).renamedFrom != null;
|
|
134
|
-
}
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The shortcodes a note declares it used to be published under.
|
|
3
|
-
*
|
|
4
|
-
* A package's `(type, shortcode)` addresses are a published interface, and
|
|
5
|
-
* `addresses diff` reports what a build stopped publishing — telling a
|
|
6
|
-
* **rename** from a **withdrawal** by matching document ids across two
|
|
7
|
-
* releases. The property that rested on is gone: an id is derived from
|
|
8
|
-
* the canonical address, which carries the shortcode, so renaming a shortcode
|
|
9
|
-
* moves the id too: both sides of the join move together, the match finds
|
|
10
|
-
* nothing, and a rename is reported as a withdrawal with no successor named.
|
|
11
|
-
*
|
|
12
|
-
* **The remaining lever needed foresight, which is the wrong thing to ask for.**
|
|
13
|
-
* An authored `id:` still wins, and pinning one does keep a document's identity
|
|
14
|
-
* across a rename — but it has to be written *before* the rename, by an author
|
|
15
|
-
* who does not yet know they will make one. An author who has just renamed a
|
|
16
|
-
* shortcode knows exactly what the old one was, and that is the only moment
|
|
17
|
-
* anyone does.
|
|
18
|
-
*
|
|
19
|
-
* So a note states it:
|
|
20
|
-
*
|
|
21
|
-
* ```yaml
|
|
22
|
-
* type: weapongear
|
|
23
|
-
* shortcode: Taburi
|
|
24
|
-
* renamedFrom: Tabri
|
|
25
|
-
* ```
|
|
26
|
-
*
|
|
27
|
-
* **A declaration is an assertion, not a match, and the diagnostic says which
|
|
28
|
-
* it had.** That distinction is the whole ethos of `address-diff.mjs`: it
|
|
29
|
-
* refuses to guess a successor from a similar-looking string, because a wrong
|
|
30
|
-
* one sends the reader to the wrong fix. An author's declaration is neither a
|
|
31
|
-
* guess nor an identity match — it is testimony from the one party that knows —
|
|
32
|
-
* so it is used, attributed, and never silently blended with an id match.
|
|
33
|
-
*
|
|
34
|
-
* **It is a list, because renames chain.** The diff is release-to-release and a
|
|
35
|
-
* shortcode may be renamed more than once between two releases; a baseline
|
|
36
|
-
* several releases back published a name two renames ago. A single value would
|
|
37
|
-
* report that as a withdrawal again, which is the bug this closes.
|
|
38
|
-
*
|
|
39
|
-
* **It is transient.** Once every baseline a build is compared against post-dates
|
|
40
|
-
* the rename, the declaration has nothing left to say and may be deleted. That
|
|
41
|
-
* is the difference from an `id:` pin, which is permanent, opaque, and a second
|
|
42
|
-
* identity for a thing that already has one.
|
|
43
|
-
*
|
|
44
|
-
* **It is one key per note, at the top level, however many systems the note
|
|
45
|
-
* compiles into.** A shortcode is the note's, not a system block's: a note
|
|
46
|
-
* carrying `sohl:` and `hm3:` blocks compiles into two documents that share one
|
|
47
|
-
* shortcode, so a rename moves both and one declaration covers both.
|
|
48
|
-
*
|
|
49
|
-
* This module reads the key and says nothing about whether the values are
|
|
50
|
-
* *sound* — that a value is a well-formed shortcode, is not the note's own, and
|
|
51
|
-
* does not name an address some live note still publishes are cross-note
|
|
52
|
-
* questions, and `engine/content-lint.mjs` owns them. Composing a value into an
|
|
53
|
-
* address key is likewise not here: the diff defines that space and spells the
|
|
54
|
-
* key once, in `engine/address-diff.mjs`.
|
|
55
|
-
*
|
|
56
|
-
* @module
|
|
57
|
-
*/
|
|
58
|
-
/**
|
|
59
|
-
* The `renamedFrom:` entries a note authors, exactly as authored.
|
|
60
|
-
*
|
|
61
|
-
* Scalar or list, because one predecessor is the overwhelmingly common case and
|
|
62
|
-
* requiring `- ` on it would be friction with nothing behind it; both normalize
|
|
63
|
-
* here so no reader has to ask which form it got.
|
|
64
|
-
*
|
|
65
|
-
* Entries are returned **unvalidated** — a number, a nested list, a blank
|
|
66
|
-
* string all come back as they were written. The lint needs to see them to
|
|
67
|
-
* report them, and a reader that quietly dropped them would report a correct
|
|
68
|
-
* tree clean while a typo silently did nothing.
|
|
69
|
-
*
|
|
70
|
-
* @param {object|null|undefined} fm - Parsed frontmatter.
|
|
71
|
-
* @returns {readonly unknown[]} The authored entries, in authored order; empty
|
|
72
|
-
* when the note declares none.
|
|
73
|
-
*/
|
|
74
|
-
export function renamedFromEntries(fm: object | null | undefined): readonly unknown[];
|
|
75
|
-
/**
|
|
76
|
-
* The well-formed shortcodes among a note's `renamedFrom:` entries.
|
|
77
|
-
*
|
|
78
|
-
* Trimmed, de-duplicated, and in authored order. Anything that is not a
|
|
79
|
-
* non-blank string is skipped rather than coerced: it is reported by the lint,
|
|
80
|
-
* and a diff that guessed at what a number meant would claim a rename nobody
|
|
81
|
-
* declared.
|
|
82
|
-
*
|
|
83
|
-
* De-duplicating here rather than leaving it to callers is not tidiness — the
|
|
84
|
-
* predecessor index is a map, so a repeated entry would otherwise be indexed
|
|
85
|
-
* twice and the second write would look like a conflicting claim.
|
|
86
|
-
*
|
|
87
|
-
* @param {object|null|undefined} fm - Parsed frontmatter.
|
|
88
|
-
* @returns {string[]} The declared predecessor shortcodes.
|
|
89
|
-
*/
|
|
90
|
-
export function renamedFrom(fm: object | null | undefined): string[];
|
|
91
|
-
/**
|
|
92
|
-
* Whether a note declares the key at all, however malformed its value.
|
|
93
|
-
*
|
|
94
|
-
* Separate from {@link renamedFrom} returning nothing, because the two mean
|
|
95
|
-
* opposite things to a lint: a note declaring no key is silent and correct,
|
|
96
|
-
* while one declaring `renamedFrom: []` — or a single blank string — has said
|
|
97
|
-
* something that does nothing, which is worth a word.
|
|
98
|
-
*
|
|
99
|
-
* @param {object|null|undefined} fm - Parsed frontmatter.
|
|
100
|
-
* @returns {boolean} `true` when the key is present and not null.
|
|
101
|
-
*/
|
|
102
|
-
export function declaresRenamedFrom(fm: object | null | undefined): boolean;
|