@fragments-sdk/cli 0.7.2 → 0.7.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.
Files changed (73) hide show
  1. package/README.md +2 -0
  2. package/dist/bin.js +25 -17
  3. package/dist/bin.js.map +1 -1
  4. package/dist/chunk-AWYCDRPG.js +272 -0
  5. package/dist/chunk-AWYCDRPG.js.map +1 -0
  6. package/dist/chunk-EKLMXTWU.js +80 -0
  7. package/dist/chunk-EKLMXTWU.js.map +1 -0
  8. package/dist/{chunk-DH4ETVSM.js → chunk-NEJ2FBTN.js} +9 -7
  9. package/dist/{chunk-DH4ETVSM.js.map → chunk-NEJ2FBTN.js.map} +1 -1
  10. package/dist/{chunk-GHYYFAQN.js → chunk-P33AKQJW.js} +1 -76
  11. package/dist/chunk-P33AKQJW.js.map +1 -0
  12. package/dist/{chunk-3T6QL7IY.js → chunk-R6IZZSE7.js} +23 -275
  13. package/dist/chunk-R6IZZSE7.js.map +1 -0
  14. package/dist/{chunk-7KUSBMI4.js → chunk-S56I5FST.js} +174 -45
  15. package/dist/chunk-S56I5FST.js.map +1 -0
  16. package/dist/{chunk-DQHWLAUV.js → chunk-TOIE7VXF.js} +2 -2
  17. package/dist/{chunk-OOGTG5FM.js → chunk-UXLGIGSX.js} +56 -2
  18. package/dist/chunk-UXLGIGSX.js.map +1 -0
  19. package/dist/{chunk-GKX2HPZ6.js → chunk-YMPGYEWK.js} +9 -3
  20. package/dist/chunk-YMPGYEWK.js.map +1 -0
  21. package/dist/chunk-Z7EY4VHE.js +50 -0
  22. package/dist/{core-UQXZTBFZ.js → core-3NMNCLFW.js} +8 -5
  23. package/dist/discovery-Z4RDDFVR.js +28 -0
  24. package/dist/{generate-GP6ZLAQB.js → generate-23VLX7QN.js} +7 -4
  25. package/dist/{generate-GP6ZLAQB.js.map → generate-23VLX7QN.js.map} +1 -1
  26. package/dist/index.js +15 -11
  27. package/dist/index.js.map +1 -1
  28. package/dist/{init-W72WBSU2.js → init-VYVYMVHH.js} +10 -6
  29. package/dist/{init-W72WBSU2.js.map → init-VYVYMVHH.js.map} +1 -1
  30. package/dist/mcp-bin.js +5 -3
  31. package/dist/mcp-bin.js.map +1 -1
  32. package/dist/sass.node-4XJK6YBF.js +130708 -0
  33. package/dist/sass.node-4XJK6YBF.js.map +1 -0
  34. package/dist/scan-FZR6YVI5.js +15 -0
  35. package/dist/{service-PVGTYUKX.js → service-CFFBHW4X.js} +6 -4
  36. package/dist/service-CFFBHW4X.js.map +1 -0
  37. package/dist/{static-viewer-KILKIVN7.js → static-viewer-VA2JXSCX.js} +6 -4
  38. package/dist/static-viewer-VA2JXSCX.js.map +1 -0
  39. package/dist/{test-3YRYQRGV.js → test-VTD7R6G2.js} +8 -4
  40. package/dist/{test-3YRYQRGV.js.map → test-VTD7R6G2.js.map} +1 -1
  41. package/dist/{tokens-IXSQHPQK.js → tokens-7JA5CPDL.js} +10 -7
  42. package/dist/{tokens-IXSQHPQK.js.map → tokens-7JA5CPDL.js.map} +1 -1
  43. package/dist/{viewer-K42REJU2.js → viewer-WXTDDQGK.js} +403 -26
  44. package/dist/viewer-WXTDDQGK.js.map +1 -0
  45. package/package.json +5 -1
  46. package/src/build.ts +57 -5
  47. package/src/commands/init.ts +6 -2
  48. package/src/core/__tests__/token-resolver.test.ts +82 -0
  49. package/src/core/discovery.ts +7 -1
  50. package/src/core/token-parser.ts +102 -0
  51. package/src/core/token-resolver.ts +155 -0
  52. package/src/migrate/detect.ts +4 -0
  53. package/src/service/__tests__/patch-generator.test.ts +2 -2
  54. package/src/service/patch-generator.ts +8 -1
  55. package/src/viewer/components/App.tsx +63 -2
  56. package/src/viewer/components/Layout.tsx +1 -1
  57. package/src/viewer/components/LeftSidebar.tsx +35 -77
  58. package/src/viewer/preview-frame.html +1 -1
  59. package/src/viewer/render-utils.ts +141 -0
  60. package/src/viewer/styles/globals.css +2 -1
  61. package/src/viewer/vite-plugin.ts +399 -24
  62. package/dist/chunk-3T6QL7IY.js.map +0 -1
  63. package/dist/chunk-7KUSBMI4.js.map +0 -1
  64. package/dist/chunk-GHYYFAQN.js.map +0 -1
  65. package/dist/chunk-GKX2HPZ6.js.map +0 -1
  66. package/dist/chunk-OOGTG5FM.js.map +0 -1
  67. package/dist/scan-V54HWRDY.js +0 -12
  68. package/dist/viewer-K42REJU2.js.map +0 -1
  69. /package/dist/{chunk-DQHWLAUV.js.map → chunk-TOIE7VXF.js.map} +0 -0
  70. /package/dist/{core-UQXZTBFZ.js.map → chunk-Z7EY4VHE.js.map} +0 -0
  71. /package/dist/{scan-V54HWRDY.js.map → core-3NMNCLFW.js.map} +0 -0
  72. /package/dist/{service-PVGTYUKX.js.map → discovery-Z4RDDFVR.js.map} +0 -0
  73. /package/dist/{static-viewer-KILKIVN7.js.map → scan-FZR6YVI5.js.map} +0 -0
