@wildwinter/toolkit 0.3.0 → 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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @wildwinter/toolkit
2
2
 
3
+ ## 0.3.1
4
+
5
+ ### Added
6
+
7
+ - **Requirements in a manifest** (`./extensions`): `requires` names the other extensions, by id, that a project must
8
+ take for this one to work. The manifest check holds it to ids, each once, never the extension's own.
9
+ `missingRequirements(manifest, taken)` lists those a project lacks, for a host's Add Extension, its validation,
10
+ and its views.
11
+ - **A view as a field's editor** (`./extensions`): a target's own map of records can name one of the manifest's views
12
+ as its `editor`, which a host opens in place of its own record grid. The check holds it to a declared view that
13
+ opens in that host, and warns of one on any other kind of field, where it means nothing.
14
+
3
15
  ## 0.3.0
4
16
 
5
17
  ### Removed
@@ -1619,6 +1619,281 @@
1619
1619
  "severity": "warning"
1620
1620
  }
1621
1621
  ]
1622
+ },
1623
+ {
1624
+ "name": "requires and a field's editor pass",
1625
+ "manifest": {
1626
+ "schema": "wildwinter/extension@0",
1627
+ "id": "probe",
1628
+ "name": "Probe",
1629
+ "version": "1.0.0",
1630
+ "targets": {
1631
+ "patter": {
1632
+ "line": {
1633
+ "fields": [
1634
+ {
1635
+ "name": "performance",
1636
+ "type": "map",
1637
+ "key": {
1638
+ "type": "text"
1639
+ },
1640
+ "of": {
1641
+ "type": "record",
1642
+ "fields": [
1643
+ {
1644
+ "name": "look",
1645
+ "type": "text"
1646
+ }
1647
+ ]
1648
+ },
1649
+ "editor": "sheet"
1650
+ }
1651
+ ]
1652
+ }
1653
+ }
1654
+ },
1655
+ "views": [
1656
+ {
1657
+ "id": "sheet",
1658
+ "name": "Sheet",
1659
+ "entry": "view/sheet.js",
1660
+ "element": "probe-sheet",
1661
+ "hosts": [
1662
+ "patter"
1663
+ ]
1664
+ }
1665
+ ],
1666
+ "reads": [
1667
+ "patterstage"
1668
+ ],
1669
+ "requires": [
1670
+ "patterstage"
1671
+ ]
1672
+ },
1673
+ "problems": []
1674
+ },
1675
+ {
1676
+ "name": "requires names other extensions, once each",
1677
+ "manifest": {
1678
+ "schema": "wildwinter/extension@0",
1679
+ "id": "probe",
1680
+ "name": "Probe",
1681
+ "version": "1.0.0",
1682
+ "targets": {
1683
+ "patter": {
1684
+ "line": {
1685
+ "fields": [
1686
+ {
1687
+ "name": "mood",
1688
+ "type": "text"
1689
+ }
1690
+ ]
1691
+ }
1692
+ }
1693
+ },
1694
+ "requires": [
1695
+ "patterstage",
1696
+ "probe",
1697
+ "patterstage",
1698
+ "Bad"
1699
+ ]
1700
+ },
1701
+ "problems": [
1702
+ {
1703
+ "path": "requires[1]",
1704
+ "code": "requires-self"
1705
+ },
1706
+ {
1707
+ "path": "requires[2]",
1708
+ "code": "requires-duplicate"
1709
+ },
1710
+ {
1711
+ "path": "requires[3]",
1712
+ "code": "requires-id"
1713
+ }
1714
+ ]
1715
+ },
1716
+ {
1717
+ "name": "requires must be a list",
1718
+ "manifest": {
1719
+ "schema": "wildwinter/extension@0",
1720
+ "id": "probe",
1721
+ "name": "Probe",
1722
+ "version": "1.0.0",
1723
+ "targets": {
1724
+ "patter": {
1725
+ "line": {
1726
+ "fields": [
1727
+ {
1728
+ "name": "mood",
1729
+ "type": "text"
1730
+ }
1731
+ ]
1732
+ }
1733
+ }
1734
+ },
1735
+ "requires": "patterstage"
1736
+ },
1737
+ "problems": [
1738
+ {
1739
+ "path": "requires",
1740
+ "code": "not-a-list"
1741
+ }
1742
+ ]
1743
+ },
1744
+ {
1745
+ "name": "a field's editor names a view that opens in its host, on a map of records",
1746
+ "manifest": {
1747
+ "schema": "wildwinter/extension@0",
1748
+ "id": "probe",
1749
+ "name": "Probe",
1750
+ "version": "1.0.0",
1751
+ "targets": {
1752
+ "patter": {
1753
+ "line": {
1754
+ "fields": [
1755
+ {
1756
+ "name": "perf",
1757
+ "type": "map",
1758
+ "key": {
1759
+ "type": "text"
1760
+ },
1761
+ "of": {
1762
+ "type": "record",
1763
+ "fields": [
1764
+ {
1765
+ "name": "look",
1766
+ "type": "text",
1767
+ "editor": "sheet"
1768
+ }
1769
+ ]
1770
+ },
1771
+ "editor": "nope"
1772
+ },
1773
+ {
1774
+ "name": "cues",
1775
+ "type": "map",
1776
+ "key": {
1777
+ "type": "text"
1778
+ },
1779
+ "of": {
1780
+ "type": "record",
1781
+ "fields": [
1782
+ {
1783
+ "name": "at",
1784
+ "type": "number"
1785
+ }
1786
+ ]
1787
+ },
1788
+ "editor": 3
1789
+ },
1790
+ {
1791
+ "name": "mood",
1792
+ "type": "text",
1793
+ "editor": "sheet"
1794
+ }
1795
+ ]
1796
+ }
1797
+ },
1798
+ "storylets": {
1799
+ "card": {
1800
+ "fields": [
1801
+ {
1802
+ "name": "perf",
1803
+ "type": "map",
1804
+ "key": {
1805
+ "type": "text"
1806
+ },
1807
+ "of": {
1808
+ "type": "record",
1809
+ "fields": [
1810
+ {
1811
+ "name": "look",
1812
+ "type": "text"
1813
+ }
1814
+ ]
1815
+ },
1816
+ "editor": "sheet"
1817
+ }
1818
+ ]
1819
+ }
1820
+ }
1821
+ },
1822
+ "views": [
1823
+ {
1824
+ "id": "sheet",
1825
+ "name": "Sheet",
1826
+ "entry": "view/sheet.js",
1827
+ "element": "probe-sheet",
1828
+ "hosts": [
1829
+ "patter"
1830
+ ]
1831
+ }
1832
+ ]
1833
+ },
1834
+ "problems": [
1835
+ {
1836
+ "path": "targets.patter.line.fields[0].of.fields[0].editor",
1837
+ "code": "editor-nested"
1838
+ },
1839
+ {
1840
+ "path": "targets.patter.line.fields[0].editor",
1841
+ "code": "editor-view"
1842
+ },
1843
+ {
1844
+ "path": "targets.patter.line.fields[1].editor",
1845
+ "code": "editor-view"
1846
+ },
1847
+ {
1848
+ "path": "targets.patter.line.fields[2].editor",
1849
+ "code": "editor-ignored",
1850
+ "severity": "warning"
1851
+ },
1852
+ {
1853
+ "path": "targets.storylets.card.fields[0].editor",
1854
+ "code": "editor-host"
1855
+ }
1856
+ ]
1857
+ },
1858
+ {
1859
+ "name": "an editor names a view the manifest declares",
1860
+ "manifest": {
1861
+ "schema": "wildwinter/extension@0",
1862
+ "id": "probe",
1863
+ "name": "Probe",
1864
+ "version": "1.0.0",
1865
+ "targets": {
1866
+ "patter": {
1867
+ "line": {
1868
+ "fields": [
1869
+ {
1870
+ "name": "perf",
1871
+ "type": "map",
1872
+ "key": {
1873
+ "type": "text"
1874
+ },
1875
+ "of": {
1876
+ "type": "record",
1877
+ "fields": [
1878
+ {
1879
+ "name": "look",
1880
+ "type": "text"
1881
+ }
1882
+ ]
1883
+ },
1884
+ "editor": "sheet"
1885
+ }
1886
+ ]
1887
+ }
1888
+ }
1889
+ }
1890
+ },
1891
+ "problems": [
1892
+ {
1893
+ "path": "targets.patter.line.fields[0].editor",
1894
+ "code": "editor-view"
1895
+ }
1896
+ ]
1622
1897
  }
