@heroiclands/package-build 6.1.0 → 8.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.
Files changed (41) hide show
  1. package/CHANGELOG.md +840 -0
  2. package/CONTENT.md +21 -1
  3. package/bin/content-build.mjs +105 -10
  4. package/bin/package-build.mjs +114 -1
  5. package/config.mjs +62 -3
  6. package/content-config.mjs +254 -22
  7. package/engine/base-compiler.mjs +25 -0
  8. package/engine/content-links.mjs +132 -27
  9. package/engine/diagnostics.mjs +61 -1
  10. package/engine/foreign-catalog.mjs +47 -0
  11. package/engine/generate.mjs +10 -5
  12. package/engine/helpers.mjs +38 -0
  13. package/engine/journals.mjs +8 -1
  14. package/engine/macros.mjs +2 -0
  15. package/engine/pack-config.mjs +143 -13
  16. package/engine/prose-lint.mjs +10 -2
  17. package/engine/scenes.mjs +2 -2
  18. package/engine/schema-check.mjs +332 -0
  19. package/engine/schema-extract.mjs +611 -0
  20. package/engine/web-wikilinks.mjs +13 -4
  21. package/engine/wikilink-syntax.mjs +25 -0
  22. package/engine/wikilinks.mjs +6 -3
  23. package/manifest.mjs +37 -2
  24. package/package.json +5 -3
  25. package/sohl/actors.mjs +23 -10
  26. package/sohl/item-fields.mjs +0 -35
  27. package/sohl/items.mjs +1 -1
  28. package/types/content-config.d.mts +14 -0
  29. package/types/engine/base-compiler.d.mts +18 -1
  30. package/types/engine/content-links.d.mts +11 -3
  31. package/types/engine/diagnostics.d.mts +33 -1
  32. package/types/engine/foreign-catalog.d.mts +15 -0
  33. package/types/engine/generate.d.mts +3 -2
  34. package/types/engine/helpers.d.mts +29 -3
  35. package/types/engine/journals.d.mts +7 -1
  36. package/types/engine/pack-config.d.mts +22 -0
  37. package/types/engine/prose-lint.d.mts +10 -2
  38. package/types/engine/schema-check.d.mts +176 -0
  39. package/types/engine/schema-extract.d.mts +61 -0
  40. package/types/engine/wikilink-syntax.d.mts +24 -0
  41. package/types/sohl/actors.d.mts +3 -3
