@wildwinter/toolkit 0.2.5 → 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 CHANGED
@@ -1,5 +1,15 @@
1
1
  # @wildwinter/toolkit
2
2
 
3
+ ## 0.2.6
4
+
5
+ ### Added
6
+
7
+ - **Views and reads in a manifest** (`./extensions`, tier 2): `views` declares the web components an extension's
8
+ installed package supplies, each with an `id`, a `name`, an `entry` script inside the package, the custom `element`
9
+ it defines, and optionally the `hosts` that offer it; `reads` names the other extensions whose data the views read.
10
+ The manifest check holds both to their shape. `viewsFor(manifest, host)` lists the views a host offers, and
11
+ `isViewEntry` keeps an entry inside its package.
12
+
3
13
  ## 0.2.5
4
14
 
5
15
  ### Added
@@ -1438,6 +1438,238 @@
1438
1438
  "code": "default-misfit"
1439
1439
  }
1440
1440
  ]
1441
+ },
1442
+ {
1443
+ "name": "views and reads pass",
1444
+ "manifest": {
1445
+ "schema": "wildwinter/extension@0",
1446
+ "id": "probe",
1447
+ "name": "Probe",
1448
+ "version": "1.0.0",
1449
+ "targets": {
1450
+ "patter": {
1451
+ "line": {
1452
+ "fields": [
1453
+ {
1454
+ "name": "mood",
1455
+ "type": "text"
1456
+ }
1457
+ ]
1458
+ }
1459
+ }
1460
+ },
1461
+ "views": [
1462
+ {
1463
+ "id": "sheet",
1464
+ "name": "Sheet",
1465
+ "entry": "view/sheet.js",
1466
+ "element": "probe-sheet"
1467
+ },
1468
+ {
1469
+ "id": "cards",
1470
+ "name": "Cards",
1471
+ "entry": "cards.js",
1472
+ "element": "probe-cards",
1473
+ "hosts": [
1474
+ "patter"
1475
+ ]
1476
+ }
1477
+ ],
1478
+ "reads": [
1479
+ "patterstage"
1480
+ ]
1481
+ },
1482
+ "problems": []
1483
+ },
1484
+ {
1485
+ "name": "a view's shape is checked",
1486
+ "manifest": {
1487
+ "schema": "wildwinter/extension@0",
1488
+ "id": "probe",
1489
+ "name": "Probe",
1490
+ "version": "1.0.0",
1491
+ "targets": {
1492
+ "patter": {
1493
+ "line": {
1494
+ "fields": [
1495
+ {
1496
+ "name": "mood",
1497
+ "type": "text"
1498
+ }
1499
+ ]
1500
+ }
1501
+ }
1502
+ },
1503
+ "views": [
1504
+ {
1505
+ "id": "Sheet",
1506
+ "name": " ",
1507
+ "entry": "../sheet.js",
1508
+ "element": "sheet",
1509
+ "colour": "red"
1510
+ },
1511
+ {
1512
+ "id": "two",
1513
+ "name": "Two",
1514
+ "entry": "/abs/two.js",
1515
+ "element": "Probe-Two",
1516
+ "hosts": []
1517
+ },
1518
+ {
1519
+ "id": "three",
1520
+ "name": "Three",
1521
+ "entry": "view\\three.js",
1522
+ "element": "probe-three",
1523
+ "hosts": [
1524
+ "ink",
1525
+ "storylets"
1526
+ ]
1527
+ },
1528
+ {
1529
+ "id": "three",
1530
+ "name": "Again",
1531
+ "entry": "view/again.mjs",
1532
+ "element": "probe-three"
1533
+ }
1534
+ ]
1535
+ },
1536
+ "problems": [
1537
+ {
1538
+ "path": "views[0].colour",
1539
+ "code": "unknown-key",
1540
+ "severity": "warning"
1541
+ },
1542
+ {
1543
+ "path": "views[0].id",
1544
+ "code": "view-id"
1545
+ },
1546
+ {
1547
+ "path": "views[0].name",
1548
+ "code": "view-name"
1549
+ },
1550
+ {
1551
+ "path": "views[0].entry",
1552
+ "code": "view-entry"
1553
+ },
1554
+ {
1555
+ "path": "views[0].element",
1556
+ "code": "view-element"
1557
+ },
1558
+ {
1559
+ "path": "views[1].entry",
1560
+ "code": "view-entry"
1561
+ },
1562
+ {
1563
+ "path": "views[1].element",
1564
+ "code": "view-element"
1565
+ },
1566
+ {
1567
+ "path": "views[1].hosts",
1568
+ "code": "view-hosts"
1569
+ },
1570
+ {
1571
+ "path": "views[2].entry",
1572
+ "code": "view-entry"
1573
+ },
1574
+ {
1575
+ "path": "views[2].hosts[0]",
1576
+ "code": "unknown-host"
1577
+ },
1578
+ {
1579
+ "path": "views[2].hosts[1]",
1580
+ "code": "view-host-untargeted",
1581
+ "severity": "warning"
1582
+ },
1583
+ {
1584
+ "path": "views[3].id",
1585
+ "code": "view-duplicate"
1586
+ },
1587
+ {
1588
+ "path": "views[3].entry",
1589
+ "code": "view-entry"
1590
+ },
1591
+ {
1592
+ "path": "views[3].element",
1593
+ "code": "view-duplicate"
1594
+ }
1595
+ ]
1596
+ },
1597
+ {
1598
+ "name": "reads name other extensions, once each",
1599
+ "manifest": {
1600
+ "schema": "wildwinter/extension@0",
1601
+ "id": "probe",
1602
+ "name": "Probe",
1603
+ "version": "1.0.0",
1604
+ "targets": {
1605
+ "patter": {
1606
+ "line": {
1607
+ "fields": [
1608
+ {
1609
+ "name": "mood",
1610
+ "type": "text"
1611
+ }
1612
+ ]
1613
+ }
1614
+ }
1615
+ },
1616
+ "reads": [
1617
+ "patterstage",
1618
+ "probe",
1619
+ "patterstage",
1620
+ "Bad"
1621
+ ]
1622
+ },
1623
+ "problems": [
1624
+ {
1625
+ "path": "reads[1]",
1626
+ "code": "reads-self",
1627
+ "severity": "warning"
1628
+ },
1629
+ {
1630
+ "path": "reads[2]",
1631
+ "code": "reads-duplicate"
1632
+ },
1633
+ {
1634
+ "path": "reads[3]",
1635
+ "code": "reads-id"
1636
+ }
1637
+ ]
1638
+ },
1639
+ {
1640
+ "name": "views and reads must be lists",
1641
+ "manifest": {
1642
+ "schema": "wildwinter/extension@0",
1643
+ "id": "probe",
1644
+ "name": "Probe",
1645
+ "version": "1.0.0",
1646
+ "targets": {
1647
+ "patter": {
1648
+ "line": {
1649
+ "fields": [
1650
+ {
1651
+ "name": "mood",
1652
+ "type": "text"
1653
+ }
1654
+ ]
1655
+ }
1656
+ }
1657
+ },
1658
+ "views": {
1659
+ "id": "sheet"
1660
+ },
1661
+ "reads": "patterstage"
1662
+ },
1663
+ "problems": [
1664
+ {
1665
+ "path": "views",
1666
+ "code": "not-a-list"
1667
+ },
1668
+ {
1669
+ "path": "reads",
1670
+ "code": "not-a-list"
1671
+ }
1672
+ ]
1441
1673
  }
