@agent-scope/cli 1.18.1 → 1.19.0
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/cli.js +608 -233
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +414 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +415 -46
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/dist/index.cjs
CHANGED
|
@@ -541,10 +541,10 @@ async function getCompiledCssForClasses(cwd, classes) {
|
|
|
541
541
|
return build3(deduped);
|
|
542
542
|
}
|
|
543
543
|
async function compileGlobalCssFile(cssFilePath, cwd) {
|
|
544
|
-
const { existsSync:
|
|
544
|
+
const { existsSync: existsSync16, readFileSync: readFileSync14 } = await import('fs');
|
|
545
545
|
const { createRequire: createRequire3 } = await import('module');
|
|
546
|
-
if (!
|
|
547
|
-
const raw =
|
|
546
|
+
if (!existsSync16(cssFilePath)) return null;
|
|
547
|
+
const raw = readFileSync14(cssFilePath, "utf-8");
|
|
548
548
|
const needsCompile = /@tailwind|@import\s+['"]tailwindcss/.test(raw);
|
|
549
549
|
if (!needsCompile) {
|
|
550
550
|
return raw;
|
|
@@ -1013,6 +1013,157 @@ function createCiCommand() {
|
|
|
1013
1013
|
}
|
|
1014
1014
|
);
|
|
1015
1015
|
}
|
|
1016
|
+
function collectSourceFiles(dir) {
|
|
1017
|
+
if (!fs.existsSync(dir)) return [];
|
|
1018
|
+
const results = [];
|
|
1019
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
1020
|
+
const full = path.join(dir, entry.name);
|
|
1021
|
+
if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== ".reactscope") {
|
|
1022
|
+
results.push(...collectSourceFiles(full));
|
|
1023
|
+
} else if (entry.isFile() && /\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
1024
|
+
results.push(full);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
return results;
|
|
1028
|
+
}
|
|
1029
|
+
function checkConfig(cwd) {
|
|
1030
|
+
const configPath = path.resolve(cwd, "reactscope.config.json");
|
|
1031
|
+
if (!fs.existsSync(configPath)) {
|
|
1032
|
+
return {
|
|
1033
|
+
name: "config",
|
|
1034
|
+
status: "error",
|
|
1035
|
+
message: "reactscope.config.json not found \u2014 run `scope init`"
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
try {
|
|
1039
|
+
JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
1040
|
+
return { name: "config", status: "ok", message: "reactscope.config.json valid" };
|
|
1041
|
+
} catch {
|
|
1042
|
+
return { name: "config", status: "error", message: "reactscope.config.json is not valid JSON" };
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
function checkTokens(cwd) {
|
|
1046
|
+
const configPath = path.resolve(cwd, "reactscope.config.json");
|
|
1047
|
+
let tokensPath = path.resolve(cwd, "reactscope.tokens.json");
|
|
1048
|
+
if (fs.existsSync(configPath)) {
|
|
1049
|
+
try {
|
|
1050
|
+
const cfg = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
1051
|
+
if (cfg.tokens?.file) tokensPath = path.resolve(cwd, cfg.tokens.file);
|
|
1052
|
+
} catch {
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
if (!fs.existsSync(tokensPath)) {
|
|
1056
|
+
return {
|
|
1057
|
+
name: "tokens",
|
|
1058
|
+
status: "warn",
|
|
1059
|
+
message: `Token file not found at ${tokensPath} \u2014 run \`scope init\``
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
try {
|
|
1063
|
+
const raw = JSON.parse(fs.readFileSync(tokensPath, "utf-8"));
|
|
1064
|
+
if (!raw.version) {
|
|
1065
|
+
return { name: "tokens", status: "warn", message: "Token file is missing a `version` field" };
|
|
1066
|
+
}
|
|
1067
|
+
return { name: "tokens", status: "ok", message: "Token file valid" };
|
|
1068
|
+
} catch {
|
|
1069
|
+
return { name: "tokens", status: "error", message: "Token file is not valid JSON" };
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
function checkGlobalCss(cwd) {
|
|
1073
|
+
const configPath = path.resolve(cwd, "reactscope.config.json");
|
|
1074
|
+
let globalCss = [];
|
|
1075
|
+
if (fs.existsSync(configPath)) {
|
|
1076
|
+
try {
|
|
1077
|
+
const cfg = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
1078
|
+
globalCss = cfg.components?.wrappers?.globalCSS ?? [];
|
|
1079
|
+
} catch {
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
if (globalCss.length === 0) {
|
|
1083
|
+
return {
|
|
1084
|
+
name: "globalCSS",
|
|
1085
|
+
status: "warn",
|
|
1086
|
+
message: "No globalCSS configured \u2014 Tailwind styles won't apply to renders. Add `components.wrappers.globalCSS` to reactscope.config.json"
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
const missing = globalCss.filter((f) => !fs.existsSync(path.resolve(cwd, f)));
|
|
1090
|
+
if (missing.length > 0) {
|
|
1091
|
+
return {
|
|
1092
|
+
name: "globalCSS",
|
|
1093
|
+
status: "error",
|
|
1094
|
+
message: `globalCSS file(s) not found: ${missing.join(", ")}`
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
return {
|
|
1098
|
+
name: "globalCSS",
|
|
1099
|
+
status: "ok",
|
|
1100
|
+
message: `${globalCss.length} globalCSS file(s) present`
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
function checkManifest(cwd) {
|
|
1104
|
+
const manifestPath = path.resolve(cwd, ".reactscope", "manifest.json");
|
|
1105
|
+
if (!fs.existsSync(manifestPath)) {
|
|
1106
|
+
return {
|
|
1107
|
+
name: "manifest",
|
|
1108
|
+
status: "warn",
|
|
1109
|
+
message: "Manifest not found \u2014 run `scope manifest generate`"
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
const manifestMtime = fs.statSync(manifestPath).mtimeMs;
|
|
1113
|
+
const sourceDir = path.resolve(cwd, "src");
|
|
1114
|
+
const sourceFiles = collectSourceFiles(sourceDir);
|
|
1115
|
+
const stale = sourceFiles.filter((f) => fs.statSync(f).mtimeMs > manifestMtime);
|
|
1116
|
+
if (stale.length > 0) {
|
|
1117
|
+
return {
|
|
1118
|
+
name: "manifest",
|
|
1119
|
+
status: "warn",
|
|
1120
|
+
message: `Manifest may be stale \u2014 ${stale.length} source file(s) modified since last generate. Run \`scope manifest generate\``
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
return { name: "manifest", status: "ok", message: "Manifest present and up to date" };
|
|
1124
|
+
}
|
|
1125
|
+
var ICONS = { ok: "\u2713", warn: "!", error: "\u2717" };
|
|
1126
|
+
function formatCheck(check) {
|
|
1127
|
+
return ` [${ICONS[check.status]}] ${check.name.padEnd(12)} ${check.message}`;
|
|
1128
|
+
}
|
|
1129
|
+
function createDoctorCommand() {
|
|
1130
|
+
return new commander.Command("doctor").description("Check the health of your Scope setup (config, tokens, CSS, manifest)").option("--json", "Emit structured JSON output", false).action((opts) => {
|
|
1131
|
+
const cwd = process.cwd();
|
|
1132
|
+
const checks = [
|
|
1133
|
+
checkConfig(cwd),
|
|
1134
|
+
checkTokens(cwd),
|
|
1135
|
+
checkGlobalCss(cwd),
|
|
1136
|
+
checkManifest(cwd)
|
|
1137
|
+
];
|
|
1138
|
+
const errors = checks.filter((c) => c.status === "error").length;
|
|
1139
|
+
const warnings = checks.filter((c) => c.status === "warn").length;
|
|
1140
|
+
if (opts.json) {
|
|
1141
|
+
process.stdout.write(
|
|
1142
|
+
`${JSON.stringify({ passed: checks.length - errors - warnings, warnings, errors, checks }, null, 2)}
|
|
1143
|
+
`
|
|
1144
|
+
);
|
|
1145
|
+
if (errors > 0) process.exit(1);
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
process.stdout.write("\nScope Doctor\n");
|
|
1149
|
+
process.stdout.write("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
1150
|
+
for (const check of checks) process.stdout.write(`${formatCheck(check)}
|
|
1151
|
+
`);
|
|
1152
|
+
process.stdout.write("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
1153
|
+
if (errors > 0) {
|
|
1154
|
+
process.stdout.write(` ${errors} error(s), ${warnings} warning(s)
|
|
1155
|
+
|
|
1156
|
+
`);
|
|
1157
|
+
process.exit(1);
|
|
1158
|
+
} else if (warnings > 0) {
|
|
1159
|
+
process.stdout.write(` ${warnings} warning(s) \u2014 everything works but could be better
|
|
1160
|
+
|
|
1161
|
+
`);
|
|
1162
|
+
} else {
|
|
1163
|
+
process.stdout.write(" All checks passed!\n\n");
|
|
1164
|
+
}
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1016
1167
|
function hasConfigFile(dir, stem) {
|
|
1017
1168
|
if (!fs.existsSync(dir)) return false;
|
|
1018
1169
|
try {
|
|
@@ -1223,9 +1374,9 @@ function createRL() {
|
|
|
1223
1374
|
});
|
|
1224
1375
|
}
|
|
1225
1376
|
async function ask(rl, question) {
|
|
1226
|
-
return new Promise((
|
|
1377
|
+
return new Promise((resolve19) => {
|
|
1227
1378
|
rl.question(question, (answer) => {
|
|
1228
|
-
|
|
1379
|
+
resolve19(answer.trim());
|
|
1229
1380
|
});
|
|
1230
1381
|
});
|
|
1231
1382
|
}
|
|
@@ -1300,7 +1451,7 @@ function extractTailwindTokens(tokenSources) {
|
|
|
1300
1451
|
}
|
|
1301
1452
|
}
|
|
1302
1453
|
if (Object.keys(colorTokens).length > 0) {
|
|
1303
|
-
tokens
|
|
1454
|
+
tokens.color = colorTokens;
|
|
1304
1455
|
}
|
|
1305
1456
|
}
|
|
1306
1457
|
}
|
|
@@ -1313,7 +1464,7 @@ function extractTailwindTokens(tokenSources) {
|
|
|
1313
1464
|
for (const [key, val] of Object.entries(spacingValues)) {
|
|
1314
1465
|
spacingTokens[key] = { value: val, type: "dimension" };
|
|
1315
1466
|
}
|
|
1316
|
-
tokens
|
|
1467
|
+
tokens.spacing = spacingTokens;
|
|
1317
1468
|
}
|
|
1318
1469
|
}
|
|
1319
1470
|
const fontFamilyMatch = raw.match(/fontFamily\s*:\s*\{([\s\S]*?)\n\s*\}/);
|
|
@@ -1326,7 +1477,7 @@ function extractTailwindTokens(tokenSources) {
|
|
|
1326
1477
|
}
|
|
1327
1478
|
}
|
|
1328
1479
|
if (Object.keys(fontTokens).length > 0) {
|
|
1329
|
-
tokens
|
|
1480
|
+
tokens.font = fontTokens;
|
|
1330
1481
|
}
|
|
1331
1482
|
}
|
|
1332
1483
|
const borderRadiusMatch = raw.match(/borderRadius\s*:\s*\{([\s\S]*?)\n\s*\}/);
|
|
@@ -1337,7 +1488,7 @@ function extractTailwindTokens(tokenSources) {
|
|
|
1337
1488
|
for (const [key, val] of Object.entries(radiusValues)) {
|
|
1338
1489
|
radiusTokens[key] = { value: val, type: "dimension" };
|
|
1339
1490
|
}
|
|
1340
|
-
tokens
|
|
1491
|
+
tokens.radius = radiusTokens;
|
|
1341
1492
|
}
|
|
1342
1493
|
}
|
|
1343
1494
|
return Object.keys(tokens).length > 0 ? tokens : null;
|
|
@@ -1456,7 +1607,28 @@ async function runInit(options) {
|
|
|
1456
1607
|
process.stdout.write(` ${p}
|
|
1457
1608
|
`);
|
|
1458
1609
|
}
|
|
1459
|
-
process.stdout.write("\n
|
|
1610
|
+
process.stdout.write("\n Scanning components...\n");
|
|
1611
|
+
try {
|
|
1612
|
+
const manifestConfig = {
|
|
1613
|
+
include: config.components.include,
|
|
1614
|
+
rootDir
|
|
1615
|
+
};
|
|
1616
|
+
const manifest$1 = await manifest.generateManifest(manifestConfig);
|
|
1617
|
+
const manifestCount = Object.keys(manifest$1.components).length;
|
|
1618
|
+
const manifestOutPath = path.join(rootDir, config.output.dir, "manifest.json");
|
|
1619
|
+
fs.mkdirSync(path.join(rootDir, config.output.dir), { recursive: true });
|
|
1620
|
+
fs.writeFileSync(manifestOutPath, `${JSON.stringify(manifest$1, null, 2)}
|
|
1621
|
+
`);
|
|
1622
|
+
process.stdout.write(
|
|
1623
|
+
` Found ${manifestCount} component(s) \u2014 manifest written to ${manifestOutPath}
|
|
1624
|
+
`
|
|
1625
|
+
);
|
|
1626
|
+
} catch {
|
|
1627
|
+
process.stdout.write(
|
|
1628
|
+
" (manifest generate skipped \u2014 run `scope manifest generate` manually)\n"
|
|
1629
|
+
);
|
|
1630
|
+
}
|
|
1631
|
+
process.stdout.write("\n");
|
|
1460
1632
|
return {
|
|
1461
1633
|
success: true,
|
|
1462
1634
|
message: "Project initialised successfully.",
|
|
@@ -3370,7 +3542,7 @@ function buildRenderer(filePath, componentName, viewportWidth, viewportHeight, g
|
|
|
3370
3542
|
}
|
|
3371
3543
|
});
|
|
3372
3544
|
return [...set];
|
|
3373
|
-
});
|
|
3545
|
+
}) ?? [];
|
|
3374
3546
|
const projectCss2 = await getCompiledCssForClasses(rootDir, classes);
|
|
3375
3547
|
if (projectCss2 != null && projectCss2.length > 0) {
|
|
3376
3548
|
await page.addStyleTag({ content: projectCss2 });
|
|
@@ -3383,49 +3555,147 @@ function buildRenderer(filePath, componentName, viewportWidth, viewportHeight, g
|
|
|
3383
3555
|
`Component "${componentName}" rendered with zero bounding box \u2014 it may be invisible or not mounted`
|
|
3384
3556
|
);
|
|
3385
3557
|
}
|
|
3386
|
-
const PAD =
|
|
3387
|
-
const MIN_W = 320;
|
|
3388
|
-
const MIN_H = 200;
|
|
3558
|
+
const PAD = 8;
|
|
3389
3559
|
const clipX = Math.max(0, boundingBox.x - PAD);
|
|
3390
3560
|
const clipY = Math.max(0, boundingBox.y - PAD);
|
|
3391
3561
|
const rawW = boundingBox.width + PAD * 2;
|
|
3392
3562
|
const rawH = boundingBox.height + PAD * 2;
|
|
3393
|
-
const
|
|
3394
|
-
const
|
|
3395
|
-
const safeW = Math.min(clipW, viewportWidth - clipX);
|
|
3396
|
-
const safeH = Math.min(clipH, viewportHeight - clipY);
|
|
3563
|
+
const safeW = Math.min(rawW, viewportWidth - clipX);
|
|
3564
|
+
const safeH = Math.min(rawH, viewportHeight - clipY);
|
|
3397
3565
|
const screenshot = await page.screenshot({
|
|
3398
3566
|
clip: { x: clipX, y: clipY, width: safeW, height: safeH },
|
|
3399
3567
|
type: "png"
|
|
3400
3568
|
});
|
|
3569
|
+
const STYLE_PROPS = [
|
|
3570
|
+
"display",
|
|
3571
|
+
"width",
|
|
3572
|
+
"height",
|
|
3573
|
+
"color",
|
|
3574
|
+
"backgroundColor",
|
|
3575
|
+
"fontSize",
|
|
3576
|
+
"fontFamily",
|
|
3577
|
+
"fontWeight",
|
|
3578
|
+
"lineHeight",
|
|
3579
|
+
"padding",
|
|
3580
|
+
"paddingTop",
|
|
3581
|
+
"paddingRight",
|
|
3582
|
+
"paddingBottom",
|
|
3583
|
+
"paddingLeft",
|
|
3584
|
+
"margin",
|
|
3585
|
+
"marginTop",
|
|
3586
|
+
"marginRight",
|
|
3587
|
+
"marginBottom",
|
|
3588
|
+
"marginLeft",
|
|
3589
|
+
"gap",
|
|
3590
|
+
"borderRadius",
|
|
3591
|
+
"borderWidth",
|
|
3592
|
+
"borderColor",
|
|
3593
|
+
"borderStyle",
|
|
3594
|
+
"boxShadow",
|
|
3595
|
+
"opacity",
|
|
3596
|
+
"position",
|
|
3597
|
+
"flexDirection",
|
|
3598
|
+
"alignItems",
|
|
3599
|
+
"justifyContent",
|
|
3600
|
+
"overflow"
|
|
3601
|
+
];
|
|
3602
|
+
const _domResult = await page.evaluate(
|
|
3603
|
+
(args) => {
|
|
3604
|
+
let count = 0;
|
|
3605
|
+
const styles = {};
|
|
3606
|
+
function captureStyles(el, id, propList) {
|
|
3607
|
+
const computed = window.getComputedStyle(el);
|
|
3608
|
+
const out = {};
|
|
3609
|
+
for (const prop of propList) {
|
|
3610
|
+
const val = computed[prop] ?? "";
|
|
3611
|
+
if (val && val !== "none" && val !== "normal" && val !== "auto") out[prop] = val;
|
|
3612
|
+
}
|
|
3613
|
+
styles[id] = out;
|
|
3614
|
+
}
|
|
3615
|
+
function walk(node) {
|
|
3616
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
3617
|
+
return {
|
|
3618
|
+
tag: "#text",
|
|
3619
|
+
attrs: {},
|
|
3620
|
+
text: node.textContent?.trim() ?? "",
|
|
3621
|
+
children: []
|
|
3622
|
+
};
|
|
3623
|
+
}
|
|
3624
|
+
const el = node;
|
|
3625
|
+
const id = count++;
|
|
3626
|
+
captureStyles(el, id, args.props);
|
|
3627
|
+
const attrs = {};
|
|
3628
|
+
for (const attr of Array.from(el.attributes)) {
|
|
3629
|
+
attrs[attr.name] = attr.value;
|
|
3630
|
+
}
|
|
3631
|
+
const children = Array.from(el.childNodes).filter(
|
|
3632
|
+
(n) => n.nodeType === Node.ELEMENT_NODE || n.nodeType === Node.TEXT_NODE && (n.textContent?.trim() ?? "").length > 0
|
|
3633
|
+
).map(walk);
|
|
3634
|
+
return { tag: el.tagName.toLowerCase(), attrs, nodeId: id, children };
|
|
3635
|
+
}
|
|
3636
|
+
const root = document.querySelector(args.sel);
|
|
3637
|
+
if (!root)
|
|
3638
|
+
return {
|
|
3639
|
+
tree: { tag: "div", attrs: {}, children: [] },
|
|
3640
|
+
elementCount: 0,
|
|
3641
|
+
nodeStyles: {}
|
|
3642
|
+
};
|
|
3643
|
+
return { tree: walk(root), elementCount: count, nodeStyles: styles };
|
|
3644
|
+
},
|
|
3645
|
+
{ sel: "[data-reactscope-root] > *", props: STYLE_PROPS }
|
|
3646
|
+
);
|
|
3647
|
+
const domTree = _domResult?.tree ?? { tag: "div", attrs: {}, children: [] };
|
|
3648
|
+
const elementCount = _domResult?.elementCount ?? 0;
|
|
3649
|
+
const nodeStyles = _domResult?.nodeStyles ?? {};
|
|
3401
3650
|
const computedStyles = {};
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
"fontFamily",
|
|
3415
|
-
"padding",
|
|
3416
|
-
"margin"
|
|
3417
|
-
]) {
|
|
3418
|
-
out[prop] = computed.getPropertyValue(prop);
|
|
3651
|
+
if (nodeStyles[0]) computedStyles["[data-reactscope-root] > *"] = nodeStyles[0];
|
|
3652
|
+
for (const [nodeId, styles] of Object.entries(nodeStyles)) {
|
|
3653
|
+
computedStyles[`#node-${nodeId}`] = styles;
|
|
3654
|
+
}
|
|
3655
|
+
const dom = {
|
|
3656
|
+
tree: domTree,
|
|
3657
|
+
elementCount,
|
|
3658
|
+
boundingBox: {
|
|
3659
|
+
x: boundingBox.x,
|
|
3660
|
+
y: boundingBox.y,
|
|
3661
|
+
width: boundingBox.width,
|
|
3662
|
+
height: boundingBox.height
|
|
3419
3663
|
}
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3664
|
+
};
|
|
3665
|
+
const a11yInfo = await page.evaluate((sel) => {
|
|
3666
|
+
const wrapper = document.querySelector(sel);
|
|
3667
|
+
const el = wrapper?.firstElementChild ?? wrapper;
|
|
3668
|
+
if (!el) return { role: "generic", name: "" };
|
|
3669
|
+
return {
|
|
3670
|
+
role: el.getAttribute("role") ?? el.tagName.toLowerCase() ?? "generic",
|
|
3671
|
+
name: el.getAttribute("aria-label") ?? el.getAttribute("aria-labelledby") ?? el.textContent?.trim().slice(0, 100) ?? ""
|
|
3672
|
+
};
|
|
3673
|
+
}, "[data-reactscope-root]") ?? {
|
|
3674
|
+
role: "generic",
|
|
3675
|
+
name: ""
|
|
3676
|
+
};
|
|
3677
|
+
const imgViolations = await page.evaluate((sel) => {
|
|
3678
|
+
const container = document.querySelector(sel);
|
|
3679
|
+
if (!container) return [];
|
|
3680
|
+
const issues = [];
|
|
3681
|
+
container.querySelectorAll("img").forEach((img) => {
|
|
3682
|
+
if (!img.alt) issues.push("Image missing accessible name");
|
|
3683
|
+
});
|
|
3684
|
+
return issues;
|
|
3685
|
+
}, "[data-reactscope-root]") ?? [];
|
|
3686
|
+
const accessibility = {
|
|
3687
|
+
role: a11yInfo.role,
|
|
3688
|
+
name: a11yInfo.name,
|
|
3689
|
+
violations: imgViolations
|
|
3690
|
+
};
|
|
3423
3691
|
return {
|
|
3424
3692
|
screenshot,
|
|
3425
3693
|
width: Math.round(safeW),
|
|
3426
3694
|
height: Math.round(safeH),
|
|
3427
3695
|
renderTimeMs,
|
|
3428
|
-
computedStyles
|
|
3696
|
+
computedStyles,
|
|
3697
|
+
dom,
|
|
3698
|
+
accessibility
|
|
3429
3699
|
};
|
|
3430
3700
|
} finally {
|
|
3431
3701
|
pool.release(slot);
|
|
@@ -3506,6 +3776,11 @@ Available: ${available}`
|
|
|
3506
3776
|
const wrapperScript = scopeData?.hasWrapper === true ? await buildWrapperScript(scopeData.filePath) : void 0;
|
|
3507
3777
|
const scenarios = buildScenarioMap(opts, scopeData);
|
|
3508
3778
|
const globalCssFiles = loadGlobalCssFilesFromConfig(rootDir);
|
|
3779
|
+
if (globalCssFiles.length === 0) {
|
|
3780
|
+
process.stderr.write(
|
|
3781
|
+
"warning: No globalCSS files configured. Tailwind/CSS styles will not be applied to renders.\n Add `components.wrappers.globalCSS` to reactscope.config.json\n"
|
|
3782
|
+
);
|
|
3783
|
+
}
|
|
3509
3784
|
const renderer = buildRenderer(
|
|
3510
3785
|
filePath,
|
|
3511
3786
|
componentName,
|
|
@@ -3759,17 +4034,31 @@ function registerRenderAll(renderCmd) {
|
|
|
3759
4034
|
process.stderr.write(`Rendering ${total} components (concurrency: ${concurrency})\u2026
|
|
3760
4035
|
`);
|
|
3761
4036
|
const results = [];
|
|
4037
|
+
const complianceStylesMap = {};
|
|
3762
4038
|
let completed = 0;
|
|
3763
4039
|
const renderOne = async (name) => {
|
|
3764
4040
|
const descriptor = manifest.components[name];
|
|
3765
4041
|
if (descriptor === void 0) return;
|
|
3766
4042
|
const filePath = path.resolve(rootDir, descriptor.filePath);
|
|
3767
4043
|
const allCssFiles = loadGlobalCssFilesFromConfig(process.cwd());
|
|
3768
|
-
const
|
|
4044
|
+
const scopeData = await loadScopeFileForComponent(filePath);
|
|
4045
|
+
const scenarioEntries = scopeData !== null ? Object.entries(scopeData.scenarios) : [];
|
|
4046
|
+
const defaultEntry = scenarioEntries.find(([k]) => k === "default") ?? scenarioEntries[0];
|
|
4047
|
+
const renderProps = defaultEntry !== void 0 ? defaultEntry[1] : {};
|
|
4048
|
+
const wrapperScript = scopeData?.hasWrapper === true ? await buildWrapperScript(scopeData.filePath) : void 0;
|
|
4049
|
+
const renderer = buildRenderer(
|
|
4050
|
+
filePath,
|
|
4051
|
+
name,
|
|
4052
|
+
375,
|
|
4053
|
+
812,
|
|
4054
|
+
allCssFiles,
|
|
4055
|
+
process.cwd(),
|
|
4056
|
+
wrapperScript
|
|
4057
|
+
);
|
|
3769
4058
|
const outcome = await render.safeRender(
|
|
3770
|
-
() => renderer.renderCell(
|
|
4059
|
+
() => renderer.renderCell(renderProps, descriptor.complexityClass),
|
|
3771
4060
|
{
|
|
3772
|
-
props:
|
|
4061
|
+
props: renderProps,
|
|
3773
4062
|
sourceLocation: {
|
|
3774
4063
|
file: descriptor.filePath,
|
|
3775
4064
|
line: descriptor.loc.start,
|
|
@@ -3809,6 +4098,77 @@ function registerRenderAll(renderCmd) {
|
|
|
3809
4098
|
fs.writeFileSync(pngPath, result.screenshot);
|
|
3810
4099
|
const jsonPath = path.resolve(outputDir, `${name}.json`);
|
|
3811
4100
|
fs.writeFileSync(jsonPath, JSON.stringify(formatRenderJson(name, {}, result), null, 2));
|
|
4101
|
+
const rawStyles = result.computedStyles["[data-reactscope-root] > *"] ?? {};
|
|
4102
|
+
const compStyles = {
|
|
4103
|
+
colors: {},
|
|
4104
|
+
spacing: {},
|
|
4105
|
+
typography: {},
|
|
4106
|
+
borders: {},
|
|
4107
|
+
shadows: {}
|
|
4108
|
+
};
|
|
4109
|
+
for (const [prop, val] of Object.entries(rawStyles)) {
|
|
4110
|
+
if (!val || val === "none" || val === "") continue;
|
|
4111
|
+
const lower = prop.toLowerCase();
|
|
4112
|
+
if (lower.includes("color") || lower.includes("background")) {
|
|
4113
|
+
compStyles.colors[prop] = val;
|
|
4114
|
+
} else if (lower.includes("padding") || lower.includes("margin") || lower.includes("gap") || lower.includes("width") || lower.includes("height")) {
|
|
4115
|
+
compStyles.spacing[prop] = val;
|
|
4116
|
+
} else if (lower.includes("font") || lower.includes("lineheight") || lower.includes("letterspacing") || lower.includes("texttransform")) {
|
|
4117
|
+
compStyles.typography[prop] = val;
|
|
4118
|
+
} else if (lower.includes("border") || lower.includes("radius") || lower.includes("outline")) {
|
|
4119
|
+
compStyles.borders[prop] = val;
|
|
4120
|
+
} else if (lower.includes("shadow")) {
|
|
4121
|
+
compStyles.shadows[prop] = val;
|
|
4122
|
+
}
|
|
4123
|
+
}
|
|
4124
|
+
complianceStylesMap[name] = compStyles;
|
|
4125
|
+
if (scopeData !== null && Object.keys(scopeData.scenarios).length >= 2) {
|
|
4126
|
+
try {
|
|
4127
|
+
const scenarioEntries2 = Object.entries(scopeData.scenarios);
|
|
4128
|
+
const scenarioAxis = {
|
|
4129
|
+
name: "scenario",
|
|
4130
|
+
values: scenarioEntries2.map(([k]) => k)
|
|
4131
|
+
};
|
|
4132
|
+
const scenarioPropsMap = Object.fromEntries(scenarioEntries2);
|
|
4133
|
+
const matrixRenderer = buildRenderer(
|
|
4134
|
+
filePath,
|
|
4135
|
+
name,
|
|
4136
|
+
375,
|
|
4137
|
+
812,
|
|
4138
|
+
allCssFiles,
|
|
4139
|
+
process.cwd(),
|
|
4140
|
+
wrapperScript
|
|
4141
|
+
);
|
|
4142
|
+
const wrappedRenderer = {
|
|
4143
|
+
_satori: matrixRenderer._satori,
|
|
4144
|
+
async renderCell(props, cc) {
|
|
4145
|
+
const scenarioName = props.scenario;
|
|
4146
|
+
const realProps = scenarioName !== void 0 ? scenarioPropsMap[scenarioName] ?? props : props;
|
|
4147
|
+
return matrixRenderer.renderCell(realProps, cc ?? "simple");
|
|
4148
|
+
}
|
|
4149
|
+
};
|
|
4150
|
+
const matrix = new render.RenderMatrix(wrappedRenderer, [scenarioAxis], {
|
|
4151
|
+
concurrency: 2
|
|
4152
|
+
});
|
|
4153
|
+
const matrixResult = await matrix.render();
|
|
4154
|
+
const matrixCells = matrixResult.cells.map((cell) => ({
|
|
4155
|
+
axisValues: [scenarioEntries2[cell.axisIndices[0] ?? 0]?.[0] ?? ""],
|
|
4156
|
+
screenshot: cell.result.screenshot.toString("base64"),
|
|
4157
|
+
width: cell.result.width,
|
|
4158
|
+
height: cell.result.height,
|
|
4159
|
+
renderTimeMs: cell.result.renderTimeMs
|
|
4160
|
+
}));
|
|
4161
|
+
const existingJson = JSON.parse(fs.readFileSync(jsonPath, "utf-8"));
|
|
4162
|
+
existingJson.cells = matrixCells;
|
|
4163
|
+
existingJson.axisLabels = [scenarioAxis.values];
|
|
4164
|
+
fs.writeFileSync(jsonPath, JSON.stringify(existingJson, null, 2));
|
|
4165
|
+
} catch (matrixErr) {
|
|
4166
|
+
process.stderr.write(
|
|
4167
|
+
` [warn] Matrix render for ${name} failed: ${matrixErr instanceof Error ? matrixErr.message : String(matrixErr)}
|
|
4168
|
+
`
|
|
4169
|
+
);
|
|
4170
|
+
}
|
|
4171
|
+
}
|
|
3812
4172
|
if (isTTY()) {
|
|
3813
4173
|
process.stdout.write(
|
|
3814
4174
|
`\u2713 ${name} \u2192 ${opts.outputDir}/${name}.png (${result.width}\xD7${result.height}, ${result.renderTimeMs.toFixed(0)}ms)
|
|
@@ -3832,6 +4192,14 @@ function registerRenderAll(renderCmd) {
|
|
|
3832
4192
|
}
|
|
3833
4193
|
await Promise.all(workers);
|
|
3834
4194
|
await shutdownPool3();
|
|
4195
|
+
const compStylesPath = path.resolve(
|
|
4196
|
+
path.resolve(process.cwd(), opts.outputDir),
|
|
4197
|
+
"..",
|
|
4198
|
+
"compliance-styles.json"
|
|
4199
|
+
);
|
|
4200
|
+
fs.writeFileSync(compStylesPath, JSON.stringify(complianceStylesMap, null, 2));
|
|
4201
|
+
process.stderr.write(`[scope/render] \u2713 Wrote compliance-styles.json
|
|
4202
|
+
`);
|
|
3835
4203
|
process.stderr.write("\n");
|
|
3836
4204
|
const summary = formatSummaryText(results, outputDir);
|
|
3837
4205
|
process.stderr.write(`${summary}
|
|
@@ -4031,12 +4399,12 @@ async function runBaseline(options = {}) {
|
|
|
4031
4399
|
fs.mkdirSync(rendersDir, { recursive: true });
|
|
4032
4400
|
let manifest$1;
|
|
4033
4401
|
if (manifestPath !== void 0) {
|
|
4034
|
-
const { readFileSync:
|
|
4402
|
+
const { readFileSync: readFileSync14 } = await import('fs');
|
|
4035
4403
|
const absPath = path.resolve(rootDir, manifestPath);
|
|
4036
4404
|
if (!fs.existsSync(absPath)) {
|
|
4037
4405
|
throw new Error(`Manifest not found at ${absPath}.`);
|
|
4038
4406
|
}
|
|
4039
|
-
manifest$1 = JSON.parse(
|
|
4407
|
+
manifest$1 = JSON.parse(readFileSync14(absPath, "utf-8"));
|
|
4040
4408
|
process.stderr.write(`Loaded manifest from ${manifestPath}
|
|
4041
4409
|
`);
|
|
4042
4410
|
} else {
|
|
@@ -6224,6 +6592,7 @@ function createProgram(options = {}) {
|
|
|
6224
6592
|
program.addCommand(createInstrumentCommand());
|
|
6225
6593
|
program.addCommand(createInitCommand());
|
|
6226
6594
|
program.addCommand(createCiCommand());
|
|
6595
|
+
program.addCommand(createDoctorCommand());
|
|
6227
6596
|
const existingReportCmd = program.commands.find((c) => c.name() === "report");
|
|
6228
6597
|
if (existingReportCmd !== void 0) {
|
|
6229
6598
|
registerBaselineSubCommand(existingReportCmd);
|
|
@@ -6236,6 +6605,7 @@ function createProgram(options = {}) {
|
|
|
6236
6605
|
|
|
6237
6606
|
exports.CI_EXIT = CI_EXIT;
|
|
6238
6607
|
exports.createCiCommand = createCiCommand;
|
|
6608
|
+
exports.createDoctorCommand = createDoctorCommand;
|
|
6239
6609
|
exports.createInitCommand = createInitCommand;
|
|
6240
6610
|
exports.createInstrumentCommand = createInstrumentCommand;
|
|
6241
6611
|
exports.createManifestCommand = createManifestCommand;
|