@@ -0,0 +1,176 @@
1
+ /**
2
+ * A system's published field sets.
3
+ *
4
+ * @typedef {object} SchemaArtifact
5
+ * @property {number} version - {@link SCHEMA_ARTIFACT_VERSION}.
6
+ * @property {string} system - The system id the schemas belong to.
7
+ * @property {string} systemVersion - The system version they were read from.
8
+ * @property {Record<string, Record<string, {own: string[], inherited: string[]}>>} documents
9
+ * Document type → subtype → its field paths, dotted for nested schema fields.
10
+ */
11
+ /**
12
+ * Why `own` and `inherited` are recorded apart.
13
+ *
14
+ * A subtype's schema spreads its parent's — `MysticalAbilityDataModel` spreads
15
+ * `SohlItemDataModel`, which spreads the common one — so `notes`, `docHtml` and
16
+ * the rest arrive on every subtype. Those are the system's own runtime
17
+ * concerns, filled by the system rather than by a content builder, and a
18
+ * builder is not expected to emit them.
19
+ *
20
+ * Collapsing the two sets would make the *declared, not emitted* direction
21
+ * report every inherited field on every type: a wall of findings that are all
22
+ * correct and none actionable, which is the shape of report people learn to
23
+ * skip. So the two directions read different sets:
24
+ *
25
+ * | direction | read against | severity |
26
+ * | --- | --- | --- |
27
+ * | emitted, not declared | `own` ∪ `inherited` — the field must exist *somewhere* | error |
28
+ * | declared, not emitted | `own` only — what this subtype adds is what its builder answers for | report |
29
+ *
30
+ * @param {SchemaArtifact} artifact - The published schemas.
31
+ * @param {string} documentType - `Item`, `Actor`, …
32
+ * @param {string} subtype - The document subtype.
33
+ * @returns {{own: Set<string>, all: Set<string>}|null} The sets, or `null` when
34
+ * the artifact declares no such subtype.
35
+ */
36
+ export function declaredFields(artifact: SchemaArtifact, documentType: string, subtype: string): {
37
+ own: Set<string>;
38
+ all: Set<string>;
39
+ } | null;
40
+ /**
41
+ * Every `system` path a field declaration can emit.
42
+ *
43
+ * `buildFromFields` writes each field at its `to`, so the declaration is the
44
+ * emitted key set. A nested `to` (`charges.value`) is recorded whole, and its
45
+ * parents are recorded too: a schema declares `charges` as a `SchemaField` and
46
+ * the path beneath it separately, so a comparison that knew only the leaf would
47
+ * report the container as unemitted and the leaf as undeclared.
48
+ *
49
+ * @param {readonly {to: string}[]} fields - A type's field declaration.
50
+ * @returns {Set<string>} The paths, parents included.
51
+ */
52
+ export function emittedFields(fields: readonly {
53
+ to: string;
54
+ }[]): Set<string>;
55
+ /**
56
+ * Compare one system's builders against one system's published schemas.
57
+ *
58
+ * Pure: field declarations in, findings out. The caller supplies both halves so
59
+ * that a system checking itself and a module checking against a vendored
60
+ * artifact run the identical comparison.
61
+ *
62
+ * @param {object} opts
63
+ * @param {Record<string, readonly {to: string}[]>} opts.builders - Type →
64
+ * field declaration, as `ITEM_FIELDS` holds it.
65
+ * @param {SchemaArtifact} opts.artifact - The receiving system's schemas.
66
+ * @param {string} [opts.documentType="Item"] - Which document type the builders
67
+ * compile into.
68
+ * @param {(type: string) => string} [opts.subtypeOf] - Maps a builder's type to
69
+ * the document subtype it emits. Defaults to identity, which is what the
70
+ * coincidence of names amounts to today (#79) — stated as a seam so that the
71
+ * explicit map replaces a default rather than a hard-coded assumption.
72
+ * @returns {{undeclared: object[], unemitted: object[], skipped: string[]}}
73
+ * `undeclared` fails a build; `unemitted` is reported; `skipped` names the
74
+ * types the artifact says nothing about.
75
+ */
76
+ export function compareFields({ builders, artifact, documentType, subtypeOf, }: {
77
+ builders: Record<string, readonly {
78
+ to: string;
79
+ }[]>;
80
+ artifact: SchemaArtifact;
81
+ documentType?: string | undefined;
82
+ subtypeOf?: ((type: string) => string) | undefined;
83
+ }): {
84
+ undeclared: object[];
85
+ unemitted: object[];
86
+ skipped: string[];
87
+ };
88
+ /**
89
+ * The published schema this build should check itself against, or `null`.
90
+ *
91
+ * **Which system, and which version, are already settled.** `stats.systemId`
92
+ * and `stats.systemVersion` are derived rather than authored (#48) — a system
93
+ * package is its own system, and a module takes the one it requires — and the
94
+ * version is the `compatibility.verified` it pins. So the question "whose
95
+ * schema, at what version" has one answer here rather than a second set of
96
+ * configuration to disagree with the first.
97
+ *
98
+ * Two places to find it, because a system checks itself against source it owns
99
+ * while a module checks against a dependency it fetched:
100
+ *
101
+ * - **A system**: its own `schema.json`, generated from its `src/` and
102
+ * committed beside it.
103
+ * - **A module**: the copy cached by `content-build deps fetch`, from the
104
+ * archive of the version it pins — which is what makes the comparison happen
105
+ * at `verified` rather than against whatever the system's `main` holds today.
106
+ * That distinction is the whole of the `affiliation.subType` case.
107
+ *
108
+ * `null` where there is nothing to check against: a system-agnostic module
109
+ * stamps no system at all, and a system that has not adopted the artifact yet
110
+ * is simply unchecked. Neither is an error, and the caller says which it was.
111
+ *
112
+ * @param {object} config - The resolved build configuration.
113
+ * @returns {{artifact: SchemaArtifact, source: string}|null} The schema and
114
+ * where it was read from.
115
+ */
116
+ export function resolveSchemaArtifact(config: object): {
117
+ artifact: SchemaArtifact;
118
+ source: string;
119
+ } | null;
120
+ /**
121
+ * What an author is told about a field the target system does not define.
122
+ *
123
+ * Names the version, because the same field may be perfectly well defined on
124
+ * the system's `main` and simply unreleased — which is exactly the kethira case,
125
+ * and the difference between "you typed it wrong" and "you are ahead of your
126
+ * pin".
127
+ *
128
+ * @param {object} finding - One entry from `undeclared`.
129
+ * @returns {string} The message.
130
+ */
131
+ export function undeclaredMessage(finding: object): string;
132
+ /**
133
+ * What an author is told about a declared field no builder writes.
134
+ *
135
+ * Advisory rather than fatal: a field the system fills at runtime, or one added
136
+ * ahead of the content that will use it, is not a defect. Only fields the
137
+ * subtype declares *itself* are reported — see {@link declaredFields}.
138
+ *
139
+ * @param {object} finding - One entry from `unemitted`.
140
+ * @returns {string} The message.
141
+ */
142
+ export function unemittedMessage(finding: object): string;
143
+ /**
144
+ * The artifact version this module reads.
145
+ *
146
+ * A mismatch stops the check rather than resolving anyway: a schema read under
147
+ * the wrong shape would report confident nonsense in both directions, and a
148
+ * silently skipped check is the state #60 exists to leave.
149
+ *
150
+ * @type {number}
151
+ */
152
+ export const SCHEMA_ARTIFACT_VERSION: number;
153
+ /**
154
+ * A system's published field sets.
155
+ */
156
+ export type SchemaArtifact = {
157
+ /**
158
+ * - {@link SCHEMA_ARTIFACT_VERSION}.
159
+ */
160
+ version: number;
161
+ /**
162
+ * - The system id the schemas belong to.
163
+ */
164
+ system: string;
165
+ /**
166
+ * - The system version they were read from.
167
+ */
168
+ systemVersion: string;
169
+ /**
170
+ * Document type → subtype → its field paths, dotted for nested schema fields.
171
+ */
172
+ documents: Record<string, Record<string, {
173
+ own: string[];
174
+ inherited: string[];
175
+ }>>;
176
+ };
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The `subtype: ClassName` entries of a registry object literal.
3
+ *
4
+ * @param {ts.SourceFile} src - The parsed file holding the registry.
5
+ * @param {string} name - The registry binding, e.g. `itemModels`.
6
+ * @returns {Map<string, string>} Subtype to DataModel class name.
7
+ */
8
+ export function registryOf(src: ts.SourceFile, name: string): Map<string, string>;
9
+ /**
10
+ * The `tsconfig.json` path aliases, read rather than restated.
11
+ *
12
+ * A TypeScript repository may import every DataModel through `@src/…`, so a
13
+ * resolver that understood only relative specifiers would find none of them.
14
+ * Reading the mapping means adding an alias there does not silently make a
15
+ * subtype unreadable here.
16
+ *
17
+ * Absent for a JavaScript repository, which is not an error — it simply has no
18
+ * aliases to resolve.
19
+ *
20
+ * @param {string} rootDir - The repository root.
21
+ * @returns {[string, string][]} Prefix to directory, longest prefix first.
22
+ */
23
+ export function pathAliases(rootDir: string): [string, string][];
24
+ /**
25
+ * Resolve one subtype's schema into own and inherited field paths.
26
+ *
27
+ * @param {object} opts - Resolution inputs.
28
+ * @param {string} opts.file - The file naming the class (registry or importer).
29
+ * @param {string} opts.className - The DataModel class.
30
+ * @param {[string, string][]} opts.aliases - From {@link pathAliases}.
31
+ * @param {Map<string, ts.SourceFile>} opts.cache - The per-run parse memo.
32
+ * @param {string} opts.rootDir - For readable error paths.
33
+ * @returns {{own: string[], inherited: string[]}}
34
+ */
35
+ export function fieldsForClass({ file, className, aliases, cache, rootDir }: {
36
+ file: string;
37
+ className: string;
38
+ aliases: [string, string][];
39
+ cache: Map<string, ts.SourceFile>;
40
+ rootDir: string;
41
+ }): {
42
+ own: string[];
43
+ inherited: string[];
44
+ };
45
+ /**
46
+ * Build the whole artifact from a package's source.
47
+ *
48
+ * @param {object} opts - Inputs.
49
+ * @param {string} opts.rootDir - The repository root.
50
+ * @param {object[]} opts.registries - `{documentType, from, registry}` entries.
51
+ * @param {string} opts.packageId - The Foundry package id.
52
+ * @param {string} opts.version - The package version.
53
+ * @returns {object} The artifact `schema-check.mjs` reads.
54
+ */
55
+ export function buildSchemaArtifact({ rootDir, registries, packageId, version, }: {
56
+ rootDir: string;
57
+ registries: object[];
58
+ packageId: string;
59
+ version: string;
60
+ }): object;
61
+ import ts from "typescript";
@@ -23,6 +23,30 @@
23
23
  * @returns {ParsedWikilink} The parts, each trimmed.
