@wildwinter/toolkit 0.2.4 → 0.2.5

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.
@@ -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;
@@ -162,16 +175,54 @@ declare function effectiveClaim<C extends ClaimDecl>(claim: C, hostDecl: {
162
175
  readonly default?: unknown;
163
176
  }): C;
164
177
 
165
- /** What the host knows about its nodes, for references. */
178
+ /** What the host knows about its nodes, for references, and about the extension's project lists, for picks. */
166
179
  interface ValueContext {
167
180
  /** Does a node of `kind` with `id` exist? Absent, references are not checked
168
181
  * (a host checking a manifest's default, say, has no nodes to hand). */
169
182
  readonly exists?: (kind: string, id: string) => boolean;
183
+ /** The entries of a list of text on the extension's own project node, by field name (`pickLists`), for a
184
+ * pick's `list` source. Absent, list sources are not checked. */
185
+ readonly list?: (name: string) => readonly string[] | undefined;
170
186
  }
171
187
  /** Every problem with `value` as a value of `decl`, paths relative to the value. */
172
188
  declare function valueProblems(decl: TypeDecl, value: unknown, ctx?: ValueContext, path?: string): Problem[];
173
189
  /** Every problem with one node's entry, against its target. */
174
190
  declare function entryProblems(target: TargetDecl, entry: unknown, ctx?: ValueContext, path?: string): Problem[];
191
+ /** What a host offers a pick: the nodes of a reference kind, and the extension's project lists. */
192
+ interface PickSources {
193
+ /** The nodes of `kind`, by id, with the name the author knows each by (the id when absent). */
194
+ readonly nodes?: (kind: string) => readonly {
195
+ readonly id: string;
196
+ readonly label?: string;
197
+ }[];
198
+ /** As `ValueContext.list`. */
199
+ readonly list?: (name: string) => readonly string[] | undefined;
200
+ }
201
+ /** One choice of a pick: the text a value holds, the name it is shown by, and where it comes from. */
202
+ interface PickChoice {
203
+ readonly value: string;
204
+ readonly label: string;
205
+ readonly source: PickSource;
206
+ }
207
+ /**
208
+ * Every choice a pick offers, source by source in its declared order, each source's own in its order. A name
209
+ * in two sources appears under each (`pickClashes` reports it); an empty entry is left out, as empty means
210
+ * automatic.
211
+ */
212
+ declare function pickChoices(decl: TypeDecl, src: PickSources): PickChoice[];
213
+ /**
214
+ * A pick's names that more than one of its sources holds (a character and a point of interest both called
215
+ * Gate): a warning each, once, for the project; each value naming one has its own from `valueProblems`.
216
+ */
217
+ declare function pickClashes(decl: TypeDecl, src: PickSources, path?: string): Problem[];
218
+ /**
219
+ * The lists of text a pick's `list` sources name, read off the extension's project entry: each a list field of
220
+ * the extension's `project` target holding text, the entry's own value else the field's default. Undefined for a
221
+ * name that is no such field.
222
+ */
223
+ declare function pickLists(projectTarget: TargetDecl | undefined, projectEntry: Readonly<Record<string, unknown>> | undefined): (name: string) => readonly string[] | undefined;
224
+ /** A list without ids holding text: what a pick's `list` source names. */
225
+ declare function isTextList(decl: TypeDecl): boolean;
175
226
  /**
176
227
  * A declaration's default: the one it gives, else its type's. A scalar's is
177
228
  * the kernel's (`defaultFor`: false, 0, "", the first enum value, no flags,
@@ -345,4 +396,4 @@ declare const atKey: (base: string, key: string) => string;
345
396
  /** `base` and then list position `i`. */
346
397
  declare const atIndex: (base: string, i: number) => string;
347
398
 
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 };
399
+ 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, atIndex, atKey, claimClashes, compileEntry, compiledTarget, defaultOf, effectiveClaim, entryProblems, extensionDataStrategy, isTextList, keyedBy, manifestProblems, mapOf, markUnresolved, mergeComments, mergeExtensionData, mergeTree, objectOf, orphanedEntries, parseManifest, pickChoices, pickClashes, pickLists, renameStages, resolvedClaims, settingValues, stableStringify, strategyForTarget, strategyForType, targetOf, unresolvedOf, unresolvedProblems, valueProblems, withoutMark };
@@ -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;
@@ -162,16 +175,54 @@ declare function effectiveClaim<C extends ClaimDecl>(claim: C, hostDecl: {
162
175
  readonly default?: unknown;
163
176
  }): C;
164
177
 
