@terrazzo/parser 2.0.0-alpha.3 → 2.0.0-alpha.5
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/dist/index.d.ts +44 -31
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +488 -426
- package/dist/index.js.map +1 -1
- package/package.json +13 -4
- package/src/build/index.ts +7 -4
- package/src/config.ts +1 -8
- package/src/lib/resolver-utils.ts +35 -0
- package/src/lint/index.ts +4 -3
- package/src/logger.ts +19 -7
- package/src/parse/index.ts +39 -17
- package/src/parse/load.ts +20 -110
- package/src/parse/process.ts +123 -0
- package/src/parse/token.ts +9 -8
- package/src/resolver/create-synthetic-resolver.ts +86 -0
- package/src/resolver/load.ts +90 -35
- package/src/resolver/normalize.ts +18 -11
- package/src/resolver/validate.ts +23 -7
- package/src/types.ts +16 -13
package/dist/index.js
CHANGED
|
@@ -1,42 +1,12 @@
|
|
|
1
1
|
import wcmatch from "wildcard-match";
|
|
2
2
|
import * as momoa from "@humanwhocodes/momoa";
|
|
3
|
-
import { evaluate } from "@humanwhocodes/momoa";
|
|
4
3
|
import pc from "picocolors";
|
|
5
4
|
import { merge } from "merge-anything";
|
|
6
5
|
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";
|
|
7
6
|
import { clampChroma, wcagContrast } from "culori";
|
|
8
|
-
import {
|
|
7
|
+
import { camelCase, kebabCase, pascalCase, snakeCase } from "scule";
|
|
8
|
+
import { bundle, getObjMember, getObjMembers, isPure$ref, maybeRawJSON, parseRef, replaceNode, traverse } from "@terrazzo/json-schema-tools";
|
|
9
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
|
|
40
10
|
//#region src/lib/code-frame.ts
|
|
41
11
|
/**
|
|
42
12
|
* Extract what lines should be marked and highlighted.
|
|
@@ -146,6 +116,7 @@ function formatMessage(entry, severity) {
|
|
|
146
116
|
let message = entry.message;
|
|
147
117
|
message = `[${entry.group}${entry.label ? `:${entry.label}` : ""}] ${message}`;
|
|
148
118
|
if (severity in MESSAGE_COLOR) message = MESSAGE_COLOR[severity](message);
|
|
119
|
+
if (typeof entry.timing === "number") message = `${message} ${formatTiming(entry.timing)}`;
|
|
149
120
|
if (entry.node) {
|
|
150
121
|
const start = entry.node?.loc?.start ?? {
|
|
151
122
|
line: 0,
|
|
@@ -211,12 +182,7 @@ var Logger = class {
|
|
|
211
182
|
if (this.debugScope !== "*" && !wcmatch(this.debugScope)(debugPrefix)) return;
|
|
212
183
|
message.replace(/\[config[^\]]+\]/, (match) => pc.green(match)).replace(/\[parser[^\]]+\]/, (match) => pc.magenta(match)).replace(/\[lint[^\]]+\]/, (match) => pc.yellow(match)).replace(/\[plugin[^\]]+\]/, (match) => pc.cyan(match));
|
|
213
184
|
message = `${pc.dim(timeFormatter.format(performance.now()))} ${message}`;
|
|
214
|
-
if (typeof entry.timing === "number") {
|
|
215
|
-
let timing = "";
|
|
216
|
-
if (entry.timing < 1e3) timing = `${Math.round(entry.timing * 100) / 100}ms`;
|
|
217
|
-
else if (entry.timing < 6e4) timing = `${Math.round(entry.timing * 100) / 1e5}s`;
|
|
218
|
-
message = `${message}${timing ? pc.dim(` [${timing}]`) : ""}`;
|
|
219
|
-
}
|
|
185
|
+
if (typeof entry.timing === "number") message = `${message} ${formatTiming(entry.timing)}`;
|
|
220
186
|
console.log(message);
|
|
221
187
|
}
|
|
222
188
|
}
|
|
@@ -230,6 +196,13 @@ var Logger = class {
|
|
|
230
196
|
};
|
|
231
197
|
}
|
|
232
198
|
};
|
|
199
|
+
function formatTiming(timing) {
|
|
200
|
+
let output = "";
|
|
201
|
+
if (timing < 1e3) output = `${Math.round(timing * 100) / 100}ms`;
|
|
202
|
+
else if (timing < 6e4) output = `${Math.round(timing) / 1e3}s`;
|
|
203
|
+
else output = `${Math.round(timing / 1e3) / 60}m`;
|
|
204
|
+
return pc.dim(`[${output}]`);
|
|
205
|
+
}
|
|
233
206
|
var TokensJSONError = class extends Error {
|
|
234
207
|
constructor(message) {
|
|
235
208
|
super(message);
|
|
@@ -258,7 +231,7 @@ function validateTransformParams({ params, logger, pluginName }) {
|
|
|
258
231
|
});
|
|
259
232
|
}
|
|
260
233
|
/** Run build stage */
|
|
261
|
-
async function build(tokens, { sources, logger = new Logger(), config }) {
|
|
234
|
+
async function build(tokens, { resolver, sources, logger = new Logger(), config }) {
|
|
262
235
|
const formats = {};
|
|
263
236
|
const result = { outputFiles: [] };
|
|
264
237
|
function getTransforms(params) {
|
|
@@ -321,7 +294,8 @@ async function build(tokens, { sources, logger = new Logger(), config }) {
|
|
|
321
294
|
formats[params.format][foundTokenI].value = cleanValue;
|
|
322
295
|
formats[params.format][foundTokenI].type = typeof cleanValue === "string" ? SINGLE_VALUE : MULTI_VALUE;
|
|
323
296
|
}
|
|
324
|
-
}
|
|
297
|
+
},
|
|
298
|
+
resolver
|
|
325
299
|
});
|
|
326
300
|
transformsLocked = true;
|
|
327
301
|
logger.debug({
|
|
@@ -339,6 +313,7 @@ async function build(tokens, { sources, logger = new Logger(), config }) {
|
|
|
339
313
|
tokens,
|
|
340
314
|
sources,
|
|
341
315
|
getTransforms,
|
|
316
|
+
resolver,
|
|
342
317
|
outputFile(filename, contents) {
|
|
343
318
|
const resolved = new URL(filename, config.outDir);
|
|
344
319
|
if (result.outputFiles.some((f) => new URL(f.filename, config.outDir).href === resolved.href)) logger.error({
|
|
@@ -546,76 +521,6 @@ const rule$24 = {
|
|
|
546
521
|
};
|
|
547
522
|
var colorspace_default = rule$24;
|
|
548
523
|
|
|
549
|
-
//#endregion
|
|
550
|
-
//#region ../../node_modules/.pnpm/scule@1.3.0/node_modules/scule/dist/index.mjs
|
|
551
|
-
const NUMBER_CHAR_RE = /\d/;
|
|
552
|
-
const STR_SPLITTERS = [
|
|
553
|
-
"-",
|
|
554
|
-
"_",
|
|
555
|
-
"/",
|
|
556
|
-
"."
|
|
557
|
-
];
|
|
558
|
-
function isUppercase(char = "") {
|
|
559
|
-
if (NUMBER_CHAR_RE.test(char)) return;
|
|
560
|
-
return char !== char.toLowerCase();
|
|
561
|
-
}
|
|
562
|
-
function splitByCase(str, separators) {
|
|
563
|
-
const splitters = separators ?? STR_SPLITTERS;
|
|
564
|
-
const parts = [];
|
|
565
|
-
if (!str || typeof str !== "string") return parts;
|
|
566
|
-
let buff = "";
|
|
567
|
-
let previousUpper;
|
|
568
|
-
let previousSplitter;
|
|
569
|
-
for (const char of str) {
|
|
570
|
-
const isSplitter = splitters.includes(char);
|
|
571
|
-
if (isSplitter === true) {
|
|
572
|
-
parts.push(buff);
|
|
573
|
-
buff = "";
|
|
574
|
-
previousUpper = void 0;
|
|
575
|
-
continue;
|
|
576
|
-
}
|
|
577
|
-
const isUpper = isUppercase(char);
|
|
578
|
-
if (previousSplitter === false) {
|
|
579
|
-
if (previousUpper === false && isUpper === true) {
|
|
580
|
-
parts.push(buff);
|
|
581
|
-
buff = char;
|
|
582
|
-
previousUpper = isUpper;
|
|
583
|
-
continue;
|
|
584
|
-
}
|
|
585
|
-
if (previousUpper === true && isUpper === false && buff.length > 1) {
|
|
586
|
-
const lastChar = buff.at(-1);
|
|
587
|
-
parts.push(buff.slice(0, Math.max(0, buff.length - 1)));
|
|
588
|
-
buff = lastChar + char;
|
|
589
|
-
previousUpper = isUpper;
|
|
590
|
-
continue;
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
buff += char;
|
|
594
|
-
previousUpper = isUpper;
|
|
595
|
-
previousSplitter = isSplitter;
|
|
596
|
-
}
|
|
597
|
-
parts.push(buff);
|
|
598
|
-
return parts;
|
|
599
|
-
}
|
|
600
|
-
function upperFirst(str) {
|
|
601
|
-
return str ? str[0].toUpperCase() + str.slice(1) : "";
|
|
602
|
-
}
|
|
603
|
-
function lowerFirst(str) {
|
|
604
|
-
return str ? str[0].toLowerCase() + str.slice(1) : "";
|
|
605
|
-
}
|
|
606
|
-
function pascalCase(str, opts) {
|
|
607
|
-
return str ? (Array.isArray(str) ? str : splitByCase(str)).map((p) => upperFirst(opts?.normalize ? p.toLowerCase() : p)).join("") : "";
|
|
608
|
-
}
|
|
609
|
-
function camelCase(str, opts) {
|
|
610
|
-
return lowerFirst(pascalCase(str || "", opts));
|
|
611
|
-
}
|
|
612
|
-
function kebabCase(str, joiner) {
|
|
613
|
-
return str ? (Array.isArray(str) ? str : splitByCase(str)).map((p) => p.toLowerCase()).join(joiner ?? "-") : "";
|
|
614
|
-
}
|
|
615
|
-
function snakeCase(str) {
|
|
616
|
-
return kebabCase(str || "", "_");
|
|
617
|
-
}
|
|
618
|
-
|
|
619
524
|
//#endregion
|
|
620
525
|
//#region src/lint/plugin-core/rules/consistent-naming.ts
|
|
621
526
|
const CONSISTENT_NAMING = "core/consistent-naming";
|
|
@@ -2206,7 +2111,7 @@ function defineConfig(rawConfig, { logger = new Logger(), cwd } = {}) {
|
|
|
2206
2111
|
config,
|
|
2207
2112
|
logger
|
|
2208
2113
|
});
|
|
2209
|
-
for (const plugin of config.plugins) plugin.config?.({ ...config });
|
|
2114
|
+
for (const plugin of config.plugins) plugin.config?.({ ...config }, { logger });
|
|
2210
2115
|
logger.debug({
|
|
2211
2116
|
group: "parser",
|
|
2212
2117
|
label: "config",
|
|
@@ -2375,7 +2280,6 @@ function normalizeLint({ config, logger }) {
|
|
|
2375
2280
|
message: `Expected string or number, received ${JSON.stringify(value)}`
|
|
2376
2281
|
});
|
|
2377
2282
|
}
|
|
2378
|
-
for (const [id, severity] of Object.entries(RECOMMENDED_CONFIG)) if (!(id in config.lint.rules)) config.lint.rules[id] = severity;
|
|
2379
2283
|
}
|
|
2380
2284
|
} else config.lint = {
|
|
2381
2285
|
build: { enabled: true },
|
|
@@ -2489,40 +2393,8 @@ async function lintRunner({ tokens, filename, config = {}, sources, logger }) {
|
|
|
2489
2393
|
});
|
|
2490
2394
|
}
|
|
2491
2395
|
|
|
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
|
-
|
|
2523
2396
|
//#endregion
|
|
2524
2397
|
//#region src/lib/momoa.ts
|
|
2525
|
-
var import_fast_deep_equal = /* @__PURE__ */ __toESM(require_fast_deep_equal(), 1);
|
|
2526
2398
|
/** Momoa’s default parser, with preferred settings. */
|
|
2527
2399
|
function toMomoa(srcRaw) {
|
|
2528
2400
|
return momoa.parse(typeof srcRaw === "string" ? srcRaw : JSON.stringify(srcRaw, void 0, 2), {
|
|
@@ -2532,6 +2404,32 @@ function toMomoa(srcRaw) {
|
|
|
2532
2404
|
});
|
|
2533
2405
|
}
|
|
2534
2406
|
|
|
2407
|
+
//#endregion
|
|
2408
|
+
//#region src/lib/resolver-utils.ts
|
|
2409
|
+
/**
|
|
2410
|
+
* If tokens are found inside a resolver, strip out the resolver paths (don’t
|
|
2411
|
+
* include "sets"/"modifiers" in the token ID etc.)
|
|
2412
|
+
*/
|
|
2413
|
+
function filterResolverPaths(path) {
|
|
2414
|
+
switch (path[0]) {
|
|
2415
|
+
case "sets": return path.slice(4);
|
|
2416
|
+
case "modifiers": return path.slice(5);
|
|
2417
|
+
case "resolutionOrder":
|
|
2418
|
+
switch (path[2]) {
|
|
2419
|
+
case "sources": return path.slice(4);
|
|
2420
|
+
case "contexts": return path.slice(5);
|
|
2421
|
+
}
|
|
2422
|
+
break;
|
|
2423
|
+
}
|
|
2424
|
+
return path;
|
|
2425
|
+
}
|
|
2426
|
+
/**
|
|
2427
|
+
* Make a deterministic string from an object
|
|
2428
|
+
*/
|
|
2429
|
+
function makeInputKey(input) {
|
|
2430
|
+
return JSON.stringify(Object.fromEntries(Object.entries(input).sort((a, b) => a[0].localeCompare(b[0], "en-us", { numeric: true }))));
|
|
2431
|
+
}
|
|
2432
|
+
|
|
2535
2433
|
//#endregion
|
|
2536
2434
|
//#region src/resolver/validate.ts
|
|
2537
2435
|
/**
|
|
@@ -2550,7 +2448,7 @@ function isLikelyResolver(doc) {
|
|
|
2550
2448
|
case "name":
|
|
2551
2449
|
case "description":
|
|
2552
2450
|
case "version":
|
|
2553
|
-
if (member.
|
|
2451
|
+
if (member.value.type === "String") return true;
|
|
2554
2452
|
break;
|
|
2555
2453
|
case "sets":
|
|
2556
2454
|
case "modifiers":
|
|
@@ -2685,6 +2583,7 @@ function validateResolver(node, { logger, src }) {
|
|
|
2685
2583
|
node: member.value
|
|
2686
2584
|
});
|
|
2687
2585
|
break;
|
|
2586
|
+
case "$schema":
|
|
2688
2587
|
case "$ref":
|
|
2689
2588
|
if (member.value.type !== "String") errors.push({
|
|
2690
2589
|
...entry,
|
|
@@ -2868,6 +2767,21 @@ function validateModifier(node, isInline = false, { src }) {
|
|
|
2868
2767
|
node: context.value
|
|
2869
2768
|
});
|
|
2870
2769
|
break;
|
|
2770
|
+
case "default":
|
|
2771
|
+
if (member.value.type !== "String") errors.push({
|
|
2772
|
+
...entry,
|
|
2773
|
+
message: `Expected string`,
|
|
2774
|
+
node: member.value
|
|
2775
|
+
});
|
|
2776
|
+
else {
|
|
2777
|
+
const contexts = getObjMember(node, "contexts");
|
|
2778
|
+
if (!contexts || !getObjMember(contexts, member.value.value)) errors.push({
|
|
2779
|
+
...entry,
|
|
2780
|
+
message: "Invalid default context",
|
|
2781
|
+
node: member.value
|
|
2782
|
+
});
|
|
2783
|
+
}
|
|
2784
|
+
break;
|
|
2871
2785
|
case "$defs":
|
|
2872
2786
|
case "$extensions":
|
|
2873
2787
|
if (member.value.type !== "Object") errors.push({
|
|
@@ -2910,204 +2824,6 @@ function validateModifier(node, isInline = false, { src }) {
|
|
|
2910
2824
|
return errors;
|
|
2911
2825
|
}
|
|
2912
2826
|
|
|
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
|
-
|
|
3111
2827
|
//#endregion
|
|
3112
2828
|
//#region src/parse/normalize.ts
|
|
3113
2829
|
/**
|
|
@@ -3227,14 +2943,14 @@ function aliasToRef(alias, mode) {
|
|
|
3227
2943
|
}
|
|
3228
2944
|
/** Generate a TokenNormalized from a Momoa node */
|
|
3229
2945
|
function tokenFromNode(node, { groups, path, source, ignore }) {
|
|
3230
|
-
if (!(node.type === "Object" && getObjMember(node, "$value") && !path.includes("$extensions"))) return;
|
|
2946
|
+
if (!(node.type === "Object" && !!getObjMember(node, "$value") && !path.includes("$extensions"))) return;
|
|
3231
2947
|
const jsonID = `#/${path.join("/")}`;
|
|
3232
2948
|
const id = path.join(".");
|
|
3233
2949
|
const originalToken = momoa.evaluate(node);
|
|
3234
2950
|
const group = groups[`#/${path.slice(0, -1).join("/")}`];
|
|
3235
2951
|
if (group?.tokens && !group.tokens.includes(id)) group.tokens.push(id);
|
|
3236
2952
|
const nodeSource = {
|
|
3237
|
-
filename: source.filename
|
|
2953
|
+
filename: source.filename.href,
|
|
3238
2954
|
node
|
|
3239
2955
|
};
|
|
3240
2956
|
const token = {
|
|
@@ -3244,6 +2960,7 @@ function tokenFromNode(node, { groups, path, source, ignore }) {
|
|
|
3244
2960
|
$deprecated: originalToken.$deprecated ?? group.$deprecated ?? void 0,
|
|
3245
2961
|
$value: originalToken.$value,
|
|
3246
2962
|
$extensions: originalToken.$extensions || void 0,
|
|
2963
|
+
$extends: originalToken.$extends || void 0,
|
|
3247
2964
|
aliasChain: void 0,
|
|
3248
2965
|
aliasedBy: void 0,
|
|
3249
2966
|
aliasOf: void 0,
|
|
@@ -3271,7 +2988,7 @@ function tokenFromNode(node, { groups, path, source, ignore }) {
|
|
|
3271
2988
|
const $extensions = getObjMember(node, "$extensions");
|
|
3272
2989
|
if ($extensions) {
|
|
3273
2990
|
const modeNode = getObjMember($extensions, "mode");
|
|
3274
|
-
for (const mode of Object.keys(token.$extensions.mode)) {
|
|
2991
|
+
for (const mode of Object.keys(token.$extensions.mode ?? {})) {
|
|
3275
2992
|
const modeValue = token.$extensions.mode[mode];
|
|
3276
2993
|
token.mode[mode] = {
|
|
3277
2994
|
$value: modeValue,
|
|
@@ -3579,80 +3296,28 @@ function resolveAliases(tokens, { logger, refMap, sources }) {
|
|
|
3579
3296
|
}
|
|
3580
3297
|
|
|
3581
3298
|
//#endregion
|
|
3582
|
-
//#region src/parse/
|
|
3583
|
-
|
|
3584
|
-
async function loadSources(inputs, { config, logger, req, continueOnError, yamlToMomoa, transform }) {
|
|
3299
|
+
//#region src/parse/process.ts
|
|
3300
|
+
function processTokens(rootSource, { config, logger, sourceByFilename, refMap }) {
|
|
3585
3301
|
const entry = {
|
|
3586
3302
|
group: "parser",
|
|
3587
3303
|
label: "init"
|
|
3588
3304
|
};
|
|
3589
|
-
const
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
const
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
/** Mapping of all final $ref resolutions. This will be used to generate the graph later. */
|
|
3600
|
-
let refMap = {};
|
|
3601
|
-
try {
|
|
3602
|
-
const result = await bundle(sources, {
|
|
3603
|
-
req,
|
|
3604
|
-
parse: transform ? transformer(transform) : void 0,
|
|
3605
|
-
yamlToMomoa
|
|
3606
|
-
});
|
|
3607
|
-
document = result.document;
|
|
3608
|
-
sourceByFilename = result.sources;
|
|
3609
|
-
refMap = result.refMap;
|
|
3610
|
-
for (const [filename, source] of Object.entries(result.sources)) {
|
|
3611
|
-
const i = sources.findIndex((s) => s.filename.href === filename);
|
|
3612
|
-
if (i === -1) sources.push(source);
|
|
3613
|
-
else {
|
|
3614
|
-
sources[i].src = source.src;
|
|
3615
|
-
sources[i].document = source.document;
|
|
3616
|
-
}
|
|
3617
|
-
}
|
|
3618
|
-
} catch (err) {
|
|
3619
|
-
let src = sources.find((s) => s.filename.href === err.filename)?.src;
|
|
3620
|
-
if (src && typeof src !== "string") src = JSON.stringify(src, void 0, 2);
|
|
3621
|
-
logger.error({
|
|
3622
|
-
...entry,
|
|
3623
|
-
continueOnError,
|
|
3624
|
-
message: err.message,
|
|
3625
|
-
node: err.node,
|
|
3626
|
-
src
|
|
3627
|
-
});
|
|
3628
|
-
}
|
|
3629
|
-
logger.debug({
|
|
3630
|
-
...entry,
|
|
3631
|
-
message: `JSON loaded`,
|
|
3632
|
-
timing: performance.now() - firstLoad
|
|
3633
|
-
});
|
|
3634
|
-
const artificialSource = {
|
|
3635
|
-
src: momoa.print(document, { indent: 2 }),
|
|
3636
|
-
document
|
|
3637
|
-
};
|
|
3638
|
-
const firstPass = performance.now();
|
|
3639
|
-
const tokens = {};
|
|
3640
|
-
const tokenIDs = [];
|
|
3641
|
-
const groups = {};
|
|
3642
|
-
await traverseAsync(document, { async enter(node, _parent, path) {
|
|
3643
|
-
if (node.type !== "Object") return;
|
|
3644
|
-
groupFromNode(node, {
|
|
3645
|
-
path,
|
|
3646
|
-
groups
|
|
3305
|
+
const firstPass = performance.now();
|
|
3306
|
+
const tokens = {};
|
|
3307
|
+
const tokenIDs = [];
|
|
3308
|
+
const groups = {};
|
|
3309
|
+
const isResolver = isLikelyResolver(rootSource.document);
|
|
3310
|
+
traverse(rootSource.document, { enter(node, _parent, rawPath) {
|
|
3311
|
+
if (node.type !== "Object") return;
|
|
3312
|
+
groupFromNode(node, {
|
|
3313
|
+
path: isResolver ? filterResolverPaths(rawPath) : rawPath,
|
|
3314
|
+
groups
|
|
3647
3315
|
});
|
|
3648
3316
|
const token = tokenFromNode(node, {
|
|
3649
3317
|
groups,
|
|
3650
3318
|
ignore: config.ignore,
|
|
3651
|
-
path,
|
|
3652
|
-
source:
|
|
3653
|
-
src: artificialSource,
|
|
3654
|
-
document
|
|
3655
|
-
}
|
|
3319
|
+
path: isResolver ? filterResolverPaths(rawPath) : rawPath,
|
|
3320
|
+
source: rootSource
|
|
3656
3321
|
});
|
|
3657
3322
|
if (token) {
|
|
3658
3323
|
tokenIDs.push(token.jsonID);
|
|
@@ -3665,7 +3330,7 @@ async function loadSources(inputs, { config, logger, req, continueOnError, yamlT
|
|
|
3665
3330
|
timing: performance.now() - firstPass
|
|
3666
3331
|
});
|
|
3667
3332
|
const secondPass = performance.now();
|
|
3668
|
-
for (const source of Object.values(sourceByFilename))
|
|
3333
|
+
for (const source of Object.values(sourceByFilename)) traverse(source.document, { enter(node, _parent, path) {
|
|
3669
3334
|
if (node.type !== "Object") return;
|
|
3670
3335
|
const tokenRawValues = tokenRawValuesFromNode(node, {
|
|
3671
3336
|
filename: source.filename.href,
|
|
@@ -3722,8 +3387,378 @@ async function loadSources(inputs, { config, logger, req, continueOnError, yamlT
|
|
|
3722
3387
|
tokensSorted[id] = tokens[path];
|
|
3723
3388
|
}
|
|
3724
3389
|
for (const group of Object.values(groups)) group.tokens.sort((a, b) => a.localeCompare(b, "en-us", { numeric: true }));
|
|
3390
|
+
return tokensSorted;
|
|
3391
|
+
}
|
|
3392
|
+
|
|
3393
|
+
//#endregion
|
|
3394
|
+
//#region src/resolver/normalize.ts
|
|
3395
|
+
/** Normalize resolver (assuming it’s been validated) */
|
|
3396
|
+
async function normalizeResolver(node, { filename, req, src, logger, yamlToMomoa }) {
|
|
3397
|
+
const resolverSource = momoa.evaluate(node);
|
|
3398
|
+
const resolutionOrder = getObjMember(node.body, "resolutionOrder");
|
|
3725
3399
|
return {
|
|
3726
|
-
|
|
3400
|
+
name: resolverSource.name,
|
|
3401
|
+
version: resolverSource.version,
|
|
3402
|
+
description: resolverSource.description,
|
|
3403
|
+
sets: resolverSource.sets,
|
|
3404
|
+
modifiers: resolverSource.modifiers,
|
|
3405
|
+
resolutionOrder: await Promise.all(resolutionOrder.elements.map(async (element, i) => {
|
|
3406
|
+
const layer = element.value;
|
|
3407
|
+
const members = getObjMembers(layer);
|
|
3408
|
+
let item = layer;
|
|
3409
|
+
if (members.$ref) {
|
|
3410
|
+
const entry = {
|
|
3411
|
+
group: "parser",
|
|
3412
|
+
label: "init",
|
|
3413
|
+
node: members.$ref,
|
|
3414
|
+
src
|
|
3415
|
+
};
|
|
3416
|
+
const { url, subpath } = parseRef(members.$ref.value);
|
|
3417
|
+
if (url === ".") if (!subpath?.[0]) logger.error({
|
|
3418
|
+
...entry,
|
|
3419
|
+
message: "$ref can’t refer to the root document."
|
|
3420
|
+
});
|
|
3421
|
+
else if (subpath[0] !== "sets" && subpath[0] !== "modifiers") logger.error({
|
|
3422
|
+
...entry,
|
|
3423
|
+
message: "Local $ref in resolutionOrder must point to either #/sets/[set] or #/modifiers/[modifiers]."
|
|
3424
|
+
});
|
|
3425
|
+
else {
|
|
3426
|
+
const resolvedItem = resolverSource[subpath[0]]?.[subpath[1]];
|
|
3427
|
+
if (!resolvedItem) logger.error({
|
|
3428
|
+
...entry,
|
|
3429
|
+
message: "Invalid $ref"
|
|
3430
|
+
});
|
|
3431
|
+
else item = {
|
|
3432
|
+
type: subpath[0] === "sets" ? "set" : "modifier",
|
|
3433
|
+
name: subpath[1],
|
|
3434
|
+
...resolvedItem
|
|
3435
|
+
};
|
|
3436
|
+
}
|
|
3437
|
+
else {
|
|
3438
|
+
const result = await bundle([{
|
|
3439
|
+
filename: new URL(url, filename),
|
|
3440
|
+
src: resolverSource.resolutionOrder[i]
|
|
3441
|
+
}], {
|
|
3442
|
+
req,
|
|
3443
|
+
yamlToMomoa
|
|
3444
|
+
});
|
|
3445
|
+
if (result.document.body.type === "Object") {
|
|
3446
|
+
const type = getObjMember(result.document.body, "type");
|
|
3447
|
+
if (type?.type === "String" && type.value === "set") {
|
|
3448
|
+
validateSet(result.document.body, true, src);
|
|
3449
|
+
item = momoa.evaluate(result.document.body);
|
|
3450
|
+
} else if (type?.type === "String" && type.value === "modifier") {
|
|
3451
|
+
validateModifier(result.document.body, true, src);
|
|
3452
|
+
item = momoa.evaluate(result.document.body);
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
3455
|
+
logger.error({
|
|
3456
|
+
...entry,
|
|
3457
|
+
message: "$ref did not resolve to a valid Set or Modifier."
|
|
3458
|
+
});
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
const finalResult = await bundle([{
|
|
3462
|
+
filename,
|
|
3463
|
+
src: item
|
|
3464
|
+
}], {
|
|
3465
|
+
req,
|
|
3466
|
+
yamlToMomoa
|
|
3467
|
+
});
|
|
3468
|
+
return momoa.evaluate(finalResult.document.body);
|
|
3469
|
+
})),
|
|
3470
|
+
_source: {
|
|
3471
|
+
filename,
|
|
3472
|
+
node
|
|
3473
|
+
}
|
|
3474
|
+
};
|
|
3475
|
+
}
|
|
3476
|
+
|
|
3477
|
+
//#endregion
|
|
3478
|
+
//#region src/resolver/load.ts
|
|
3479
|
+
/** Quick-parse input sources and find a resolver */
|
|
3480
|
+
async function loadResolver(inputs, { config, logger, req, yamlToMomoa }) {
|
|
3481
|
+
let resolverDoc;
|
|
3482
|
+
let tokens = {};
|
|
3483
|
+
const entry = {
|
|
3484
|
+
group: "parser",
|
|
3485
|
+
label: "init"
|
|
3486
|
+
};
|
|
3487
|
+
for (const input of inputs) {
|
|
3488
|
+
let document;
|
|
3489
|
+
if (typeof input.src === "string") if (maybeRawJSON(input.src)) document = toMomoa(input.src);
|
|
3490
|
+
else if (yamlToMomoa) document = yamlToMomoa(input.src);
|
|
3491
|
+
else logger.error({
|
|
3492
|
+
...entry,
|
|
3493
|
+
message: `Install yaml-to-momoa package to parse YAML, and pass in as option, e.g.:
|
|
3494
|
+
|
|
3495
|
+
import { bundle } from '@terrazzo/json-schema-tools';
|
|
3496
|
+
import yamlToMomoa from 'yaml-to-momoa';
|
|
3497
|
+
|
|
3498
|
+
bundle(yamlString, { yamlToMomoa });`
|
|
3499
|
+
});
|
|
3500
|
+
else if (input.src && typeof input.src === "object") document = toMomoa(JSON.stringify(input.src, void 0, 2));
|
|
3501
|
+
else logger.error({
|
|
3502
|
+
...entry,
|
|
3503
|
+
message: `Could not parse ${input.filename}. Is this valid JSON or YAML?`
|
|
3504
|
+
});
|
|
3505
|
+
if (!document || !isLikelyResolver(document)) continue;
|
|
3506
|
+
if (inputs.length > 1) logger.error({
|
|
3507
|
+
...entry,
|
|
3508
|
+
message: `Resolver must be the only input, found ${inputs.length} sources.`
|
|
3509
|
+
});
|
|
3510
|
+
resolverDoc = document;
|
|
3511
|
+
break;
|
|
3512
|
+
}
|
|
3513
|
+
let resolver;
|
|
3514
|
+
if (resolverDoc) {
|
|
3515
|
+
validateResolver(resolverDoc, {
|
|
3516
|
+
logger,
|
|
3517
|
+
src: inputs[0].src
|
|
3518
|
+
});
|
|
3519
|
+
resolver = createResolver(await normalizeResolver(resolverDoc, {
|
|
3520
|
+
filename: inputs[0].filename,
|
|
3521
|
+
logger,
|
|
3522
|
+
req,
|
|
3523
|
+
src: inputs[0].src,
|
|
3524
|
+
yamlToMomoa
|
|
3525
|
+
}), {
|
|
3526
|
+
config,
|
|
3527
|
+
logger,
|
|
3528
|
+
sources: [{
|
|
3529
|
+
...inputs[0],
|
|
3530
|
+
document: resolverDoc
|
|
3531
|
+
}]
|
|
3532
|
+
});
|
|
3533
|
+
const firstInput = {};
|
|
3534
|
+
for (const m of resolver.source.resolutionOrder) {
|
|
3535
|
+
if (m.type !== "modifier") continue;
|
|
3536
|
+
firstInput[m.name] = typeof m.default === "string" ? m.default : Object.keys(m.contexts)[0];
|
|
3537
|
+
}
|
|
3538
|
+
tokens = resolver.apply(firstInput);
|
|
3539
|
+
}
|
|
3540
|
+
return {
|
|
3541
|
+
resolver,
|
|
3542
|
+
tokens,
|
|
3543
|
+
sources: [{
|
|
3544
|
+
...inputs[0],
|
|
3545
|
+
document: resolverDoc
|
|
3546
|
+
}]
|
|
3547
|
+
};
|
|
3548
|
+
}
|
|
3549
|
+
/** Create an interface to resolve permutations */
|
|
3550
|
+
function createResolver(resolverSource, { config, logger, sources }) {
|
|
3551
|
+
const inputDefaults = {};
|
|
3552
|
+
const validContexts = {};
|
|
3553
|
+
const allPermutations = [];
|
|
3554
|
+
const resolverCache = {};
|
|
3555
|
+
for (const m of resolverSource.resolutionOrder) if (m.type === "modifier") {
|
|
3556
|
+
if (typeof m.default === "string") inputDefaults[m.name] = m.default;
|
|
3557
|
+
validContexts[m.name] = Object.keys(m.contexts);
|
|
3558
|
+
}
|
|
3559
|
+
return {
|
|
3560
|
+
apply(inputRaw) {
|
|
3561
|
+
let tokensRaw = {};
|
|
3562
|
+
const input = {
|
|
3563
|
+
...inputDefaults,
|
|
3564
|
+
...inputRaw
|
|
3565
|
+
};
|
|
3566
|
+
const inputKey = makeInputKey(input);
|
|
3567
|
+
if (resolverCache[inputKey]) return resolverCache[inputKey];
|
|
3568
|
+
for (const item of resolverSource.resolutionOrder) switch (item.type) {
|
|
3569
|
+
case "set":
|
|
3570
|
+
for (const s of item.sources) tokensRaw = merge(tokensRaw, s);
|
|
3571
|
+
break;
|
|
3572
|
+
case "modifier": {
|
|
3573
|
+
const context = input[item.name];
|
|
3574
|
+
const sources$1 = item.contexts[context];
|
|
3575
|
+
if (!sources$1) logger.error({
|
|
3576
|
+
group: "parser",
|
|
3577
|
+
label: "resolver",
|
|
3578
|
+
message: `Modifier ${item.name} has no context ${JSON.stringify(context)}.`
|
|
3579
|
+
});
|
|
3580
|
+
for (const s of sources$1 ?? []) tokensRaw = merge(tokensRaw, s);
|
|
3581
|
+
break;
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3584
|
+
const src = JSON.stringify(tokensRaw, void 0, 2);
|
|
3585
|
+
const rootSource = {
|
|
3586
|
+
filename: resolverSource._source.filename,
|
|
3587
|
+
document: toMomoa(src),
|
|
3588
|
+
src
|
|
3589
|
+
};
|
|
3590
|
+
const tokens = processTokens(rootSource, {
|
|
3591
|
+
config,
|
|
3592
|
+
logger,
|
|
3593
|
+
sourceByFilename: { [resolverSource._source.filename.href]: rootSource },
|
|
3594
|
+
refMap: {},
|
|
3595
|
+
sources
|
|
3596
|
+
});
|
|
3597
|
+
resolverCache[inputKey] = tokens;
|
|
3598
|
+
return tokens;
|
|
3599
|
+
},
|
|
3600
|
+
source: resolverSource,
|
|
3601
|
+
listPermutations() {
|
|
3602
|
+
if (!allPermutations.length) allPermutations.push(...calculatePermutations(Object.entries(validContexts)));
|
|
3603
|
+
return allPermutations;
|
|
3604
|
+
},
|
|
3605
|
+
isValidInput(input) {
|
|
3606
|
+
if (!input || typeof input !== "object") logger.error({
|
|
3607
|
+
group: "parser",
|
|
3608
|
+
label: "resolver",
|
|
3609
|
+
message: `Invalid input: ${JSON.stringify(input)}.`
|
|
3610
|
+
});
|
|
3611
|
+
if (!Object.keys(input).every((k) => k in validContexts)) return false;
|
|
3612
|
+
for (const [name, contexts] of Object.entries(validContexts)) if (name in input) {
|
|
3613
|
+
if (!contexts.includes(input[name])) return false;
|
|
3614
|
+
} else if (!(name in inputDefaults)) return false;
|
|
3615
|
+
return true;
|
|
3616
|
+
}
|
|
3617
|
+
};
|
|
3618
|
+
}
|
|
3619
|
+
/** Calculate all permutations */
|
|
3620
|
+
function calculatePermutations(options) {
|
|
3621
|
+
const permutationCount = [1];
|
|
3622
|
+
for (const [_name, contexts] of options) permutationCount.push(contexts.length * (permutationCount.at(-1) || 1));
|
|
3623
|
+
const permutations = [];
|
|
3624
|
+
for (let i = 0; i < permutationCount.at(-1); i++) {
|
|
3625
|
+
const input = {};
|
|
3626
|
+
for (let j = 0; j < options.length; j++) {
|
|
3627
|
+
const [name, contexts] = options[j];
|
|
3628
|
+
input[name] = contexts[Math.floor(i / permutationCount[j]) % contexts.length];
|
|
3629
|
+
}
|
|
3630
|
+
permutations.push(input);
|
|
3631
|
+
}
|
|
3632
|
+
return permutations.length ? permutations : [{}];
|
|
3633
|
+
}
|
|
3634
|
+
|
|
3635
|
+
//#endregion
|
|
3636
|
+
//#region src/resolver/create-synthetic-resolver.ts
|
|
3637
|
+
/**
|
|
3638
|
+
* Interop layer upgrading legacy Terrazzo modes to resolvers
|
|
3639
|
+
*/
|
|
3640
|
+
async function createSyntheticResolver(tokens, { config, logger, req, sources }) {
|
|
3641
|
+
const contexts = {};
|
|
3642
|
+
for (const token of Object.values(tokens)) for (const [mode, value] of Object.entries(token.mode)) {
|
|
3643
|
+
if (mode === ".") continue;
|
|
3644
|
+
if (!(mode in contexts)) contexts[mode] = [{}];
|
|
3645
|
+
addToken(contexts[mode][0], {
|
|
3646
|
+
...token,
|
|
3647
|
+
$value: value.$value
|
|
3648
|
+
}, { logger });
|
|
3649
|
+
}
|
|
3650
|
+
const src = JSON.stringify({
|
|
3651
|
+
name: "Terrazzo",
|
|
3652
|
+
version: "2025.10",
|
|
3653
|
+
resolutionOrder: [{ $ref: "#/sets/allTokens" }, { $ref: "#/modifiers/tzMode" }],
|
|
3654
|
+
sets: { allTokens: { sources: [simpleFlatten(tokens, { logger })] } },
|
|
3655
|
+
modifiers: { tzMode: {
|
|
3656
|
+
description: "Automatically built from $extensions.mode",
|
|
3657
|
+
contexts
|
|
3658
|
+
} }
|
|
3659
|
+
}, void 0, 2);
|
|
3660
|
+
return createResolver(await normalizeResolver(momoa.parse(src), {
|
|
3661
|
+
filename: new URL("file:///virtual:resolver.json"),
|
|
3662
|
+
logger,
|
|
3663
|
+
req,
|
|
3664
|
+
src
|
|
3665
|
+
}), {
|
|
3666
|
+
config,
|
|
3667
|
+
logger,
|
|
3668
|
+
sources
|
|
3669
|
+
});
|
|
3670
|
+
}
|
|
3671
|
+
/** Add a normalized token back into an arbitrary, hierarchial structure */
|
|
3672
|
+
function addToken(structure, token, { logger }) {
|
|
3673
|
+
let node = structure;
|
|
3674
|
+
const parts = token.id.split(".");
|
|
3675
|
+
const localID = parts.pop();
|
|
3676
|
+
for (const part of parts) {
|
|
3677
|
+
if (!(part in node)) node[part] = {};
|
|
3678
|
+
node = node[part];
|
|
3679
|
+
}
|
|
3680
|
+
if (localID in node) logger.error({
|
|
3681
|
+
group: "parser",
|
|
3682
|
+
label: "resolver",
|
|
3683
|
+
message: `${localID} already exists!`
|
|
3684
|
+
});
|
|
3685
|
+
node[localID] = {
|
|
3686
|
+
$type: token.$type,
|
|
3687
|
+
$value: token.$value
|
|
3688
|
+
};
|
|
3689
|
+
}
|
|
3690
|
+
/** Downconvert normalized tokens back into a simplified, hierarchial shape. This is extremely lossy, and only done to build a resolver. */
|
|
3691
|
+
function simpleFlatten(tokens, { logger }) {
|
|
3692
|
+
const group = {};
|
|
3693
|
+
for (const token of Object.values(tokens)) addToken(group, token, { logger });
|
|
3694
|
+
return group;
|
|
3695
|
+
}
|
|
3696
|
+
|
|
3697
|
+
//#endregion
|
|
3698
|
+
//#region src/parse/load.ts
|
|
3699
|
+
/** Load from multiple entries, while resolving remote files */
|
|
3700
|
+
async function loadSources(inputs, { config, logger, req, continueOnError, yamlToMomoa, transform }) {
|
|
3701
|
+
const entry = {
|
|
3702
|
+
group: "parser",
|
|
3703
|
+
label: "init"
|
|
3704
|
+
};
|
|
3705
|
+
const firstLoad = performance.now();
|
|
3706
|
+
let document = {};
|
|
3707
|
+
/** The original user inputs, in original order, with parsed ASTs */
|
|
3708
|
+
const sources = inputs.map((input, i) => ({
|
|
3709
|
+
...input,
|
|
3710
|
+
document: {},
|
|
3711
|
+
filename: input.filename || new URL(`virtual:${i}`)
|
|
3712
|
+
}));
|
|
3713
|
+
/** The sources array, indexed by filename */
|
|
3714
|
+
let sourceByFilename = {};
|
|
3715
|
+
/** Mapping of all final $ref resolutions. This will be used to generate the graph later. */
|
|
3716
|
+
let refMap = {};
|
|
3717
|
+
try {
|
|
3718
|
+
const result = await bundle(sources, {
|
|
3719
|
+
req,
|
|
3720
|
+
parse: transform ? transformer(transform) : void 0,
|
|
3721
|
+
yamlToMomoa
|
|
3722
|
+
});
|
|
3723
|
+
document = result.document;
|
|
3724
|
+
sourceByFilename = result.sources;
|
|
3725
|
+
refMap = result.refMap;
|
|
3726
|
+
for (const [filename, source] of Object.entries(result.sources)) {
|
|
3727
|
+
const i = sources.findIndex((s) => s.filename.href === filename);
|
|
3728
|
+
if (i === -1) sources.push(source);
|
|
3729
|
+
else {
|
|
3730
|
+
sources[i].src = source.src;
|
|
3731
|
+
sources[i].document = source.document;
|
|
3732
|
+
}
|
|
3733
|
+
}
|
|
3734
|
+
} catch (err) {
|
|
3735
|
+
let src = sources.find((s) => s.filename.href === err.filename)?.src;
|
|
3736
|
+
if (src && typeof src !== "string") src = JSON.stringify(src, void 0, 2);
|
|
3737
|
+
logger.error({
|
|
3738
|
+
...entry,
|
|
3739
|
+
continueOnError,
|
|
3740
|
+
message: err.message,
|
|
3741
|
+
node: err.node,
|
|
3742
|
+
src
|
|
3743
|
+
});
|
|
3744
|
+
}
|
|
3745
|
+
logger.debug({
|
|
3746
|
+
...entry,
|
|
3747
|
+
message: `JSON loaded`,
|
|
3748
|
+
timing: performance.now() - firstLoad
|
|
3749
|
+
});
|
|
3750
|
+
return {
|
|
3751
|
+
tokens: processTokens({
|
|
3752
|
+
filename: sources[0].filename,
|
|
3753
|
+
document,
|
|
3754
|
+
src: momoa.print(document, { indent: 2 })
|
|
3755
|
+
}, {
|
|
3756
|
+
config,
|
|
3757
|
+
logger,
|
|
3758
|
+
refMap,
|
|
3759
|
+
sources,
|
|
3760
|
+
sourceByFilename
|
|
3761
|
+
}),
|
|
3727
3762
|
sources
|
|
3728
3763
|
};
|
|
3729
3764
|
}
|
|
@@ -3740,7 +3775,9 @@ function transformer(transform) {
|
|
|
3740
3775
|
});
|
|
3741
3776
|
if (result) document = result;
|
|
3742
3777
|
}
|
|
3743
|
-
|
|
3778
|
+
const isResolver = isLikelyResolver(document);
|
|
3779
|
+
traverse(document, { enter(node, parent, rawPath) {
|
|
3780
|
+
const path = isResolver ? filterResolverPaths(rawPath) : rawPath;
|
|
3744
3781
|
if (node.type !== "Object" || !path.length) return;
|
|
3745
3782
|
const ctx = {
|
|
3746
3783
|
filename,
|
|
@@ -3775,21 +3812,33 @@ function transformer(transform) {
|
|
|
3775
3812
|
/** Parse */
|
|
3776
3813
|
async function parse(_input, { logger = new Logger(), req = defaultReq, skipLint = false, config = {}, continueOnError = false, yamlToMomoa, transform } = {}) {
|
|
3777
3814
|
const inputs = Array.isArray(_input) ? _input : [_input];
|
|
3815
|
+
let tokens = {};
|
|
3816
|
+
let resolver;
|
|
3817
|
+
let sources = [];
|
|
3778
3818
|
const totalStart = performance.now();
|
|
3779
|
-
const
|
|
3819
|
+
const initStart = performance.now();
|
|
3820
|
+
const resolverResult = await loadResolver(inputs, {
|
|
3821
|
+
config,
|
|
3780
3822
|
logger,
|
|
3781
3823
|
req,
|
|
3782
3824
|
yamlToMomoa
|
|
3783
3825
|
});
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3826
|
+
if (resolverResult.resolver) {
|
|
3827
|
+
tokens = resolverResult.tokens;
|
|
3828
|
+
sources = resolverResult.sources;
|
|
3829
|
+
resolver = resolverResult.resolver;
|
|
3830
|
+
} else {
|
|
3831
|
+
const tokenResult = await loadSources(inputs, {
|
|
3832
|
+
req,
|
|
3833
|
+
logger,
|
|
3834
|
+
config,
|
|
3835
|
+
continueOnError,
|
|
3836
|
+
yamlToMomoa,
|
|
3837
|
+
transform
|
|
3838
|
+
});
|
|
3839
|
+
tokens = tokenResult.tokens;
|
|
3840
|
+
sources = tokenResult.sources;
|
|
3841
|
+
}
|
|
3793
3842
|
logger.debug({
|
|
3794
3843
|
message: "Loaded tokens",
|
|
3795
3844
|
group: "parser",
|
|
@@ -3811,6 +3860,19 @@ async function parse(_input, { logger = new Logger(), req = defaultReq, skipLint
|
|
|
3811
3860
|
timing: performance.now() - lintStart
|
|
3812
3861
|
});
|
|
3813
3862
|
}
|
|
3863
|
+
const resolverTiming = performance.now();
|
|
3864
|
+
const finalResolver = resolver || await createSyntheticResolver(tokens, {
|
|
3865
|
+
config,
|
|
3866
|
+
logger,
|
|
3867
|
+
req,
|
|
3868
|
+
sources
|
|
3869
|
+
});
|
|
3870
|
+
logger.debug({
|
|
3871
|
+
message: "Resolver finalized",
|
|
3872
|
+
group: "parser",
|
|
3873
|
+
label: "core",
|
|
3874
|
+
timing: performance.now() - resolverTiming
|
|
3875
|
+
});
|
|
3814
3876
|
logger.debug({
|
|
3815
3877
|
message: "Finish all parser tasks",
|
|
3816
3878
|
group: "parser",
|
|
@@ -3827,7 +3889,7 @@ async function parse(_input, { logger = new Logger(), req = defaultReq, skipLint
|
|
|
3827
3889
|
return {
|
|
3828
3890
|
tokens,
|
|
3829
3891
|
sources,
|
|
3830
|
-
resolver
|
|
3892
|
+
resolver: finalResolver
|
|
3831
3893
|
};
|
|
3832
3894
|
}
|
|
3833
3895
|
let fs;
|