@heroiclands/package-build 7.0.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.
- package/CHANGELOG.md +226 -0
- package/bin/content-build.mjs +76 -0
- package/bin/package-build.mjs +106 -0
- package/config.mjs +37 -0
- package/engine/foreign-catalog.mjs +47 -0
- package/engine/schema-check.mjs +332 -0
- package/engine/schema-extract.mjs +611 -0
- package/package.json +1 -1
- package/sohl/item-fields.mjs +0 -35
- package/types/engine/foreign-catalog.d.mts +15 -0
- package/types/engine/schema-check.d.mts +176 -0
- package/types/engine/schema-extract.d.mts +61 -0
|
@@ -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";
|