@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.
- package/README.md +2 -0
- package/dist/bin.js +25 -17
- package/dist/bin.js.map +1 -1
- package/dist/chunk-AWYCDRPG.js +272 -0
- package/dist/chunk-AWYCDRPG.js.map +1 -0
- package/dist/chunk-EKLMXTWU.js +80 -0
- package/dist/chunk-EKLMXTWU.js.map +1 -0
- package/dist/{chunk-DH4ETVSM.js → chunk-NEJ2FBTN.js} +9 -7
- package/dist/{chunk-DH4ETVSM.js.map → chunk-NEJ2FBTN.js.map} +1 -1
- package/dist/{chunk-GHYYFAQN.js → chunk-P33AKQJW.js} +1 -76
- package/dist/chunk-P33AKQJW.js.map +1 -0
- package/dist/{chunk-3T6QL7IY.js → chunk-R6IZZSE7.js} +23 -275
- package/dist/chunk-R6IZZSE7.js.map +1 -0
- package/dist/{chunk-7KUSBMI4.js → chunk-S56I5FST.js} +174 -45
- package/dist/chunk-S56I5FST.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-Z4RDDFVR.js +28 -0
- package/dist/{generate-GP6ZLAQB.js → generate-23VLX7QN.js} +7 -4
- package/dist/{generate-GP6ZLAQB.js.map → generate-23VLX7QN.js.map} +1 -1
- package/dist/index.js +15 -11
- package/dist/index.js.map +1 -1
- package/dist/{init-W72WBSU2.js → init-VYVYMVHH.js} +10 -6
- package/dist/{init-W72WBSU2.js.map → init-VYVYMVHH.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-FZR6YVI5.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-VTD7R6G2.js} +8 -4
- package/dist/{test-3YRYQRGV.js.map → test-VTD7R6G2.js.map} +1 -1
- package/dist/{tokens-IXSQHPQK.js → tokens-7JA5CPDL.js} +10 -7
- package/dist/{tokens-IXSQHPQK.js.map → tokens-7JA5CPDL.js.map} +1 -1
- package/dist/{viewer-K42REJU2.js → viewer-WXTDDQGK.js} +403 -26
- package/dist/viewer-WXTDDQGK.js.map +1 -0
- package/package.json +5 -1
- package/src/build.ts +57 -5
- package/src/commands/init.ts +6 -2
- package/src/core/__tests__/token-resolver.test.ts +82 -0
- package/src/core/discovery.ts +7 -1
- package/src/core/token-parser.ts +102 -0
- package/src/core/token-resolver.ts +155 -0
- package/src/migrate/detect.ts +4 -0
- package/src/service/__tests__/patch-generator.test.ts +2 -2
- package/src/service/patch-generator.ts +8 -1
- package/src/viewer/components/App.tsx +63 -2
- package/src/viewer/components/Layout.tsx +1 -1
- package/src/viewer/components/LeftSidebar.tsx +35 -77
- package/src/viewer/preview-frame.html +1 -1
- package/src/viewer/render-utils.ts +141 -0
- package/src/viewer/styles/globals.css +2 -1
- package/src/viewer/vite-plugin.ts +399 -24
- 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-Z4RDDFVR.js.map} +0 -0
- /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-
|
|
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-
|
|
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({
|
|
@@ -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 =
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
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-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
2199
|
+
//# sourceMappingURL=viewer-WXTDDQGK.js.map
|