@fragments-sdk/cli 0.7.2 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +20 -16
- package/dist/bin.js.map +1 -1
- package/dist/chunk-D34Q6A7S.js +266 -0
- package/dist/chunk-D34Q6A7S.js.map +1 -0
- package/dist/chunk-EKLMXTWU.js +80 -0
- package/dist/chunk-EKLMXTWU.js.map +1 -0
- package/dist/{chunk-GHYYFAQN.js → chunk-P33AKQJW.js} +1 -76
- package/dist/chunk-P33AKQJW.js.map +1 -0
- package/dist/{chunk-7KUSBMI4.js → chunk-QPY4DUFB.js} +174 -45
- package/dist/chunk-QPY4DUFB.js.map +1 -0
- package/dist/{chunk-DH4ETVSM.js → chunk-R2YH7NLN.js} +9 -7
- package/dist/{chunk-DH4ETVSM.js.map → chunk-R2YH7NLN.js.map} +1 -1
- package/dist/{chunk-3T6QL7IY.js → chunk-R6IZZSE7.js} +23 -275
- package/dist/chunk-R6IZZSE7.js.map +1 -0
- package/dist/{chunk-DQHWLAUV.js → chunk-TOIE7VXF.js} +2 -2
- package/dist/{chunk-OOGTG5FM.js → chunk-UXLGIGSX.js} +56 -2
- package/dist/chunk-UXLGIGSX.js.map +1 -0
- package/dist/{chunk-GKX2HPZ6.js → chunk-YMPGYEWK.js} +9 -3
- package/dist/chunk-YMPGYEWK.js.map +1 -0
- package/dist/chunk-Z7EY4VHE.js +50 -0
- package/dist/{core-UQXZTBFZ.js → core-3NMNCLFW.js} +8 -5
- package/dist/discovery-AKGA6CJD.js +28 -0
- package/dist/{generate-GP6ZLAQB.js → generate-JAUEHKK7.js} +7 -4
- package/dist/{generate-GP6ZLAQB.js.map → generate-JAUEHKK7.js.map} +1 -1
- package/dist/index.js +15 -11
- package/dist/index.js.map +1 -1
- package/dist/{init-W72WBSU2.js → init-DZQOT54X.js} +6 -4
- package/dist/{init-W72WBSU2.js.map → init-DZQOT54X.js.map} +1 -1
- package/dist/mcp-bin.js +5 -3
- package/dist/mcp-bin.js.map +1 -1
- package/dist/sass.node-4XJK6YBF.js +130708 -0
- package/dist/sass.node-4XJK6YBF.js.map +1 -0
- package/dist/scan-OJRCVKK2.js +15 -0
- package/dist/{service-PVGTYUKX.js → service-CFFBHW4X.js} +6 -4
- package/dist/service-CFFBHW4X.js.map +1 -0
- package/dist/{static-viewer-KILKIVN7.js → static-viewer-VA2JXSCX.js} +6 -4
- package/dist/static-viewer-VA2JXSCX.js.map +1 -0
- package/dist/{test-3YRYQRGV.js → test-O7DZNKDC.js} +8 -4
- package/dist/{test-3YRYQRGV.js.map → test-O7DZNKDC.js.map} +1 -1
- package/dist/{tokens-IXSQHPQK.js → tokens-N7THFD6J.js} +10 -7
- package/dist/{tokens-IXSQHPQK.js.map → tokens-N7THFD6J.js.map} +1 -1
- package/dist/{viewer-K42REJU2.js → viewer-QTR7QJMM.js} +390 -25
- package/dist/viewer-QTR7QJMM.js.map +1 -0
- package/package.json +1 -1
- package/src/build.ts +57 -5
- package/src/core/__tests__/token-resolver.test.ts +82 -0
- package/src/core/token-parser.ts +102 -0
- package/src/core/token-resolver.ts +155 -0
- package/src/service/__tests__/patch-generator.test.ts +2 -2
- package/src/service/patch-generator.ts +8 -1
- package/src/viewer/render-utils.ts +141 -0
- package/src/viewer/vite-plugin.ts +381 -23
- package/dist/chunk-3T6QL7IY.js.map +0 -1
- package/dist/chunk-7KUSBMI4.js.map +0 -1
- package/dist/chunk-GHYYFAQN.js.map +0 -1
- package/dist/chunk-GKX2HPZ6.js.map +0 -1
- package/dist/chunk-OOGTG5FM.js.map +0 -1
- package/dist/scan-V54HWRDY.js +0 -12
- package/dist/viewer-K42REJU2.js.map +0 -1
- /package/dist/{chunk-DQHWLAUV.js.map → chunk-TOIE7VXF.js.map} +0 -0
- /package/dist/{core-UQXZTBFZ.js.map → chunk-Z7EY4VHE.js.map} +0 -0
- /package/dist/{scan-V54HWRDY.js.map → core-3NMNCLFW.js.map} +0 -0
- /package/dist/{service-PVGTYUKX.js.map → discovery-AKGA6CJD.js.map} +0 -0
- /package/dist/{static-viewer-KILKIVN7.js.map → scan-OJRCVKK2.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-
|
|
7
|
+
} from "./chunk-R6IZZSE7.js";
|
|
8
|
+
import {
|
|
9
|
+
discoverFragmentFiles,
|
|
10
|
+
discoverInstalledFragments
|
|
11
|
+
} from "./chunk-D34Q6A7S.js";
|
|
10
12
|
import {
|
|
11
13
|
generateContext
|
|
12
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-UXLGIGSX.js";
|
|
15
|
+
import "./chunk-P33AKQJW.js";
|
|
13
16
|
import {
|
|
14
17
|
BRAND
|
|
15
|
-
} from "./chunk-
|
|
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-
|
|
363
|
+
browserPoolModule = await import("./service-CFFBHW4X.js");
|
|
244
364
|
}
|
|
245
365
|
if (!sharedRenderPool) {
|
|
246
366
|
sharedRenderPool = new browserPoolModule.BrowserPool({
|
|
@@ -302,7 +422,7 @@ function fragmentsPlugin(options) {
|
|
|
302
422
|
if (req.url === "/fragments/render" && req.method === "POST") {
|
|
303
423
|
try {
|
|
304
424
|
const body = await parseJsonBody(req);
|
|
305
|
-
const { component, props = {}, viewport } = body;
|
|
425
|
+
const { component, props = {}, viewport, variant } = body;
|
|
306
426
|
if (!component) {
|
|
307
427
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
308
428
|
res.end(
|
|
@@ -337,7 +457,11 @@ function fragmentsPlugin(options) {
|
|
|
337
457
|
);
|
|
338
458
|
return;
|
|
339
459
|
}
|
|
340
|
-
const renderScript =
|
|
460
|
+
const renderScript = variant ? generateVariantRenderScript(
|
|
461
|
+
fragmentFile.absolutePath,
|
|
462
|
+
fragmentInfo.name,
|
|
463
|
+
variant
|
|
464
|
+
) : generateRenderScript(
|
|
341
465
|
fragmentFile.absolutePath,
|
|
342
466
|
fragmentInfo.name,
|
|
343
467
|
props
|
|
@@ -469,7 +593,7 @@ function fragmentsPlugin(options) {
|
|
|
469
593
|
const address = _server.httpServer?.address();
|
|
470
594
|
const port = typeof address === "object" && address ? address.port : 6006;
|
|
471
595
|
const renderViewport = viewport || { width: 800, height: 600 };
|
|
472
|
-
const { FigmaClient, bufferToBase64Url } = await import("./service-
|
|
596
|
+
const { FigmaClient, bufferToBase64Url } = await import("./service-CFFBHW4X.js");
|
|
473
597
|
const figmaClient = new FigmaClient({
|
|
474
598
|
accessToken: figmaToken
|
|
475
599
|
});
|
|
@@ -560,7 +684,7 @@ function fragmentsPlugin(options) {
|
|
|
560
684
|
);
|
|
561
685
|
return;
|
|
562
686
|
}
|
|
563
|
-
const { FigmaClient } = await import("./service-
|
|
687
|
+
const { FigmaClient } = await import("./service-CFFBHW4X.js");
|
|
564
688
|
const figmaClient = new FigmaClient({ accessToken: figmaToken });
|
|
565
689
|
const { fileKey, nodeId } = figmaClient.parseUrl(figmaUrl);
|
|
566
690
|
const figmaDesignProps = await figmaClient.getNodeProperties(
|
|
@@ -601,7 +725,7 @@ function fragmentsPlugin(options) {
|
|
|
601
725
|
}));
|
|
602
726
|
return;
|
|
603
727
|
}
|
|
604
|
-
const { getSharedTokenRegistry } = await import("./service-
|
|
728
|
+
const { getSharedTokenRegistry } = await import("./service-CFFBHW4X.js");
|
|
605
729
|
const registry = getSharedTokenRegistry();
|
|
606
730
|
if (!registry.isInitialized()) {
|
|
607
731
|
await registry.initialize(config.tokens, projectRoot);
|
|
@@ -661,7 +785,7 @@ function fragmentsPlugin(options) {
|
|
|
661
785
|
}));
|
|
662
786
|
return;
|
|
663
787
|
}
|
|
664
|
-
const { getSharedTokenRegistry } = await import("./service-
|
|
788
|
+
const { getSharedTokenRegistry } = await import("./service-CFFBHW4X.js");
|
|
665
789
|
const registry = getSharedTokenRegistry();
|
|
666
790
|
if (!registry.isInitialized()) {
|
|
667
791
|
await registry.initialize(config.tokens, projectRoot);
|
|
@@ -723,7 +847,7 @@ function fragmentsPlugin(options) {
|
|
|
723
847
|
res.end(JSON.stringify({ error: "Could not resolve fragment file path" }));
|
|
724
848
|
return;
|
|
725
849
|
}
|
|
726
|
-
const { getSharedTokenRegistry } = await import("./service-
|
|
850
|
+
const { getSharedTokenRegistry } = await import("./service-CFFBHW4X.js");
|
|
727
851
|
const registry = getSharedTokenRegistry();
|
|
728
852
|
if (!registry.isInitialized()) {
|
|
729
853
|
await registry.initialize(config.tokens, projectRoot);
|
|
@@ -849,7 +973,7 @@ function fragmentsPlugin(options) {
|
|
|
849
973
|
}
|
|
850
974
|
const { writeFile, mkdir } = await import("fs/promises");
|
|
851
975
|
const { join: join2 } = await import("path");
|
|
852
|
-
const { BRAND: BRAND2 } = await import("./core-
|
|
976
|
+
const { BRAND: BRAND2 } = await import("./core-3NMNCLFW.js");
|
|
853
977
|
const fragmentsDir = join2(projectRoot, BRAND2.dataDir, BRAND2.componentsDir);
|
|
854
978
|
await mkdir(fragmentsDir, { recursive: true });
|
|
855
979
|
const fragmentPath = join2(
|
|
@@ -884,12 +1008,30 @@ function fragmentsPlugin(options) {
|
|
|
884
1008
|
return;
|
|
885
1009
|
}
|
|
886
1010
|
if (!config.tokens || !config.tokens.include || config.tokens.include.length === 0) {
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
1011
|
+
try {
|
|
1012
|
+
const { discoverTokenFiles } = await import("./discovery-AKGA6CJD.js");
|
|
1013
|
+
const discovered = await discoverTokenFiles(projectRoot);
|
|
1014
|
+
if (discovered.length > 0) {
|
|
1015
|
+
config.tokens = {
|
|
1016
|
+
...config.tokens,
|
|
1017
|
+
include: discovered.map((f) => f.relativePath)
|
|
1018
|
+
};
|
|
1019
|
+
} else {
|
|
1020
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1021
|
+
res.end(JSON.stringify({
|
|
1022
|
+
error: "No token files found",
|
|
1023
|
+
suggestion: "Add 'tokens' config to fragments.config.ts or add token files matching default patterns (_variables.scss, tokens.scss, etc.)"
|
|
1024
|
+
}));
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
} catch {
|
|
1028
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1029
|
+
res.end(JSON.stringify({
|
|
1030
|
+
error: "No token configuration found and auto-discovery failed",
|
|
1031
|
+
suggestion: "Add 'tokens' config to fragments.config.ts to enable fix generation"
|
|
1032
|
+
}));
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
893
1035
|
}
|
|
894
1036
|
const loadedFragments = await loadFragmentsForRender(fragmentFiles, projectRoot);
|
|
895
1037
|
const fragmentInfo = findFragmentByName(component, loadedFragments);
|
|
@@ -904,7 +1046,7 @@ function fragmentsPlugin(options) {
|
|
|
904
1046
|
const {
|
|
905
1047
|
getSharedTokenRegistry,
|
|
906
1048
|
generateTokenPatches
|
|
907
|
-
} = await import("./service-
|
|
1049
|
+
} = await import("./service-CFFBHW4X.js");
|
|
908
1050
|
const registry = getSharedTokenRegistry();
|
|
909
1051
|
if (!registry.isInitialized()) {
|
|
910
1052
|
await registry.initialize(config.tokens, projectRoot);
|
|
@@ -913,10 +1055,56 @@ function fragmentsPlugin(options) {
|
|
|
913
1055
|
(f) => f.relativePath === fragmentInfo.path
|
|
914
1056
|
);
|
|
915
1057
|
const sourceFile = fragmentFile?.relativePath || `${component}.tsx`;
|
|
1058
|
+
let styleDiffs = [];
|
|
1059
|
+
if (fragmentFile) {
|
|
1060
|
+
try {
|
|
1061
|
+
const renderScript = generateRenderScript(
|
|
1062
|
+
fragmentFile.absolutePath,
|
|
1063
|
+
fragmentInfo.name,
|
|
1064
|
+
{}
|
|
1065
|
+
);
|
|
1066
|
+
const requestId = Date.now().toString(36) + Math.random().toString(36).slice(2);
|
|
1067
|
+
pendingRenders.set(requestId, {
|
|
1068
|
+
script: renderScript,
|
|
1069
|
+
viewport: { width: 800, height: 600 }
|
|
1070
|
+
});
|
|
1071
|
+
const address = _server.httpServer?.address();
|
|
1072
|
+
const port = typeof address === "object" && address ? address.port : 6006;
|
|
1073
|
+
const { computedStyles } = await captureRenderWithStyles(
|
|
1074
|
+
`http://localhost:${port}/fragments/__render__/${requestId}`,
|
|
1075
|
+
{ width: 800, height: 600 },
|
|
1076
|
+
true
|
|
1077
|
+
);
|
|
1078
|
+
pendingRenders.delete(requestId);
|
|
1079
|
+
if (computedStyles) {
|
|
1080
|
+
const tokenValues = /* @__PURE__ */ new Map();
|
|
1081
|
+
const allTokens = registry.getAllTokens();
|
|
1082
|
+
for (const t of allTokens) {
|
|
1083
|
+
if (t.resolvedValue) {
|
|
1084
|
+
tokenValues.set(t.resolvedValue, t.name);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
for (const [prop, value] of Object.entries(computedStyles)) {
|
|
1088
|
+
if (!value || value === "transparent" || value === "rgba(0, 0, 0, 0)") continue;
|
|
1089
|
+
const matchesToken = tokenValues.has(value);
|
|
1090
|
+
if (!matchesToken) {
|
|
1091
|
+
styleDiffs.push({
|
|
1092
|
+
property: prop,
|
|
1093
|
+
figma: value,
|
|
1094
|
+
// Using rendered as "expected" since we have no Figma
|
|
1095
|
+
rendered: value,
|
|
1096
|
+
match: false
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
} catch (renderErr) {
|
|
1102
|
+
console.warn("[Fragments] Could not render for style extraction:", renderErr);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
916
1105
|
const result = generateTokenPatches(
|
|
917
1106
|
component,
|
|
918
|
-
|
|
919
|
-
// Would be populated by actual style diffs
|
|
1107
|
+
styleDiffs,
|
|
920
1108
|
registry,
|
|
921
1109
|
{ sourceFile }
|
|
922
1110
|
);
|
|
@@ -936,6 +1124,148 @@ function fragmentsPlugin(options) {
|
|
|
936
1124
|
}
|
|
937
1125
|
return;
|
|
938
1126
|
}
|
|
1127
|
+
if (req.url === "/fragments/a11y" && req.method === "POST") {
|
|
1128
|
+
try {
|
|
1129
|
+
const body = await parseJsonBody(req);
|
|
1130
|
+
const { component, variant: variantName, standard = "AA" } = body;
|
|
1131
|
+
if (!component) {
|
|
1132
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1133
|
+
res.end(
|
|
1134
|
+
JSON.stringify({ error: "Missing required field: component" })
|
|
1135
|
+
);
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1138
|
+
const loadedFragments = await loadFragmentsForRender(
|
|
1139
|
+
fragmentFiles,
|
|
1140
|
+
projectRoot
|
|
1141
|
+
);
|
|
1142
|
+
const fragmentInfo = findFragmentByName(component, loadedFragments);
|
|
1143
|
+
if (!fragmentInfo) {
|
|
1144
|
+
const available = getAvailableComponents(loadedFragments);
|
|
1145
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1146
|
+
res.end(
|
|
1147
|
+
JSON.stringify({
|
|
1148
|
+
error: `Component '${component}' not found. Available: ${available.join(", ")}`
|
|
1149
|
+
})
|
|
1150
|
+
);
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
const fragmentFile = fragmentFiles.find(
|
|
1154
|
+
(f) => f.relativePath === fragmentInfo.path
|
|
1155
|
+
);
|
|
1156
|
+
if (!fragmentFile) {
|
|
1157
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1158
|
+
res.end(
|
|
1159
|
+
JSON.stringify({ error: "Could not resolve fragment file path" })
|
|
1160
|
+
);
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
const variantNames = [];
|
|
1164
|
+
if (variantName) {
|
|
1165
|
+
variantNames.push(variantName);
|
|
1166
|
+
} else {
|
|
1167
|
+
const fullData = await loadFullFragmentData(projectRoot);
|
|
1168
|
+
const fragmentData = fullData ? Object.values(fullData.fragments).find(
|
|
1169
|
+
(f) => f.meta.name.toLowerCase() === component.toLowerCase()
|
|
1170
|
+
) : null;
|
|
1171
|
+
if (fragmentData && fragmentData.variants?.length > 0) {
|
|
1172
|
+
for (const v of fragmentData.variants) {
|
|
1173
|
+
variantNames.push(v.name);
|
|
1174
|
+
}
|
|
1175
|
+
} else {
|
|
1176
|
+
variantNames.push("Default");
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
const address = _server.httpServer?.address();
|
|
1180
|
+
const port = typeof address === "object" && address ? address.port : 6006;
|
|
1181
|
+
const results = [];
|
|
1182
|
+
for (const vName of variantNames) {
|
|
1183
|
+
const a11yScript = generateA11yRenderScript(
|
|
1184
|
+
fragmentFile.absolutePath,
|
|
1185
|
+
fragmentInfo.name,
|
|
1186
|
+
vName === "Default" && !variantName ? void 0 : vName
|
|
1187
|
+
);
|
|
1188
|
+
const requestId = Date.now().toString(36) + Math.random().toString(36).slice(2);
|
|
1189
|
+
pendingRenders.set(requestId, {
|
|
1190
|
+
script: a11yScript,
|
|
1191
|
+
viewport: { width: 800, height: 600 }
|
|
1192
|
+
});
|
|
1193
|
+
try {
|
|
1194
|
+
const auditResult = await captureA11yAudit(
|
|
1195
|
+
`http://localhost:${port}/fragments/__render__/${requestId}`,
|
|
1196
|
+
{ width: 800, height: 600 }
|
|
1197
|
+
);
|
|
1198
|
+
let critical = 0;
|
|
1199
|
+
let serious = 0;
|
|
1200
|
+
let moderate = 0;
|
|
1201
|
+
let minor = 0;
|
|
1202
|
+
for (const violation of auditResult.violations ?? []) {
|
|
1203
|
+
switch (violation.impact) {
|
|
1204
|
+
case "critical":
|
|
1205
|
+
critical++;
|
|
1206
|
+
break;
|
|
1207
|
+
case "serious":
|
|
1208
|
+
serious++;
|
|
1209
|
+
break;
|
|
1210
|
+
case "moderate":
|
|
1211
|
+
moderate++;
|
|
1212
|
+
break;
|
|
1213
|
+
case "minor":
|
|
1214
|
+
minor++;
|
|
1215
|
+
break;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
results.push({
|
|
1219
|
+
variant: vName,
|
|
1220
|
+
violations: auditResult.violations?.length ?? 0,
|
|
1221
|
+
passes: auditResult.passes?.length ?? 0,
|
|
1222
|
+
incomplete: auditResult.incomplete?.length ?? 0,
|
|
1223
|
+
summary: {
|
|
1224
|
+
total: critical + serious + moderate + minor,
|
|
1225
|
+
critical,
|
|
1226
|
+
serious,
|
|
1227
|
+
moderate,
|
|
1228
|
+
minor
|
|
1229
|
+
},
|
|
1230
|
+
violationDetails: (auditResult.violations ?? []).map((v) => ({
|
|
1231
|
+
id: v.id,
|
|
1232
|
+
impact: v.impact,
|
|
1233
|
+
description: v.description,
|
|
1234
|
+
helpUrl: v.helpUrl,
|
|
1235
|
+
nodes: v.nodes.length
|
|
1236
|
+
}))
|
|
1237
|
+
});
|
|
1238
|
+
} catch (err) {
|
|
1239
|
+
results.push({
|
|
1240
|
+
variant: vName,
|
|
1241
|
+
violations: 0,
|
|
1242
|
+
passes: 0,
|
|
1243
|
+
incomplete: 0,
|
|
1244
|
+
summary: {
|
|
1245
|
+
total: 0,
|
|
1246
|
+
critical: 0,
|
|
1247
|
+
serious: 0,
|
|
1248
|
+
moderate: 0,
|
|
1249
|
+
minor: 0
|
|
1250
|
+
}
|
|
1251
|
+
});
|
|
1252
|
+
} finally {
|
|
1253
|
+
pendingRenders.delete(requestId);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
res.setHeader("Content-Type", "application/json");
|
|
1257
|
+
res.end(JSON.stringify({ results }));
|
|
1258
|
+
} catch (error) {
|
|
1259
|
+
console.error("[Fragments] Error running a11y audit:", error);
|
|
1260
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1261
|
+
res.end(
|
|
1262
|
+
JSON.stringify({
|
|
1263
|
+
error: error instanceof Error ? error.message : "A11y audit failed"
|
|
1264
|
+
})
|
|
1265
|
+
);
|
|
1266
|
+
}
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
939
1269
|
if (req.url?.startsWith("/fragments/preview")) {
|
|
940
1270
|
if (req.url === "/fragments/preview") {
|
|
941
1271
|
res.writeHead(302, { Location: "/fragments/preview/" });
|
|
@@ -1356,6 +1686,41 @@ async function loadFragmentsForRender(fragmentFiles, configDir) {
|
|
|
1356
1686
|
};
|
|
1357
1687
|
});
|
|
1358
1688
|
}
|
|
1689
|
+
async function loadFullFragmentData(configDir) {
|
|
1690
|
+
const { join: join2 } = await import("path");
|
|
1691
|
+
const fragmentsJsonPath = join2(configDir, BRAND.outFile);
|
|
1692
|
+
try {
|
|
1693
|
+
const content = await readFile(fragmentsJsonPath, "utf-8");
|
|
1694
|
+
return JSON.parse(content);
|
|
1695
|
+
} catch {
|
|
1696
|
+
return null;
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
async function captureA11yAudit(url, viewport) {
|
|
1700
|
+
const { pool } = await getSharedRenderPool();
|
|
1701
|
+
const ctx = await pool.acquire();
|
|
1702
|
+
const page = await ctx.newPage();
|
|
1703
|
+
try {
|
|
1704
|
+
await page.setViewportSize(viewport);
|
|
1705
|
+
await page.goto(url, { waitUntil: "networkidle" });
|
|
1706
|
+
await page.waitForFunction(
|
|
1707
|
+
() => window.__RENDER_READY__ === true,
|
|
1708
|
+
{ timeout: 15e3 }
|
|
1709
|
+
);
|
|
1710
|
+
const error = await page.evaluate(() => window.__AXE_ERROR__);
|
|
1711
|
+
if (error) {
|
|
1712
|
+
throw new Error(`A11y audit error: ${error}`);
|
|
1713
|
+
}
|
|
1714
|
+
const results = await page.evaluate(() => window.__AXE_RESULTS__);
|
|
1715
|
+
if (!results) {
|
|
1716
|
+
throw new Error("Axe results not available \u2014 axe-core may not be installed");
|
|
1717
|
+
}
|
|
1718
|
+
return results;
|
|
1719
|
+
} finally {
|
|
1720
|
+
await page.close();
|
|
1721
|
+
pool.release(ctx);
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1359
1724
|
async function serveRenderHTML(res, server, renderScript) {
|
|
1360
1725
|
const viewerRoot2 = viewerAssetsRoot;
|
|
1361
1726
|
try {
|
|
@@ -1566,7 +1931,7 @@ async function loadFullFragmentForCompare(_server, _fragmentFiles, componentName
|
|
|
1566
1931
|
}
|
|
1567
1932
|
}
|
|
1568
1933
|
async function compareImages(image1Base64, image2Base64, threshold) {
|
|
1569
|
-
const { DiffEngine, base64UrlToBuffer, bufferToBase64Url } = await import("./service-
|
|
1934
|
+
const { DiffEngine, base64UrlToBuffer, bufferToBase64Url } = await import("./service-CFFBHW4X.js");
|
|
1570
1935
|
const { PNG } = await import("pngjs");
|
|
1571
1936
|
const buffer1 = base64UrlToBuffer(image1Base64);
|
|
1572
1937
|
const buffer2 = base64UrlToBuffer(image2Base64);
|
|
@@ -1819,4 +2184,4 @@ export {
|
|
|
1819
2184
|
createDevServer,
|
|
1820
2185
|
fragmentsPlugin
|
|
1821
2186
|
};
|
|
1822
|
-
//# sourceMappingURL=viewer-
|
|
2187
|
+
//# sourceMappingURL=viewer-QTR7QJMM.js.map
|