@vizejs/vite-plugin-musea 0.0.1-alpha.83 → 0.0.1-alpha.84

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 CHANGED
@@ -23,6 +23,9 @@ interface DesignToken {
23
23
  type?: string;
24
24
  description?: string;
25
25
  attributes?: Record<string, unknown>;
26
+ $tier?: "primitive" | "semantic";
27
+ $reference?: string;
28
+ $resolvedValue?: string | number;
26
29
  }
27
30
  /**
28
31
  * Token category (e.g., colors, spacing, typography).
@@ -74,6 +77,18 @@ type TokenTransform = (token: DesignToken, path: string[]) => DesignToken;
74
77
  * Parse Style Dictionary tokens file.
75
78
  */
76
79
  declare function parseTokens(tokensPath: string): Promise<TokenCategory[]>;
80
+ /**
81
+ * Flatten nested categories into a flat map keyed by dot-path.
82
+ */
83
+ declare function buildTokenMap(categories: TokenCategory[], prefix?: string[]): Record<string, DesignToken>;
84
+ /**
85
+ * Resolve references in categories, setting $tier, $reference, and $resolvedValue.
86
+ */
87
+ declare function resolveReferences(categories: TokenCategory[], tokenMap: Record<string, DesignToken>): void;
88
+ /**
89
+ * Read raw JSON token file.
90
+ */
91
+
77
92
  /**
78
93
  * Generate HTML documentation for tokens.
79
94
  */
@@ -85,13 +100,44 @@ declare function generateTokensMarkdown(categories: TokenCategory[]): string;
85
100
  /**
86
101
  * Style Dictionary plugin for Musea.
87
102
  */
88
- declare function processStyleDictionary(config: StyleDictionaryConfig): Promise<StyleDictionaryOutput>; //#endregion
103
+ declare function processStyleDictionary(config: StyleDictionaryConfig): Promise<StyleDictionaryOutput>;
104
+ interface TokenUsageMatch {
105
+ line: number;
106
+ lineContent: string;
107
+ property: string;
108
+ }
109
+ interface TokenUsageEntry {
110
+ artPath: string;
111
+ artTitle: string;
112
+ artCategory?: string;
113
+ matches: TokenUsageMatch[];
114
+ }
115
+ type TokenUsageMap = Record<string, TokenUsageEntry[]>;
116
+ /**
117
+ * Normalize a token value for comparison.
118
+ * - Lowercase, trim
119
+ * - Leading-zero: `.5rem` → `0.5rem`
120
+ * - Short hex: `#fff` → `#ffffff`
121
+ */
122
+
123
+ /**
124
+ * Scan art file sources for token value matches in `<style>` blocks.
125
+ */
126
+ declare function scanTokenUsage(artFiles: Map<string, {
127
+ path: string;
128
+ metadata: {
129
+ title: string;
130
+ category?: string;
131
+ };
132
+ }>, tokenMap: Record<string, DesignToken>): TokenUsageMap; //#endregion
89
133
  //#region src/index.d.ts
134
+
135
+ //# sourceMappingURL=style-dictionary.d.ts.map
90
136
  /**
91
137
  * Create Musea Vite plugin.
92
138
  */
93
139
  declare function musea(options?: MuseaOptions): Plugin[];
94
140
 
95
141
  //#endregion
