@wildwinter/toolkit 0.2.4 → 0.2.6
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 +22 -0
- package/conformance/extensions/defaults.json +1 -0
- package/conformance/extensions/manifest.json +546 -0
- package/conformance/extensions/values.json +527 -1
- package/dist/extensions.cjs +231 -3
- package/dist/extensions.cjs.map +1 -1
- package/dist/extensions.d.cts +76 -3
- package/dist/extensions.d.ts +76 -3
- package/dist/extensions.js +226 -4
- package/dist/extensions.js.map +1 -1
- package/extensions.md +50 -1
- package/package.json +1 -1
package/dist/extensions.d.cts
CHANGED
|
@@ -18,7 +18,7 @@ declare const HOSTS: Readonly<Record<HostName, HostSpec>>;
|
|
|
18
18
|
declare const SCALAR_TYPES: readonly ["text", "multiline", "number", "boolean", "enum", "flags", "quality"];
|
|
19
19
|
type ScalarType = typeof SCALAR_TYPES[number];
|
|
20
20
|
/** Every type a value can take. */
|
|
21
|
-
declare const VALUE_TYPES: readonly ["text", "multiline", "number", "boolean", "enum", "flags", "quality", "reference", "record", "map", "list"];
|
|
21
|
+
declare const VALUE_TYPES: readonly ["text", "multiline", "number", "boolean", "enum", "flags", "quality", "reference", "pick", "record", "map", "list"];
|
|
22
22
|
type ValueType = typeof VALUE_TYPES[number];
|
|
23
23
|
/** A JSON value, as extension data holds it. */
|
|
24
24
|
type JsonValue = null | boolean | number | string | JsonValue[] | {
|
|
@@ -39,6 +39,8 @@ interface TypeDecl {
|
|
|
39
39
|
readonly renamedStages?: Readonly<Record<string, string>>;
|
|
40
40
|
/** A reference's node kind. */
|
|
41
41
|
readonly to?: string;
|
|
42
|
+
/** A pick's sources, in the order its choices are shown. */
|
|
43
|
+
readonly from?: readonly PickSource[];
|
|
42
44
|
/** A record's fields. */
|
|
43
45
|
readonly fields?: readonly FieldDecl[];
|
|
44
46
|
/** A map's key. */
|
|
@@ -58,6 +60,17 @@ interface TypeDecl {
|
|
|
58
60
|
/** Optional in source: the type's own default when absent (`defaultOf`). */
|
|
59
61
|
readonly default?: unknown;
|
|
60
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Where a pick's choices come from: fixed words, the nodes of a reference kind (by id, as a reference holds
|
|
65
|
+
* them), or a list of text on the extension's own project node, by that field's name.
|
|
66
|
+
*/
|
|
67
|
+
type PickSource = {
|
|
68
|
+
readonly words: readonly string[];
|
|
69
|
+
} | {
|
|
70
|
+
readonly to: string;
|
|
71
|
+
} | {
|
|
72
|
+
readonly list: string;
|
|
73
|
+
};
|
|
61
74
|
/** A named field: a target's, or a record's. */
|
|
62
75
|
interface FieldDecl extends TypeDecl {
|
|
63
76
|
readonly name: string;
|
|
@@ -98,6 +111,23 @@ interface Manifest {
|
|
|
98
111
|
readonly docs?: string;
|
|
99
112
|
readonly settings?: readonly SettingDecl[];
|
|
100
113
|
readonly targets: Readonly<Partial<Record<HostName, Readonly<Record<string, TargetDecl>>>>>;
|
|
114
|
+
/** Views the extension's installed package supplies (tier 2), each a web component a host opens in a window. */
|
|
115
|
+
readonly views?: readonly ViewDecl[];
|
|
116
|
+
/** Other extensions whose data this one's views read, by id. */
|
|
117
|
+
readonly reads?: readonly string[];
|
|
118
|
+
}
|
|
119
|
+
/** A view: a web component in the extension's installed package, opened by a host in a floating window. */
|
|
120
|
+
interface ViewDecl {
|
|
121
|
+
/** Lower case, digits and hyphens, unique within the manifest. */
|
|
122
|
+
readonly id: string;
|
|
123
|
+
/** The window's title and the menu item. */
|
|
124
|
+
readonly name: string;
|
|
125
|
+
/** The component's script, a relative path within the package, such as `view/sheet.js`. */
|
|
126
|
+
readonly entry: string;
|
|
127
|
+
/** The custom element the script defines, such as `pattershot-sheet`. */
|
|
128
|
+
readonly element: string;
|
|
129
|
+
/** The hosts that offer it; every host the manifest targets when absent. */
|
|
130
|
+
readonly hosts?: readonly HostName[];
|
|
101
131
|
}
|
|
102
132
|
/** One thing wrong, with where it is. A host words it in its own report shape. */
|
|
103
133
|
interface Problem {
|
|
@@ -132,6 +162,11 @@ interface UnresolvedEntry {
|
|
|
132
162
|
declare function parseManifest(text: string): Manifest;
|
|
133
163
|
/** Every problem with a manifest, by path. An empty list means it is sound. */
|
|
134
164
|
declare function manifestProblems(manifest: unknown): Problem[];
|
|
165
|
+
/** A package path a view's entry may name: relative, forward slashes, no `.` or `..` segment, a `.js` file. */
|
|
166
|
+
declare function isViewEntry(entry: unknown): entry is string;
|
|
167
|
+
/** The views a host offers from a manifest: those naming the host, or every view when it names none and the
|
|
168
|
+
* manifest targets the host. A view that fails its checks is left out. */
|
|
169
|
+
declare function viewsFor(manifest: Manifest, host: HostName): ViewDecl[];
|
|
135
170
|
/** The host field each of a manifest's settings names, the project's choice
|
|
136
171
|
* first, else the setting's default. */
|
|
137
172
|
declare function settingValues(manifest: Manifest, chosen?: Readonly<Record<string, unknown>>): Record<string, string>;
|
|
@@ -162,16 +197,54 @@ declare function effectiveClaim<C extends ClaimDecl>(claim: C, hostDecl: {
|
|
|
162
197
|
readonly default?: unknown;
|
|
163
198
|
}): C;
|
|
164
199
|
|
|
165
|
-
/** What the host knows about its nodes, for references. */
|
|
200
|
+
/** What the host knows about its nodes, for references, and about the extension's project lists, for picks. */
|
|
166
201
|
interface ValueContext {
|
|
167
202
|
/** Does a node of `kind` with `id` exist? Absent, references are not checked
|
|
168
203
|
* (a host checking a manifest's default, say, has no nodes to hand). */
|
|
169
204
|
readonly exists?: (kind: string, id: string) => boolean;
|
|
205
|
+
/** The entries of a list of text on the extension's own project node, by field name (`pickLists`), for a
|
|
206
|
+
* pick's `list` source. Absent, list sources are not checked. */
|
|
207
|
+
readonly list?: (name: string) => readonly string[] | undefined;
|
|
170
208
|
}
|
|
171
209
|
/** Every problem with `value` as a value of `decl`, paths relative to the value. */
|
|
172
210
|
declare function valueProblems(decl: TypeDecl, value: unknown, ctx?: ValueContext, path?: string): Problem[];
|
|
173
211
|
/** Every problem with one node's entry, against its target. */
|
|
174
212
|
declare function entryProblems(target: TargetDecl, entry: unknown, ctx?: ValueContext, path?: string): Problem[];
|
|
213
|
+
/** What a host offers a pick: the nodes of a reference kind, and the extension's project lists. */
|
|
214
|
+
interface PickSources {
|
|
215
|
+
/** The nodes of `kind`, by id, with the name the author knows each by (the id when absent). */
|
|
216
|
+
readonly nodes?: (kind: string) => readonly {
|
|
217
|
+
readonly id: string;
|
|
218
|
+
readonly label?: string;
|
|
219
|
+
}[];
|
|
220
|
+
/** As `ValueContext.list`. */
|
|
221
|
+
readonly list?: (name: string) => readonly string[] | undefined;
|
|
222
|
+
}
|
|
223
|
+
/** One choice of a pick: the text a value holds, the name it is shown by, and where it comes from. */
|
|
224
|
+
interface PickChoice {
|
|
225
|
+
readonly value: string;
|
|
226
|
+
readonly label: string;
|
|
227
|
+
readonly source: PickSource;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Every choice a pick offers, source by source in its declared order, each source's own in its order. A name
|
|
231
|
+
* in two sources appears under each (`pickClashes` reports it); an empty entry is left out, as empty means
|
|
232
|
+
* automatic.
|
|
233
|
+
*/
|
|
234
|
+
declare function pickChoices(decl: TypeDecl, src: PickSources): PickChoice[];
|
|
235
|
+
/**
|
|
236
|
+
* A pick's names that more than one of its sources holds (a character and a point of interest both called
|
|
237
|
+
* Gate): a warning each, once, for the project; each value naming one has its own from `valueProblems`.
|
|
238
|
+
*/
|
|
239
|
+
declare function pickClashes(decl: TypeDecl, src: PickSources, path?: string): Problem[];
|
|
240
|
+
/**
|
|
241
|
+
* The lists of text a pick's `list` sources name, read off the extension's project entry: each a list field of
|
|
242
|
+
* the extension's `project` target holding text, the entry's own value else the field's default. Undefined for a
|
|
243
|
+
* name that is no such field.
|
|
244
|
+
*/
|
|
245
|
+
declare function pickLists(projectTarget: TargetDecl | undefined, projectEntry: Readonly<Record<string, unknown>> | undefined): (name: string) => readonly string[] | undefined;
|
|
246
|
+
/** A list without ids holding text: what a pick's `list` source names. */
|
|
247
|
+
declare function isTextList(decl: TypeDecl): boolean;
|
|
175
248
|
/**
|
|
176
249
|
* A declaration's default: the one it gives, else its type's. A scalar's is
|
|
177
250
|
* the kernel's (`defaultFor`: false, 0, "", the first enum value, no flags,
|
|
@@ -345,4 +418,4 @@ declare const atKey: (base: string, key: string) => string;
|
|
|
345
418
|
/** `base` and then list position `i`. */
|
|
346
419
|
declare const atIndex: (base: string, i: number) => string;
|
|
347
420
|
|
|
348
|
-
export { ATOMIC, COMMENTS, type ClaimDecl, type Conflict, type ConflictKind, type DataMergeResult, type ExtensionData, type FieldDecl, HOSTS, type HostName, type HostSpec, type JsonValue, MANIFEST_FILE_SUFFIX, MANIFEST_SCHEMA, type Manifest, type MergeWarning, type Problem, type ResolvedClaim, SCALAR_TYPES, SET, type ScalarType, type SettingDecl, type StageRename, type Strategy, type TargetDecl, type TreeMergeOptions, type TreeMergeResult, type TypeDecl, UNRESOLVED_KEY, type UnresolvedEntry, VALUE_TYPES, type ValueContext, type ValueType, atIndex, atKey, claimClashes, compileEntry, compiledTarget, defaultOf, effectiveClaim, entryProblems, extensionDataStrategy, keyedBy, manifestProblems, mapOf, markUnresolved, mergeComments, mergeExtensionData, mergeTree, objectOf, orphanedEntries, parseManifest, renameStages, resolvedClaims, settingValues, stableStringify, strategyForTarget, strategyForType, targetOf, unresolvedOf, unresolvedProblems, valueProblems, withoutMark };
|
|
421
|
+
export { ATOMIC, COMMENTS, type ClaimDecl, type Conflict, type ConflictKind, type DataMergeResult, type ExtensionData, type FieldDecl, HOSTS, type HostName, type HostSpec, type JsonValue, MANIFEST_FILE_SUFFIX, MANIFEST_SCHEMA, type Manifest, type MergeWarning, type PickChoice, type PickSource, type PickSources, type Problem, type ResolvedClaim, SCALAR_TYPES, SET, type ScalarType, type SettingDecl, type StageRename, type Strategy, type TargetDecl, type TreeMergeOptions, type TreeMergeResult, type TypeDecl, UNRESOLVED_KEY, type UnresolvedEntry, VALUE_TYPES, type ValueContext, type ValueType, type ViewDecl, atIndex, atKey, claimClashes, compileEntry, compiledTarget, defaultOf, effectiveClaim, entryProblems, extensionDataStrategy, isTextList, isViewEntry, keyedBy, manifestProblems, mapOf, markUnresolved, mergeComments, mergeExtensionData, mergeTree, objectOf, orphanedEntries, parseManifest, pickChoices, pickClashes, pickLists, renameStages, resolvedClaims, settingValues, stableStringify, strategyForTarget, strategyForType, targetOf, unresolvedOf, unresolvedProblems, valueProblems, viewsFor, withoutMark };
|
package/dist/extensions.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ declare const HOSTS: Readonly<Record<HostName, HostSpec>>;
|
|
|
18
18
|
declare const SCALAR_TYPES: readonly ["text", "multiline", "number", "boolean", "enum", "flags", "quality"];
|
|
19
19
|
type ScalarType = typeof SCALAR_TYPES[number];
|
|
20
20
|
/** Every type a value can take. */
|
|
21
|
-
declare const VALUE_TYPES: readonly ["text", "multiline", "number", "boolean", "enum", "flags", "quality", "reference", "record", "map", "list"];
|
|
21
|
+
declare const VALUE_TYPES: readonly ["text", "multiline", "number", "boolean", "enum", "flags", "quality", "reference", "pick", "record", "map", "list"];
|
|
22
22
|
type ValueType = typeof VALUE_TYPES[number];
|
|
23
23
|
/** A JSON value, as extension data holds it. */
|
|
24
24
|
type JsonValue = null | boolean | number | string | JsonValue[] | {
|
|
@@ -39,6 +39,8 @@ interface TypeDecl {
|
|
|
39
39
|
readonly renamedStages?: Readonly<Record<string, string>>;
|
|
40
40
|
/** A reference's node kind. */
|
|
41
41
|
readonly to?: string;
|
|
42
|
+
/** A pick's sources, in the order its choices are shown. */
|
|
43
|
+
readonly from?: readonly PickSource[];
|
|
42
44
|
/** A record's fields. */
|
|
43
45
|
readonly fields?: readonly FieldDecl[];
|
|
44
46
|
/** A map's key. */
|
|
@@ -58,6 +60,17 @@ interface TypeDecl {
|
|
|
58
60
|
/** Optional in source: the type's own default when absent (`defaultOf`). */
|
|
59
61
|
readonly default?: unknown;
|
|
60
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Where a pick's choices come from: fixed words, the nodes of a reference kind (by id, as a reference holds
|
|
65
|
+
* them), or a list of text on the extension's own project node, by that field's name.
|
|
66
|
+
*/
|
|
67
|
+
type PickSource = {
|
|
68
|
+
readonly words: readonly string[];
|
|
69
|
+
} | {
|
|
70
|
+
readonly to: string;
|
|
71
|
+
} | {
|
|
72
|
+
readonly list: string;
|
|
73
|
+
};
|
|
61
74
|
/** A named field: a target's, or a record's. */
|
|
62
75
|
interface FieldDecl extends TypeDecl {
|
|
63
76
|
readonly name: string;
|
|
@@ -98,6 +111,23 @@ interface Manifest {
|
|
|
98
111
|
readonly docs?: string;
|
|
99
112
|
readonly settings?: readonly SettingDecl[];
|
|
100
113
|
readonly targets: Readonly<Partial<Record<HostName, Readonly<Record<string, TargetDecl>>>>>;
|
|
114
|
+
/** Views the extension's installed package supplies (tier 2), each a web component a host opens in a window. */
|
|
115
|
+
readonly views?: readonly ViewDecl[];
|
|
116
|
+
/** Other extensions whose data this one's views read, by id. */
|
|
117
|
+
readonly reads?: readonly string[];
|
|
118
|
+
}
|
|
119
|
+
/** A view: a web component in the extension's installed package, opened by a host in a floating window. */
|
|
120
|
+
interface ViewDecl {
|
|
121
|
+
/** Lower case, digits and hyphens, unique within the manifest. */
|
|
122
|
+
readonly id: string;
|
|
123
|
+
/** The window's title and the menu item. */
|
|
124
|
+
readonly name: string;
|
|
125
|
+
/** The component's script, a relative path within the package, such as `view/sheet.js`. */
|
|
126
|
+
readonly entry: string;
|
|
127
|
+
/** The custom element the script defines, such as `pattershot-sheet`. */
|
|
128
|
+
readonly element: string;
|
|
129
|
+
/** The hosts that offer it; every host the manifest targets when absent. */
|
|
130
|
+
readonly hosts?: readonly HostName[];
|
|
101
131
|
}
|
|
102
132
|
/** One thing wrong, with where it is. A host words it in its own report shape. */
|
|
103
133
|
interface Problem {
|
|
@@ -132,6 +162,11 @@ interface UnresolvedEntry {
|
|
|
132
162
|
declare function parseManifest(text: string): Manifest;
|
|
133
163
|
/** Every problem with a manifest, by path. An empty list means it is sound. */
|
|
134
164
|
declare function manifestProblems(manifest: unknown): Problem[];
|
|
165
|
+
/** A package path a view's entry may name: relative, forward slashes, no `.` or `..` segment, a `.js` file. */
|
|
166
|
+
declare function isViewEntry(entry: unknown): entry is string;
|
|
167
|
+
/** The views a host offers from a manifest: those naming the host, or every view when it names none and the
|
|
168
|
+
* manifest targets the host. A view that fails its checks is left out. */
|
|
169
|
+
declare function viewsFor(manifest: Manifest, host: HostName): ViewDecl[];
|
|
135
170
|
/** The host field each of a manifest's settings names, the project's choice
|
|
136
171
|
* first, else the setting's default. */
|
|
137
172
|
declare function settingValues(manifest: Manifest, chosen?: Readonly<Record<string, unknown>>): Record<string, string>;
|
|
@@ -162,16 +197,54 @@ declare function effectiveClaim<C extends ClaimDecl>(claim: C, hostDecl: {
|
|
|
162
197
|
readonly default?: unknown;
|
|
163
198
|
}): C;
|
|
164
199
|
|
|
165
|
-
/** What the host knows about its nodes, for references. */
|
|
200
|
+
/** What the host knows about its nodes, for references, and about the extension's project lists, for picks. */
|
|
166
201
|
interface ValueContext {
|
|
167
202
|
/** Does a node of `kind` with `id` exist? Absent, references are not checked
|
|
168
203
|
* (a host checking a manifest's default, say, has no nodes to hand). */
|
|
169
204
|
readonly exists?: (kind: string, id: string) => boolean;
|
|
205
|
+
/** The entries of a list of text on the extension's own project node, by field name (`pickLists`), for a
|
|
206
|
+
* pick's `list` source. Absent, list sources are not checked. */
|
|
207
|
+
readonly list?: (name: string) => readonly string[] | undefined;
|
|
170
208
|
}
|
|
171
209
|
/** Every problem with `value` as a value of `decl`, paths relative to the value. */
|
|
172
210
|
declare function valueProblems(decl: TypeDecl, value: unknown, ctx?: ValueContext, path?: string): Problem[];
|
|
173
211
|
/** Every problem with one node's entry, against its target. */
|
|
174
212
|
declare function entryProblems(target: TargetDecl, entry: unknown, ctx?: ValueContext, path?: string): Problem[];
|
|
213
|
+
/** What a host offers a pick: the nodes of a reference kind, and the extension's project lists. */
|
|
214
|
+
interface PickSources {
|
|
215
|
+
/** The nodes of `kind`, by id, with the name the author knows each by (the id when absent). */
|
|
216
|
+
readonly nodes?: (kind: string) => readonly {
|
|
217
|
+
readonly id: string;
|
|
218
|
+
readonly label?: string;
|
|
219
|
+
}[];
|
|
220
|
+
/** As `ValueContext.list`. */
|
|
221
|
+
readonly list?: (name: string) => readonly string[] | undefined;
|
|
222
|
+
}
|
|
223
|
+
/** One choice of a pick: the text a value holds, the name it is shown by, and where it comes from. */
|
|
224
|
+
interface PickChoice {
|
|
225
|
+
readonly value: string;
|
|
226
|
+
readonly label: string;
|
|
227
|
+
readonly source: PickSource;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Every choice a pick offers, source by source in its declared order, each source's own in its order. A name
|
|
231
|
+
* in two sources appears under each (`pickClashes` reports it); an empty entry is left out, as empty means
|
|
232
|
+
* automatic.
|
|
233
|
+
*/
|
|
234
|
+
declare function pickChoices(decl: TypeDecl, src: PickSources): PickChoice[];
|
|
235
|
+
/**
|
|
236
|
+
* A pick's names that more than one of its sources holds (a character and a point of interest both called
|
|
237
|
+
* Gate): a warning each, once, for the project; each value naming one has its own from `valueProblems`.
|
|
238
|
+
*/
|
|
239
|
+
declare function pickClashes(decl: TypeDecl, src: PickSources, path?: string): Problem[];
|
|
240
|
+
/**
|
|
241
|
+
* The lists of text a pick's `list` sources name, read off the extension's project entry: each a list field of
|
|
242
|
+
* the extension's `project` target holding text, the entry's own value else the field's default. Undefined for a
|
|
243
|
+
* name that is no such field.
|
|
244
|
+
*/
|
|
245
|
+
declare function pickLists(projectTarget: TargetDecl | undefined, projectEntry: Readonly<Record<string, unknown>> | undefined): (name: string) => readonly string[] | undefined;
|
|
246
|
+
/** A list without ids holding text: what a pick's `list` source names. */
|
|
247
|
+
declare function isTextList(decl: TypeDecl): boolean;
|
|
175
248
|
/**
|
|
176
249
|
* A declaration's default: the one it gives, else its type's. A scalar's is
|
|
177
250
|
* the kernel's (`defaultFor`: false, 0, "", the first enum value, no flags,
|
|
@@ -345,4 +418,4 @@ declare const atKey: (base: string, key: string) => string;
|
|
|
345
418
|
/** `base` and then list position `i`. */
|
|
346
419
|
declare const atIndex: (base: string, i: number) => string;
|
|
347
420
|
|
|
348
|
-
export { ATOMIC, COMMENTS, type ClaimDecl, type Conflict, type ConflictKind, type DataMergeResult, type ExtensionData, type FieldDecl, HOSTS, type HostName, type HostSpec, type JsonValue, MANIFEST_FILE_SUFFIX, MANIFEST_SCHEMA, type Manifest, type MergeWarning, type Problem, type ResolvedClaim, SCALAR_TYPES, SET, type ScalarType, type SettingDecl, type StageRename, type Strategy, type TargetDecl, type TreeMergeOptions, type TreeMergeResult, type TypeDecl, UNRESOLVED_KEY, type UnresolvedEntry, VALUE_TYPES, type ValueContext, type ValueType, atIndex, atKey, claimClashes, compileEntry, compiledTarget, defaultOf, effectiveClaim, entryProblems, extensionDataStrategy, keyedBy, manifestProblems, mapOf, markUnresolved, mergeComments, mergeExtensionData, mergeTree, objectOf, orphanedEntries, parseManifest, renameStages, resolvedClaims, settingValues, stableStringify, strategyForTarget, strategyForType, targetOf, unresolvedOf, unresolvedProblems, valueProblems, withoutMark };
|
|
421
|
+
export { ATOMIC, COMMENTS, type ClaimDecl, type Conflict, type ConflictKind, type DataMergeResult, type ExtensionData, type FieldDecl, HOSTS, type HostName, type HostSpec, type JsonValue, MANIFEST_FILE_SUFFIX, MANIFEST_SCHEMA, type Manifest, type MergeWarning, type PickChoice, type PickSource, type PickSources, type Problem, type ResolvedClaim, SCALAR_TYPES, SET, type ScalarType, type SettingDecl, type StageRename, type Strategy, type TargetDecl, type TreeMergeOptions, type TreeMergeResult, type TypeDecl, UNRESOLVED_KEY, type UnresolvedEntry, VALUE_TYPES, type ValueContext, type ValueType, type ViewDecl, atIndex, atKey, claimClashes, compileEntry, compiledTarget, defaultOf, effectiveClaim, entryProblems, extensionDataStrategy, isTextList, isViewEntry, keyedBy, manifestProblems, mapOf, markUnresolved, mergeComments, mergeExtensionData, mergeTree, objectOf, orphanedEntries, parseManifest, pickChoices, pickClashes, pickLists, renameStages, resolvedClaims, settingValues, stableStringify, strategyForTarget, strategyForType, targetOf, unresolvedOf, unresolvedProblems, valueProblems, viewsFor, withoutMark };
|
package/dist/extensions.js
CHANGED
|
@@ -18,7 +18,7 @@ var HOSTS = {
|
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
20
|
var SCALAR_TYPES = ["text", "multiline", "number", "boolean", "enum", "flags", "quality"];
|
|
21
|
-
var VALUE_TYPES = [...SCALAR_TYPES, "reference", "record", "map", "list"];
|
|
21
|
+
var VALUE_TYPES = [...SCALAR_TYPES, "reference", "pick", "record", "map", "list"];
|
|
22
22
|
|
|
23
23
|
// src/extensions/path.ts
|
|
24
24
|
var IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
@@ -102,6 +102,26 @@ function valueProblems(decl, value, ctx = {}, path = "") {
|
|
|
102
102
|
out.push(err("no-such-node", path, `${where(path)} names ${decl.to} "${value}", which does not exist`));
|
|
103
103
|
}
|
|
104
104
|
break;
|
|
105
|
+
case "pick": {
|
|
106
|
+
if (typeof value !== "string") {
|
|
107
|
+
wrong("one of its choices, as text");
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
if (value === "") break;
|
|
111
|
+
const sources = decl.from ?? [];
|
|
112
|
+
const holding = sources.filter((s) => sourceHolds(s, value, ctx) === true);
|
|
113
|
+
if (holding.length > 1) {
|
|
114
|
+
out.push({
|
|
115
|
+
severity: "warning",
|
|
116
|
+
code: "pick-ambiguous",
|
|
117
|
+
path,
|
|
118
|
+
message: `${where(path)} is "${value}", which is ${holding.map(sourceWord).join(" and ")}`
|
|
119
|
+
});
|
|
120
|
+
} else if (holding.length === 0 && sources.every((s) => sourceHolds(s, value, ctx) !== void 0)) {
|
|
121
|
+
out.push(err("not-a-choice", path, `${where(path)} is "${value}", which is not one of its choices`));
|
|
122
|
+
}
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
105
125
|
case "record":
|
|
106
126
|
if (!isObject(value)) {
|
|
107
127
|
wrong("an object");
|
|
@@ -181,11 +201,69 @@ function entryProblems(target, entry, ctx = {}, path = "") {
|
|
|
181
201
|
}
|
|
182
202
|
return fieldsProblems(target.fields ?? [], entry, ctx, path);
|
|
183
203
|
}
|
|
204
|
+
function sourceHolds(source, value, ctx) {
|
|
205
|
+
if ("words" in source) return Array.isArray(source.words) && source.words.includes(value);
|
|
206
|
+
if ("to" in source) return ctx.exists === void 0 ? void 0 : ctx.exists(source.to, value);
|
|
207
|
+
if ("list" in source) return ctx.list === void 0 ? void 0 : (ctx.list(source.list) ?? []).includes(value);
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
function sourceWord(source) {
|
|
211
|
+
if ("words" in source) return "a fixed word";
|
|
212
|
+
if ("to" in source) return source.to === "cast" ? "a cast member" : `a ${source.to}`;
|
|
213
|
+
return `an entry of the list "${source.list}"`;
|
|
214
|
+
}
|
|
215
|
+
function pickChoices(decl, src) {
|
|
216
|
+
const out = [];
|
|
217
|
+
for (const source of decl.from ?? []) {
|
|
218
|
+
if ("words" in source) {
|
|
219
|
+
for (const w of source.words ?? []) if (typeof w === "string" && w !== "") out.push({ value: w, label: w, source });
|
|
220
|
+
} else if ("to" in source) {
|
|
221
|
+
for (const n of src.nodes?.(source.to) ?? []) if (n.id !== "") out.push({ value: n.id, label: n.label ?? n.id, source });
|
|
222
|
+
} else if ("list" in source) {
|
|
223
|
+
for (const e of src.list?.(source.list) ?? []) if (e !== "") out.push({ value: e, label: e, source });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return out;
|
|
227
|
+
}
|
|
228
|
+
function pickClashes(decl, src, path = "") {
|
|
229
|
+
const by = /* @__PURE__ */ new Map();
|
|
230
|
+
for (const c of pickChoices(decl, src)) {
|
|
231
|
+
const held = by.get(c.value) ?? [];
|
|
232
|
+
if (!held.includes(c.source)) held.push(c.source);
|
|
233
|
+
by.set(c.value, held);
|
|
234
|
+
}
|
|
235
|
+
const out = [];
|
|
236
|
+
for (const [name, sources] of by) {
|
|
237
|
+
if (sources.length < 2) continue;
|
|
238
|
+
out.push({
|
|
239
|
+
severity: "warning",
|
|
240
|
+
code: "pick-clash",
|
|
241
|
+
path,
|
|
242
|
+
options: [name],
|
|
243
|
+
message: `"${name}" is ${sources.map(sourceWord).join(" and ")}, so a value naming it is ambiguous`
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
return out;
|
|
247
|
+
}
|
|
248
|
+
function pickLists(projectTarget, projectEntry) {
|
|
249
|
+
return (name) => {
|
|
250
|
+
const field = (projectTarget?.fields ?? []).find((f) => f.name === name);
|
|
251
|
+
if (field === void 0 || !isTextList(field)) return void 0;
|
|
252
|
+
const held = projectEntry !== void 0 && own(projectEntry, name) ? projectEntry[name] : void 0;
|
|
253
|
+
const value = Array.isArray(held) ? held : defaultOf(field);
|
|
254
|
+
return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function isTextList(decl) {
|
|
258
|
+
return decl.type === "list" && decl.id === void 0 && decl.of?.type === "text";
|
|
259
|
+
}
|
|
184
260
|
function defaultOf(decl) {
|
|
185
261
|
if (decl.default !== void 0) return decl.default;
|
|
186
262
|
switch (decl.type) {
|
|
187
263
|
case "reference":
|
|
188
264
|
return "";
|
|
265
|
+
case "pick":
|
|
266
|
+
return "";
|
|
189
267
|
case "record":
|
|
190
268
|
return Object.fromEntries((decl.fields ?? []).map((f) => [f.name, defaultOf(f)]));
|
|
191
269
|
case "map":
|
|
@@ -278,11 +356,13 @@ function renameStages(target, entry) {
|
|
|
278
356
|
var ID = /^[a-z][a-z0-9-]*$/;
|
|
279
357
|
var VERSION = /^\d+\.\d+\.\d+$/;
|
|
280
358
|
var NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
281
|
-
var TYPE_KEYS = ["type", "values", "stages", "renamedStages", "to", "fields", "key", "of", "id", "min", "max", "valueHelp", "default"];
|
|
359
|
+
var TYPE_KEYS = ["type", "values", "stages", "renamedStages", "to", "from", "fields", "key", "of", "id", "min", "max", "valueHelp", "default"];
|
|
282
360
|
var FIELD_KEYS = /* @__PURE__ */ new Set([...TYPE_KEYS, "name", "label", "help", "required", "group", "editorOnly"]);
|
|
283
361
|
var CLAIM_KEYS = /* @__PURE__ */ new Set([...TYPE_KEYS, "setting", "label", "help", "required"]);
|
|
284
362
|
var BARE_KEYS = new Set(TYPE_KEYS);
|
|
285
|
-
var MANIFEST_KEYS = /* @__PURE__ */ new Set(["schema", "id", "name", "version", "docs", "settings", "targets"]);
|
|
363
|
+
var MANIFEST_KEYS = /* @__PURE__ */ new Set(["schema", "id", "name", "version", "docs", "settings", "targets", "views", "reads"]);
|
|
364
|
+
var VIEW_KEYS = /* @__PURE__ */ new Set(["id", "name", "entry", "element", "hosts"]);
|
|
365
|
+
var ELEMENT = /^[a-z][a-z0-9._]*-[a-z0-9._-]*$/;
|
|
286
366
|
var err2 = (code, path, message) => ({ severity: "error", code, path, message });
|
|
287
367
|
function parseManifest(text) {
|
|
288
368
|
return parseSource(text);
|
|
@@ -304,6 +384,8 @@ function manifestProblems(manifest) {
|
|
|
304
384
|
}
|
|
305
385
|
if (m["docs"] !== void 0 && typeof m["docs"] !== "string") out.push(err2("docs", "docs", "docs is a link, as text"));
|
|
306
386
|
const settings = settingsProblems(m["settings"], out);
|
|
387
|
+
viewsProblems(m["views"], m["targets"], out);
|
|
388
|
+
readsProblems(m["reads"], m["id"], out);
|
|
307
389
|
const targets = m["targets"];
|
|
308
390
|
if (!isObject(targets) || Object.keys(targets).length === 0) {
|
|
309
391
|
out.push(err2("no-targets", "targets", "the manifest targets no host"));
|
|
@@ -328,9 +410,146 @@ function manifestProblems(manifest) {
|
|
|
328
410
|
}
|
|
329
411
|
targetProblems(host, kind, target, kindPath, settings, out);
|
|
330
412
|
}
|
|
413
|
+
pickListProblems(kinds, hostPath, out);
|
|
331
414
|
}
|
|
332
415
|
return out;
|
|
333
416
|
}
|
|
417
|
+
function pickSourcesSound(host, from, path, out) {
|
|
418
|
+
if (!Array.isArray(from) || from.length === 0) {
|
|
419
|
+
out.push(err2("pick-from", path, 'a pick lists its sources in "from": { "words": [...] }, { "to": a node kind }, or { "list": a project list }'));
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
let sound = true;
|
|
423
|
+
from.forEach((s, i) => {
|
|
424
|
+
const at = atIndex(path, i);
|
|
425
|
+
const keys = isObject(s) ? Object.keys(s).filter((k) => k === "words" || k === "to" || k === "list") : [];
|
|
426
|
+
if (!isObject(s) || keys.length !== 1) {
|
|
427
|
+
out.push(err2("pick-source", at, 'a source is one of { "words": [...] }, { "to": a node kind }, { "list": a project list }'));
|
|
428
|
+
sound = false;
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
unknownKeys(s, new Set(keys), at, out);
|
|
432
|
+
if (keys[0] === "words") {
|
|
433
|
+
const words = s["words"];
|
|
434
|
+
const seen = /* @__PURE__ */ new Set();
|
|
435
|
+
if (!Array.isArray(words) || words.length === 0) {
|
|
436
|
+
out.push(err2("pick-words", atKey(at, "words"), "fixed words are a list of at least one"));
|
|
437
|
+
sound = false;
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
words.forEach((w, j) => {
|
|
441
|
+
if (typeof w !== "string" || w.trim() === "") {
|
|
442
|
+
out.push(err2("value-empty", atIndex(atKey(at, "words"), j), "a word has a name"));
|
|
443
|
+
sound = false;
|
|
444
|
+
} else if (seen.has(w)) {
|
|
445
|
+
out.push(err2("value-duplicate", atIndex(atKey(at, "words"), j), `"${w}" is listed twice`));
|
|
446
|
+
sound = false;
|
|
447
|
+
} else seen.add(w);
|
|
448
|
+
});
|
|
449
|
+
} else if (keys[0] === "to") {
|
|
450
|
+
if (typeof s["to"] !== "string" || !HOSTS[host].references.includes(s["to"])) {
|
|
451
|
+
out.push(err2("reference-to", atKey(at, "to"), `a source's nodes are of a kind, one of ${HOSTS[host].references.join(", ")}`));
|
|
452
|
+
sound = false;
|
|
453
|
+
}
|
|
454
|
+
} else if (typeof s["list"] !== "string" || !NAME.test(s["list"])) {
|
|
455
|
+
out.push(err2("pick-list", atKey(at, "list"), "a list source names a field of the extension's project node"));
|
|
456
|
+
sound = false;
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
return sound;
|
|
460
|
+
}
|
|
461
|
+
function pickListProblems(kinds, hostPath, out) {
|
|
462
|
+
const project = isObject(kinds["project"]) && Array.isArray(kinds["project"]["fields"]) ? kinds["project"]["fields"] : [];
|
|
463
|
+
const lists = new Set(project.filter((f) => isObject(f) && isTextList(f)).map((f) => f.name));
|
|
464
|
+
const walk = (decl, path) => {
|
|
465
|
+
if (!isObject(decl)) return;
|
|
466
|
+
const d = decl;
|
|
467
|
+
if (d.type === "pick" && Array.isArray(d.from)) {
|
|
468
|
+
d.from.forEach((s, i) => {
|
|
469
|
+
if (isObject(s) && typeof s.list === "string" && !lists.has(s.list)) {
|
|
470
|
+
const name = s.list;
|
|
471
|
+
out.push(err2(
|
|
472
|
+
"pick-list",
|
|
473
|
+
atKey(atIndex(atKey(path, "from"), i), "list"),
|
|
474
|
+
`"${name}" is not a list of text on the extension's project node; declare it there, as { "name": "${name}", "type": "list", "of": { "type": "text" } }`
|
|
475
|
+
));
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
if (Array.isArray(d.fields)) d.fields.forEach((f, i) => walk(f, atIndex(atKey(path, "fields"), i)));
|
|
480
|
+
if (d.of !== void 0) walk(d.of, atKey(path, "of"));
|
|
481
|
+
};
|
|
482
|
+
for (const [kind, target] of Object.entries(kinds)) {
|
|
483
|
+
if (!isObject(target) || !Array.isArray(target["fields"])) continue;
|
|
484
|
+
target["fields"].forEach((f, i) => walk(f, atIndex(atKey(atKey(hostPath, kind), "fields"), i)));
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function isViewEntry(entry) {
|
|
488
|
+
if (typeof entry !== "string" || !entry.endsWith(".js") || entry.includes("\\")) return false;
|
|
489
|
+
const parts = entry.split("/");
|
|
490
|
+
return parts.every((p) => p !== "" && p !== "." && p !== "..");
|
|
491
|
+
}
|
|
492
|
+
function viewsProblems(views, targets, out) {
|
|
493
|
+
if (views === void 0) return;
|
|
494
|
+
if (!Array.isArray(views)) {
|
|
495
|
+
out.push(err2("not-a-list", "views", "views are a list"));
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
const ids = /* @__PURE__ */ new Set();
|
|
499
|
+
const elements = /* @__PURE__ */ new Set();
|
|
500
|
+
const targeted = isObject(targets) ? Object.keys(targets) : [];
|
|
501
|
+
views.forEach((v, i) => {
|
|
502
|
+
const at = atIndex("views", i);
|
|
503
|
+
if (!isObject(v)) {
|
|
504
|
+
out.push(err2("not-an-object", at, "a view is an object"));
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
unknownKeys(v, VIEW_KEYS, at, out);
|
|
508
|
+
const id = v["id"];
|
|
509
|
+
if (typeof id !== "string" || !ID.test(id)) out.push(err2("view-id", atKey(at, "id"), "a view's id is lower case letters, digits and hyphens, starting with a letter"));
|
|
510
|
+
else if (ids.has(id)) out.push(err2("view-duplicate", atKey(at, "id"), `two views are called "${id}"`));
|
|
511
|
+
else ids.add(id);
|
|
512
|
+
if (typeof v["name"] !== "string" || v["name"].trim() === "") out.push(err2("view-name", atKey(at, "name"), "a view has a name, shown as its window's title"));
|
|
513
|
+
if (!isViewEntry(v["entry"])) {
|
|
514
|
+
out.push(err2("view-entry", atKey(at, "entry"), "a view's entry is a .js file in the extension's package, as a relative path such as view/sheet.js"));
|
|
515
|
+
}
|
|
516
|
+
const element = v["element"];
|
|
517
|
+
if (typeof element !== "string" || !ELEMENT.test(element)) {
|
|
518
|
+
out.push(err2("view-element", atKey(at, "element"), "a view's element is a custom element name: lower case, starting with a letter, with a hyphen, such as probe-view"));
|
|
519
|
+
} else if (elements.has(element)) out.push(err2("view-duplicate", atKey(at, "element"), `two views define <${element}>`));
|
|
520
|
+
else elements.add(element);
|
|
521
|
+
const hosts = v["hosts"];
|
|
522
|
+
if (hosts === void 0) return;
|
|
523
|
+
if (!Array.isArray(hosts) || hosts.length === 0) {
|
|
524
|
+
out.push(err2("view-hosts", atKey(at, "hosts"), "a view's hosts are a list of at least one host"));
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
hosts.forEach((h, j) => {
|
|
528
|
+
const hp = atIndex(atKey(at, "hosts"), j);
|
|
529
|
+
if (typeof h !== "string" || !own(HOSTS, h)) out.push(err2("unknown-host", hp, `${JSON.stringify(h)} is not a host; a view opens in "patter" or "storylets"`));
|
|
530
|
+
else if (!targeted.includes(h)) out.push({ severity: "warning", code: "view-host-untargeted", path: hp, message: `the view opens in ${h}, but the manifest targets nothing there` });
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
function readsProblems(reads, self, out) {
|
|
535
|
+
if (reads === void 0) return;
|
|
536
|
+
if (!Array.isArray(reads)) {
|
|
537
|
+
out.push(err2("not-a-list", "reads", "reads is a list of extension ids"));
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
const seen = /* @__PURE__ */ new Set();
|
|
541
|
+
reads.forEach((r, i) => {
|
|
542
|
+
const at = atIndex("reads", i);
|
|
543
|
+
if (typeof r !== "string" || !ID.test(r)) out.push(err2("reads-id", at, "an extension read is named by its id: lower case letters, digits and hyphens"));
|
|
544
|
+
else if (r === self) out.push({ severity: "warning", code: "reads-self", path: at, message: "an extension always reads its own data; this entry is ignored" });
|
|
545
|
+
else if (seen.has(r)) out.push(err2("reads-duplicate", at, `"${r}" is listed twice`));
|
|
546
|
+
else seen.add(r);
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
function viewsFor(manifest, host) {
|
|
550
|
+
const targeted = Object.keys(manifest.targets ?? {});
|
|
551
|
+
return (Array.isArray(manifest.views) ? manifest.views : []).filter((v) => isObject(v) && typeof v.id === "string" && ID.test(v.id) && typeof v.name === "string" && isViewEntry(v.entry) && typeof v.element === "string" && ELEMENT.test(v.element) && (v.hosts === void 0 ? targeted.includes(host) : Array.isArray(v.hosts) && v.hosts.includes(host)));
|
|
552
|
+
}
|
|
334
553
|
function unknownKeys(obj, known, path, out) {
|
|
335
554
|
for (const k of Object.keys(obj)) {
|
|
336
555
|
if (!known.has(k)) {
|
|
@@ -491,6 +710,9 @@ function typeProblems(host, decl, path, named, out, claim = false) {
|
|
|
491
710
|
sound = false;
|
|
492
711
|
}
|
|
493
712
|
break;
|
|
713
|
+
case "pick":
|
|
714
|
+
if (!pickSourcesSound(host, decl.from, at("from"), out)) sound = false;
|
|
715
|
+
break;
|
|
494
716
|
case "record":
|
|
495
717
|
if (!Array.isArray(decl.fields) || decl.fields.length === 0) {
|
|
496
718
|
out.push(err2("record-empty", at("fields"), "a record declares its fields"));
|
|
@@ -965,6 +1187,6 @@ function orphanedEntries(data, exists) {
|
|
|
965
1187
|
return out;
|
|
966
1188
|
}
|
|
967
1189
|
|
|
968
|
-
export { ATOMIC, COMMENTS, HOSTS, MANIFEST_FILE_SUFFIX, MANIFEST_SCHEMA, SCALAR_TYPES, SET, UNRESOLVED_KEY, VALUE_TYPES, atIndex, atKey, claimClashes, compileEntry, compiledTarget, defaultOf, effectiveClaim, entryProblems, extensionDataStrategy, keyedBy, manifestProblems, mapOf, markUnresolved, mergeComments, mergeExtensionData, mergeTree, objectOf, orphanedEntries, parseManifest, renameStages, resolvedClaims, settingValues, stableStringify, strategyForTarget, strategyForType, targetOf, unresolvedOf, unresolvedProblems, valueProblems, withoutMark };
|
|
1190
|
+
export { ATOMIC, COMMENTS, HOSTS, MANIFEST_FILE_SUFFIX, MANIFEST_SCHEMA, SCALAR_TYPES, SET, UNRESOLVED_KEY, VALUE_TYPES, atIndex, atKey, claimClashes, compileEntry, compiledTarget, defaultOf, effectiveClaim, entryProblems, extensionDataStrategy, isTextList, isViewEntry, keyedBy, manifestProblems, mapOf, markUnresolved, mergeComments, mergeExtensionData, mergeTree, objectOf, orphanedEntries, parseManifest, pickChoices, pickClashes, pickLists, renameStages, resolvedClaims, settingValues, stableStringify, strategyForTarget, strategyForType, targetOf, unresolvedOf, unresolvedProblems, valueProblems, viewsFor, withoutMark };
|
|
969
1191
|
//# sourceMappingURL=extensions.js.map
|
|
970
1192
|
//# sourceMappingURL=extensions.js.map
|