@wildwinter/toolkit 0.2.5 → 0.2.7
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 +17 -0
- package/conformance/extensions/manifest.json +307 -0
- package/dist/extensions.cjs +82 -4
- package/dist/extensions.cjs.map +1 -1
- package/dist/extensions.d.cts +23 -1
- package/dist/extensions.d.ts +23 -1
- package/dist/extensions.js +81 -5
- package/dist/extensions.js.map +1 -1
- package/extensions.md +26 -2
- package/package.json +1 -1
package/dist/extensions.d.cts
CHANGED
|
@@ -111,6 +111,23 @@ interface Manifest {
|
|
|
111
111
|
readonly docs?: string;
|
|
112
112
|
readonly settings?: readonly SettingDecl[];
|
|
113
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[];
|
|
114
131
|
}
|
|
115
132
|
/** One thing wrong, with where it is. A host words it in its own report shape. */
|
|
116
133
|
interface Problem {
|
|
@@ -145,6 +162,11 @@ interface UnresolvedEntry {
|
|
|
145
162
|
declare function parseManifest(text: string): Manifest;
|
|
146
163
|
/** Every problem with a manifest, by path. An empty list means it is sound. */
|
|
147
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[];
|
|
148
170
|
/** The host field each of a manifest's settings names, the project's choice
|
|
149
171
|
* first, else the setting's default. */
|
|
150
172
|
declare function settingValues(manifest: Manifest, chosen?: Readonly<Record<string, unknown>>): Record<string, string>;
|
|
@@ -396,4 +418,4 @@ declare const atKey: (base: string, key: string) => string;
|
|
|
396
418
|
/** `base` and then list position `i`. */
|
|
397
419
|
declare const atIndex: (base: string, i: number) => string;
|
|
398
420
|
|
|
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 };
|
|
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
|
@@ -111,6 +111,23 @@ interface Manifest {
|
|
|
111
111
|
readonly docs?: string;
|
|
112
112
|
readonly settings?: readonly SettingDecl[];
|
|
113
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[];
|
|
114
131
|
}
|
|
115
132
|
/** One thing wrong, with where it is. A host words it in its own report shape. */
|
|
116
133
|
interface Problem {
|
|
@@ -145,6 +162,11 @@ interface UnresolvedEntry {
|
|
|
145
162
|
declare function parseManifest(text: string): Manifest;
|
|
146
163
|
/** Every problem with a manifest, by path. An empty list means it is sound. */
|
|
147
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[];
|
|
148
170
|
/** The host field each of a manifest's settings names, the project's choice
|
|
149
171
|
* first, else the setting's default. */
|
|
150
172
|
declare function settingValues(manifest: Manifest, chosen?: Readonly<Record<string, unknown>>): Record<string, string>;
|
|
@@ -396,4 +418,4 @@ declare const atKey: (base: string, key: string) => string;
|
|
|
396
418
|
/** `base` and then list position `i`. */
|
|
397
419
|
declare const atIndex: (base: string, i: number) => string;
|
|
398
420
|
|
|
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 };
|
|
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
|
@@ -360,7 +360,9 @@ var TYPE_KEYS = ["type", "values", "stages", "renamedStages", "to", "from", "fie
|
|
|
360
360
|
var FIELD_KEYS = /* @__PURE__ */ new Set([...TYPE_KEYS, "name", "label", "help", "required", "group", "editorOnly"]);
|
|
361
361
|
var CLAIM_KEYS = /* @__PURE__ */ new Set([...TYPE_KEYS, "setting", "label", "help", "required"]);
|
|
362
362
|
var BARE_KEYS = new Set(TYPE_KEYS);
|
|
363
|
-
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._-]*$/;
|
|
364
366
|
var err2 = (code, path, message) => ({ severity: "error", code, path, message });
|
|
365
367
|
function parseManifest(text) {
|
|
366
368
|
return parseSource(text);
|
|
@@ -382,6 +384,8 @@ function manifestProblems(manifest) {
|
|
|
382
384
|
}
|
|
383
385
|
if (m["docs"] !== void 0 && typeof m["docs"] !== "string") out.push(err2("docs", "docs", "docs is a link, as text"));
|
|
384
386
|
const settings = settingsProblems(m["settings"], out);
|
|
387
|
+
viewsProblems(m["views"], m["targets"], out);
|
|
388
|
+
readsProblems(m["reads"], m["id"], out);
|
|
385
389
|
const targets = m["targets"];
|
|
386
390
|
if (!isObject(targets) || Object.keys(targets).length === 0) {
|
|
387
391
|
out.push(err2("no-targets", "targets", "the manifest targets no host"));
|
|
@@ -410,6 +414,10 @@ function manifestProblems(manifest) {
|
|
|
410
414
|
}
|
|
411
415
|
return out;
|
|
412
416
|
}
|
|
417
|
+
function pickWords(from) {
|
|
418
|
+
if (!Array.isArray(from)) return [];
|
|
419
|
+
return from.flatMap((s) => isObject(s) && Array.isArray(s["words"]) ? s["words"].filter((w) => typeof w === "string") : []);
|
|
420
|
+
}
|
|
413
421
|
function pickSourcesSound(host, from, path, out) {
|
|
414
422
|
if (!Array.isArray(from) || from.length === 0) {
|
|
415
423
|
out.push(err2("pick-from", path, 'a pick lists its sources in "from": { "words": [...] }, { "to": a node kind }, or { "list": a project list }'));
|
|
@@ -480,6 +488,72 @@ function pickListProblems(kinds, hostPath, out) {
|
|
|
480
488
|
target["fields"].forEach((f, i) => walk(f, atIndex(atKey(atKey(hostPath, kind), "fields"), i)));
|
|
481
489
|
}
|
|
482
490
|
}
|
|
491
|
+
function isViewEntry(entry) {
|
|
492
|
+
if (typeof entry !== "string" || !entry.endsWith(".js") || entry.includes("\\")) return false;
|
|
493
|
+
const parts = entry.split("/");
|
|
494
|
+
return parts.every((p) => p !== "" && p !== "." && p !== "..");
|
|
495
|
+
}
|
|
496
|
+
function viewsProblems(views, targets, out) {
|
|
497
|
+
if (views === void 0) return;
|
|
498
|
+
if (!Array.isArray(views)) {
|
|
499
|
+
out.push(err2("not-a-list", "views", "views are a list"));
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
const ids = /* @__PURE__ */ new Set();
|
|
503
|
+
const elements = /* @__PURE__ */ new Set();
|
|
504
|
+
const targeted = isObject(targets) ? Object.keys(targets) : [];
|
|
505
|
+
views.forEach((v, i) => {
|
|
506
|
+
const at = atIndex("views", i);
|
|
507
|
+
if (!isObject(v)) {
|
|
508
|
+
out.push(err2("not-an-object", at, "a view is an object"));
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
unknownKeys(v, VIEW_KEYS, at, out);
|
|
512
|
+
const id = v["id"];
|
|
513
|
+
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"));
|
|
514
|
+
else if (ids.has(id)) out.push(err2("view-duplicate", atKey(at, "id"), `two views are called "${id}"`));
|
|
515
|
+
else ids.add(id);
|
|
516
|
+
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"));
|
|
517
|
+
if (!isViewEntry(v["entry"])) {
|
|
518
|
+
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"));
|
|
519
|
+
}
|
|
520
|
+
const element = v["element"];
|
|
521
|
+
if (typeof element !== "string" || !ELEMENT.test(element)) {
|
|
522
|
+
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"));
|
|
523
|
+
} else if (elements.has(element)) out.push(err2("view-duplicate", atKey(at, "element"), `two views define <${element}>`));
|
|
524
|
+
else elements.add(element);
|
|
525
|
+
const hosts = v["hosts"];
|
|
526
|
+
if (hosts === void 0) return;
|
|
527
|
+
if (!Array.isArray(hosts) || hosts.length === 0) {
|
|
528
|
+
out.push(err2("view-hosts", atKey(at, "hosts"), "a view's hosts are a list of at least one host"));
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
hosts.forEach((h, j) => {
|
|
532
|
+
const hp = atIndex(atKey(at, "hosts"), j);
|
|
533
|
+
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"`));
|
|
534
|
+
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` });
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
function readsProblems(reads, self, out) {
|
|
539
|
+
if (reads === void 0) return;
|
|
540
|
+
if (!Array.isArray(reads)) {
|
|
541
|
+
out.push(err2("not-a-list", "reads", "reads is a list of extension ids"));
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
const seen = /* @__PURE__ */ new Set();
|
|
545
|
+
reads.forEach((r, i) => {
|
|
546
|
+
const at = atIndex("reads", i);
|
|
547
|
+
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"));
|
|
548
|
+
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" });
|
|
549
|
+
else if (seen.has(r)) out.push(err2("reads-duplicate", at, `"${r}" is listed twice`));
|
|
550
|
+
else seen.add(r);
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
function viewsFor(manifest, host) {
|
|
554
|
+
const targeted = Object.keys(manifest.targets ?? {});
|
|
555
|
+
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)));
|
|
556
|
+
}
|
|
483
557
|
function unknownKeys(obj, known, path, out) {
|
|
484
558
|
for (const k of Object.keys(obj)) {
|
|
485
559
|
if (!known.has(k)) {
|
|
@@ -691,12 +765,14 @@ function typeProblems(host, decl, path, named, out, claim = false) {
|
|
|
691
765
|
out.push(err2("value-help", at("valueHelp"), "value help describes the values of an enum or flags field, or a quality's stages"));
|
|
692
766
|
}
|
|
693
767
|
} else if (decl.valueHelp !== void 0) {
|
|
694
|
-
const vocabulary = decl.type === "quality" ? decl.stages : decl.type === "enum" || decl.type === "flags" ? decl.values : void 0;
|
|
768
|
+
const vocabulary = decl.type === "quality" ? decl.stages : decl.type === "enum" || decl.type === "flags" ? decl.values : decl.type === "pick" ? pickWords(decl.from) : void 0;
|
|
695
769
|
if (vocabulary === void 0 || !isObject(decl.valueHelp)) {
|
|
696
|
-
out.push(err2("value-help", at("valueHelp"), "value help describes the values of an enum or flags field, or a
|
|
770
|
+
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"));
|
|
697
771
|
} else {
|
|
698
772
|
for (const k of Object.keys(decl.valueHelp)) {
|
|
699
|
-
if (!vocabulary.includes(k))
|
|
773
|
+
if (!vocabulary.includes(k)) {
|
|
774
|
+
out.push(err2("value-help", atKey(at("valueHelp"), k), decl.type === "pick" ? `"${k}" is not one of its words: value help describes a pick's fixed words, never the project's lists or nodes` : `"${k}" is not one of its values`));
|
|
775
|
+
}
|
|
700
776
|
}
|
|
701
777
|
}
|
|
702
778
|
}
|
|
@@ -1117,6 +1193,6 @@ function orphanedEntries(data, exists) {
|
|
|
1117
1193
|
return out;
|
|
1118
1194
|
}
|
|
1119
1195
|
|
|
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 };
|
|
1196
|
+
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 };
|
|
1121
1197
|
//# sourceMappingURL=extensions.js.map
|
|
1122
1198
|
//# sourceMappingURL=extensions.js.map
|