@wildwinter/toolkit 0.2.7 → 0.3.1

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.
@@ -14,13 +14,11 @@ var MANIFEST_FILE_SUFFIX = ".extension.json";
14
14
  var HOSTS = {
15
15
  patter: {
16
16
  kinds: ["scene", "block", "snippet", "line", "text", "gameEvent", "option", "project"],
17
- references: ["cast", "scene", "block", "snippet", "line"],
18
- claimable: ["scene", "block", "snippet", "line", "text", "gameEvent", "option"]
17
+ references: ["cast", "scene", "block", "snippet", "line"]
19
18
  },
20
19
  storylets: {
21
20
  kinds: ["box", "tagGroup", "tag", "deck", "card", "outcome", "handTemplate", "hand", "project"],
22
- references: ["box", "deck", "card", "outcome", "tag"],
23
- claimable: ["card", "outcome"]
21
+ references: ["box", "deck", "card", "outcome", "tag"]
24
22
  }
25
23
  };
26
24
  var SCALAR_TYPES = ["text", "multiline", "number", "boolean", "enum", "flags", "quality"];
@@ -367,10 +365,15 @@ var ID = /^[a-z][a-z0-9-]*$/;
367
365
  var VERSION = /^\d+\.\d+\.\d+$/;
368
366
  var NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
369
367
  var TYPE_KEYS = ["type", "values", "stages", "renamedStages", "to", "from", "fields", "key", "of", "id", "min", "max", "valueHelp", "default"];
370
- var FIELD_KEYS = /* @__PURE__ */ new Set([...TYPE_KEYS, "name", "label", "help", "required", "group", "editorOnly"]);
371
- var CLAIM_KEYS = /* @__PURE__ */ new Set([...TYPE_KEYS, "setting", "label", "help", "required"]);
368
+ var FIELD_KEYS = /* @__PURE__ */ new Set([...TYPE_KEYS, "name", "label", "help", "required", "group", "editorOnly", "editor"]);
372
369
  var BARE_KEYS = new Set(TYPE_KEYS);
373
- var MANIFEST_KEYS = /* @__PURE__ */ new Set(["schema", "id", "name", "version", "docs", "settings", "targets", "views", "reads"]);
370
+ var MANIFEST_KEYS = /* @__PURE__ */ new Set(["schema", "id", "name", "version", "docs", "targets", "views", "reads", "requires"]);
371
+ var REMOVED_MANIFEST_KEYS = {
372
+ settings: "settings were removed with claims; declare the extension's own fields, or a project field for a project-wide value"
373
+ };
374
+ var REMOVED_TARGET_KEYS = {
375
+ claims: "claims on host fields were removed; declare a field of the extension's own, which the runtimes hand the game"
376
+ };
374
377
  var VIEW_KEYS = /* @__PURE__ */ new Set(["id", "name", "entry", "element", "hosts"]);
375
378
  var ELEMENT = /^[a-z][a-z0-9._]*-[a-z0-9._-]*$/;
376
379
  var err2 = (code, path, message) => ({ severity: "error", code, path, message });
