@terrazzo/parser 2.0.0-alpha.2 → 2.0.0-alpha.3
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 +5 -12
- package/README.md +1 -1
- package/dist/index.d.ts +159 -40
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +711 -58
- package/dist/index.js.map +1 -1
- package/package.json +6 -5
- package/src/config.ts +1 -2
- package/src/index.ts +2 -0
- package/src/lint/plugin-core/index.ts +3 -4
- package/src/lint/plugin-core/lib/docs.ts +1 -1
- package/src/lint/plugin-core/rules/{no-type-on-alias.ts → required-type.ts} +5 -6
- package/src/parse/index.ts +36 -2
- package/src/parse/load.ts +10 -6
- package/src/parse/token.ts +6 -1
- package/src/resolver/index.ts +7 -0
- package/src/resolver/load.ts +161 -0
- package/src/resolver/normalize.ts +99 -0
- package/src/resolver/validate.ts +359 -0
- package/src/types.ts +104 -34
package/dist/index.js
CHANGED
|
@@ -1,11 +1,42 @@
|
|
|
1
1
|
import wcmatch from "wildcard-match";
|
|
2
2
|
import * as momoa from "@humanwhocodes/momoa";
|
|
3
|
+
import { evaluate } from "@humanwhocodes/momoa";
|
|
3
4
|
import pc from "picocolors";
|
|
4
5
|
import { merge } from "merge-anything";
|
|
5
6
|
import { BORDER_REQUIRED_PROPERTIES, COLORSPACE, FONT_WEIGHTS, GRADIENT_REQUIRED_STOP_PROPERTIES, SHADOW_REQUIRED_PROPERTIES, STROKE_STYLE_LINE_CAP_VALUES, STROKE_STYLE_OBJECT_REQUIRED_PROPERTIES, STROKE_STYLE_STRING_VALUES, TRANSITION_REQUIRED_PROPERTIES, isAlias, parseAlias, parseColor, pluralize, tokenToCulori } from "@terrazzo/token-tools";
|
|
6
7
|
import { clampChroma, wcagContrast } from "culori";
|
|
7
|
-
import { bundle, getObjMember, getObjMembers, isPure$ref, parseRef, replaceNode, traverseAsync } from "@terrazzo/json-schema-tools";
|
|
8
|
+
import { bundle, getObjMember, getObjMembers, isPure$ref, maybeRawJSON, parseRef, replaceNode, traverseAsync } from "@terrazzo/json-schema-tools";
|
|
8
9
|
|
|
10
|
+
//#region rolldown:runtime
|
|
11
|
+
var __create = Object.create;
|
|
12
|
+
var __defProp = Object.defineProperty;
|
|
13
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
14
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
15
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
16
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
17
|
+
var __commonJS = (cb, mod) => function() {
|
|
18
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
19
|
+
};
|
|
20
|
+
var __copyProps = (to, from, except, desc) => {
|
|
21
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
22
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
23
|
+
key = keys[i];
|
|
24
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
25
|
+
__defProp(to, key, {
|
|
26
|
+
get: ((k) => from[k]).bind(null, key),
|
|
27
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return to;
|
|
33
|
+
};
|
|
34
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
35
|
+
value: mod,
|
|
36
|
+
enumerable: true
|
|
37
|
+
}) : target, mod));
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
9
40
|
//#region src/lib/code-frame.ts
|
|
10
41
|
/**
|
|
11
42
|
* Extract what lines should be marked and highlighted.
|
|
@@ -34,10 +65,8 @@ function getMarkerLines(loc, source, opts = {}) {
|
|
|
34
65
|
if (lineDiff) for (let i = 0; i <= lineDiff; i++) {
|
|
35
66
|
const lineNumber = i + startLine;
|
|
36
67
|
if (!startColumn) markerLines[lineNumber] = true;
|
|
37
|
-
else if (i === 0)
|
|
38
|
-
|
|
39
|
-
markerLines[lineNumber] = [startColumn, sourceLength - startColumn + 1];
|
|
40
|
-
} else if (i === lineDiff) markerLines[lineNumber] = [0, endColumn];
|
|
68
|
+
else if (i === 0) markerLines[lineNumber] = [startColumn, source[lineNumber - 1].length - startColumn + 1];
|
|
69
|
+
else if (i === lineDiff) markerLines[lineNumber] = [0, endColumn];
|
|
41
70
|
else markerLines[lineNumber] = [0, source[lineNumber - i].length];
|
|
42
71
|
}
|
|
43
72
|
else if (startColumn === endColumn) if (startColumn) markerLines[startLine] = [startColumn, 0];
|
|
@@ -55,8 +84,7 @@ function getMarkerLines(loc, source, opts = {}) {
|
|
|
55
84
|
const NEWLINE = /\r\n|[\n\r\u2028\u2029]/;
|
|
56
85
|
function codeFrameColumns(rawLines, loc, opts = {}) {
|
|
57
86
|
if (typeof rawLines !== "string") throw new Error(`Expected string, got ${rawLines}`);
|
|
58
|
-
const
|
|
59
|
-
const { start, end, markerLines } = getMarkerLines(loc, lines, opts);
|
|
87
|
+
const { start, end, markerLines } = getMarkerLines(loc, rawLines.split(NEWLINE), opts);
|
|
60
88
|
const hasColumns = loc.start && typeof loc.start.column === "number";
|
|
61
89
|
const numberMaxWidth = String(end).length;
|
|
62
90
|
let frame = rawLines.split(NEWLINE, end).slice(start, end).map((line, index) => {
|
|
@@ -354,7 +382,7 @@ async function build(tokens, { sources, logger = new Logger(), config }) {
|
|
|
354
382
|
//#endregion
|
|
355
383
|
//#region src/lint/plugin-core/lib/docs.ts
|
|
356
384
|
function docsLink(ruleName) {
|
|
357
|
-
return `https://terrazzo.app/docs/
|
|
385
|
+
return `https://terrazzo.app/docs/linting#${ruleName.replaceAll("/", "")}`;
|
|
358
386
|
}
|
|
359
387
|
|
|
360
388
|
//#endregion
|
|
@@ -390,9 +418,7 @@ const rule$26 = {
|
|
|
390
418
|
if (tokens[foreground].$type !== "color") throw new Error(`Token ${foreground} isn’t a color`);
|
|
391
419
|
if (!tokens[background]) throw new Error(`Token ${background} does not exist`);
|
|
392
420
|
if (tokens[background].$type !== "color") throw new Error(`Token ${background} isn’t a color`);
|
|
393
|
-
const
|
|
394
|
-
const b = tokenToCulori(tokens[background].$value);
|
|
395
|
-
const contrast = wcagContrast(a, b);
|
|
421
|
+
const contrast = wcagContrast(tokenToCulori(tokens[foreground].$value), tokenToCulori(tokens[background].$value));
|
|
396
422
|
const min = WCAG2_MIN_CONTRAST[options.level ?? "AA"][largeText ? "large" : "default"];
|
|
397
423
|
if (contrast < min) report({
|
|
398
424
|
messageId: ERROR_INSUFFICIENT_CONTRAST,
|
|
@@ -719,8 +745,7 @@ function isWithinGamut(color, gamut) {
|
|
|
719
745
|
"hsl",
|
|
720
746
|
"hwb"
|
|
721
747
|
].includes(parsed.mode)) return true;
|
|
722
|
-
|
|
723
|
-
return isWithinThreshold(parsed, clamped);
|
|
748
|
+
return isWithinThreshold(parsed, clampChroma(parsed, parsed.mode, gamut === "srgb" ? "rgb" : gamut));
|
|
724
749
|
}
|
|
725
750
|
/** is Color A close enough to Color B? */
|
|
726
751
|
function isWithinThreshold(a, b, tolerance = TOLERANCE) {
|
|
@@ -807,36 +832,13 @@ const rule$20 = {
|
|
|
807
832
|
};
|
|
808
833
|
var max_gamut_default = rule$20;
|
|
809
834
|
|
|
810
|
-
//#endregion
|
|
811
|
-
//#region src/lint/plugin-core/rules/no-type-on-alias.ts
|
|
812
|
-
const NO_TYPE_ON_ALIAS = "core/no-type-on-alias";
|
|
813
|
-
const ERROR$10 = "ERROR";
|
|
814
|
-
const rule$19 = {
|
|
815
|
-
meta: {
|
|
816
|
-
messages: { [ERROR$10]: "Remove $type from aliased value." },
|
|
817
|
-
docs: {
|
|
818
|
-
description: "If a $value is aliased it already has a $type defined.",
|
|
819
|
-
url: docsLink(NO_TYPE_ON_ALIAS)
|
|
820
|
-
}
|
|
821
|
-
},
|
|
822
|
-
defaultOptions: {},
|
|
823
|
-
create({ tokens, report }) {
|
|
824
|
-
for (const t of Object.values(tokens)) if (isAlias(t.originalValue.$value) && t.originalValue?.$type) report({
|
|
825
|
-
messageId: ERROR$10,
|
|
826
|
-
node: t.source.node,
|
|
827
|
-
filename: t.source.filename
|
|
828
|
-
});
|
|
829
|
-
}
|
|
830
|
-
};
|
|
831
|
-
var no_type_on_alias_default = rule$19;
|
|
832
|
-
|
|
833
835
|
//#endregion
|
|
834
836
|
//#region src/lint/plugin-core/rules/required-children.ts
|
|
835
837
|
const REQUIRED_CHILDREN = "core/required-children";
|
|
836
838
|
const ERROR_EMPTY_MATCH = "EMPTY_MATCH";
|
|
837
839
|
const ERROR_MISSING_REQUIRED_TOKENS = "MISSING_REQUIRED_TOKENS";
|
|
838
840
|
const ERROR_MISSING_REQUIRED_GROUP = "MISSING_REQUIRED_GROUP";
|
|
839
|
-
const rule$
|
|
841
|
+
const rule$19 = {
|
|
840
842
|
meta: {
|
|
841
843
|
messages: {
|
|
842
844
|
[ERROR_EMPTY_MATCH]: "No tokens matched {{ matcher }}",
|
|
@@ -894,12 +896,12 @@ const rule$18 = {
|
|
|
894
896
|
}
|
|
895
897
|
}
|
|
896
898
|
};
|
|
897
|
-
var required_children_default = rule$
|
|
899
|
+
var required_children_default = rule$19;
|
|
898
900
|
|
|
899
901
|
//#endregion
|
|
900
902
|
//#region src/lint/plugin-core/rules/required-modes.ts
|
|
901
903
|
const REQUIRED_MODES = "core/required-modes";
|
|
902
|
-
const rule$
|
|
904
|
+
const rule$18 = {
|
|
903
905
|
meta: { docs: {
|
|
904
906
|
description: "Enforce certain tokens have specific modes.",
|
|
905
907
|
url: docsLink(REQUIRED_MODES)
|
|
@@ -930,7 +932,30 @@ const rule$17 = {
|
|
|
930
932
|
}
|
|
931
933
|
}
|
|
932
934
|
};
|
|
933
|
-
var required_modes_default = rule$
|
|
935
|
+
var required_modes_default = rule$18;
|
|
936
|
+
|
|
937
|
+
//#endregion
|
|
938
|
+
//#region src/lint/plugin-core/rules/required-type.ts
|
|
939
|
+
const REQUIRED_TYPE = "core/required-type";
|
|
940
|
+
const ERROR$10 = "ERROR";
|
|
941
|
+
const rule$17 = {
|
|
942
|
+
meta: {
|
|
943
|
+
messages: { [ERROR$10]: "Token missing $type." },
|
|
944
|
+
docs: {
|
|
945
|
+
description: "Requiring every token to have $type, even aliases, simplifies computation.",
|
|
946
|
+
url: docsLink(REQUIRED_TYPE)
|
|
947
|
+
}
|
|
948
|
+
},
|
|
949
|
+
defaultOptions: {},
|
|
950
|
+
create({ tokens, report }) {
|
|
951
|
+
for (const t of Object.values(tokens)) if (!t.originalValue?.$type) report({
|
|
952
|
+
messageId: ERROR$10,
|
|
953
|
+
node: t.source.node,
|
|
954
|
+
filename: t.source.filename
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
};
|
|
958
|
+
var required_type_default = rule$17;
|
|
934
959
|
|
|
935
960
|
//#endregion
|
|
936
961
|
//#region src/lint/plugin-core/rules/required-typography-properties.ts
|
|
@@ -1340,8 +1365,7 @@ const rule$11 = {
|
|
|
1340
1365
|
break;
|
|
1341
1366
|
case "strokeStyle":
|
|
1342
1367
|
if (typeof t.originalValue.$value === "object" && Array.isArray(t.originalValue.$value.dashArray)) {
|
|
1343
|
-
const
|
|
1344
|
-
const dashArray = getObjMember($valueNode, "dashArray");
|
|
1368
|
+
const dashArray = getObjMember(getObjMember(t.source.node, "$value"), "dashArray");
|
|
1345
1369
|
for (let i = 0; i < t.originalValue.$value.dashArray.length; i++) {
|
|
1346
1370
|
if (isAlias(t.originalValue.$value.dashArray[i])) continue;
|
|
1347
1371
|
validateDimension(t.originalValue.$value.dashArray[i], {
|
|
@@ -1359,8 +1383,7 @@ const rule$11 = {
|
|
|
1359
1383
|
filename: t.source.filename
|
|
1360
1384
|
});
|
|
1361
1385
|
if (typeof t.originalValue.$value.style === "object" && Array.isArray(t.originalValue.$value.style.dashArray)) {
|
|
1362
|
-
const
|
|
1363
|
-
const dashArray = getObjMember(style, "dashArray");
|
|
1386
|
+
const dashArray = getObjMember(getObjMember($valueNode, "style"), "dashArray");
|
|
1364
1387
|
for (let i = 0; i < t.originalValue.$value.style.dashArray.length; i++) {
|
|
1365
1388
|
if (isAlias(t.originalValue.$value.style.dashArray[i])) continue;
|
|
1366
1389
|
validateDimension(t.originalValue.$value.style.dashArray[i], {
|
|
@@ -1580,8 +1603,7 @@ const rule$9 = {
|
|
|
1580
1603
|
case "typography":
|
|
1581
1604
|
if (typeof t.originalValue.$value === "object" && t.originalValue.$value.fontFamily) {
|
|
1582
1605
|
if (t.partialAliasOf?.fontFamily) continue;
|
|
1583
|
-
const
|
|
1584
|
-
const properties = getObjMembers($value);
|
|
1606
|
+
const properties = getObjMembers(getObjMember(t.source.node, "$value"));
|
|
1585
1607
|
validateFontFamily(t.originalValue.$value.fontFamily, {
|
|
1586
1608
|
node: properties.fontFamily,
|
|
1587
1609
|
filename: t.source.filename
|
|
@@ -1643,8 +1665,7 @@ const rule$8 = {
|
|
|
1643
1665
|
case "typography":
|
|
1644
1666
|
if (typeof t.originalValue.$value === "object" && t.originalValue.$value.fontWeight) {
|
|
1645
1667
|
if (t.partialAliasOf?.fontWeight) continue;
|
|
1646
|
-
const
|
|
1647
|
-
const properties = getObjMembers($value);
|
|
1668
|
+
const properties = getObjMembers(getObjMember(t.source.node, "$value"));
|
|
1648
1669
|
validateFontWeight(t.originalValue.$value.fontWeight, {
|
|
1649
1670
|
node: properties.fontWeight,
|
|
1650
1671
|
filename: t.source.filename
|
|
@@ -2113,9 +2134,9 @@ const ALL_RULES = {
|
|
|
2113
2134
|
[DESCRIPTIONS]: descriptions_default,
|
|
2114
2135
|
[DUPLICATE_VALUES]: duplicate_values_default,
|
|
2115
2136
|
[MAX_GAMUT]: max_gamut_default,
|
|
2116
|
-
[NO_TYPE_ON_ALIAS]: no_type_on_alias_default,
|
|
2117
2137
|
[REQUIRED_CHILDREN]: required_children_default,
|
|
2118
2138
|
[REQUIRED_MODES]: required_modes_default,
|
|
2139
|
+
[REQUIRED_TYPE]: required_type_default,
|
|
2119
2140
|
[REQUIRED_TYPOGRAPHY_PROPERTIES]: required_typography_properties_default,
|
|
2120
2141
|
[A11Y_MIN_CONTRAST]: a11y_min_contrast_default,
|
|
2121
2142
|
[A11Y_MIN_FONT_SIZE]: a11y_min_font_size_default
|
|
@@ -2145,8 +2166,7 @@ const RECOMMENDED_CONFIG = {
|
|
|
2145
2166
|
[VALID_SHADOW]: ["error", {}],
|
|
2146
2167
|
[VALID_GRADIENT]: ["error", {}],
|
|
2147
2168
|
[VALID_TYPOGRAPHY]: ["error", {}],
|
|
2148
|
-
[CONSISTENT_NAMING]: ["warn", { format: "kebab-case" }]
|
|
2149
|
-
[NO_TYPE_ON_ALIAS]: ["warn", {}]
|
|
2169
|
+
[CONSISTENT_NAMING]: ["warn", { format: "kebab-case" }]
|
|
2150
2170
|
};
|
|
2151
2171
|
|
|
2152
2172
|
//#endregion
|
|
@@ -2321,7 +2341,7 @@ function normalizeLint({ config, logger }) {
|
|
|
2321
2341
|
});
|
|
2322
2342
|
const value = config.lint.rules[id];
|
|
2323
2343
|
let severity = "off";
|
|
2324
|
-
let options;
|
|
2344
|
+
let options = {};
|
|
2325
2345
|
if (typeof value === "number" || typeof value === "string") severity = value;
|
|
2326
2346
|
else if (Array.isArray(value)) {
|
|
2327
2347
|
severity = value[0];
|
|
@@ -2469,8 +2489,40 @@ async function lintRunner({ tokens, filename, config = {}, sources, logger }) {
|
|
|
2469
2489
|
});
|
|
2470
2490
|
}
|
|
2471
2491
|
|
|
2492
|
+
//#endregion
|
|
2493
|
+
//#region ../../node_modules/.pnpm/fast-deep-equal@3.1.3/node_modules/fast-deep-equal/index.js
|
|
2494
|
+
var require_fast_deep_equal = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/fast-deep-equal@3.1.3/node_modules/fast-deep-equal/index.js": ((exports, module) => {
|
|
2495
|
+
module.exports = function equal(a, b) {
|
|
2496
|
+
if (a === b) return true;
|
|
2497
|
+
if (a && b && typeof a == "object" && typeof b == "object") {
|
|
2498
|
+
if (a.constructor !== b.constructor) return false;
|
|
2499
|
+
var length, i, keys;
|
|
2500
|
+
if (Array.isArray(a)) {
|
|
2501
|
+
length = a.length;
|
|
2502
|
+
if (length != b.length) return false;
|
|
2503
|
+
for (i = length; i-- !== 0;) if (!equal(a[i], b[i])) return false;
|
|
2504
|
+
return true;
|
|
2505
|
+
}
|
|
2506
|
+
if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags;
|
|
2507
|
+
if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf();
|
|
2508
|
+
if (a.toString !== Object.prototype.toString) return a.toString() === b.toString();
|
|
2509
|
+
keys = Object.keys(a);
|
|
2510
|
+
length = keys.length;
|
|
2511
|
+
if (length !== Object.keys(b).length) return false;
|
|
2512
|
+
for (i = length; i-- !== 0;) if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;
|
|
2513
|
+
for (i = length; i-- !== 0;) {
|
|
2514
|
+
var key = keys[i];
|
|
2515
|
+
if (!equal(a[key], b[key])) return false;
|
|
2516
|
+
}
|
|
2517
|
+
return true;
|
|
2518
|
+
}
|
|
2519
|
+
return a !== a && b !== b;
|
|
2520
|
+
};
|
|
2521
|
+
}) });
|
|
2522
|
+
|
|
2472
2523
|
//#endregion
|
|
2473
2524
|
//#region src/lib/momoa.ts
|
|
2525
|
+
var import_fast_deep_equal = /* @__PURE__ */ __toESM(require_fast_deep_equal(), 1);
|
|
2474
2526
|
/** Momoa’s default parser, with preferred settings. */
|
|
2475
2527
|
function toMomoa(srcRaw) {
|
|
2476
2528
|
return momoa.parse(typeof srcRaw === "string" ? srcRaw : JSON.stringify(srcRaw, void 0, 2), {
|
|
@@ -2480,6 +2532,582 @@ function toMomoa(srcRaw) {
|
|
|
2480
2532
|
});
|
|
2481
2533
|
}
|
|
2482
2534
|
|
|
2535
|
+
//#endregion
|
|
2536
|
+
//#region src/resolver/validate.ts
|
|
2537
|
+
/**
|
|
2538
|
+
* Determine whether this is likely a resolver
|
|
2539
|
+
* We use terms the word “likely” because this occurs before validation. Since
|
|
2540
|
+
* we may be dealing with a doc _intended_ to be a resolver, but may be lacking
|
|
2541
|
+
* some critical information, how can we determine intent? There’s a bit of
|
|
2542
|
+
* guesswork here, but we try and find a reasonable edge case where we sniff out
|
|
2543
|
+
* invalid DTCG syntax that a resolver doc would have.
|
|
2544
|
+
*/
|
|
2545
|
+
function isLikelyResolver(doc) {
|
|
2546
|
+
if (doc.body.type !== "Object") return false;
|
|
2547
|
+
for (const member of doc.body.members) {
|
|
2548
|
+
if (member.name.type !== "String") continue;
|
|
2549
|
+
switch (member.name.value) {
|
|
2550
|
+
case "name":
|
|
2551
|
+
case "description":
|
|
2552
|
+
case "version":
|
|
2553
|
+
if (member.name.type === "String") return true;
|
|
2554
|
+
break;
|
|
2555
|
+
case "sets":
|
|
2556
|
+
case "modifiers":
|
|
2557
|
+
if (member.value.type !== "Object") continue;
|
|
2558
|
+
if (getObjMember(member.value, "description")?.type === "String") return true;
|
|
2559
|
+
if (member.name.value === "sets" && getObjMember(member.value, "sources")?.type === "Array") return true;
|
|
2560
|
+
else if (member.name.value === "modifiers") {
|
|
2561
|
+
const contexts = getObjMember(member.value, "contexts");
|
|
2562
|
+
if (contexts?.type === "Object" && contexts.members.some((m) => m.value.type === "Array")) return true;
|
|
2563
|
+
}
|
|
2564
|
+
break;
|
|
2565
|
+
case "resolutionOrder":
|
|
2566
|
+
if (member.value.type === "Array") return true;
|
|
2567
|
+
break;
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
return false;
|
|
2571
|
+
}
|
|
2572
|
+
const MESSAGE_EXPECTED = {
|
|
2573
|
+
STRING: "Expected string.",
|
|
2574
|
+
OBJECT: "Expected object.",
|
|
2575
|
+
ARRAY: "Expected array."
|
|
2576
|
+
};
|
|
2577
|
+
/**
|
|
2578
|
+
* Validate a resolver document.
|
|
2579
|
+
* There’s a ton of boilerplate here, only to surface detailed code frames. Is there a better abstraction?
|
|
2580
|
+
*/
|
|
2581
|
+
function validateResolver(node, { logger, src }) {
|
|
2582
|
+
const entry = {
|
|
2583
|
+
group: "parser",
|
|
2584
|
+
label: "resolver",
|
|
2585
|
+
src
|
|
2586
|
+
};
|
|
2587
|
+
if (node.body.type !== "Object") logger.error({
|
|
2588
|
+
...entry,
|
|
2589
|
+
message: MESSAGE_EXPECTED.OBJECT,
|
|
2590
|
+
node
|
|
2591
|
+
});
|
|
2592
|
+
const errors = [];
|
|
2593
|
+
let hasVersion = false;
|
|
2594
|
+
let hasResolutionOrder = false;
|
|
2595
|
+
for (const member of node.body.members) {
|
|
2596
|
+
if (member.name.type !== "String") continue;
|
|
2597
|
+
switch (member.name.value) {
|
|
2598
|
+
case "name":
|
|
2599
|
+
case "description":
|
|
2600
|
+
if (member.value.type !== "String") errors.push({
|
|
2601
|
+
...entry,
|
|
2602
|
+
message: MESSAGE_EXPECTED.STRING
|
|
2603
|
+
});
|
|
2604
|
+
break;
|
|
2605
|
+
case "version":
|
|
2606
|
+
hasVersion = true;
|
|
2607
|
+
if (member.value.type !== "String" || member.value.value !== "2025.10") errors.push({
|
|
2608
|
+
...entry,
|
|
2609
|
+
message: `Expected "version" to be "2025.10".`,
|
|
2610
|
+
node: member.value
|
|
2611
|
+
});
|
|
2612
|
+
break;
|
|
2613
|
+
case "sets":
|
|
2614
|
+
case "modifiers":
|
|
2615
|
+
if (member.value.type !== "Object") errors.push({
|
|
2616
|
+
...entry,
|
|
2617
|
+
message: MESSAGE_EXPECTED.OBJECT,
|
|
2618
|
+
node: member.value
|
|
2619
|
+
});
|
|
2620
|
+
else for (const item of member.value.members) if (item.value.type !== "Object") errors.push({
|
|
2621
|
+
...entry,
|
|
2622
|
+
message: MESSAGE_EXPECTED.OBJECT,
|
|
2623
|
+
node: item.value
|
|
2624
|
+
});
|
|
2625
|
+
else {
|
|
2626
|
+
const validator = member.name.value === "sets" ? validateSet : validateModifier;
|
|
2627
|
+
errors.push(...validator(item.value, false, {
|
|
2628
|
+
logger,
|
|
2629
|
+
src
|
|
2630
|
+
}));
|
|
2631
|
+
}
|
|
2632
|
+
break;
|
|
2633
|
+
case "resolutionOrder":
|
|
2634
|
+
hasResolutionOrder = true;
|
|
2635
|
+
if (member.value.type !== "Array") errors.push({
|
|
2636
|
+
...entry,
|
|
2637
|
+
message: MESSAGE_EXPECTED.ARRAY,
|
|
2638
|
+
node: member.value
|
|
2639
|
+
});
|
|
2640
|
+
else if (member.value.elements.length === 0) errors.push({
|
|
2641
|
+
...entry,
|
|
2642
|
+
message: `"resolutionOrder" can’t be empty array.`,
|
|
2643
|
+
node: member.value
|
|
2644
|
+
});
|
|
2645
|
+
else for (const item of member.value.elements) if (item.value.type !== "Object") errors.push({
|
|
2646
|
+
...entry,
|
|
2647
|
+
message: MESSAGE_EXPECTED.OBJECT,
|
|
2648
|
+
node: item.value
|
|
2649
|
+
});
|
|
2650
|
+
else {
|
|
2651
|
+
const itemMembers = getObjMembers(item.value);
|
|
2652
|
+
if (itemMembers.$ref?.type === "String") continue;
|
|
2653
|
+
if (itemMembers.type?.type === "String") if (itemMembers.type.value === "set") validateSet(item.value, true, {
|
|
2654
|
+
logger,
|
|
2655
|
+
src
|
|
2656
|
+
});
|
|
2657
|
+
else if (itemMembers.type.value === "modifier") validateModifier(item.value, true, {
|
|
2658
|
+
logger,
|
|
2659
|
+
src
|
|
2660
|
+
});
|
|
2661
|
+
else errors.push({
|
|
2662
|
+
...entry,
|
|
2663
|
+
message: `Unknown type ${JSON.stringify(itemMembers.type.value)}`,
|
|
2664
|
+
node: itemMembers.type
|
|
2665
|
+
});
|
|
2666
|
+
if (itemMembers.sources?.type === "Array") validateSet(item.value, true, {
|
|
2667
|
+
logger,
|
|
2668
|
+
src
|
|
2669
|
+
});
|
|
2670
|
+
else if (itemMembers.contexts?.type === "Object") validateModifier(item.value, true, {
|
|
2671
|
+
logger,
|
|
2672
|
+
src
|
|
2673
|
+
});
|
|
2674
|
+
else if (itemMembers.name?.type === "String" || itemMembers.description?.type === "String") validateSet(item.value, true, {
|
|
2675
|
+
logger,
|
|
2676
|
+
src
|
|
2677
|
+
});
|
|
2678
|
+
}
|
|
2679
|
+
break;
|
|
2680
|
+
case "$defs":
|
|
2681
|
+
case "$extensions":
|
|
2682
|
+
if (member.value.type !== "Object") errors.push({
|
|
2683
|
+
...entry,
|
|
2684
|
+
message: `Expected object`,
|
|
2685
|
+
node: member.value
|
|
2686
|
+
});
|
|
2687
|
+
break;
|
|
2688
|
+
case "$ref":
|
|
2689
|
+
if (member.value.type !== "String") errors.push({
|
|
2690
|
+
...entry,
|
|
2691
|
+
message: `Expected string`,
|
|
2692
|
+
node: member.value
|
|
2693
|
+
});
|
|
2694
|
+
break;
|
|
2695
|
+
default:
|
|
2696
|
+
errors.push({
|
|
2697
|
+
...entry,
|
|
2698
|
+
message: `Unknown key ${JSON.stringify(member.name.value)}`,
|
|
2699
|
+
node: member.name,
|
|
2700
|
+
src
|
|
2701
|
+
});
|
|
2702
|
+
break;
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
if (!hasVersion) errors.push({
|
|
2706
|
+
...entry,
|
|
2707
|
+
message: `Missing "version".`,
|
|
2708
|
+
node,
|
|
2709
|
+
src
|
|
2710
|
+
});
|
|
2711
|
+
if (!hasResolutionOrder) errors.push({
|
|
2712
|
+
...entry,
|
|
2713
|
+
message: `Missing "resolutionOrder".`,
|
|
2714
|
+
node,
|
|
2715
|
+
src
|
|
2716
|
+
});
|
|
2717
|
+
if (errors.length) logger.error(...errors);
|
|
2718
|
+
}
|
|
2719
|
+
function validateSet(node, isInline = false, { src }) {
|
|
2720
|
+
const entry = {
|
|
2721
|
+
group: "parser",
|
|
2722
|
+
label: "resolver",
|
|
2723
|
+
src
|
|
2724
|
+
};
|
|
2725
|
+
const errors = [];
|
|
2726
|
+
let hasName = !isInline;
|
|
2727
|
+
let hasType = !isInline;
|
|
2728
|
+
let hasSources = false;
|
|
2729
|
+
for (const member of node.members) {
|
|
2730
|
+
if (member.name.type !== "String") continue;
|
|
2731
|
+
switch (member.name.value) {
|
|
2732
|
+
case "name":
|
|
2733
|
+
hasName = true;
|
|
2734
|
+
if (member.value.type !== "String") errors.push({
|
|
2735
|
+
...entry,
|
|
2736
|
+
message: MESSAGE_EXPECTED.STRING,
|
|
2737
|
+
node: member.value
|
|
2738
|
+
});
|
|
2739
|
+
break;
|
|
2740
|
+
case "description":
|
|
2741
|
+
if (member.value.type !== "String") errors.push({
|
|
2742
|
+
...entry,
|
|
2743
|
+
message: MESSAGE_EXPECTED.STRING,
|
|
2744
|
+
node: member.value
|
|
2745
|
+
});
|
|
2746
|
+
break;
|
|
2747
|
+
case "type":
|
|
2748
|
+
hasType = true;
|
|
2749
|
+
if (member.value.type !== "String") errors.push({
|
|
2750
|
+
...entry,
|
|
2751
|
+
message: MESSAGE_EXPECTED.STRING,
|
|
2752
|
+
node: member.value
|
|
2753
|
+
});
|
|
2754
|
+
else if (member.value.value !== "set") errors.push({
|
|
2755
|
+
...entry,
|
|
2756
|
+
message: "\"type\" must be \"set\"."
|
|
2757
|
+
});
|
|
2758
|
+
break;
|
|
2759
|
+
case "sources":
|
|
2760
|
+
hasSources = true;
|
|
2761
|
+
if (member.value.type !== "Array") errors.push({
|
|
2762
|
+
...entry,
|
|
2763
|
+
message: MESSAGE_EXPECTED.ARRAY,
|
|
2764
|
+
node: member.value
|
|
2765
|
+
});
|
|
2766
|
+
else if (member.value.elements.length === 0) errors.push({
|
|
2767
|
+
...entry,
|
|
2768
|
+
message: `"sources" can’t be empty array.`,
|
|
2769
|
+
node: member.value
|
|
2770
|
+
});
|
|
2771
|
+
break;
|
|
2772
|
+
case "$defs":
|
|
2773
|
+
case "$extensions":
|
|
2774
|
+
if (member.value.type !== "Object") errors.push({
|
|
2775
|
+
...entry,
|
|
2776
|
+
message: `Expected object`,
|
|
2777
|
+
node: member.value
|
|
2778
|
+
});
|
|
2779
|
+
break;
|
|
2780
|
+
case "$ref":
|
|
2781
|
+
if (member.value.type !== "String") errors.push({
|
|
2782
|
+
...entry,
|
|
2783
|
+
message: `Expected string`,
|
|
2784
|
+
node: member.value
|
|
2785
|
+
});
|
|
2786
|
+
break;
|
|
2787
|
+
default:
|
|
2788
|
+
errors.push({
|
|
2789
|
+
...entry,
|
|
2790
|
+
message: `Unknown key ${JSON.stringify(member.name.value)}`,
|
|
2791
|
+
node: member.name
|
|
2792
|
+
});
|
|
2793
|
+
break;
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
if (!hasName) errors.push({
|
|
2797
|
+
...entry,
|
|
2798
|
+
message: `Missing "name".`,
|
|
2799
|
+
node
|
|
2800
|
+
});
|
|
2801
|
+
if (!hasType) errors.push({
|
|
2802
|
+
...entry,
|
|
2803
|
+
message: `"type": "set" missing.`,
|
|
2804
|
+
node
|
|
2805
|
+
});
|
|
2806
|
+
if (!hasSources) errors.push({
|
|
2807
|
+
...entry,
|
|
2808
|
+
message: `Missing "sources".`,
|
|
2809
|
+
node
|
|
2810
|
+
});
|
|
2811
|
+
return errors;
|
|
2812
|
+
}
|
|
2813
|
+
function validateModifier(node, isInline = false, { src }) {
|
|
2814
|
+
const errors = [];
|
|
2815
|
+
const entry = {
|
|
2816
|
+
group: "parser",
|
|
2817
|
+
label: "resolver",
|
|
2818
|
+
src
|
|
2819
|
+
};
|
|
2820
|
+
let hasName = !isInline;
|
|
2821
|
+
let hasType = !isInline;
|
|
2822
|
+
let hasContexts = false;
|
|
2823
|
+
for (const member of node.members) {
|
|
2824
|
+
if (member.name.type !== "String") continue;
|
|
2825
|
+
switch (member.name.value) {
|
|
2826
|
+
case "name":
|
|
2827
|
+
hasName = true;
|
|
2828
|
+
if (member.value.type !== "String") errors.push({
|
|
2829
|
+
...entry,
|
|
2830
|
+
message: MESSAGE_EXPECTED.STRING,
|
|
2831
|
+
node: member.value
|
|
2832
|
+
});
|
|
2833
|
+
break;
|
|
2834
|
+
case "description":
|
|
2835
|
+
if (member.value.type !== "String") errors.push({
|
|
2836
|
+
...entry,
|
|
2837
|
+
message: MESSAGE_EXPECTED.STRING,
|
|
2838
|
+
node: member.value
|
|
2839
|
+
});
|
|
2840
|
+
break;
|
|
2841
|
+
case "type":
|
|
2842
|
+
hasType = true;
|
|
2843
|
+
if (member.value.type !== "String") errors.push({
|
|
2844
|
+
...entry,
|
|
2845
|
+
message: MESSAGE_EXPECTED.STRING,
|
|
2846
|
+
node: member.value
|
|
2847
|
+
});
|
|
2848
|
+
else if (member.value.value !== "modifier") errors.push({
|
|
2849
|
+
...entry,
|
|
2850
|
+
message: "\"type\" must be \"modifier\"."
|
|
2851
|
+
});
|
|
2852
|
+
break;
|
|
2853
|
+
case "contexts":
|
|
2854
|
+
hasContexts = true;
|
|
2855
|
+
if (member.value.type !== "Object") errors.push({
|
|
2856
|
+
...entry,
|
|
2857
|
+
message: MESSAGE_EXPECTED.OBJECT,
|
|
2858
|
+
node: member.value
|
|
2859
|
+
});
|
|
2860
|
+
else if (member.value.members.length === 0) errors.push({
|
|
2861
|
+
...entry,
|
|
2862
|
+
message: `"contexts" can’t be empty object.`,
|
|
2863
|
+
node: member.value
|
|
2864
|
+
});
|
|
2865
|
+
else for (const context of member.value.members) if (context.value.type !== "Array") errors.push({
|
|
2866
|
+
...entry,
|
|
2867
|
+
message: MESSAGE_EXPECTED.ARRAY,
|
|
2868
|
+
node: context.value
|
|
2869
|
+
});
|
|
2870
|
+
break;
|
|
2871
|
+
case "$defs":
|
|
2872
|
+
case "$extensions":
|
|
2873
|
+
if (member.value.type !== "Object") errors.push({
|
|
2874
|
+
...entry,
|
|
2875
|
+
message: `Expected object`,
|
|
2876
|
+
node: member.value
|
|
2877
|
+
});
|
|
2878
|
+
break;
|
|
2879
|
+
case "$ref":
|
|
2880
|
+
if (member.value.type !== "String") errors.push({
|
|
2881
|
+
...entry,
|
|
2882
|
+
message: `Expected string`,
|
|
2883
|
+
node: member.value
|
|
2884
|
+
});
|
|
2885
|
+
break;
|
|
2886
|
+
default:
|
|
2887
|
+
errors.push({
|
|
2888
|
+
...entry,
|
|
2889
|
+
message: `Unknown key ${JSON.stringify(member.name.value)}`,
|
|
2890
|
+
node: member.name
|
|
2891
|
+
});
|
|
2892
|
+
break;
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
if (!hasName) errors.push({
|
|
2896
|
+
...entry,
|
|
2897
|
+
message: `Missing "name".`,
|
|
2898
|
+
node
|
|
2899
|
+
});
|
|
2900
|
+
if (!hasType) errors.push({
|
|
2901
|
+
...entry,
|
|
2902
|
+
message: `"type": "modifier" missing.`,
|
|
2903
|
+
node
|
|
2904
|
+
});
|
|
2905
|
+
if (!hasContexts) errors.push({
|
|
2906
|
+
...entry,
|
|
2907
|
+
message: `Missing "contexts".`,
|
|
2908
|
+
node
|
|
2909
|
+
});
|
|
2910
|
+
return errors;
|
|
2911
|
+
}
|
|
2912
|
+
|
|
2913
|
+
//#endregion
|
|
2914
|
+
//#region src/resolver/normalize.ts
|
|
2915
|
+
/** Normalize resolver (assuming it’s been validated) */
|
|
2916
|
+
async function normalizeResolver(node, { filename, req, src, logger, yamlToMomoa }) {
|
|
2917
|
+
const resolverSource = evaluate(node);
|
|
2918
|
+
const resolutionOrder = getObjMember(node.body, "resolutionOrder");
|
|
2919
|
+
return {
|
|
2920
|
+
name: resolverSource.name,
|
|
2921
|
+
version: resolverSource.version,
|
|
2922
|
+
description: resolverSource.description,
|
|
2923
|
+
sets: resolverSource.sets,
|
|
2924
|
+
modifiers: resolverSource.modifiers,
|
|
2925
|
+
resolutionOrder: await Promise.all(resolutionOrder.elements.map(async (element, i) => {
|
|
2926
|
+
const layer = element.value;
|
|
2927
|
+
const members = getObjMembers(layer);
|
|
2928
|
+
let item = layer;
|
|
2929
|
+
if (members.$ref) {
|
|
2930
|
+
const entry = {
|
|
2931
|
+
group: "parser",
|
|
2932
|
+
label: "init",
|
|
2933
|
+
node: members.$ref,
|
|
2934
|
+
src
|
|
2935
|
+
};
|
|
2936
|
+
const { url, subpath } = parseRef(members.$ref.value);
|
|
2937
|
+
if (url === ".") if (!subpath?.[0]) logger.error({
|
|
2938
|
+
...entry,
|
|
2939
|
+
message: "$ref can’t refer to the root document."
|
|
2940
|
+
});
|
|
2941
|
+
else if (subpath[0] !== "sets" && subpath[0] !== "modifiers") logger.error({
|
|
2942
|
+
...entry,
|
|
2943
|
+
message: "Local $ref in resolutionOrder must point to either #/sets/[set] or #/modifiers/[modifiers]."
|
|
2944
|
+
});
|
|
2945
|
+
else {
|
|
2946
|
+
const resolvedItem = resolverSource[subpath[0]]?.[subpath[1]];
|
|
2947
|
+
if (!resolvedItem) logger.error({
|
|
2948
|
+
...entry,
|
|
2949
|
+
message: "Invalid $ref"
|
|
2950
|
+
});
|
|
2951
|
+
else item = {
|
|
2952
|
+
type: subpath[0] === "sets" ? "set" : "modifier",
|
|
2953
|
+
...resolvedItem
|
|
2954
|
+
};
|
|
2955
|
+
}
|
|
2956
|
+
else {
|
|
2957
|
+
const result = await bundle([{
|
|
2958
|
+
filename: new URL(url, filename),
|
|
2959
|
+
src: resolverSource.resolutionOrder[i]
|
|
2960
|
+
}], {
|
|
2961
|
+
req,
|
|
2962
|
+
yamlToMomoa
|
|
2963
|
+
});
|
|
2964
|
+
if (result.document.body.type === "Object") {
|
|
2965
|
+
const type = getObjMember(result.document.body, "type");
|
|
2966
|
+
if (type?.type === "String" && type.value === "set") {
|
|
2967
|
+
validateSet(result.document.body, true, src);
|
|
2968
|
+
item = evaluate(result.document.body);
|
|
2969
|
+
} else if (type?.type === "String" && type.value === "modifier") {
|
|
2970
|
+
validateModifier(result.document.body, true, src);
|
|
2971
|
+
item = evaluate(result.document.body);
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
logger.error({
|
|
2975
|
+
...entry,
|
|
2976
|
+
message: "$ref did not resolve to a valid Set or Modifier."
|
|
2977
|
+
});
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
return evaluate((await bundle([{
|
|
2981
|
+
filename,
|
|
2982
|
+
src: item
|
|
2983
|
+
}], {
|
|
2984
|
+
req,
|
|
2985
|
+
yamlToMomoa
|
|
2986
|
+
})).document.body);
|
|
2987
|
+
}))
|
|
2988
|
+
};
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
//#endregion
|
|
2992
|
+
//#region src/resolver/load.ts
|
|
2993
|
+
/** Quick-parse input sources and find a resolver */
|
|
2994
|
+
async function loadResolver(inputs, { logger, req, yamlToMomoa }) {
|
|
2995
|
+
let resolverDoc;
|
|
2996
|
+
const entry = {
|
|
2997
|
+
group: "parser",
|
|
2998
|
+
label: "init"
|
|
2999
|
+
};
|
|
3000
|
+
for (const input of inputs) {
|
|
3001
|
+
let document;
|
|
3002
|
+
if (typeof input.src === "string") if (maybeRawJSON(input.src)) document = toMomoa(input.src);
|
|
3003
|
+
else if (yamlToMomoa) document = yamlToMomoa(input.src);
|
|
3004
|
+
else logger.error({
|
|
3005
|
+
...entry,
|
|
3006
|
+
message: `Install yaml-to-momoa package to parse YAML, and pass in as option, e.g.:
|
|
3007
|
+
|
|
3008
|
+
import { bundle } from '@terrazzo/json-schema-tools';
|
|
3009
|
+
import yamlToMomoa from 'yaml-to-momoa';
|
|
3010
|
+
|
|
3011
|
+
bundle(yamlString, { yamlToMomoa });`
|
|
3012
|
+
});
|
|
3013
|
+
else if (input.src && typeof input.src === "object") document = toMomoa(JSON.stringify(input.src, void 0, 2));
|
|
3014
|
+
else logger.error({
|
|
3015
|
+
...entry,
|
|
3016
|
+
message: `Could not parse ${input.filename}. Is this valid JSON or YAML?`
|
|
3017
|
+
});
|
|
3018
|
+
if (!document || !isLikelyResolver(document)) continue;
|
|
3019
|
+
if (inputs.length > 1) logger.error({
|
|
3020
|
+
...entry,
|
|
3021
|
+
message: `Resolver must be the only input, found ${inputs.length} sources.`
|
|
3022
|
+
});
|
|
3023
|
+
resolverDoc = document;
|
|
3024
|
+
break;
|
|
3025
|
+
}
|
|
3026
|
+
if (resolverDoc) {
|
|
3027
|
+
validateResolver(resolverDoc, {
|
|
3028
|
+
logger,
|
|
3029
|
+
src: inputs[0].src
|
|
3030
|
+
});
|
|
3031
|
+
return createResolver(await normalizeResolver(resolverDoc, {
|
|
3032
|
+
filename: inputs[0].filename,
|
|
3033
|
+
logger,
|
|
3034
|
+
req,
|
|
3035
|
+
src: inputs[0].src,
|
|
3036
|
+
yamlToMomoa
|
|
3037
|
+
}), { logger });
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
/** Create an interface to resolve permutations */
|
|
3041
|
+
function createResolver(resolverSource, { logger }) {
|
|
3042
|
+
const inputDefaults = {};
|
|
3043
|
+
const modifierPermutations = [];
|
|
3044
|
+
for (const [name, m] of Object.entries(resolverSource.modifiers ?? {})) {
|
|
3045
|
+
if (typeof m.default === "string") inputDefaults[name] = m.default;
|
|
3046
|
+
modifierPermutations.push([name, Object.keys(m.contexts)]);
|
|
3047
|
+
}
|
|
3048
|
+
for (const m of resolverSource.resolutionOrder) {
|
|
3049
|
+
if (!("type" in m) || m.type !== "modifier") continue;
|
|
3050
|
+
if (typeof m.default === "string") inputDefaults[m.name] = m.default;
|
|
3051
|
+
modifierPermutations.push([m.name, Object.keys(m.contexts)]);
|
|
3052
|
+
}
|
|
3053
|
+
const permutations = calculatePermutations(modifierPermutations);
|
|
3054
|
+
return {
|
|
3055
|
+
apply(inputRaw) {
|
|
3056
|
+
let tokens = {};
|
|
3057
|
+
const input = {
|
|
3058
|
+
...inputDefaults,
|
|
3059
|
+
...inputRaw
|
|
3060
|
+
};
|
|
3061
|
+
for (const item of resolverSource.resolutionOrder) switch (item.type) {
|
|
3062
|
+
case "set":
|
|
3063
|
+
for (const s of item.sources) tokens = merge(tokens, s);
|
|
3064
|
+
break;
|
|
3065
|
+
case "modifier": {
|
|
3066
|
+
const context = input[item.name];
|
|
3067
|
+
const sources = item.contexts[context];
|
|
3068
|
+
if (!sources) logger.error({
|
|
3069
|
+
group: "parser",
|
|
3070
|
+
label: "resolver",
|
|
3071
|
+
message: `Modifier ${item.name} has no context ${JSON.stringify(context)}.`
|
|
3072
|
+
});
|
|
3073
|
+
for (const s of sources ?? []) tokens = merge(tokens, s);
|
|
3074
|
+
break;
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
return tokens;
|
|
3078
|
+
},
|
|
3079
|
+
source: resolverSource,
|
|
3080
|
+
permutations,
|
|
3081
|
+
isValidInput(inputRaw) {
|
|
3082
|
+
if (!inputRaw || typeof inputRaw !== "object") logger.error({
|
|
3083
|
+
group: "parser",
|
|
3084
|
+
label: "resolver",
|
|
3085
|
+
message: `Invalid input: ${JSON.stringify(inputRaw)}.`
|
|
3086
|
+
});
|
|
3087
|
+
const input = {
|
|
3088
|
+
...inputDefaults,
|
|
3089
|
+
...inputRaw
|
|
3090
|
+
};
|
|
3091
|
+
return permutations.findIndex((p) => (0, import_fast_deep_equal.default)(input, p)) !== -1;
|
|
3092
|
+
}
|
|
3093
|
+
};
|
|
3094
|
+
}
|
|
3095
|
+
/** Calculate all permutations */
|
|
3096
|
+
function calculatePermutations(options) {
|
|
3097
|
+
const permutationCount = [1];
|
|
3098
|
+
for (const [_name, contexts] of options) permutationCount.push(contexts.length * (permutationCount.at(-1) || 1));
|
|
3099
|
+
const permutations = [];
|
|
3100
|
+
for (let i = 0; i < permutationCount.at(-1); i++) {
|
|
3101
|
+
const input = {};
|
|
3102
|
+
for (let j = 0; j < options.length; j++) {
|
|
3103
|
+
const [name, contexts] = options[j];
|
|
3104
|
+
input[name] = contexts[Math.floor(i / permutationCount[j]) % contexts.length];
|
|
3105
|
+
}
|
|
3106
|
+
permutations.push(input);
|
|
3107
|
+
}
|
|
3108
|
+
return permutations.length ? permutations : [{}];
|
|
3109
|
+
}
|
|
3110
|
+
|
|
2483
3111
|
//#endregion
|
|
2484
3112
|
//#region src/parse/normalize.ts
|
|
2485
3113
|
/**
|
|
@@ -2603,8 +3231,7 @@ function tokenFromNode(node, { groups, path, source, ignore }) {
|
|
|
2603
3231
|
const jsonID = `#/${path.join("/")}`;
|
|
2604
3232
|
const id = path.join(".");
|
|
2605
3233
|
const originalToken = momoa.evaluate(node);
|
|
2606
|
-
const
|
|
2607
|
-
const group = groups[groupID];
|
|
3234
|
+
const group = groups[`#/${path.slice(0, -1).join("/")}`];
|
|
2608
3235
|
if (group?.tokens && !group.tokens.includes(id)) group.tokens.push(id);
|
|
2609
3236
|
const nodeSource = {
|
|
2610
3237
|
filename: source.filename?.href,
|
|
@@ -2873,6 +3500,13 @@ function resolveAliases(tokens, { logger, refMap, sources }) {
|
|
|
2873
3500
|
for (const mode of Object.keys(token.mode)) {
|
|
2874
3501
|
function resolveInner(alias, refChain) {
|
|
2875
3502
|
const nextRef = aliasToRef(alias, mode)?.$ref;
|
|
3503
|
+
if (!nextRef) {
|
|
3504
|
+
logger.error({
|
|
3505
|
+
...aliasEntry,
|
|
3506
|
+
message: `Internal error resolving ${JSON.stringify(refChain)}`
|
|
3507
|
+
});
|
|
3508
|
+
throw new Error("Internal error");
|
|
3509
|
+
}
|
|
2876
3510
|
if (refChain.includes(nextRef)) logger.error({
|
|
2877
3511
|
...aliasEntry,
|
|
2878
3512
|
message: "Circular alias detected."
|
|
@@ -2947,7 +3581,7 @@ function resolveAliases(tokens, { logger, refMap, sources }) {
|
|
|
2947
3581
|
//#endregion
|
|
2948
3582
|
//#region src/parse/load.ts
|
|
2949
3583
|
/** Load from multiple entries, while resolving remote files */
|
|
2950
|
-
async function loadSources(inputs, { config, logger, continueOnError, yamlToMomoa, transform }) {
|
|
3584
|
+
async function loadSources(inputs, { config, logger, req, continueOnError, yamlToMomoa, transform }) {
|
|
2951
3585
|
const entry = {
|
|
2952
3586
|
group: "parser",
|
|
2953
3587
|
label: "init"
|
|
@@ -2966,6 +3600,7 @@ async function loadSources(inputs, { config, logger, continueOnError, yamlToMomo
|
|
|
2966
3600
|
let refMap = {};
|
|
2967
3601
|
try {
|
|
2968
3602
|
const result = await bundle(sources, {
|
|
3603
|
+
req,
|
|
2969
3604
|
parse: transform ? transformer(transform) : void 0,
|
|
2970
3605
|
yamlToMomoa
|
|
2971
3606
|
});
|
|
@@ -3138,11 +3773,17 @@ function transformer(transform) {
|
|
|
3138
3773
|
//#endregion
|
|
3139
3774
|
//#region src/parse/index.ts
|
|
3140
3775
|
/** Parse */
|
|
3141
|
-
async function parse(_input, { logger = new Logger(), skipLint = false, config = {}, continueOnError = false, yamlToMomoa, transform } = {}) {
|
|
3776
|
+
async function parse(_input, { logger = new Logger(), req = defaultReq, skipLint = false, config = {}, continueOnError = false, yamlToMomoa, transform } = {}) {
|
|
3142
3777
|
const inputs = Array.isArray(_input) ? _input : [_input];
|
|
3143
3778
|
const totalStart = performance.now();
|
|
3779
|
+
const resolver = await loadResolver(inputs, {
|
|
3780
|
+
logger,
|
|
3781
|
+
req,
|
|
3782
|
+
yamlToMomoa
|
|
3783
|
+
});
|
|
3144
3784
|
const initStart = performance.now();
|
|
3145
3785
|
const { tokens, sources } = await loadSources(inputs, {
|
|
3786
|
+
req,
|
|
3146
3787
|
logger,
|
|
3147
3788
|
config,
|
|
3148
3789
|
continueOnError,
|
|
@@ -3185,10 +3826,22 @@ async function parse(_input, { logger = new Logger(), skipLint = false, config =
|
|
|
3185
3826
|
}
|
|
3186
3827
|
return {
|
|
3187
3828
|
tokens,
|
|
3188
|
-
sources
|
|
3829
|
+
sources,
|
|
3830
|
+
resolver
|
|
3189
3831
|
};
|
|
3190
3832
|
}
|
|
3833
|
+
let fs;
|
|
3834
|
+
/** Fallback req */
|
|
3835
|
+
async function defaultReq(src, _origin) {
|
|
3836
|
+
if (src.protocol === "file:") {
|
|
3837
|
+
if (!fs) fs = await import("node:fs/promises");
|
|
3838
|
+
return await fs.readFile(src, "utf8");
|
|
3839
|
+
}
|
|
3840
|
+
const res = await fetch(src);
|
|
3841
|
+
if (!res.ok) throw new Error(`${src} responded with ${res.status}\n${await res.text()}`);
|
|
3842
|
+
return await res.text();
|
|
3843
|
+
}
|
|
3191
3844
|
|
|
3192
3845
|
//#endregion
|
|
3193
|
-
export { LOG_ORDER, Logger, MULTI_VALUE, RECOMMENDED_CONFIG, SINGLE_VALUE, TokensJSONError, build, defineConfig, formatMessage, lintRunner, mergeConfigs, parse };
|
|
3846
|
+
export { LOG_ORDER, Logger, MULTI_VALUE, RECOMMENDED_CONFIG, SINGLE_VALUE, TokensJSONError, build, calculatePermutations, createResolver, defineConfig, formatMessage, isLikelyResolver, lintRunner, loadResolver, mergeConfigs, normalizeResolver, parse, validateModifier, validateResolver, validateSet };
|
|
3194
3847
|
//# sourceMappingURL=index.js.map
|