@@ -1,18 +1,22 @@
1
1
  import { createRequire as __banner_createRequire } from 'module'; const require = __banner_createRequire(import.meta.url);
2
2
  import {
3
- discoverFragmentFiles,
4
- discoverInstalledFragments,
5
3
  findPreviewConfigPath,
6
4
  findStorybookDir,
7
5
  generatePreviewModule,
8
6
  loadConfig
9
- } from "./chunk-3T6QL7IY.js";
7
+ } from "./chunk-R6IZZSE7.js";
8
+ import {
9
+ discoverFragmentFiles,
10
+ discoverInstalledFragments
11
+ } from "./chunk-AWYCDRPG.js";
10
12
  import {
11
13
  generateContext
12
- } from "./chunk-OOGTG5FM.js";
14
+ } from "./chunk-UXLGIGSX.js";
15
+ import "./chunk-P33AKQJW.js";
13
16
  import {
14
17
  BRAND
15
- } from "./chunk-GHYYFAQN.js";
18
+ } from "./chunk-EKLMXTWU.js";
19
+ import "./chunk-Z7EY4VHE.js";
16
20
 
17
21
  // src/viewer/server.ts
18
22
  import {
@@ -123,6 +127,122 @@ async function render() {
123
127
  }
124
128
  }
125
129
 