96
- export { A11yOptions, A11yResult, A11ySummary, AnalysisApiResponse, ArtFileInfo, ArtMetadata, ArtVariant, AutogenOptions, AutogenOutput, CaptureConfig, CiConfig, ComparisonConfig, CsfOutput, DesignToken, GeneratedVariant, MuseaA11yRunner, MuseaOptions, MuseaTheme, MuseaThemeColors, MuseaVrtRunner, PaletteApiResponse, PropDefinition, StyleDictionaryConfig, StyleDictionaryOutput, TokenCategory, ViewportConfig, VrtOptions, VrtResult, VrtSummary, musea as default, generateArtFile, generateTokensHtml, generateTokensMarkdown, generateVrtJsonReport, generateVrtReport, musea, parseTokens, processStyleDictionary, writeArtFile };
142
+ export { A11yOptions, A11yResult, A11ySummary, AnalysisApiResponse, ArtFileInfo, ArtMetadata, ArtVariant, AutogenOptions, AutogenOutput, CaptureConfig, CiConfig, ComparisonConfig, CsfOutput, DesignToken, GeneratedVariant, MuseaA11yRunner, MuseaOptions, MuseaTheme, MuseaThemeColors, MuseaVrtRunner, PaletteApiResponse, PropDefinition, StyleDictionaryConfig, StyleDictionaryOutput, TokenCategory, TokenUsageMap, ViewportConfig, VrtOptions, VrtResult, VrtSummary, buildTokenMap, musea as default, generateArtFile, generateTokensHtml, generateTokensMarkdown, generateVrtJsonReport, generateVrtReport, musea, parseTokens, processStyleDictionary, resolveReferences, scanTokenUsage, writeArtFile };
97
143
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/style-dictionary.ts","../src/index.ts"],"sourcesContent":null,"mappings":";;;;;;;;;;;;;;;;;;;;UAWiB,WAAA;;;;eAIF;;AAJf;;;UAUiB,aAAA;EAAA,IAAA,EAAA,MAAA;EAAa,MAAA,EAEpB,MAFoB,CAAA,MAAA,EAEL,WAFK,CAAA;EAAA,aAEL,CAAA,EACP,aADO,EAAA;;;AACM;;UAMd,qBAAA;cACH;EADG,QAAA,EAAA;;;;EAYA,CAAA;;;;AA2BjB;AAA0B,UA3BT,qBAAA,CA2BS;EAAA;;AAAsD;;;;AAKhF;;EAAiC,YAA8B,CAAA,EAAA,MAAA,GAAA,MAAA,GAAA,UAAA;EAAa;AAAd;;;;EAgJ9C;;;eA3JD;AA6Rf;;;;AAsCsB,KA7TV,cAAA,GA6TgC,CAAA,KAAA,EA7TP,WA6TO,EAAA,IAAA,EAAA,MAAA,EAAA,EAAA,GA7TyB,WA6TzB;;;;AAEzC,iBA1TmB,WAAA,CA0TnB,UAAA,EAAA,MAAA,CAAA,EA1ToD,OA0TpD,CA1T4D,aA0T5D,EAAA,CAAA;AAAO;;;iBA1KM,kBAAA,aAA+B;;;;ACU/B,iBDwHA,sBAAA,CCxHK,UAAA,EDwH8B,aCxH9B,EAAA,CAAA,EAAA,MAAA;;;;AAAoC,iBD8JnC,sBAAA,CC9JmC,MAAA,ED+J/C,qBC/J+C,CAAA,EDgKtD,OChKsD,CDgK9C,qBChK8C,CAAA,CAAA;;AD5M1B;;;iBC4Mf,KAAA,WAAe,eAAoB"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/style-dictionary.ts","../src/index.ts"],"sourcesContent":null,"mappings":";;;;;;;;;;;;;;;;;;;;UAWiB,WAAA;;;;eAIF;;EAJE,UAAA,CAAA,EAAA,MAAW;;;;AAa5B;;AAEyB,UAFR,aAAA,CAEQ;EAAW,IAA1B,EAAA,MAAA;EAAM,MACE,EADR,MACQ,CAAA,MAAA,EADO,WACP,CAAA;EAAa,aAAA,CAAA,EAAb,aAAa,EAAA;;;;AAM/B;UAAiB,qBAAA;cACH;;IAWG,IAAA,EAAA,MAAA;;;;AA2BjB;;;;AAAgF,UA3B/D,qBAAA,CA2B+D;;;;EAK1D,UAAA,EAAA,MAAW;EAAA;;;AAA6B;;;;AAuJ9D;;EAA6B,SACf,CAAA,EAAA,MAAA;EAAa;;AAElB;eArKM;;;AAkMf;;AACc,KA7LF,cAAA,GA6LE,CAAA,KAAA,EA7LuB,WA6LvB,EAAA,IAAA,EAAA,MAAA,EAAA,EAAA,GA7LuD,WA6LvD;;;AACI;iBAzLI,WAAA,sBAAiC,QAAQ;;;AAsY/D;iBA/OgB,aAAA,aACF,qCAEX,eAAe;;;AA8WlB;iBAjVgB,iBAAA,aACF,2BACF,eAAe;;;AAqX3B;;;;;iBAxKgB,kBAAA,aAA+B;ACjO/C;;;AAAmD,iBDmWnC,sBAAA,CCnWmC,UAAA,EDmWA,aCnWA,EAAA,CAAA,EAAA,MAAA;AAAM;;;iBDyYnC,sBAAA,SACZ,wBACP,QAAQ;UA6CM,eAAA;;;;;UAMA,eAAA;;;;WAIN;;KAGC,aAAA,GAAgB,eAAe;;;;;;;;;;;iBAgC3B,cAAA,WACJ;;;;;;cACA,eAAe,eACxB;;;;AA/rB4B;;;iBCuNf,KAAA,WAAe,eAAoB"}
package/dist/index.js CHANGED
@@ -92,12 +92,15 @@ function isTokenValue(value) {
92
92
  * Normalize token to DesignToken interface.
93
93
  */
94
94
  function normalizeToken(raw) {
95
- return {
95
+ const token = {
96
96
  value: raw.value,
97
97
  type: raw.type,
98
98
  description: raw.description,
99
99
  attributes: raw.attributes
100
100
  };
101
+ if (raw.$tier === "primitive" || raw.$tier === "semantic") token.$tier = raw.$tier;
102
+ if (typeof raw.$reference === "string") token.$reference = raw.$reference;
103
+ return token;
101
104
  }
102
105
  /**
103
106
  * Format category name for display.
@@ -106,6 +109,168 @@ function formatCategoryName(name) {
106
109
  return name.replace(/[-_]/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
107
110
  }
108
111
  /**
112
+ * Flatten nested categories into a flat map keyed by dot-path.
113
+ */
114
+ function buildTokenMap(categories, prefix = []) {
115
+ const map = {};
116
+ for (const cat of categories) {
117
+ const catKey = cat.name.toLowerCase().replace(/\s+/g, "-");
118
+ const catPath = [...prefix, catKey];
119
+ for (const [name, token] of Object.entries(cat.tokens)) {
120
+ const dotPath = [...catPath, name].join(".");
121
+ map[dotPath] = token;
122
+ }
123
+ if (cat.subcategories) {
124
+ const subMap = buildTokenMap(cat.subcategories, catPath);
125
+ Object.assign(map, subMap);
126
+ }
127
+ }
128
+ return map;
129
+ }
130
+ const REFERENCE_PATTERN = /^\{(.+)\}$/;
131
+ const MAX_RESOLVE_DEPTH = 10;
132
+ /**
133
+ * Resolve references in categories, setting $tier, $reference, and $resolvedValue.
134
+ */
135
+ function resolveReferences(categories, tokenMap) {
136
+ for (const cat of categories) {
137
+ for (const token of Object.values(cat.tokens)) resolveTokenReference(token, tokenMap);
138
+ if (cat.subcategories) resolveReferences(cat.subcategories, tokenMap);
139
+ }
140
+ }
141
+ function resolveTokenReference(token, tokenMap) {
142
+ if (typeof token.value === "string") {
143
+ const match = token.value.match(REFERENCE_PATTERN);
144
+ if (match) {
145
+ token.$tier = token.$tier ?? "semantic";
146
+ token.$reference = match[1];
147
+ token.$resolvedValue = resolveValue(match[1], tokenMap, 0, new Set());
148
+ return;
149
+ }
150
+ }
151
+ token.$tier = token.$tier ?? "primitive";
152
+ }
153
+ function resolveValue(ref, tokenMap, depth, visited) {
154
+ if (depth >= MAX_RESOLVE_DEPTH || visited.has(ref)) return void 0;
155
+ visited.add(ref);
156
+ const target = tokenMap[ref];
157
+ if (!target) return void 0;
158
+ if (typeof target.value === "string") {
159
+ const match = target.value.match(REFERENCE_PATTERN);
160
+ if (match) return resolveValue(match[1], tokenMap, depth + 1, visited);
161
+ }
162
+ return target.value;
163
+ }
164
+ /**
165
+ * Read raw JSON token file.
166
+ */
167
+ async function readRawTokenFile(tokensPath) {
168
+ const content = await fs.promises.readFile(tokensPath, "utf-8");
169
+ return JSON.parse(content);
170
+ }
171
+ /**
172
+ * Write raw JSON token file atomically (write tmp, rename).
173
+ */
174
+ async function writeRawTokenFile(tokensPath, data) {
175
+ const tmpPath = tokensPath + ".tmp";
176
+ await fs.promises.writeFile(tmpPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
177
+ await fs.promises.rename(tmpPath, tokensPath);
178
+ }
179
+ /**
180
+ * Set a token at a dot-separated path in the raw JSON structure.
181
+ */
182
+ function setTokenAtPath(data, dotPath, token) {
183
+ const parts = dotPath.split(".");
184
+ let current = data;
185
+ for (let i = 0; i < parts.length - 1; i++) {
186
+ const key = parts[i];
187
+ if (typeof current[key] !== "object" || current[key] === null) current[key] = {};
188
+ current = current[key];
189
+ }
190
+ const leafKey = parts[parts.length - 1];
191
+ const raw = { value: token.value };
192
+ if (token.type) raw.type = token.type;
193
+ if (token.description) raw.description = token.description;
194
+ if (token.$tier) raw.$tier = token.$tier;
195
+ if (token.$reference) raw.$reference = token.$reference;
196
+ if (token.attributes) raw.attributes = token.attributes;
197
+ current[leafKey] = raw;
198
+ }
199
+ /**
200
+ * Delete a token at a dot-separated path, cleaning empty parents.
201
+ */
202
+ function deleteTokenAtPath(data, dotPath) {
203
+ const parts = dotPath.split(".");
204
+ const parents = [];
205
+ let current = data;
206
+ for (let i = 0; i < parts.length - 1; i++) {
207
+ const key = parts[i];
208
+ if (typeof current[key] !== "object" || current[key] === null) return false;
209
+ parents.push({
210
+ obj: current,
211
+ key
212
+ });
213
+ current = current[key];
214
+ }
215
+ const leafKey = parts[parts.length - 1];
216
+ if (!(leafKey in current)) return false;
217
+ delete current[leafKey];
218
+ for (let i = parents.length - 1; i >= 0; i--) {
219
+ const { obj, key } = parents[i];
220
+ const child = obj[key];
221
+ if (Object.keys(child).length === 0) delete obj[key];
222
+ else break;
223
+ }
224
+ return true;
225
+ }
226
+ /**
227
+ * Validate that a semantic reference points to an existing token and has no cycles.
228
+ */
229
+ function validateSemanticReference(tokenMap, reference, selfPath) {
230
+ if (!tokenMap[reference]) return {
231
+ valid: false,
232
+ error: `Reference target "${reference}" does not exist`
233
+ };
234
+ const visited = new Set();
235
+ if (selfPath) visited.add(selfPath);
236
+ let current = reference;
237
+ let depth = 0;
238
+ while (depth < MAX_RESOLVE_DEPTH) {
239
+ if (visited.has(current)) return {
240
+ valid: false,
241
+ error: `Circular reference detected at "${current}"`
242
+ };
243
+ visited.add(current);
244
+ const target = tokenMap[current];
245
+ if (!target) break;
246
+ if (typeof target.value === "string") {
247
+ const match = target.value.match(REFERENCE_PATTERN);
248
+ if (match) {
249
+ current = match[1];
250
+ depth++;
251
+ continue;
252
+ }
253
+ }
254
+ break;
255
+ }
256
+ if (depth >= MAX_RESOLVE_DEPTH) return {
257
+ valid: false,
258
+ error: "Reference chain too deep (max 10)"
259
+ };
260
+ return { valid: true };
261
+ }
262
+ /**
263
+ * Find all tokens that reference the given path.
264
+ */
265
+ function findDependentTokens(tokenMap, targetPath) {
266
+ const dependents = [];
267
+ for (const [path$1, token] of Object.entries(tokenMap)) if (typeof token.value === "string") {
268
+ const match = token.value.match(REFERENCE_PATTERN);
269
+ if (match && match[1] === targetPath) dependents.push(path$1);
270
+ }
271
+ return dependents;
272
+ }
273
+ /**
109
274
  * Generate HTML documentation for tokens.
110
275
  */
111
276
  function generateTokensHtml(categories) {
@@ -275,6 +440,97 @@ async function processStyleDictionary(config) {
275
440
  }
276
441
  };
277
442
  }
443
+ /**
444
+ * Normalize a token value for comparison.
445
+ * - Lowercase, trim
446
+ * - Leading-zero: `.5rem` → `0.5rem`
447
+ * - Short hex: `#fff` → `#ffffff`
448
+ */
449
+ function normalizeTokenValue(value) {
450
+ let v = String(value).trim().toLowerCase();
451
+ const shortHex = v.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])?$/);
452
+ if (shortHex) {
453
+ const [, r, g, b, a] = shortHex;
454
+ v = a ? `#${r}${r}${g}${g}${b}${b}${a}${a}` : `#${r}${r}${g}${g}${b}${b}`;
455
+ }
456
+ v = v.replace(/(?<![0-9])\.(\d)/g, "0.$1");
457
+ return v;
458
+ }
459
+ const STYLE_BLOCK_RE = /<style[^>]*>([\s\S]*?)<\/style>/g;
460
+ const CSS_PROPERTY_RE = /^\s*([\w-]+)\s*:\s*(.+?)\s*;?\s*$/;
461
+ /**
462
+ * Scan art file sources for token value matches in `<style>` blocks.
463
+ */
464
+ function scanTokenUsage(artFiles, tokenMap) {
465
+ const valueLookup = new Map();
466
+ for (const [tokenPath, token] of Object.entries(tokenMap)) {
467
+ const rawValue = token.$resolvedValue ?? token.value;
468
+ const normalized = normalizeTokenValue(rawValue);
469
+ if (!normalized) continue;
470
+ const existing = valueLookup.get(normalized);
471
+ if (existing) existing.push(tokenPath);
472
+ else valueLookup.set(normalized, [tokenPath]);
473
+ }
474
+ const usageMap = {};
475
+ for (const [artPath, artInfo] of artFiles) {
476
+ let source;
477
+ try {
478
+ source = fs.readFileSync(artPath, "utf-8");
479
+ } catch {
480
+ continue;
481
+ }
482
+ const allLines = source.split("\n");
483
+ const styleRegions = [];
484
+ let match;
485
+ STYLE_BLOCK_RE.lastIndex = 0;
486
+ while ((match = STYLE_BLOCK_RE.exec(source)) !== null) {
487
+ const beforeMatch = source.slice(0, match.index);
488
+ const startTag = source.slice(match.index, match.index + match[0].indexOf(match[1]));
489
+ const startLine = beforeMatch.split("\n").length + startTag.split("\n").length - 1;
490
+ styleRegions.push({
491
+ startLine,
492
+ content: match[1]
493
+ });
494
+ }
495
+ for (const region of styleRegions) {
496
+ const lines = region.content.split("\n");
497
+ for (let i = 0; i < lines.length; i++) {
498
+ const line = lines[i];
499
+ const propMatch = line.match(CSS_PROPERTY_RE);
500
+ if (!propMatch) continue;
501
+ const property = propMatch[1];
502
+ const valueStr = propMatch[2];
503
+ const valueParts = valueStr.split(/\s+/);
504
+ for (const part of valueParts) {
505
+ const normalizedPart = normalizeTokenValue(part);
506
+ const matchingTokens = valueLookup.get(normalizedPart);
507
+ if (!matchingTokens) continue;
508
+ const lineNumber = region.startLine + i;
509
+ const lineContent = allLines[lineNumber - 1]?.trim() ?? line.trim();
510
+ for (const tokenPath of matchingTokens) {
511
+ if (!usageMap[tokenPath]) usageMap[tokenPath] = [];
512
+ let entry = usageMap[tokenPath].find((e) => e.artPath === artPath);
513
+ if (!entry) {
514
+ entry = {
515
+ artPath,
516
+ artTitle: artInfo.metadata.title,
517
+ artCategory: artInfo.metadata.category,
518
+ matches: []
519
+ };
520
+ usageMap[tokenPath].push(entry);
521
+ }
522
+ if (!entry.matches.some((m) => m.line === lineNumber && m.property === property)) entry.matches.push({
523
+ line: lineNumber,
524
+ lineContent,
525
+ property
526
+ });
527
+ }
528
+ }
529
+ }
530
+ }
531
+ }
532
+ return usageMap;
533
+ }
278
534
 