1623
1898
  ]
1624
1899
  }
@@ -365,9 +365,9 @@ var ID = /^[a-z][a-z0-9-]*$/;
365
365
  var VERSION = /^\d+\.\d+\.\d+$/;
366
366
  var NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
367
367
  var TYPE_KEYS = ["type", "values", "stages", "renamedStages", "to", "from", "fields", "key", "of", "id", "min", "max", "valueHelp", "default"];
368
- var FIELD_KEYS = /* @__PURE__ */ new Set([...TYPE_KEYS, "name", "label", "help", "required", "group", "editorOnly"]);
368
+ var FIELD_KEYS = /* @__PURE__ */ new Set([...TYPE_KEYS, "name", "label", "help", "required", "group", "editorOnly", "editor"]);
369
369
  var BARE_KEYS = new Set(TYPE_KEYS);
370
- var MANIFEST_KEYS = /* @__PURE__ */ new Set(["schema", "id", "name", "version", "docs", "targets", "views", "reads"]);
370
+ var MANIFEST_KEYS = /* @__PURE__ */ new Set(["schema", "id", "name", "version", "docs", "targets", "views", "reads", "requires"]);
371
371
  var REMOVED_MANIFEST_KEYS = {
372
372
  settings: "settings were removed with claims; declare the extension's own fields, or a project field for a project-wide value"
373
373
  };