24
24
  */
25
25
  export function parseWikilink(rawInner: string): ParsedWikilink;
26
+ /**
27
+ * The label an author actually supplied, or `null` when they supplied none.
28
+ *
29
+ * **An empty label is not a label.** `[[x|]]` is deliberately writable — it
30
+ * means "address this target, and show the target's own name" — so `display:
31
+ * ""` has to read as *absent* everywhere a fallback is chosen, exactly as
32
+ * `display: null` does. The two are still distinguishable through
33
+ * {@link ParsedWikilink.labelled}, which is the thing that genuinely differs
34
+ * and which #1409 depends on.
35
+ *
36
+ * Stated here because the two resolvers had already drawn the line in two
37
+ * places and drawn it differently: the packs tested falsiness and were right,
38
+ * the web tested `??` — which falls through on `null` only — and emitted
39
+ * `[](/url/)`, a link with no clickable text, through every build (#113). That
40
+ * is the same drift this module exists to prevent, in the case its own
41
+ * {@link ParsedWikilink} docstring calls out. One reading, one place.
42
+ *
43
+ * @param {{display: string|null}} parsed - A parsed wikilink, or anything
44
+ * carrying its `display`.
45
+ * @returns {string|null} The label, or `null` when there is none to show.
46
+ */
47
+ export function authoredLabel({ display }: {
48
+ display: string | null;
49
+ }): string | null;
26
50
  /**
27
51
  * Whether a parsed link addresses a section of the page it is written on.
28
52
  *
@@ -1,7 +1,7 @@
1
1
  export class Actors extends BasePackCompiler {
2
2
  constructor({ itemsSourceDirs, foreignSourceDirs, ...options }: {
3
3
  [x: string]: any;
4
- itemsSourceDirs: any;
4
+ itemsSourceDirs?: never[] | undefined;
5
5
  foreignSourceDirs?: never[] | undefined;
6
6
  });
7
7
  /** @type {readonly string[]} */
@@ -60,8 +60,8 @@ export class Actors extends BasePackCompiler {
60
60
  system: {
61
61
  shortcode: any;
62
62
  portrait: string;
63
- appearance: any;
64
- dossier: any;
63
+ appearance: string;
64
+ dossier: string;
65
65
  };
66
66
  items: any[];
67
67
  prototypeToken: {