279
535
  //#endregion
280
536
  //#region src/index.ts
@@ -498,30 +754,263 @@ function musea(options = {}) {
498
754
  sendJson(Array.from(artFiles.values()));
499
755
  return;
500
756
  }
757
+ if (req.url === "/tokens/usage" && req.method === "GET") {
758
+ if (!tokensPath) {
759
+ sendJson({});
760
+ return;
761
+ }
762
+ try {
763
+ const absoluteTokensPath = path.resolve(config.root, tokensPath);
764
+ const categories = await parseTokens(absoluteTokensPath);
765
+ const tokenMap = buildTokenMap(categories);
766
+ resolveReferences(categories, tokenMap);
767
+ const resolvedTokenMap = buildTokenMap(categories);
768
+ const usage = scanTokenUsage(artFiles, resolvedTokenMap);
769
+ sendJson(usage);
770
+ } catch (e) {
771
+ console.error("[musea] Failed to scan token usage:", e);
772
+ sendJson({});
773
+ }
774
+ return;
775
+ }
501
776
  if (req.url === "/tokens" && req.method === "GET") {
502
777
  if (!tokensPath) {
503
- sendJson({ categories: [] });
778
+ sendJson({
779
+ categories: [],
780
+ tokenMap: {},
781
+ meta: {
782
+ filePath: "",
783
+ tokenCount: 0,
784
+ primitiveCount: 0,
785
+ semanticCount: 0
786
+ }
787
+ });
504
788
  return;
505
789
  }
506
790
  try {
507
791
  const absoluteTokensPath = path.resolve(config.root, tokensPath);
508
792
  const categories = await parseTokens(absoluteTokensPath);
509
- sendJson({ categories });
793
+ const tokenMap = buildTokenMap(categories);
794
+ resolveReferences(categories, tokenMap);
795
+ const resolvedTokenMap = buildTokenMap(categories);
796
+ let primitiveCount = 0;
797
+ let semanticCount = 0;
798
+ for (const token of Object.values(resolvedTokenMap)) if (token.$tier === "semantic") semanticCount++;
799
+ else primitiveCount++;
800
+ sendJson({
801
+ categories,
802
+ tokenMap: resolvedTokenMap,
803
+ meta: {
804
+ filePath: absoluteTokensPath,
805
+ tokenCount: Object.keys(resolvedTokenMap).length,
806
+ primitiveCount,
807
+ semanticCount
808
+ }
809
+ });
510
810
  } catch (e) {
511
811
  console.error("[musea] Failed to load tokens:", e);
512
812
  sendJson({
513
813
  categories: [],
814
+ tokenMap: {},
514
815
  error: String(e)
515
816
  });
516
817
  }
517
818
  return;
518
819
  }
