@terrazzo/parser 2.0.0-alpha.3 → 2.0.0-alpha.4

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.js CHANGED
@@ -1,42 +1,11 @@
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 { bundle, getObjMember, getObjMembers, isPure$ref, maybeRawJSON, parseRef, replaceNode, traverseAsync } from "@terrazzo/json-schema-tools";
7
+ import { bundle, getObjMember, getObjMembers, isPure$ref, maybeRawJSON, parseRef, replaceNode, traverse } from "@terrazzo/json-schema-tools";
9
8
 
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
9
  //#region src/lib/code-frame.ts
41
10
  /**
42
11
  * Extract what lines should be marked and highlighted.
@@ -2489,40 +2458,8 @@ async function lintRunner({ tokens, filename, config = {}, sources, logger }) {
2489
2458
  });
2490
2459
  }
2491
2460
 
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
2461
  //#endregion
2524
2462
  //#region src/lib/momoa.ts
2525
- var import_fast_deep_equal = /* @__PURE__ */ __toESM(require_fast_deep_equal(), 1);
2526
2463
  /** Momoa’s default parser, with preferred settings. */
2527
2464
  function toMomoa(srcRaw) {
2528
2465
  return momoa.parse(typeof srcRaw === "string" ? srcRaw : JSON.stringify(srcRaw, void 0, 2), {
@@ -2532,6 +2469,32 @@ function toMomoa(srcRaw) {
2532
2469
  });
2533
2470
  }
2534
2471
 
2472
+ //#endregion
2473
+ //#region src/lib/resolver-utils.ts
2474
+ /**
2475
+ * If tokens are found inside a resolver, strip out the resolver paths (don’t
2476
+ * include "sets"/"modifiers" in the token ID etc.)
2477
+ */
2478
+ function filterResolverPaths(path) {
2479
+ switch (path[0]) {
2480
+ case "sets": return path.slice(4);
2481
+ case "modifiers": return path.slice(5);
2482
+ case "resolutionOrder":
2483
+ switch (path[2]) {
2484
+ case "sources": return path.slice(4);
2485
+ case "contexts": return path.slice(5);
2486
+ }
2487
+ break;
2488
+ }
2489
+ return path;
2490
+ }
2491
+ /**
2492
+ * Make a deterministic string from an object
2493
+ */
2494
+ function makeInputKey(input) {
2495
+ return JSON.stringify(Object.fromEntries(Object.entries(input).sort((a, b) => a[0].localeCompare(b[0], "en-us", { numeric: true }))));
2496
+ }
2497
+
2535
2498
  //#endregion
2536
2499
  //#region src/resolver/validate.ts
2537
2500
  /**
@@ -2910,204 +2873,6 @@ function validateModifier(node, isInline = false, { src }) {
2910
2873
  return errors;
2911
2874
  }
2912
2875
 
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
2876
  //#endregion
3112
2877
  //#region src/parse/normalize.ts
3113
2878
  /**
@@ -3234,7 +2999,7 @@ function tokenFromNode(node, { groups, path, source, ignore }) {
3234
2999
  const group = groups[`#/${path.slice(0, -1).join("/")}`];
3235
3000
  if (group?.tokens && !group.tokens.includes(id)) group.tokens.push(id);
3236
3001
  const nodeSource = {
3237
- filename: source.filename?.href,
3002
+ filename: source.filename.href,
3238
3003
  node
3239
3004
  };
3240
3005
  const token = {
@@ -3579,68 +3344,20 @@ function resolveAliases(tokens, { logger, refMap, sources }) {
3579
3344
  }
3580
3345
 
3581
3346
  //#endregion
3582
- //#region src/parse/load.ts
3583
- /** Load from multiple entries, while resolving remote files */
3584
- async function loadSources(inputs, { config, logger, req, continueOnError, yamlToMomoa, transform }) {
3347
+ //#region src/parse/process.ts
3348
+ function processTokens(rootSource, { config, logger, sourceByFilename, refMap }) {
3585
3349
  const entry = {
3586
3350
  group: "parser",
3587
3351
  label: "init"
3588
3352
  };
3589
- const firstLoad = performance.now();
3590
- let document = {};
3591
- /** The original user inputs, in original order, with parsed ASTs */
3592
- const sources = inputs.map((input, i) => ({
3593
- ...input,
3594
- document: {},
3595
- filename: input.filename || new URL(`virtual:${i}`)
3596
- }));
3597
- /** The sources array, indexed by filename */
3598
- let sourceByFilename = {};
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
3353
  const firstPass = performance.now();
3639
3354
  const tokens = {};
3640
3355
  const tokenIDs = [];
3641
3356
  const groups = {};
3642
- await traverseAsync(document, { async enter(node, _parent, path) {
3357
+ const isResolver = isLikelyResolver(rootSource.document);
3358
+ traverse(rootSource.document, { enter(node, _parent, rawPath) {
3643
3359
  if (node.type !== "Object") return;
3360
+ const path = isResolver ? filterResolverPaths(rawPath) : rawPath;
3644
3361
  groupFromNode(node, {
3645
3362
  path,
3646
3363
  groups
@@ -3649,10 +3366,7 @@ async function loadSources(inputs, { config, logger, req, continueOnError, yamlT
3649
3366
  groups,
3650
3367
  ignore: config.ignore,
3651
3368
  path,
3652
- source: {
3653
- src: artificialSource,
3654
- document
3655
- }
3369
+ source: rootSource
3656
3370
  });
3657
3371
  if (token) {
3658
3372
  tokenIDs.push(token.jsonID);
@@ -3665,7 +3379,7 @@ async function loadSources(inputs, { config, logger, req, continueOnError, yamlT
3665
3379
  timing: performance.now() - firstPass
3666
3380
  });
3667
3381
  const secondPass = performance.now();
3668
- for (const source of Object.values(sourceByFilename)) await traverseAsync(source.document, { async enter(node, _parent, path) {
3382
+ for (const source of Object.values(sourceByFilename)) traverse(source.document, { enter(node, _parent, path) {
3669
3383
  if (node.type !== "Object") return;
3670
3384
  const tokenRawValues = tokenRawValuesFromNode(node, {
3671
3385
  filename: source.filename.href,
@@ -3722,17 +3436,386 @@ async function loadSources(inputs, { config, logger, req, continueOnError, yamlT
3722
3436
  tokensSorted[id] = tokens[path];
3723
3437
  }
3724
3438
  for (const group of Object.values(groups)) group.tokens.sort((a, b) => a.localeCompare(b, "en-us", { numeric: true }));
3725
- return {
3726
- tokens: tokensSorted,
3727
- sources
3728
- };
3439
+ return tokensSorted;
3729
3440
  }
3730
- function transformer(transform) {
3731
- return async (src, filename) => {
3732
- let document = toMomoa(src);
3733
- let lastPath = "#/";
3734
- let last$type;
3735
- if (transform.root) {
3441
+
3442
+ //#endregion
3443
+ //#region src/resolver/normalize.ts
3444
+ /** Normalize resolver (assuming it’s been validated) */
3445
+ async function normalizeResolver(node, { filename, req, src, logger, yamlToMomoa }) {
3446
+ const resolverSource = momoa.evaluate(node);
3447
+ const resolutionOrder = getObjMember(node.body, "resolutionOrder");
3448
+ return {
3449
+ name: resolverSource.name,
3450
+ version: resolverSource.version,
3451
+ description: resolverSource.description,
3452
+ sets: resolverSource.sets,
3453
+ modifiers: resolverSource.modifiers,
3454
+ resolutionOrder: await Promise.all(resolutionOrder.elements.map(async (element, i) => {
3455
+ const layer = element.value;
3456
+ const members = getObjMembers(layer);
3457
+ let item = layer;
3458
+ if (members.$ref) {
3459
+ const entry = {
3460
+ group: "parser",
3461
+ label: "init",
3462
+ node: members.$ref,
3463
+ src
3464
+ };
3465
+ const { url, subpath } = parseRef(members.$ref.value);
3466
+ if (url === ".") if (!subpath?.[0]) logger.error({
3467
+ ...entry,
3468
+ message: "$ref can’t refer to the root document."
3469
+ });
3470
+ else if (subpath[0] !== "sets" && subpath[0] !== "modifiers") logger.error({
3471
+ ...entry,
3472
+ message: "Local $ref in resolutionOrder must point to either #/sets/[set] or #/modifiers/[modifiers]."
3473
+ });
3474
+ else {
3475
+ const resolvedItem = resolverSource[subpath[0]]?.[subpath[1]];
3476
+ if (!resolvedItem) logger.error({
3477
+ ...entry,
3478
+ message: "Invalid $ref"
3479
+ });
3480
+ else item = {
3481
+ type: subpath[0] === "sets" ? "set" : "modifier",
3482
+ name: subpath[1],
3483
+ ...resolvedItem
3484
+ };
3485
+ }
3486
+ else {
3487
+ const result = await bundle([{
3488
+ filename: new URL(url, filename),
3489
+ src: resolverSource.resolutionOrder[i]
3490
+ }], {
3491
+ req,
3492
+ yamlToMomoa
3493
+ });
3494
+ if (result.document.body.type === "Object") {
3495
+ const type = getObjMember(result.document.body, "type");
3496
+ if (type?.type === "String" && type.value === "set") {
3497
+ validateSet(result.document.body, true, src);
3498
+ item = momoa.evaluate(result.document.body);
3499
+ } else if (type?.type === "String" && type.value === "modifier") {
3500
+ validateModifier(result.document.body, true, src);
3501
+ item = momoa.evaluate(result.document.body);
3502
+ }
3503
+ }
3504
+ logger.error({
3505
+ ...entry,
3506
+ message: "$ref did not resolve to a valid Set or Modifier."
3507
+ });
3508
+ }
3509
+ }
3510
+ const finalResult = await bundle([{
3511
+ filename,
3512
+ src: item
3513
+ }], {
3514
+ req,
3515
+ yamlToMomoa
3516
+ });
3517
+ return momoa.evaluate(finalResult.document.body);
3518
+ })),
3519
+ _source: {
3520
+ filename,
3521
+ node
3522
+ }
3523
+ };
3524
+ }
3525
+
3526
+ //#endregion
3527
+ //#region src/resolver/load.ts
3528
+ /** Quick-parse input sources and find a resolver */
3529
+ async function loadResolver(inputs, { config, logger, req, yamlToMomoa }) {
3530
+ let resolverDoc;
3531
+ let tokens = {};
3532
+ const entry = {
3533
+ group: "parser",
3534
+ label: "init"
3535
+ };
3536
+ for (const input of inputs) {
3537
+ let document;
3538
+ if (typeof input.src === "string") if (maybeRawJSON(input.src)) document = toMomoa(input.src);
3539
+ else if (yamlToMomoa) document = yamlToMomoa(input.src);
3540
+ else logger.error({
3541
+ ...entry,
3542
+ message: `Install yaml-to-momoa package to parse YAML, and pass in as option, e.g.:
3543
+
3544
+ import { bundle } from '@terrazzo/json-schema-tools';
3545
+ import yamlToMomoa from 'yaml-to-momoa';
3546
+
3547
+ bundle(yamlString, { yamlToMomoa });`
3548
+ });
3549
+ else if (input.src && typeof input.src === "object") document = toMomoa(JSON.stringify(input.src, void 0, 2));
3550
+ else logger.error({
3551
+ ...entry,
3552
+ message: `Could not parse ${input.filename}. Is this valid JSON or YAML?`
3553
+ });
3554
+ if (!document || !isLikelyResolver(document)) continue;
3555
+ if (inputs.length > 1) logger.error({
3556
+ ...entry,
3557
+ message: `Resolver must be the only input, found ${inputs.length} sources.`
3558
+ });
3559
+ resolverDoc = document;
3560
+ break;
3561
+ }
3562
+ let resolver;
3563
+ if (resolverDoc) {
3564
+ validateResolver(resolverDoc, {
3565
+ logger,
3566
+ src: inputs[0].src
3567
+ });
3568
+ resolver = createResolver(await normalizeResolver(resolverDoc, {
3569
+ filename: inputs[0].filename,
3570
+ logger,
3571
+ req,
3572
+ src: inputs[0].src,
3573
+ yamlToMomoa
3574
+ }), {
3575
+ config,
3576
+ logger,
3577
+ sources: [{
3578
+ ...inputs[0],
3579
+ document: resolverDoc
3580
+ }]
3581
+ });
3582
+ const firstInput = {};
3583
+ for (const m of resolver.source.resolutionOrder) {
3584
+ if (m.type !== "modifier") continue;
3585
+ firstInput[m.name] = typeof m.default === "string" ? m.default : Object.keys(m.contexts)[0];
3586
+ }
3587
+ tokens = resolver.apply(firstInput);
3588
+ }
3589
+ return {
3590
+ resolver,
3591
+ tokens,
3592
+ sources: [{
3593
+ ...inputs[0],
3594
+ document: resolverDoc
3595
+ }]
3596
+ };
3597
+ }
3598
+ /** Create an interface to resolve permutations */
3599
+ function createResolver(resolverSource, { config, logger, sources }) {
3600
+ const inputDefaults = {};
3601
+ const validContexts = {};
3602
+ const allPermutations = [];
3603
+ const resolverCache = {};
3604
+ for (const m of resolverSource.resolutionOrder) if (m.type === "modifier") {
3605
+ if (typeof m.default === "string") inputDefaults[m.name] = m.default;
3606
+ validContexts[m.name] = Object.keys(m.contexts);
3607
+ }
3608
+ return {
3609
+ apply(inputRaw) {
3610
+ let tokensRaw = {};
3611
+ const input = {
3612
+ ...inputDefaults,
3613
+ ...inputRaw
3614
+ };
3615
+ const inputKey = makeInputKey(input);
3616
+ if (resolverCache[inputKey]) return resolverCache[inputKey];
3617
+ for (const item of resolverSource.resolutionOrder) switch (item.type) {
3618
+ case "set":
3619
+ for (const s of item.sources) tokensRaw = merge(tokensRaw, s);
3620
+ break;
3621
+ case "modifier": {
3622
+ const context = input[item.name];
3623
+ const sources$1 = item.contexts[context];
3624
+ if (!sources$1) logger.error({
3625
+ group: "parser",
3626
+ label: "resolver",
3627
+ message: `Modifier ${item.name} has no context ${JSON.stringify(context)}.`
3628
+ });
3629
+ for (const s of sources$1 ?? []) tokensRaw = merge(tokensRaw, s);
3630
+ break;
3631
+ }
3632
+ }
3633
+ const src = JSON.stringify(tokensRaw, void 0, 2);
3634
+ const tokens = processTokens({
3635
+ filename: resolverSource._source.filename,
3636
+ document: toMomoa(src),
3637
+ src
3638
+ }, {
3639
+ config,
3640
+ logger,
3641
+ sourceByFilename: {},
3642
+ refMap: {},
3643
+ sources
3644
+ });
3645
+ resolverCache[inputKey] = tokens;
3646
+ return tokens;
3647
+ },
3648
+ source: resolverSource,
3649
+ listPermutations() {
3650
+ if (!allPermutations.length) allPermutations.push(...calculatePermutations(Object.entries(validContexts)));
3651
+ return allPermutations;
3652
+ },
3653
+ isValidInput(input) {
3654
+ if (!input || typeof input !== "object") logger.error({
3655
+ group: "parser",
3656
+ label: "resolver",
3657
+ message: `Invalid input: ${JSON.stringify(input)}.`
3658
+ });
3659
+ if (!Object.keys(input).every((k) => k in validContexts)) return false;
3660
+ for (const [name, contexts] of Object.entries(validContexts)) if (name in input) {
3661
+ if (!contexts.includes(input[name])) return false;
3662
+ } else if (!(name in inputDefaults)) return false;
3663
+ return true;
3664
+ }
3665
+ };
3666
+ }
3667
+ /** Calculate all permutations */
3668
+ function calculatePermutations(options) {
3669
+ const permutationCount = [1];
3670
+ for (const [_name, contexts] of options) permutationCount.push(contexts.length * (permutationCount.at(-1) || 1));
3671
+ const permutations = [];
3672
+ for (let i = 0; i < permutationCount.at(-1); i++) {
3673
+ const input = {};
3674
+ for (let j = 0; j < options.length; j++) {
3675
+ const [name, contexts] = options[j];
3676
+ input[name] = contexts[Math.floor(i / permutationCount[j]) % contexts.length];
3677
+ }
3678
+ permutations.push(input);
3679
+ }
3680
+ return permutations.length ? permutations : [{}];
3681
+ }
3682
+
3683
+ //#endregion
3684
+ //#region src/resolver/create-synthetic-resolver.ts
3685
+ /**
3686
+ * Interop layer upgrading legacy Terrazzo modes to resolvers
3687
+ */
3688
+ async function createSyntheticResolver(tokens, { config, logger, req, sources }) {
3689
+ const contexts = {};
3690
+ for (const token of Object.values(tokens)) for (const [mode, value] of Object.entries(token.mode)) {
3691
+ if (mode === ".") continue;
3692
+ if (!(mode in contexts)) contexts[mode] = [{}];
3693
+ addToken(contexts[mode][0], {
3694
+ ...token,
3695
+ $value: value.$value
3696
+ }, { logger });
3697
+ }
3698
+ const src = JSON.stringify({
3699
+ name: "Terrazzo",
3700
+ version: "2025.10",
3701
+ resolutionOrder: [{ $ref: "#/sets/allTokens" }, { $ref: "#/modifiers/tzMode" }],
3702
+ sets: { allTokens: { sources: [simpleFlatten(tokens, { logger })] } },
3703
+ modifiers: { tzMode: {
3704
+ description: "Automatically built from $extensions.mode",
3705
+ contexts
3706
+ } }
3707
+ }, void 0, 2);
3708
+ return createResolver(await normalizeResolver(momoa.parse(src), {
3709
+ filename: new URL("file:///virtual:resolver.json"),
3710
+ logger,
3711
+ req,
3712
+ src
3713
+ }), {
3714
+ config,
3715
+ logger,
3716
+ sources
3717
+ });
3718
+ }
3719
+ /** Add a normalized token back into an arbitrary, hierarchial structure */
3720
+ function addToken(structure, token, { logger }) {
3721
+ let node = structure;
3722
+ const parts = token.id.split(".");
3723
+ const localID = parts.pop();
3724
+ for (const part of parts) {
3725
+ if (!(part in node)) node[part] = {};
3726
+ node = node[part];
3727
+ }
3728
+ if (localID in node) logger.error({
3729
+ group: "parser",
3730
+ label: "resolver",
3731
+ message: `${localID} already exists!`
3732
+ });
3733
+ node[localID] = {
3734
+ $type: token.$type,
3735
+ $value: token.$value
3736
+ };
3737
+ }
3738
+ /** Downconvert normalized tokens back into a simplified, hierarchial shape. This is extremely lossy, and only done to build a resolver. */
3739
+ function simpleFlatten(tokens, { logger }) {
3740
+ const group = {};
3741
+ for (const token of Object.values(tokens)) addToken(group, token, { logger });
3742
+ return group;
3743
+ }
3744
+
3745
+ //#endregion
3746
+ //#region src/parse/load.ts
3747
+ /** Load from multiple entries, while resolving remote files */
3748
+ async function loadSources(inputs, { config, logger, req, continueOnError, yamlToMomoa, transform }) {
3749
+ const entry = {
3750
+ group: "parser",
3751
+ label: "init"
3752
+ };
3753
+ const firstLoad = performance.now();
3754
+ let document = {};
3755
+ /** The original user inputs, in original order, with parsed ASTs */
3756
+ const sources = inputs.map((input, i) => ({
3757
+ ...input,
3758
+ document: {},
3759
+ filename: input.filename || new URL(`virtual:${i}`)
3760
+ }));
3761
+ /** The sources array, indexed by filename */
3762
+ let sourceByFilename = {};
3763
+ /** Mapping of all final $ref resolutions. This will be used to generate the graph later. */
3764
+ let refMap = {};
3765
+ try {
3766
+ const result = await bundle(sources, {
3767
+ req,
3768
+ parse: transform ? transformer(transform) : void 0,
3769
+ yamlToMomoa
3770
+ });
3771
+ document = result.document;
3772
+ sourceByFilename = result.sources;
3773
+ refMap = result.refMap;
3774
+ for (const [filename, source] of Object.entries(result.sources)) {
3775
+ const i = sources.findIndex((s) => s.filename.href === filename);
3776
+ if (i === -1) sources.push(source);
3777
+ else {
3778
+ sources[i].src = source.src;
3779
+ sources[i].document = source.document;
3780
+ }
3781
+ }
3782
+ } catch (err) {
3783
+ let src = sources.find((s) => s.filename.href === err.filename)?.src;
3784
+ if (src && typeof src !== "string") src = JSON.stringify(src, void 0, 2);
3785
+ logger.error({
3786
+ ...entry,
3787
+ continueOnError,
3788
+ message: err.message,
3789
+ node: err.node,
3790
+ src
3791
+ });
3792
+ }
3793
+ logger.debug({
3794
+ ...entry,
3795
+ message: `JSON loaded`,
3796
+ timing: performance.now() - firstLoad
3797
+ });
3798
+ return {
3799
+ tokens: processTokens({
3800
+ filename: sources[0].filename,
3801
+ document,
3802
+ src: momoa.print(document, { indent: 2 })
3803
+ }, {
3804
+ config,
3805
+ logger,
3806
+ refMap,
3807
+ sources,
3808
+ sourceByFilename
3809
+ }),
3810
+ sources
3811
+ };
3812
+ }
3813
+ function transformer(transform) {
3814
+ return async (src, filename) => {
3815
+ let document = toMomoa(src);
3816
+ let lastPath = "#/";
3817
+ let last$type;
3818
+ if (transform.root) {
3736
3819
  const result = transform.root(document, {
3737
3820
  filename,
3738
3821
  parent: void 0,
@@ -3740,7 +3823,9 @@ function transformer(transform) {
3740
3823
  });
3741
3824
  if (result) document = result;
3742
3825
  }
3743
- await traverseAsync(document, { async enter(node, parent, path) {
3826
+ const isResolver = isLikelyResolver(document);
3827
+ traverse(document, { enter(node, parent, rawPath) {
3828
+ const path = isResolver ? filterResolverPaths(rawPath) : rawPath;
3744
3829
  if (node.type !== "Object" || !path.length) return;
3745
3830
  const ctx = {
3746
3831
  filename,
@@ -3775,21 +3860,33 @@ function transformer(transform) {
3775
3860
  /** Parse */
3776
3861
  async function parse(_input, { logger = new Logger(), req = defaultReq, skipLint = false, config = {}, continueOnError = false, yamlToMomoa, transform } = {}) {
3777
3862
  const inputs = Array.isArray(_input) ? _input : [_input];
3863
+ let tokens = {};
3864
+ let resolver;
3865
+ let sources = [];
3778
3866
  const totalStart = performance.now();
3779
- const resolver = await loadResolver(inputs, {
3867
+ const initStart = performance.now();
3868
+ const resolverResult = await loadResolver(inputs, {
3869
+ config,
3780
3870
  logger,
3781
3871
  req,
3782
3872
  yamlToMomoa
3783
3873
  });
3784
- const initStart = performance.now();
3785
- const { tokens, sources } = await loadSources(inputs, {
3786
- req,
3787
- logger,
3788
- config,
3789
- continueOnError,
3790
- yamlToMomoa,
3791
- transform
3792
- });
3874
+ if (resolverResult.resolver) {
3875
+ tokens = resolverResult.tokens;
3876
+ sources = resolverResult.sources;
3877
+ resolver = resolverResult.resolver;
3878
+ } else {
3879
+ const tokenResult = await loadSources(inputs, {
3880
+ req,
3881
+ logger,
3882
+ config,
3883
+ continueOnError,
3884
+ yamlToMomoa,
3885
+ transform
3886
+ });
3887
+ tokens = tokenResult.tokens;
3888
+ sources = tokenResult.sources;
3889
+ }
3793
3890
  logger.debug({
3794
3891
  message: "Loaded tokens",
3795
3892
  group: "parser",
@@ -3827,7 +3924,12 @@ async function parse(_input, { logger = new Logger(), req = defaultReq, skipLint
3827
3924
  return {
3828
3925
  tokens,
3829
3926
  sources,
3830
- resolver
3927
+ resolver: resolver || await createSyntheticResolver(tokens, {
3928
+ config,
3929
+ logger,
3930
+ req,
3931
+ sources
3932
+ })
3831
3933
  };
3832
3934
  }
3833
3935
  let fs;