165
- /** What the host knows about its nodes, for references. */
178
+ /** What the host knows about its nodes, for references, and about the extension's project lists, for picks. */
166
179
  interface ValueContext {
167
180
  /** Does a node of `kind` with `id` exist? Absent, references are not checked
168
181
  * (a host checking a manifest's default, say, has no nodes to hand). */
169
182
  readonly exists?: (kind: string, id: string) => boolean;
183
+ /** The entries of a list of text on the extension's own project node, by field name (`pickLists`), for a
184
+ * pick's `list` source. Absent, list sources are not checked. */
185
+ readonly list?: (name: string) => readonly string[] | undefined;
170
186
  }
171
187
  /** Every problem with `value` as a value of `decl`, paths relative to the value. */
172
188
  declare function valueProblems(decl: TypeDecl, value: unknown, ctx?: ValueContext, path?: string): Problem[];
173
189
  /** Every problem with one node's entry, against its target. */
174
190
  declare function entryProblems(target: TargetDecl, entry: unknown, ctx?: ValueContext, path?: string): Problem[];
191
+ /** What a host offers a pick: the nodes of a reference kind, and the extension's project lists. */
192
+ interface PickSources {
193
+ /** The nodes of `kind`, by id, with the name the author knows each by (the id when absent). */
194
+ readonly nodes?: (kind: string) => readonly {
195
+ readonly id: string;
196
+ readonly label?: string;
197
+ }[];
198
+ /** As `ValueContext.list`. */
199
+ readonly list?: (name: string) => readonly string[] | undefined;
200
+ }
201
+ /** One choice of a pick: the text a value holds, the name it is shown by, and where it comes from. */
202
+ interface PickChoice {
203
+ readonly value: string;
204
+ readonly label: string;
205
+ readonly source: PickSource;
206
+ }
207
+ /**
208
+ * Every choice a pick offers, source by source in its declared order, each source's own in its order. A name
209
+ * in two sources appears under each (`pickClashes` reports it); an empty entry is left out, as empty means
210
+ * automatic.
211
+ */
212
+ declare function pickChoices(decl: TypeDecl, src: PickSources): PickChoice[];
213
+ /**
214
+ * A pick's names that more than one of its sources holds (a character and a point of interest both called
215
+ * Gate): a warning each, once, for the project; each value naming one has its own from `valueProblems`.
216
+ */
217
+ declare function pickClashes(decl: TypeDecl, src: PickSources, path?: string): Problem[];
218
+ /**
219
+ * The lists of text a pick's `list` sources name, read off the extension's project entry: each a list field of
220
+ * the extension's `project` target holding text, the entry's own value else the field's default. Undefined for a
221
+ * name that is no such field.
222
+ */
223
+ declare function pickLists(projectTarget: TargetDecl | undefined, projectEntry: Readonly<Record<string, unknown>> | undefined): (name: string) => readonly string[] | undefined;
224
+ /** A list without ids holding text: what a pick's `list` source names. */
225
+ declare function isTextList(decl: TypeDecl): boolean;
175
226
  /**
176
227
  * A declaration's default: the one it gives, else its type's. A scalar's is
177
228
  * the kernel's (`defaultFor`: false, 0, "", the first enum value, no flags,
@@ -345,4 +396,4 @@ declare const atKey: (base: string, key: string) => string;
345
396
  /** `base` and then list position `i`. */
346
397
  declare const atIndex: (base: string, i: number) => string;
347
398
 
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 };
399
+ 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, atIndex, atKey, claimClashes, compileEntry, compiledTarget, defaultOf, effectiveClaim, entryProblems, extensionDataStrategy, isTextList, keyedBy, manifestProblems, mapOf, markUnresolved, mergeComments, mergeExtensionData, mergeTree, objectOf, orphanedEntries, parseManifest, pickChoices, pickClashes, pickLists, renameStages, resolvedClaims, settingValues, stableStringify, strategyForTarget, strategyForType, targetOf, unresolvedOf, unresolvedProblems, valueProblems, withoutMark };
@@ -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,7 +356,7 @@ 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);
@@ -328,9 +406,80 @@ function manifestProblems(manifest) {
328
406
  }
329
407
  targetProblems(host, kind, target, kindPath, settings, out);
330
408
  }
409
+ pickListProblems(kinds, hostPath, out);
331
410
  }
332
411
  return out;
333
412
  }