130
+ render();
131
+ `;
132
+ }
133
+ function generateVariantRenderScript(fragmentPath, componentName, variantName) {
134
+ const variantNameLower = JSON.stringify(variantName.toLowerCase());
135
+ return `
136
+ import React from "react";
137
+ import { createRoot } from "react-dom/client";
138
+
139
+ async function render() {
140
+ const root = document.getElementById("render-root");
141
+
142
+ try {
143
+ const fragmentModule = await import("${fragmentPath}");
144
+ const fragment = fragmentModule.default;
145
+
146
+ if (!fragment || !fragment.variants || fragment.variants.length === 0) {
147
+ throw new Error("Fragment has no variants");
148
+ }
149
+
150
+ const variant = fragment.variants.find(
151
+ v => v.name.toLowerCase() === ${variantNameLower}
152
+ );
153
+
154
+ if (!variant) {
155
+ const available = fragment.variants.map(v => v.name).join(", ");
156
+ throw new Error("Variant '" + ${JSON.stringify(variantName)} + "' not found. Available: " + available);
157
+ }
158
+
159
+ const element = variant.render();
160
+
161
+ const reactRoot = createRoot(root);
162
+ reactRoot.render(element);
163
+
164
+ requestAnimationFrame(() => {
165
+ requestAnimationFrame(() => {
166
+ root.classList.add("ready");
167
+ window.__RENDER_READY__ = true;
168
+ });
169
+ });
170
+ } catch (error) {
171
+ console.error("Render error:", error);
172
+ root.innerHTML = \`
173
+ <div class="render-error">
174
+ <strong>Render Error</strong>
175
+ <pre>\${error.message}</pre>
176
+ </div>
177
+ \`;
178
+ root.classList.add("ready");
179
+ window.__RENDER_READY__ = true;
180
+ window.__RENDER_ERROR__ = error.message;
181
+ }
182
+ }
183
+
184
+ render();
185
+ `;
186
+ }
187
+ function generateA11yRenderScript(fragmentPath, componentName, variantName) {
188
+ const variantLookup = variantName ? `
189
+ const variant = fragment.variants?.find(
190
+ v => v.name.toLowerCase() === ${JSON.stringify(variantName.toLowerCase())}
191
+ );
192
+ if (!variant) {
193
+ throw new Error("Variant '${variantName}' not found");
194
+ }
195
+ element = variant.render();` : `
196
+ element = React.createElement(fragment.component, {});`;
197
+ return `
198
+ import React from "react";
199
+ import { createRoot } from "react-dom/client";
200
+ import axe from "axe-core";
201
+
202
+ async function render() {
203
+ const root = document.getElementById("render-root");
204
+
205
+ try {
206
+ const fragmentModule = await import("${fragmentPath}");
207
+ const fragment = fragmentModule.default;
208
+
209
+ if (!fragment || !fragment.component) {
210
+ throw new Error("Fragment does not export a component");
211
+ }
212
+
213
+ let element;
214
+ ${variantLookup}
215
+
216
+ const reactRoot = createRoot(root);
217
+ reactRoot.render(element);
218
+
219
+ // Wait for React to flush rendering
220
+ await new Promise(resolve => {
221
+ requestAnimationFrame(() => {
222
+ requestAnimationFrame(resolve);
223
+ });
224
+ });
225
+
226
+ // Additional settle time for CSS/animations
227
+ await new Promise(resolve => setTimeout(resolve, 100));
228
+
229
+ // Run axe-core accessibility audit
230
+ const results = await axe.run('#render-root', {
231
+ runOnly: {
232
+ type: 'tag',
233
+ values: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'best-practice'],
234
+ },
235
+ });
236
+
237
+ window.__AXE_RESULTS__ = results;
238
+ window.__RENDER_READY__ = true;
239
+ } catch (error) {
240
+ console.error("A11y audit error:", error);
241
+ window.__AXE_ERROR__ = error.message;
242
+ window.__RENDER_READY__ = true;
243
+ }
244
+ }
245
+
126
246
  render();
127
247
  `;
128
248
  }
@@ -240,7 +360,7 @@ var sharedRenderPool = null;
240
360
  var browserPoolModule = null;
241
361
  async function getSharedRenderPool() {
242
362
  if (!browserPoolModule) {
243
- browserPoolModule = await import("./service-PVGTYUKX.js");
363
+ browserPoolModule = await import("./service-CFFBHW4X.js");
244
364
  }
245
365
  if (!sharedRenderPool) {
246
366
  sharedRenderPool = new browserPoolModule.BrowserPool({
@@ -272,6 +392,16 @@ function fragmentsPlugin(options) {
272
392
  // Add process.env shim and esbuild config for Storybook compatibility
273
393
  config() {
274
394
  return {
395
+ build: {
396
+ rollupOptions: {
397
+ onwarn(warning, defaultHandler) {
398
+ if (warning.code === "MODULE_LEVEL_DIRECTIVE" || warning.message && warning.message.includes("has been externalized")) {
399
+ return;
400
+ }
401
+ defaultHandler(warning);
402
+ }
403
+ }
404
+ },
275
405
  define: {
276
406
  // Shim process.env for story files that use it (e.g., process.env.STORYBOOK_*)
277
407
  "process.env": "{}"
@@ -302,7 +432,7 @@ function fragmentsPlugin(options) {
302
432
  if (req.url === "/fragments/render" && req.method === "POST") {
303
433
  try {
304
434
  const body = await parseJsonBody(req);
305
- const { component, props = {}, viewport } = body;
435
+ const { component, props = {}, viewport, variant } = body;
306
436
  if (!component) {
307
437
  res.writeHead(400, { "Content-Type": "application/json" });
308
438
  res.end(
@@ -337,7 +467,11 @@ function fragmentsPlugin(options) {
337
467
  );
338
468
  return;
339
469
  }
340
- const renderScript = generateRenderScript(
470
+ const renderScript = variant ? generateVariantRenderScript(
471
+ fragmentFile.absolutePath,
472
+ fragmentInfo.name,
473
+ variant
474
+ ) : generateRenderScript(
341
475
  fragmentFile.absolutePath,
342
476
  fragmentInfo.name,
343
477
  props
@@ -469,7 +603,7 @@ function fragmentsPlugin(options) {
469
603
  const address = _server.httpServer?.address();
470
604
  const port = typeof address === "object" && address ? address.port : 6006;
471
605
  const renderViewport = viewport || { width: 800, height: 600 };
472
- const { FigmaClient, bufferToBase64Url } = await import("./service-PVGTYUKX.js");
606
+ const { FigmaClient, bufferToBase64Url } = await import("./service-CFFBHW4X.js");
473
607
  const figmaClient = new FigmaClient({
474
608
  accessToken: figmaToken
475
609
  });
@@ -560,7 +694,7 @@ function fragmentsPlugin(options) {
560
694
  );
561
695
  return;
562
696
  }
563
- const { FigmaClient } = await import("./service-PVGTYUKX.js");
697
+ const { FigmaClient } = await import("./service-CFFBHW4X.js");
564
698
  const figmaClient = new FigmaClient({ accessToken: figmaToken });
565
699
  const { fileKey, nodeId } = figmaClient.parseUrl(figmaUrl);
566
700
  const figmaDesignProps = await figmaClient.getNodeProperties(
@@ -601,7 +735,7 @@ function fragmentsPlugin(options) {
601
735
  }));
602
736
  return;
603
737
  }
604
- const { getSharedTokenRegistry } = await import("./service-PVGTYUKX.js");
738
+ const { getSharedTokenRegistry } = await import("./service-CFFBHW4X.js");
605
739
  const registry = getSharedTokenRegistry();
606
740
  if (!registry.isInitialized()) {
607
741
  await registry.initialize(config.tokens, projectRoot);
@@ -661,7 +795,7 @@ function fragmentsPlugin(options) {
661
795
  }));
662
796
  return;
663
797
  }
664
- const { getSharedTokenRegistry } = await import("./service-PVGTYUKX.js");
798
+ const { getSharedTokenRegistry } = await import("./service-CFFBHW4X.js");
665
799
  const registry = getSharedTokenRegistry();
666
800
  if (!registry.isInitialized()) {
667
801
  await registry.initialize(config.tokens, projectRoot);
@@ -723,7 +857,7 @@ function fragmentsPlugin(options) {
723
857
  res.end(JSON.stringify({ error: "Could not resolve fragment file path" }));
724
858
  return;
725
859
  }
726
- const { getSharedTokenRegistry } = await import("./service-PVGTYUKX.js");
860
+ const { getSharedTokenRegistry } = await import("./service-CFFBHW4X.js");
727
861
  const registry = getSharedTokenRegistry();
728
862
  if (!registry.isInitialized()) {
729
863
  await registry.initialize(config.tokens, projectRoot);
@@ -849,7 +983,7 @@ function fragmentsPlugin(options) {
849
983
  }
850
984
  const { writeFile, mkdir } = await import("fs/promises");
851
985
  const { join: join2 } = await import("path");
852
- const { BRAND: BRAND2 } = await import("./core-UQXZTBFZ.js");
986
+ const { BRAND: BRAND2 } = await import("./core-3NMNCLFW.js");
853
987
  const fragmentsDir = join2(projectRoot, BRAND2.dataDir, BRAND2.componentsDir);
854
988
  await mkdir(fragmentsDir, { recursive: true });
855
989
  const fragmentPath = join2(
@@ -884,12 +1018,30 @@ function fragmentsPlugin(options) {
884
1018
  return;
885
1019
  }
886
1020
  if (!config.tokens || !config.tokens.include || config.tokens.include.length === 0) {
887
- res.writeHead(400, { "Content-Type": "application/json" });
888
- res.end(JSON.stringify({
889
- error: "No token configuration found",
890
- suggestion: "Add 'tokens' config to fragments.config.ts to enable fix generation"
891
- }));
892
- return;
1021
+ try {
1022
+ const { discoverTokenFiles } = await import("./discovery-Z4RDDFVR.js");
1023
+ const discovered = await discoverTokenFiles(projectRoot);
1024
+ if (discovered.length > 0) {
1025
+ config.tokens = {
1026
+ ...config.tokens,
1027
+ include: discovered.map((f) => f.relativePath)
1028
+ };
1029
+ } else {
1030
+ res.writeHead(400, { "Content-Type": "application/json" });
1031
+ res.end(JSON.stringify({
1032
+ error: "No token files found",
1033
+ suggestion: "Add 'tokens' config to fragments.config.ts or add token files matching default patterns (_variables.scss, tokens.scss, etc.)"
1034
+ }));
1035
+ return;
1036
+ }
1037
+ } catch {
1038
+ res.writeHead(400, { "Content-Type": "application/json" });
1039
+ res.end(JSON.stringify({
1040
+ error: "No token configuration found and auto-discovery failed",
1041
+ suggestion: "Add 'tokens' config to fragments.config.ts to enable fix generation"
1042
+ }));
1043
+ return;
1044
+ }
893
1045
  }
894
1046
  const loadedFragments = await loadFragmentsForRender(fragmentFiles, projectRoot);
895
1047
  const fragmentInfo = findFragmentByName(component, loadedFragments);
@@ -904,7 +1056,7 @@ function fragmentsPlugin(options) {
904
1056
  const {
905
1057
  getSharedTokenRegistry,
906
1058
  generateTokenPatches
907
- } = await import("./service-PVGTYUKX.js");
1059
+ } = await import("./service-CFFBHW4X.js");
908
1060
  const registry = getSharedTokenRegistry();
909
1061
  if (!registry.isInitialized()) {
910
1062
  await registry.initialize(config.tokens, projectRoot);
@@ -913,10 +1065,56 @@ function fragmentsPlugin(options) {
913
1065
  (f) => f.relativePath === fragmentInfo.path
914
1066
  );
915
1067
  const sourceFile = fragmentFile?.relativePath || `${component}.tsx`;
1068
+ let styleDiffs = [];
1069
+ if (fragmentFile) {
1070
+ try {
1071
+ const renderScript = generateRenderScript(
1072
+ fragmentFile.absolutePath,
1073
+ fragmentInfo.name,
1074
+ {}
1075
+ );
1076
+ const requestId = Date.now().toString(36) + Math.random().toString(36).slice(2);
1077
+ pendingRenders.set(requestId, {
1078
+ script: renderScript,
1079
+ viewport: { width: 800, height: 600 }
1080
+ });
1081
+ const address = _server.httpServer?.address();
1082
+ const port = typeof address === "object" && address ? address.port : 6006;
1083
+ const { computedStyles } = await captureRenderWithStyles(
1084
+ `http://localhost:${port}/fragments/__render__/${requestId}`,
1085
+ { width: 800, height: 600 },
1086
+ true
1087
+ );
1088
+ pendingRenders.delete(requestId);
1089
+ if (computedStyles) {
1090
+ const tokenValues = /* @__PURE__ */ new Map();
1091
+ const allTokens = registry.getAllTokens();
1092
+ for (const t of allTokens) {
1093
+ if (t.resolvedValue) {
1094
+ tokenValues.set(t.resolvedValue, t.name);
1095
+ }
1096
+ }
1097
+ for (const [prop, value] of Object.entries(computedStyles)) {
1098
+ if (!value || value === "transparent" || value === "rgba(0, 0, 0, 0)") continue;
1099
+ const matchesToken = tokenValues.has(value);
1100
+ if (!matchesToken) {
1101
+ styleDiffs.push({
1102
+ property: prop,
1103
+ figma: value,
1104
+ // Using rendered as "expected" since we have no Figma
1105
+ rendered: value,
1106
+ match: false
1107
+ });
1108
+ }
1109
+ }
1110
+ }
1111
+ } catch (renderErr) {
1112
+ console.warn("[Fragments] Could not render for style extraction:", renderErr);
1113
+ }
1114
+ }
916
1115
  const result = generateTokenPatches(
917
1116
  component,
918
- [],
919
- // Would be populated by actual style diffs
1117
+ styleDiffs,
920
1118
  registry,
921
1119
  { sourceFile }
922
1120
  );
@@ -936,6 +1134,148 @@ function fragmentsPlugin(options) {
936
1134
  }
937
1135
  return;
938
1136
  }
1137
+ if (req.url === "/fragments/a11y" && req.method === "POST") {
1138
+ try {
1139
+ const body = await parseJsonBody(req);
1140
+ const { component, variant: variantName, standard = "AA" } = body;
1141
+ if (!component) {
1142
+ res.writeHead(400, { "Content-Type": "application/json" });
1143
+ res.end(
1144
+ JSON.stringify({ error: "Missing required field: component" })
1145
+ );
1146
+ return;
1147
+ }
1148
+ const loadedFragments = await loadFragmentsForRender(
1149
+ fragmentFiles,
1150
+ projectRoot
1151
+ );
1152
+ const fragmentInfo = findFragmentByName(component, loadedFragments);
1153
+ if (!fragmentInfo) {
1154
+ const available = getAvailableComponents(loadedFragments);
1155
+ res.writeHead(400, { "Content-Type": "application/json" });
1156
+ res.end(
1157
+ JSON.stringify({
1158
+ error: `Component '${component}' not found. Available: ${available.join(", ")}`
1159
+ })
1160
+ );
1161
+ return;
1162
+ }
1163
+ const fragmentFile = fragmentFiles.find(
1164
+ (f) => f.relativePath === fragmentInfo.path
1165
+ );
1166
+ if (!fragmentFile) {
1167
+ res.writeHead(500, { "Content-Type": "application/json" });
1168
+ res.end(
1169
+ JSON.stringify({ error: "Could not resolve fragment file path" })
1170
+ );
1171
+ return;
1172
+ }
1173
+ const variantNames = [];
1174
+ if (variantName) {
1175
+ variantNames.push(variantName);
1176
+ } else {
1177
+ const fullData = await loadFullFragmentData(projectRoot);
1178
+ const fragmentData = fullData ? Object.values(fullData.fragments).find(
1179
+ (f) => f.meta.name.toLowerCase() === component.toLowerCase()
1180
+ ) : null;
1181
+ if (fragmentData && fragmentData.variants?.length > 0) {
1182
+ for (const v of fragmentData.variants) {
1183
+ variantNames.push(v.name);
1184
+ }
1185
+ } else {
1186
+ variantNames.push("Default");
1187
+ }
1188
+ }
1189
+ const address = _server.httpServer?.address();
1190
+ const port = typeof address === "object" && address ? address.port : 6006;
1191
+ const results = [];
1192
+ for (const vName of variantNames) {
1193
+ const a11yScript = generateA11yRenderScript(
1194
+ fragmentFile.absolutePath,
1195
+ fragmentInfo.name,
1196
+ vName === "Default" && !variantName ? void 0 : vName
1197
+ );
1198
+ const requestId = Date.now().toString(36) + Math.random().toString(36).slice(2);
1199
+ pendingRenders.set(requestId, {
1200
+ script: a11yScript,
1201
+ viewport: { width: 800, height: 600 }
1202
+ });
1203
+ try {
1204
+ const auditResult = await captureA11yAudit(
1205
+ `http://localhost:${port}/fragments/__render__/${requestId}`,
1206
+ { width: 800, height: 600 }
1207
+ );
1208
+ let critical = 0;
1209
+ let serious = 0;
1210
+ let moderate = 0;
1211
+ let minor = 0;
1212
+ for (const violation of auditResult.violations ?? []) {
1213
+ switch (violation.impact) {
1214
+ case "critical":
1215
+ critical++;
1216
+ break;
1217
+ case "serious":
1218
+ serious++;
1219
+ break;
1220
+ case "moderate":
1221
+ moderate++;
1222
+ break;
1223
+ case "minor":
1224
+ minor++;
1225
+ break;
1226
+ }
1227
+ }
1228
+ results.push({
1229
+ variant: vName,
1230
+ violations: auditResult.violations?.length ?? 0,
1231
+ passes: auditResult.passes?.length ?? 0,
1232
+ incomplete: auditResult.incomplete?.length ?? 0,
1233
+ summary: {
1234
+ total: critical + serious + moderate + minor,
1235
+ critical,
1236
+ serious,
1237
+ moderate,
1238
+ minor
1239
+ },
1240
+ violationDetails: (auditResult.violations ?? []).map((v) => ({
1241
+ id: v.id,
1242
+ impact: v.impact,
1243
+ description: v.description,
1244
+ helpUrl: v.helpUrl,
1245
+ nodes: v.nodes.length
1246
+ }))
1247
+ });
1248
+ } catch (err) {
1249
+ results.push({
1250
+ variant: vName,
1251
+ violations: 0,
1252
+ passes: 0,
1253
+ incomplete: 0,
1254
+ summary: {
1255
+ total: 0,
1256
+ critical: 0,
1257
+ serious: 0,
1258
+ moderate: 0,
1259
+ minor: 0
1260
+ }
1261
+ });
1262
+ } finally {
1263
+ pendingRenders.delete(requestId);
1264
+ }
1265
+ }
1266
+ res.setHeader("Content-Type", "application/json");
1267
+ res.end(JSON.stringify({ results }));
1268
+ } catch (error) {
1269
+ console.error("[Fragments] Error running a11y audit:", error);
1270
+ res.writeHead(500, { "Content-Type": "application/json" });
1271
+ res.end(
1272
+ JSON.stringify({
1273
+ error: error instanceof Error ? error.message : "A11y audit failed"
1274
+ })
1275
+ );
1276
+ }
1277
+ return;
1278
+ }
939
1279
  if (req.url?.startsWith("/fragments/preview")) {
940
1280
  if (req.url === "/fragments/preview") {
941
1281
  res.writeHead(302, { Location: "/fragments/preview/" });
@@ -1222,7 +1562,9 @@ export async function loadFragment(path) {
1222
1562
  }
1223
1563
  }
1224
1564
 
1225
- loadedFragments.set(path, fragment);
1565
+ if (fragment) {
1566
+ loadedFragments.set(path, fragment);
1567
+ }
1226
1568
  return fragment;
1227
1569
  }
1228
1570
 
@@ -1356,6 +1698,41 @@ async function loadFragmentsForRender(fragmentFiles, configDir) {
1356
1698
  };
1357
1699
  });
1358
1700
  }
1701
+ async function loadFullFragmentData(configDir) {
1702
+ const { join: join2 } = await import("path");
1703
+ const fragmentsJsonPath = join2(configDir, BRAND.outFile);
1704
+ try {
1705
+ const content = await readFile(fragmentsJsonPath, "utf-8");
1706
+ return JSON.parse(content);
1707
+ } catch {
1708
+ return null;
1709
+ }
1710
+ }
1711
+ async function captureA11yAudit(url, viewport) {
1712
+ const { pool } = await getSharedRenderPool();
1713
+ const ctx = await pool.acquire();
1714
+ const page = await ctx.newPage();
1715
+ try {
1716
+ await page.setViewportSize(viewport);
1717
+ await page.goto(url, { waitUntil: "networkidle" });
1718
+ await page.waitForFunction(
1719
+ () => window.__RENDER_READY__ === true,
1720
+ { timeout: 15e3 }
1721
+ );
1722
+ const error = await page.evaluate(() => window.__AXE_ERROR__);
1723
+ if (error) {
1724
+ throw new Error(`A11y audit error: ${error}`);
1725
+ }
1726
+ const results = await page.evaluate(() => window.__AXE_RESULTS__);
1727
+ if (!results) {
1728
+ throw new Error("Axe results not available \u2014 axe-core may not be installed");
1729
+ }
1730
+ return results;
1731
+ } finally {
1732
+ await page.close();
1733
+ pool.release(ctx);
1734
+ }
1735
+ }
1359
1736
  async function serveRenderHTML(res, server, renderScript) {
1360
1737
  const viewerRoot2 = viewerAssetsRoot;
1361
1738
  try {
@@ -1566,7 +1943,7 @@ async function loadFullFragmentForCompare(_server, _fragmentFiles, componentName
1566
1943
  }
1567
1944
  }
1568
1945
  async function compareImages(image1Base64, image2Base64, threshold) {
1569
- const { DiffEngine, base64UrlToBuffer, bufferToBase64Url } = await import("./service-PVGTYUKX.js");
1946
+ const { DiffEngine, base64UrlToBuffer, bufferToBase64Url } = await import("./service-CFFBHW4X.js");
1570
1947
  const { PNG } = await import("pngjs");
1571
1948
  const buffer1 = base64UrlToBuffer(image1Base64);
1572
1949
  const buffer2 = base64UrlToBuffer(image2Base64);
@@ -1819,4 +2196,4 @@ export {
1819
2196
  createDevServer,
1820
2197
  fragmentsPlugin
1821
2198
  };
1822
- //# sourceMappingURL=viewer-K42REJU2.js.map
2199
+ //# sourceMappingURL=viewer-WXTDDQGK.js.map