1442
1674
  ]
1443
1675
  }
@@ -370,7 +370,9 @@ var TYPE_KEYS = ["type", "values", "stages", "renamedStages", "to", "from", "fie
370
370
  var FIELD_KEYS = /* @__PURE__ */ new Set([...TYPE_KEYS, "name", "label", "help", "required", "group", "editorOnly"]);
371
371
  var CLAIM_KEYS = /* @__PURE__ */ new Set([...TYPE_KEYS, "setting", "label", "help", "required"]);
372
372
  var BARE_KEYS = new Set(TYPE_KEYS);
373
- var MANIFEST_KEYS = /* @__PURE__ */ new Set(["schema", "id", "name", "version", "docs", "settings", "targets"]);
373
+ var MANIFEST_KEYS = /* @__PURE__ */ new Set(["schema", "id", "name", "version", "docs", "settings", "targets", "views", "reads"]);
374
+ var VIEW_KEYS = /* @__PURE__ */ new Set(["id", "name", "entry", "element", "hosts"]);
375
+ var ELEMENT = /^[a-z][a-z0-9._]*-[a-z0-9._-]*$/;
374
376
  var err2 = (code, path, message) => ({ severity: "error", code, path, message });
375
377
  function parseManifest(text) {
376
378
  return parseSource(text);
@@ -392,6 +394,8 @@ function manifestProblems(manifest) {
392
394
  }
393
395
  if (m["docs"] !== void 0 && typeof m["docs"] !== "string") out.push(err2("docs", "docs", "docs is a link, as text"));
394
396
  const settings = settingsProblems(m["settings"], out);
397
+ viewsProblems(m["views"], m["targets"], out);
398
+ readsProblems(m["reads"], m["id"], out);
395
399
  const targets = m["targets"];
396
400
  if (!isObject(targets) || Object.keys(targets).length === 0) {
397
401
  out.push(err2("no-targets", "targets", "the manifest targets no host"));
@@ -490,6 +494,72 @@ function pickListProblems(kinds, hostPath, out) {
490
494
  target["fields"].forEach((f, i) => walk(f, atIndex(atKey(atKey(hostPath, kind), "fields"), i)));
491
495
  }
492
496
  }
497
+ function isViewEntry(entry) {
498
+ if (typeof entry !== "string" || !entry.endsWith(".js") || entry.includes("\\")) return false;
499
+ const parts = entry.split("/");
500
+ return parts.every((p) => p !== "" && p !== "." && p !== "..");
501
+ }
502
+ function viewsProblems(views, targets, out) {
503
+ if (views === void 0) return;
504
+ if (!Array.isArray(views)) {
505
+ out.push(err2("not-a-list", "views", "views are a list"));
506
+ return;
507
+ }
508
+ const ids = /* @__PURE__ */ new Set();
509
+ const elements = /* @__PURE__ */ new Set();
510
+ const targeted = isObject(targets) ? Object.keys(targets) : [];
511
+ views.forEach((v, i) => {
512
+ const at = atIndex("views", i);
513
+ if (!isObject(v)) {
514
+ out.push(err2("not-an-object", at, "a view is an object"));
515
+ return;
516
+ }
517
+ unknownKeys(v, VIEW_KEYS, at, out);
518
+ const id = v["id"];
519
+ 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"));
520
+ else if (ids.has(id)) out.push(err2("view-duplicate", atKey(at, "id"), `two views are called "${id}"`));
521
+ else ids.add(id);
522
+ 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"));
523
+ if (!isViewEntry(v["entry"])) {
524
+ 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"));
525
+ }
526
+ const element = v["element"];
527
+ if (typeof element !== "string" || !ELEMENT.test(element)) {
528
+ 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"));
529
+ } else if (elements.has(element)) out.push(err2("view-duplicate", atKey(at, "element"), `two views define <${element}>`));
530
+ else elements.add(element);
531
+ const hosts = v["hosts"];
532
+ if (hosts === void 0) return;
533
+ if (!Array.isArray(hosts) || hosts.length === 0) {
534
+ out.push(err2("view-hosts", atKey(at, "hosts"), "a view's hosts are a list of at least one host"));
535
+ return;
536
+ }
537
+ hosts.forEach((h, j) => {
538
+ const hp = atIndex(atKey(at, "hosts"), j);
539
+ 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"`));
540
+ 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` });
541
+ });
542
+ });
543
+ }
544
+ function readsProblems(reads, self, out) {
545
+ if (reads === void 0) return;
546
+ if (!Array.isArray(reads)) {
547
+ out.push(err2("not-a-list", "reads", "reads is a list of extension ids"));
548
+ return;
549
+ }
550
+ const seen = /* @__PURE__ */ new Set();
551
+ reads.forEach((r, i) => {
552
+ const at = atIndex("reads", i);
553
+ 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"));
554
+ 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" });
555
+ else if (seen.has(r)) out.push(err2("reads-duplicate", at, `"${r}" is listed twice`));
556
+ else seen.add(r);
557
+ });
558
+ }
559
+ function viewsFor(manifest, host) {
560
+ const targeted = Object.keys(manifest.targets ?? {});
561
+ 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)));
562
+ }
493
563
  function unknownKeys(obj, known, path, out) {
494
564
  for (const k of Object.keys(obj)) {
495
565
  if (!known.has(k)) {
@@ -1146,6 +1216,7 @@ exports.effectiveClaim = effectiveClaim;
1146
1216
  exports.entryProblems = entryProblems;
1147
1217
  exports.extensionDataStrategy = extensionDataStrategy;
1148
1218
  exports.isTextList = isTextList;
1219
+ exports.isViewEntry = isViewEntry;
1149
1220
  exports.keyedBy = keyedBy;
1150
1221
  exports.manifestProblems = manifestProblems;
1151
1222
  exports.mapOf = mapOf;
@@ -1169,6 +1240,7 @@ exports.targetOf = targetOf;
1169
1240
  exports.unresolvedOf = unresolvedOf;
1170
1241
  exports.unresolvedProblems = unresolvedProblems;
1171
1242
  exports.valueProblems = valueProblems;
1243
+ exports.viewsFor = viewsFor;
1172
1244
  exports.withoutMark = withoutMark;
1173
1245
  //# sourceMappingURL=extensions.cjs.map
1174
1246
  //# sourceMappingURL=extensions.cjs.map