@@ -381,7 +384,7 @@ function manifestProblems(manifest) {
381
384
  const out = [];
382
385
  if (!isObject(manifest)) return [err2("not-an-object", "", "a manifest is a JSON object")];
383
386
  const m = manifest;
384
- unknownKeys(m, MANIFEST_KEYS, "", out);
387
+ unknownKeys(m, MANIFEST_KEYS, "", out, REMOVED_MANIFEST_KEYS);
385
388
  if (m["schema"] !== MANIFEST_SCHEMA) {
386
389
  out.push(err2("schema", "schema", `the schema tag is ${JSON.stringify(m["schema"])}, not "${MANIFEST_SCHEMA}"`));
387
390
  }
@@ -393,9 +396,9 @@ function manifestProblems(manifest) {
393
396
  out.push(err2("version", "version", "the version is major.minor.patch, such as 1.0.0"));
394
397
  }
395
398
  if (m["docs"] !== void 0 && typeof m["docs"] !== "string") out.push(err2("docs", "docs", "docs is a link, as text"));
396
- const settings = settingsProblems(m["settings"], out);
397
399
  viewsProblems(m["views"], m["targets"], out);
398
400
  readsProblems(m["reads"], m["id"], out);
401
+ requiresProblems(m["requires"], m["id"], out);
399
402
  const targets = m["targets"];
400
403
  if (!isObject(targets) || Object.keys(targets).length === 0) {
401
404
  out.push(err2("no-targets", "targets", "the manifest targets no host"));
@@ -418,9 +421,10 @@ function manifestProblems(manifest) {
418
421
  out.push(err2("unknown-kind", kindPath, `"${kind}" is not a node kind of ${host}; it has ${spec.kinds.join(", ")}`));
419
422
  continue;
420
423
  }
421
- targetProblems(host, kind, target, kindPath, settings, out);
424
+ targetProblems(host, target, kindPath, out);
422
425
  }
423
426
  pickListProblems(kinds, hostPath, out);
427
+ editorProblems(host, kinds, m["views"], Object.keys(targets), hostPath, out);
424
428
  }
425
429
  return out;
426
430
  }
@@ -560,85 +564,75 @@ function readsProblems(reads, self, out) {
560
564
  else seen.add(r);
561
565
  });
562
566
  }
567
+ function requiresProblems(requires, self, out) {
568
+ if (requires === void 0) return;
569
+ if (!Array.isArray(requires)) {
570
+ out.push(err2("not-a-list", "requires", "requires is a list of extension ids"));
571
+ return;
572
+ }
573
+ const seen = /* @__PURE__ */ new Set();
574
+ requires.forEach((r, i) => {
575
+ const at = atIndex("requires", i);
576
+ if (typeof r !== "string" || !ID.test(r)) out.push(err2("requires-id", at, "a required extension is named by its id: lower case letters, digits and hyphens"));
577
+ else if (r === self) out.push(err2("requires-self", at, "an extension can't require itself"));
578
+ else if (seen.has(r)) out.push(err2("requires-duplicate", at, `"${r}" is listed twice`));
579
+ else seen.add(r);
580
+ });
581
+ }
582
+ function missingRequirements(manifest, taken) {
583
+ const have = new Set(taken);
584
+ const out = [];
585
+ for (const r of Array.isArray(manifest.requires) ? manifest.requires : []) {
586
+ if (typeof r === "string" && ID.test(r) && r !== manifest.id && !have.has(r) && !out.includes(r)) out.push(r);
587
+ }
588
+ return out;
589
+ }
590
+ function editorProblems(host, kinds, views, targeted, hostPath, out) {
591
+ const declared = Array.isArray(views) ? views.filter((v) => isObject(v) && typeof v["id"] === "string") : [];
592
+ for (const [kind, target] of Object.entries(kinds)) {
593
+ if (!isObject(target) || !Array.isArray(target["fields"])) continue;
594
+ target["fields"].forEach((f, i) => {
595
+ if (!isObject(f) || f["editor"] === void 0) return;
596
+ const at = atKey(atIndex(atKey(atKey(hostPath, kind), "fields"), i), "editor");
597
+ const editor = f["editor"];
598
+ const view = typeof editor === "string" ? declared.find((v) => v["id"] === editor) : void 0;
599
+ if (view === void 0) {
600
+ out.push(err2("editor-view", at, typeof editor === "string" ? `the manifest declares no view called "${editor}"; an editor names one of its views by id` : "an editor names one of the manifest's views by its id"));
601
+ return;
602
+ }
603
+ const opensIn = Array.isArray(view["hosts"]) ? view["hosts"] : targeted;
604
+ if (!opensIn.includes(host)) {
605
+ out.push(err2("editor-host", at, `the view "${editor}" doesn't open in ${host}`));
606
+ return;
607
+ }
608
+ const d = f;
609
+ if (!(d.type === "map" && isObject(d.of) && d.of.type === "record")) {
610
+ out.push({ severity: "warning", code: "editor-ignored", path: at, message: "only a map of records opens in an editor; this field is edited in place, so the editor is ignored" });
611
+ }
612
+ });
613
+ }
614
+ }
563
615
  function viewsFor(manifest, host) {
564
616
  const targeted = Object.keys(manifest.targets ?? {});
565
617
  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)));