@@ -398,6 +398,7 @@ function manifestProblems(manifest) {
398
398
  if (m["docs"] !== void 0 && typeof m["docs"] !== "string") out.push(err2("docs", "docs", "docs is a link, as text"));
399
399
  viewsProblems(m["views"], m["targets"], out);
400
400
  readsProblems(m["reads"], m["id"], out);
401
+ requiresProblems(m["requires"], m["id"], out);
401
402
  const targets = m["targets"];
402
403
  if (!isObject(targets) || Object.keys(targets).length === 0) {
403
404
  out.push(err2("no-targets", "targets", "the manifest targets no host"));
@@ -423,6 +424,7 @@ function manifestProblems(manifest) {
423
424
  targetProblems(host, target, kindPath, out);
424
425
  }
425
426
  pickListProblems(kinds, hostPath, out);
427
+ editorProblems(host, kinds, m["views"], Object.keys(targets), hostPath, out);
426
428
  }
427
429
  return out;
428
430
  }
@@ -562,6 +564,54 @@ function readsProblems(reads, self, out) {
562
564
  else seen.add(r);
563
565
  });
564
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
+ }
565
615
  function viewsFor(manifest, host) {
566
616
  const targeted = Object.keys(manifest.targets ?? {});
567
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)));
@@ -609,6 +659,9 @@ function fieldListProblems(host, fields, path, topLevel, out) {
609
659
  if (field.editorOnly !== void 0 && !topLevel) {
610
660
  out.push(err2("editor-only-nested", atKey(at, "editorOnly"), "only a target's own fields can be editor-only, not a record's"));
611
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
+ }
612
665
  for (const flag of ["required", "editorOnly"]) {
613
666
  if (field[flag] !== void 0 && typeof field[flag] !== "boolean") {
614
667
  out.push(err2("not-a-boolean", atKey(at, flag), `${flag} is true or false`));
@@ -1120,6 +1173,7 @@ exports.markUnresolved = markUnresolved;
1120
1173
  exports.mergeComments = mergeComments;
1121
1174
  exports.mergeExtensionData = mergeExtensionData;
1122
1175
  exports.mergeTree = mergeTree;
1176
+ exports.missingRequirements = missingRequirements;
1123
1177
  exports.objectOf = objectOf;
1124
1178
  exports.orphanedEntries = orphanedEntries;
1125
1179
  exports.parseManifest = parseManifest;