820
+ if (req.url === "/tokens" && req.method === "POST") {
821
+ if (!tokensPath) {
822
+ sendError("No tokens path configured", 400);
823
+ return;
824
+ }
825
+ let body = "";
826
+ req.on("data", (chunk) => {
827
+ body += chunk;
828
+ });
829
+ req.on("end", async () => {
830
+ try {
831
+ const { path: dotPath, token } = JSON.parse(body);
832
+ if (!dotPath || !token || token.value === void 0) {
833
+ sendError("Missing required fields: path, token.value", 400);
834
+ return;
835
+ }
836
+ const absoluteTokensPath = path.resolve(config.root, tokensPath);
837
+ const rawData = await readRawTokenFile(absoluteTokensPath);
838
+ const currentCategories = await parseTokens(absoluteTokensPath);
839
+ const currentMap = buildTokenMap(currentCategories);
840
+ if (currentMap[dotPath]) {
841
+ sendError(`Token already exists at path "${dotPath}"`, 409);
842
+ return;
843
+ }
844
+ if (token.$reference) {
845
+ const validation = validateSemanticReference(currentMap, token.$reference, dotPath);
846
+ if (!validation.valid) {
847
+ sendError(validation.error, 400);
848
+ return;
849
+ }
850
+ token.value = `{${token.$reference}}`;
851
+ token.$tier = "semantic";
852
+ }
853
+ setTokenAtPath(rawData, dotPath, token);
854
+ await writeRawTokenFile(absoluteTokensPath, rawData);
855
+ const categories = await parseTokens(absoluteTokensPath);
856
+ const tokenMap = buildTokenMap(categories);
857
+ resolveReferences(categories, tokenMap);
858
+ const resolvedTokenMap = buildTokenMap(categories);
859
+ sendJson({
860
+ categories,
861
+ tokenMap: resolvedTokenMap
862
+ }, 201);
863
+ } catch (e) {
864
+ sendError(e instanceof Error ? e.message : String(e));
865
+ }
866
+ });
867
+ return;
868
+ }
869
+ if (req.url === "/tokens" && req.method === "PUT") {
870
+ if (!tokensPath) {
871
+ sendError("No tokens path configured", 400);
872
+ return;
873
+ }
874
+ let body = "";
875
+ req.on("data", (chunk) => {
876
+ body += chunk;
877
+ });
878
+ req.on("end", async () => {
879
+ try {
880
+ const { path: dotPath, token } = JSON.parse(body);
881
+ if (!dotPath || !token || token.value === void 0) {
882
+ sendError("Missing required fields: path, token.value", 400);
883
+ return;
884
+ }
885
+ const absoluteTokensPath = path.resolve(config.root, tokensPath);
886
+ if (token.$reference) {
887
+ const currentCategories = await parseTokens(absoluteTokensPath);
888
+ const currentMap = buildTokenMap(currentCategories);
889
+ const validation = validateSemanticReference(currentMap, token.$reference, dotPath);
890
+ if (!validation.valid) {
891
+ sendError(validation.error, 400);
892
+ return;
893
+ }
894
+ token.value = `{${token.$reference}}`;
895
+ token.$tier = "semantic";
896
+ }
897
+ const rawData = await readRawTokenFile(absoluteTokensPath);
898
+ setTokenAtPath(rawData, dotPath, token);
899
+ await writeRawTokenFile(absoluteTokensPath, rawData);
900
+ const categories = await parseTokens(absoluteTokensPath);
901
+ const tokenMap = buildTokenMap(categories);
902
+ resolveReferences(categories, tokenMap);
903
+ const resolvedTokenMap = buildTokenMap(categories);
904
+ sendJson({
905
+ categories,
906
+ tokenMap: resolvedTokenMap
907
+ });
908
+ } catch (e) {
909
+ sendError(e instanceof Error ? e.message : String(e));
910
+ }
911
+ });
912
+ return;
913
+ }
914
+ if (req.url === "/tokens" && req.method === "DELETE") {
915
+ if (!tokensPath) {
916
+ sendError("No tokens path configured", 400);
917
+ return;
918
+ }
919
+ let body = "";
920
+ req.on("data", (chunk) => {
921
+ body += chunk;
922
+ });
923
+ req.on("end", async () => {
924
+ try {
925
+ const { path: dotPath } = JSON.parse(body);
926
+ if (!dotPath) {
927
+ sendError("Missing required field: path", 400);
928
+ return;
929
+ }
930
+ const absoluteTokensPath = path.resolve(config.root, tokensPath);
931
+ const currentCategories = await parseTokens(absoluteTokensPath);
932
+ const currentMap = buildTokenMap(currentCategories);
933
+ const dependents = findDependentTokens(currentMap, dotPath);
934
+ const rawData = await readRawTokenFile(absoluteTokensPath);
935
+ const deleted = deleteTokenAtPath(rawData, dotPath);
936
+ if (!deleted) {
937
+ sendError(`Token not found at path "${dotPath}"`, 404);
938
+ return;
939
+ }
940
+ await writeRawTokenFile(absoluteTokensPath, rawData);
941
+ const categories = await parseTokens(absoluteTokensPath);
942
+ const tokenMap = buildTokenMap(categories);
943
+ resolveReferences(categories, tokenMap);
944
+ const resolvedTokenMap = buildTokenMap(categories);
945
+ sendJson({
946
+ categories,
947
+ tokenMap: resolvedTokenMap,
948
+ dependentsWarning: dependents.length > 0 ? dependents : void 0
949
+ });
950
+ } catch (e) {
951
+ sendError(e instanceof Error ? e.message : String(e));
952
+ }
953
+ });
954
+ return;
955
+ }
956
+ if (req.url?.startsWith("/arts/") && req.method === "PUT") {
957
+ const rest = req.url.slice(6);
958
+ const sourceMatch = rest.match(/^(.+)\/source$/);
959
+ if (sourceMatch) {
960
+ const artPath = decodeURIComponent(sourceMatch[1]);
961
+ const art = artFiles.get(artPath);
962
+ if (!art) {
963
+ sendError("Art not found", 404);
964
+ return;
965
+ }
966
+ let body = "";
967
+ req.on("data", (chunk) => {
968
+ body += chunk;
969
+ });
970
+ req.on("end", async () => {
971
+ try {
972
+ const { source } = JSON.parse(body);
973
+ if (typeof source !== "string") {
974
+ sendError("Missing required field: source", 400);
975
+ return;
976
+ }
977
+ await fs.promises.writeFile(artPath, source, "utf-8");
978
+ await processArtFile(artPath);
979
+ sendJson({ success: true });
980
+ } catch (e) {
981
+ sendError(e instanceof Error ? e.message : String(e));
982
+ }
983
+ });
984
+ return;
985
+ }
986
+ next();
987
+ return;
988
+ }
519
989
  if (req.url?.startsWith("/arts/") && req.method === "GET") {
520
990
  const rest = req.url.slice(6);
991
+ const sourceMatch = rest.match(/^(.+)\/source$/);
521
992
  const paletteMatch = rest.match(/^(.+)\/palette$/);
522
993
  const analysisMatch = rest.match(/^(.+)\/analysis$/);
523
994
  const docsMatch = rest.match(/^(.+)\/docs$/);
524
995
  const a11yMatch = rest.match(/^(.+)\/variants\/([^/]+)\/a11y$/);
996
+ if (sourceMatch) {
997
+ const artPath$1 = decodeURIComponent(sourceMatch[1]);
998
+ const art$1 = artFiles.get(artPath$1);
999
+ if (!art$1) {
1000
+ sendError("Art not found", 404);
1001
+ return;
1002
+ }
1003
+ try {
1004
+ const source = await fs.promises.readFile(artPath$1, "utf-8");
1005
+ sendJson({
1006
+ source,
1007
+ path: artPath$1
1008
+ });
1009
+ } catch (e) {
1010
+ sendError(e instanceof Error ? e.message : String(e));
1011
+ }
1012
+ return;
1013
+ }
525
1014
  if (paletteMatch) {
526
1015
  const artPath$1 = decodeURIComponent(paletteMatch[1]);
527
1016
  const art$1 = artFiles.get(artPath$1);
@@ -2267,5 +2756,5 @@ function escapeHtml(str) {
2267
2756
  var src_default = musea;
2268
2757
 
2269
2758
  //#endregion
2270
- export { MuseaA11yRunner, MuseaVrtRunner, src_default as default, generateArtFile, generateTokensHtml, generateTokensMarkdown, generateVrtJsonReport, generateVrtReport, musea, parseTokens, processStyleDictionary, writeArtFile };
2759
+ export { MuseaA11yRunner, MuseaVrtRunner, buildTokenMap, src_default as default, generateArtFile, generateTokensHtml, generateTokensMarkdown, generateVrtJsonReport, generateVrtReport, musea, parseTokens, processStyleDictionary, resolveReferences, scanTokenUsage, writeArtFile };
2271
2760
  //# sourceMappingURL=index.js.map