566
618
  }
567
- function unknownKeys(obj, known, path, out) {
619
+ function unknownKeys(obj, known, path, out, removed = {}) {
568
620
  for (const k of Object.keys(obj)) {
569
- if (!known.has(k)) {
621
+ if (own(removed, k)) {
622
+ out.push({ severity: "warning", code: "removed-key", path: atKey(path, k), message: `"${k}" is ignored: ${removed[k]}` });
623
+ } else if (!known.has(k)) {
570
624
  out.push({ severity: "warning", code: "unknown-key", path: atKey(path, k), message: `"${k}" means nothing here; it is ignored` });
571
625
  }
572
626
  }
573
627
  }
574
- function settingsProblems(settings, out) {
575
- const names = /* @__PURE__ */ new Set();
576
- if (settings === void 0) return names;
577
- if (!Array.isArray(settings)) {
578
- out.push(err2("not-a-list", "settings", "settings are a list"));
579
- return names;
580
- }
581
- settings.forEach((s, i) => {
582
- const at = atIndex("settings", i);
583
- if (!isObject(s)) {
584
- out.push(err2("not-an-object", at, "a setting is an object"));
585
- return;
586
- }
587
- const name = s["name"];
588
- if (typeof name !== "string" || !NAME.test(name)) {
589
- out.push(err2("setting-name", atKey(at, "name"), "a setting's name is an identifier"));
590
- } else if (names.has(name)) {
591
- out.push(err2("setting-duplicate", atKey(at, "name"), `setting "${name}" is declared twice`));
592
- } else {
593
- names.add(name);
594
- }
595
- const dflt = s.default;
596
- if (typeof dflt !== "string" || !NAME.test(dflt)) {
597
- out.push(err2("setting-default", atKey(at, "default"), "a setting names a host field by default, so its default is a field name"));
598
- }
599
- });
600
- return names;
601
- }
602
- function targetProblems(host, kind, target, path, settings, out) {
628
+ function targetProblems(host, target, path, out) {
603
629
  if (!isObject(target)) {
604
630
  out.push(err2("not-an-object", path, "a target is an object"));
605
631
  return;
606
632
  }
607
- unknownKeys(target, /* @__PURE__ */ new Set(["fields", "claims"]), path, out);
633
+ unknownKeys(target, /* @__PURE__ */ new Set(["fields"]), path, out, REMOVED_TARGET_KEYS);
608
634
  const t = target;
609
635
  if (t.fields !== void 0) fieldListProblems(host, t.fields, atKey(path, "fields"), true, out);
610
- if (t.claims === void 0) return;
611
- if (!Array.isArray(t.claims)) {
612
- out.push(err2("not-a-list", atKey(path, "claims"), "claims are a list"));
613
- return;
614
- }
615
- const claimable = HOSTS[host].claimable.includes(kind);
616
- const claimed = /* @__PURE__ */ new Set();
617
- t.claims.forEach((c, i) => {
618
- const at = atIndex(atKey(path, "claims"), i);
619
- if (!claimable) {
620
- out.push(err2("claim-kind", at, `a ${kind} has no host fields to claim; ${host} has them on ${HOSTS[host].claimable.join(", ")}`));
621
- return;
622
- }
623
- if (!isObject(c)) {
624
- out.push(err2("not-an-object", at, "a claim is an object"));
625
- return;
626
- }
627
- const claim = c;
628
- unknownKeys(c, CLAIM_KEYS, at, out);
629
- if (!SCALAR_TYPES.includes(claim.type)) {
630
- out.push(err2("claim-type", atKey(at, "type"), "a claim presents a host field, which is a scalar"));
631
- } else {
632
- typeProblems(host, claim, at, false, out, true);
633
- }
634
- if (typeof claim.setting !== "string" || !settings.has(claim.setting)) {
635
- out.push(err2("claim-setting", atKey(at, "setting"), `a claim names its field through a setting, and "${String(claim.setting)}" is not one of the manifest's settings`));
636
- } else if (claimed.has(claim.setting)) {
637
- out.push(err2("claim-duplicate", atKey(at, "setting"), `setting "${claim.setting}" is claimed twice for one kind`));
638
- } else {
639
- claimed.add(claim.setting);
640
- }
641
- });
642
636
  }
643
637
  function fieldListProblems(host, fields, path, topLevel, out) {
644
638
  if (!Array.isArray(fields)) {
@@ -665,6 +659,9 @@ function fieldListProblems(host, fields, path, topLevel, out) {
665
659
  if (field.editorOnly !== void 0 && !topLevel) {
666
660
  out.push(err2("editor-only-nested", atKey(at, "editorOnly"), "only a target's own fields can be editor-only, not a record's"));
667
661
  }
662
+ if (field.editor !== void 0 && !topLevel) {
663
+ out.push(err2("editor-nested", atKey(at, "editor"), "only a target's own fields open in a view, not a record's"));
664
+ }
668
665
  for (const flag of ["required", "editorOnly"]) {
669
666
  if (field[flag] !== void 0 && typeof field[flag] !== "boolean") {
670
667
  out.push(err2("not-a-boolean", atKey(at, flag), `${flag} is true or false`));
@@ -672,7 +669,7 @@ function fieldListProblems(host, fields, path, topLevel, out) {
672
669
  }
673
670
  });
674
671
  }
675
- function typeProblems(host, decl, path, named, out, claim = false) {
672
+ function typeProblems(host, decl, path, named, out) {
676
673
  const typeAt = atKey(path, "type");
677
674
  if (decl.type === "string") {
678
675
  out.push(err2("string-type", typeAt, 'the text type is spelled "text"'));
@@ -688,7 +685,6 @@ function typeProblems(host, decl, path, named, out, claim = false) {
688
685
  case "enum":
689
686
  case "flags": {
690
687
  const values = decl.values;
691
- if (claim && values === void 0) break;
692
688
  if (!Array.isArray(values) || values.length === 0) {
693
689
  out.push(err2("values-empty", at("values"), `an ${decl.type === "enum" ? "enum" : "flags field"} lists its values`));
694
690
  sound = false;
@@ -707,7 +703,6 @@ function typeProblems(host, decl, path, named, out, claim = false) {
707
703
  break;
708
704
  }
709
705
  case "quality": {
710
- if (claim && decl.stages === void 0) break;
711
706
  for (const p of expr.qualityDeclarationProblems(decl.stages)) {
712
707
  out.push(err2(
713
708
  `quality-${p.kind === "default-not-a-stage" ? "default" : p.kind}`,
@@ -770,11 +765,7 @@ function typeProblems(host, decl, path, named, out, claim = false) {
770
765
  }
771
766
  }
772
767
  if (decl.renamedStages !== void 0) renamedStagesProblems(decl, at("renamedStages"), out);
773
- if (decl.valueHelp !== void 0 && claim) {
774
- if (!isObject(decl.valueHelp) || !["enum", "flags", "quality"].includes(decl.type)) {
775
- out.push(err2("value-help", at("valueHelp"), "value help describes the values of an enum or flags field, or a quality's stages"));
776
- }
777
- } else if (decl.valueHelp !== void 0) {
768
+ if (decl.valueHelp !== void 0) {
778
769
  const vocabulary = decl.type === "quality" ? decl.stages : decl.type === "enum" || decl.type === "flags" ? decl.values : decl.type === "pick" ? pickWords(decl.from) : void 0;
779
770
  if (vocabulary === void 0 || !isObject(decl.valueHelp)) {
780
771
  out.push(err2("value-help", at("valueHelp"), "value help describes the values of an enum or flags field, a quality's stages, or a pick's words"));
@@ -815,56 +806,10 @@ function renamedStagesProblems(decl, path, out) {
815
806
  }
816
807
  }
817
808
  }
818
- function settingValues(manifest, chosen) {
819
- const out = {};
820
- for (const s of manifest.settings ?? []) {
821
- const v = chosen !== void 0 && own(chosen, s.name) ? chosen[s.name] : void 0;
822
- out[s.name] = typeof v === "string" && v !== "" ? v : s.default;
823
- }
824
- return out;
825
- }
826
- function resolvedClaims(manifest, host, chosen) {
827
- const settings = settingValues(manifest, chosen);
828
- const out = [];
829
- for (const [kind, target] of Object.entries(manifest.targets[host] ?? {})) {
830
- for (const claim of target.claims ?? []) {
831
- const field = settings[claim.setting];
832
- if (field !== void 0) out.push({ extension: manifest.id, host, kind, field, claim });
833
- }
834
- }
835
- return out;
836
- }
837
- function claimClashes(claims) {
838
- const out = [];
839
- const first = /* @__PURE__ */ new Map();
840
- for (const c of claims) {
841
- const key = `${c.host}\0${c.kind}\0${c.field}`;
842
- const prior = first.get(key);
843
- if (prior === void 0) {
844
- first.set(key, c);
845
- continue;
846
- }
847
- if (prior.extension === c.extension) continue;
848
- out.push(err2(
849
- "claim-clash",
850
- `${c.kind}.${c.field}`,
851
- `extensions "${prior.extension}" and "${c.extension}" both claim the ${c.kind} field "${c.field}"`
852
- ));
853
- }
854
- return out;
855
- }
856
809
  function targetOf(manifest, host, kind) {
857
810
  const kinds = manifest?.targets[host];
858
811
  return kinds !== void 0 && own(kinds, kind) ? kinds[kind] : void 0;
859
812
  }
860
- function effectiveClaim(claim, hostDecl) {
861
- return {
862
- ...claim,
863
- ...claim.values === void 0 && hostDecl.values !== void 0 ? { values: hostDecl.values } : {},
864
- ...claim.stages === void 0 && hostDecl.stages !== void 0 ? { stages: hostDecl.stages } : {},
865
- ...claim.default === void 0 && hostDecl.default !== void 0 ? { default: hostDecl.default } : {}
866
- };
867
- }
868
813
 
869
814
  // src/extensions/merge.ts
870
815
  var ATOMIC = { kind: "atomic" };
@@ -1214,11 +1159,9 @@ exports.UNRESOLVED_KEY = UNRESOLVED_KEY;
1214
1159
  exports.VALUE_TYPES = VALUE_TYPES;
1215
1160
  exports.atIndex = atIndex;
1216
1161
  exports.atKey = atKey;
1217
- exports.claimClashes = claimClashes;
1218
1162
  exports.compileEntry = compileEntry;
1219
1163
  exports.compiledTarget = compiledTarget;
1220
1164
  exports.defaultOf = defaultOf;
1221
- exports.effectiveClaim = effectiveClaim;
1222
1165
  exports.entryProblems = entryProblems;
1223
1166
  exports.extensionDataStrategy = extensionDataStrategy;
1224
1167
  exports.isTextList = isTextList;
@@ -1230,6 +1173,7 @@ exports.markUnresolved = markUnresolved;
1230
1173
  exports.mergeComments = mergeComments;
1231
1174
  exports.mergeExtensionData = mergeExtensionData;
1232
1175
  exports.mergeTree = mergeTree;
1176
+ exports.missingRequirements = missingRequirements;
1233
1177
  exports.objectOf = objectOf;
1234
1178
  exports.orphanedEntries = orphanedEntries;
1235
1179
  exports.parseManifest = parseManifest;
@@ -1237,8 +1181,6 @@ exports.pickChoices = pickChoices;
1237
1181
  exports.pickClashes = pickClashes;
1238
1182
  exports.pickLists = pickLists;
1239
1183
  exports.renameStages = renameStages;
1240
- exports.resolvedClaims = resolvedClaims;
1241
- exports.settingValues = settingValues;
1242
1184
  exports.stableStringify = stableStringify;
1243
1185
  exports.strategyForTarget = strategyForTarget;
1244
1186
  exports.strategyForType = strategyForType;