413
+ function pickSourcesSound(host, from, path, out) {
414
+ if (!Array.isArray(from) || from.length === 0) {
415
+ out.push(err2("pick-from", path, 'a pick lists its sources in "from": { "words": [...] }, { "to": a node kind }, or { "list": a project list }'));
416
+ return false;
417
+ }
418
+ let sound = true;
419
+ from.forEach((s, i) => {
420
+ const at = atIndex(path, i);
421
+ const keys = isObject(s) ? Object.keys(s).filter((k) => k === "words" || k === "to" || k === "list") : [];
422
+ if (!isObject(s) || keys.length !== 1) {
423
+ out.push(err2("pick-source", at, 'a source is one of { "words": [...] }, { "to": a node kind }, { "list": a project list }'));
424
+ sound = false;
425
+ return;
426
+ }
427
+ unknownKeys(s, new Set(keys), at, out);
428
+ if (keys[0] === "words") {
429
+ const words = s["words"];
430
+ const seen = /* @__PURE__ */ new Set();
431
+ if (!Array.isArray(words) || words.length === 0) {
432
+ out.push(err2("pick-words", atKey(at, "words"), "fixed words are a list of at least one"));
433
+ sound = false;
434
+ return;
435
+ }
436
+ words.forEach((w, j) => {
437
+ if (typeof w !== "string" || w.trim() === "") {
438
+ out.push(err2("value-empty", atIndex(atKey(at, "words"), j), "a word has a name"));
439
+ sound = false;
440
+ } else if (seen.has(w)) {
441
+ out.push(err2("value-duplicate", atIndex(atKey(at, "words"), j), `"${w}" is listed twice`));
442
+ sound = false;
443
+ } else seen.add(w);
444
+ });
445
+ } else if (keys[0] === "to") {
446
+ if (typeof s["to"] !== "string" || !HOSTS[host].references.includes(s["to"])) {
447
+ out.push(err2("reference-to", atKey(at, "to"), `a source's nodes are of a kind, one of ${HOSTS[host].references.join(", ")}`));
448
+ sound = false;
449
+ }
450
+ } else if (typeof s["list"] !== "string" || !NAME.test(s["list"])) {
451
+ out.push(err2("pick-list", atKey(at, "list"), "a list source names a field of the extension's project node"));
452
+ sound = false;
453
+ }
454
+ });
455
+ return sound;
456
+ }
457
+ function pickListProblems(kinds, hostPath, out) {
458
+ const project = isObject(kinds["project"]) && Array.isArray(kinds["project"]["fields"]) ? kinds["project"]["fields"] : [];
459
+ const lists = new Set(project.filter((f) => isObject(f) && isTextList(f)).map((f) => f.name));
460
+ const walk = (decl, path) => {
461
+ if (!isObject(decl)) return;
462
+ const d = decl;
463
+ if (d.type === "pick" && Array.isArray(d.from)) {
464
+ d.from.forEach((s, i) => {
465
+ if (isObject(s) && typeof s.list === "string" && !lists.has(s.list)) {
466
+ const name = s.list;
467
+ out.push(err2(
468
+ "pick-list",
469
+ atKey(atIndex(atKey(path, "from"), i), "list"),
470
+ `"${name}" is not a list of text on the extension's project node; declare it there, as { "name": "${name}", "type": "list", "of": { "type": "text" } }`
471
+ ));
472
+ }
473
+ });
474
+ }
475
+ if (Array.isArray(d.fields)) d.fields.forEach((f, i) => walk(f, atIndex(atKey(path, "fields"), i)));
476
+ if (d.of !== void 0) walk(d.of, atKey(path, "of"));
477
+ };
478
+ for (const [kind, target] of Object.entries(kinds)) {
479
+ if (!isObject(target) || !Array.isArray(target["fields"])) continue;
480
+ target["fields"].forEach((f, i) => walk(f, atIndex(atKey(atKey(hostPath, kind), "fields"), i)));
481
+ }
482
+ }
334
483
  function unknownKeys(obj, known, path, out) {
335
484
  for (const k of Object.keys(obj)) {
336
485
  if (!known.has(k)) {
@@ -491,6 +640,9 @@ function typeProblems(host, decl, path, named, out, claim = false) {
491
640
  sound = false;
492
641
  }
493
642
  break;
643
+ case "pick":
644
+ if (!pickSourcesSound(host, decl.from, at("from"), out)) sound = false;
645
+ break;
494
646
  case "record":
495
647
  if (!Array.isArray(decl.fields) || decl.fields.length === 0) {
496
648
  out.push(err2("record-empty", at("fields"), "a record declares its fields"));
@@ -965,6 +1117,6 @@ function orphanedEntries(data, exists) {
965
1117
  return out;
966
1118
  }
967
1119
 
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 };
1120
+ 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, keyedBy, manifestProblems, mapOf, markUnresolved, mergeComments, mergeExtensionData, mergeTree, objectOf, orphanedEntries, parseManifest, pickChoices, pickClashes, pickLists, renameStages, resolvedClaims, settingValues, stableStringify, strategyForTarget, strategyForType, targetOf, unresolvedOf, unresolvedProblems, valueProblems, withoutMark };
969
1121
  //# sourceMappingURL=extensions.js.map
970
1122
  //# sourceMappingURL=extensions.js.map