@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/index.d.cts CHANGED
@@ -116,6 +116,15 @@ declare function formatCiReport(result: CiResult): string;
116
116
  */
117
117
  declare function createCiCommand(): Command;
118
118
 
119
+ /**
120
+ * doctor-commands.ts
121
+ *
122
+ * `scope doctor` — health check that validates the current project's Scope
123
+ * setup and reports issues with config, tokens, globalCSS, and manifest staleness.
124
+ */
125
+
126
+ declare function createDoctorCommand(): Command;
127
+
119
128
  /**
120
129
  * @agent-scope/cli — `scope init` command implementation
121
130
  *
@@ -533,4 +542,4 @@ declare function resolveTokenFilePath(fileFlag?: string): string;
533
542
  */
534
543
  declare function createTokensExportCommand(): Command;
535
544
 
536
- export { CI_EXIT, type CausalityLink, type CiCheckName, type CiCheckResult, type CiExitCode, type CiOptions, type CiResult, type ComponentHookProfile, type HookHeuristicFlag, type HookProfile, type HooksProfilingResult, type InitOptions, type InitResult, type InteractionProfile, type InteractionStep$1 as InteractionStep, type InteractionTiming, type LayoutShiftData, type ListRow, type ProfileHeuristicFlag, type QueryRow, type ReactScopeConfig, type RenderEvent, type RenderTrigger, type RendersAnalysisResult, type ScopeCLIOptions, createCiCommand, createInitCommand, createInstrumentCommand, createManifestCommand, createProgram, createTokensCommand, createTokensExportCommand, formatCiReport, isTTY, matchGlob, resolveTokenFilePath, runCi, runInit };
545
+ export { CI_EXIT, type CausalityLink, type CiCheckName, type CiCheckResult, type CiExitCode, type CiOptions, type CiResult, type ComponentHookProfile, type HookHeuristicFlag, type HookProfile, type HooksProfilingResult, type InitOptions, type InitResult, type InteractionProfile, type InteractionStep$1 as InteractionStep, type InteractionTiming, type LayoutShiftData, type ListRow, type ProfileHeuristicFlag, type QueryRow, type ReactScopeConfig, type RenderEvent, type RenderTrigger, type RendersAnalysisResult, type ScopeCLIOptions, createCiCommand, createDoctorCommand, createInitCommand, createInstrumentCommand, createManifestCommand, createProgram, createTokensCommand, createTokensExportCommand, formatCiReport, isTTY, matchGlob, resolveTokenFilePath, runCi, runInit };
package/dist/index.d.ts CHANGED
@@ -116,6 +116,15 @@ declare function formatCiReport(result: CiResult): string;
116
116
  */
117
117
  declare function createCiCommand(): Command;
118
118
 
119
+ /**
120
+ * doctor-commands.ts
121
+ *
122
+ * `scope doctor` — health check that validates the current project's Scope
123
+ * setup and reports issues with config, tokens, globalCSS, and manifest staleness.
124
+ */
125
+
126
+ declare function createDoctorCommand(): Command;
127
+
119
128
  /**
120
129
  * @agent-scope/cli — `scope init` command implementation
121
130
  *
@@ -533,4 +542,4 @@ declare function resolveTokenFilePath(fileFlag?: string): string;
533
542
  */
534
543
  declare function createTokensExportCommand(): Command;
535
544
 
536
- export { CI_EXIT, type CausalityLink, type CiCheckName, type CiCheckResult, type CiExitCode, type CiOptions, type CiResult, type ComponentHookProfile, type HookHeuristicFlag, type HookProfile, type HooksProfilingResult, type InitOptions, type InitResult, type InteractionProfile, type InteractionStep$1 as InteractionStep, type InteractionTiming, type LayoutShiftData, type ListRow, type ProfileHeuristicFlag, type QueryRow, type ReactScopeConfig, type RenderEvent, type RenderTrigger, type RendersAnalysisResult, type ScopeCLIOptions, createCiCommand, createInitCommand, createInstrumentCommand, createManifestCommand, createProgram, createTokensCommand, createTokensExportCommand, formatCiReport, isTTY, matchGlob, resolveTokenFilePath, runCi, runInit };
545
+ export { CI_EXIT, type CausalityLink, type CiCheckName, type CiCheckResult, type CiExitCode, type CiOptions, type CiResult, type ComponentHookProfile, type HookHeuristicFlag, type HookProfile, type HooksProfilingResult, type InitOptions, type InitResult, type InteractionProfile, type InteractionStep$1 as InteractionStep, type InteractionTiming, type LayoutShiftData, type ListRow, type ProfileHeuristicFlag, type QueryRow, type ReactScopeConfig, type RenderEvent, type RenderTrigger, type RendersAnalysisResult, type ScopeCLIOptions, createCiCommand, createDoctorCommand, createInitCommand, createInstrumentCommand, createManifestCommand, createProgram, createTokensCommand, createTokensExportCommand, formatCiReport, isTTY, matchGlob, resolveTokenFilePath, runCi, runInit };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { existsSync, writeFileSync, readFileSync, mkdirSync, appendFileSync, readdirSync, rmSync, statSync, createReadStream } from 'fs';
1
+ import { existsSync, writeFileSync, mkdirSync, readFileSync, statSync, appendFileSync, readdirSync, rmSync, createReadStream } from 'fs';
2
2
  import { resolve, join, extname, dirname } from 'path';
3
3
  import { generateManifest } from '@agent-scope/manifest';
4
4
  import { SpriteSheetGenerator, safeRender, BrowserPool, ALL_CONTEXT_IDS, contextAxis, stressAxis, ALL_STRESS_IDS, RenderMatrix, SatoriRenderer } from '@agent-scope/render';
@@ -517,10 +517,10 @@ async function getCompiledCssForClasses(cwd, classes) {
517
517
  return build3(deduped);
518
518
  }
519
519
  async function compileGlobalCssFile(cssFilePath, cwd) {
520
- const { existsSync: existsSync15, readFileSync: readFileSync13 } = await import('fs');
520
+ const { existsSync: existsSync16, readFileSync: readFileSync14 } = await import('fs');
521
521
  const { createRequire: createRequire3 } = await import('module');
522
- if (!existsSync15(cssFilePath)) return null;
523
- const raw = readFileSync13(cssFilePath, "utf-8");
522
+ if (!existsSync16(cssFilePath)) return null;
523
+ const raw = readFileSync14(cssFilePath, "utf-8");
524
524
  const needsCompile = /@tailwind|@import\s+['"]tailwindcss/.test(raw);
525
525
  if (!needsCompile) {
526
526
  return raw;
@@ -989,6 +989,157 @@ function createCiCommand() {
989
989
  }
990
990
  );
991
991
  }
992
+ function collectSourceFiles(dir) {
993
+ if (!existsSync(dir)) return [];
994
+ const results = [];
995
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
996
+ const full = join(dir, entry.name);
997
+ if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== ".reactscope") {
998
+ results.push(...collectSourceFiles(full));
999
+ } else if (entry.isFile() && /\.(tsx?|jsx?)$/.test(entry.name)) {
1000
+ results.push(full);
1001
+ }
1002
+ }
1003
+ return results;
1004
+ }
1005
+ function checkConfig(cwd) {
1006
+ const configPath = resolve(cwd, "reactscope.config.json");
1007
+ if (!existsSync(configPath)) {
1008
+ return {
1009
+ name: "config",
1010
+ status: "error",
1011
+ message: "reactscope.config.json not found \u2014 run `scope init`"
1012
+ };
1013
+ }
1014
+ try {
1015
+ JSON.parse(readFileSync(configPath, "utf-8"));
1016
+ return { name: "config", status: "ok", message: "reactscope.config.json valid" };
1017
+ } catch {
1018
+ return { name: "config", status: "error", message: "reactscope.config.json is not valid JSON" };
1019
+ }
1020
+ }
1021
+ function checkTokens(cwd) {
1022
+ const configPath = resolve(cwd, "reactscope.config.json");
1023
+ let tokensPath = resolve(cwd, "reactscope.tokens.json");
1024
+ if (existsSync(configPath)) {
1025
+ try {
1026
+ const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
1027
+ if (cfg.tokens?.file) tokensPath = resolve(cwd, cfg.tokens.file);
1028
+ } catch {
1029
+ }
1030
+ }
1031
+ if (!existsSync(tokensPath)) {
1032
+ return {
1033
+ name: "tokens",
1034
+ status: "warn",
1035
+ message: `Token file not found at ${tokensPath} \u2014 run \`scope init\``
1036
+ };
1037
+ }
1038
+ try {
1039
+ const raw = JSON.parse(readFileSync(tokensPath, "utf-8"));
1040
+ if (!raw.version) {
1041
+ return { name: "tokens", status: "warn", message: "Token file is missing a `version` field" };
1042
+ }
1043
+ return { name: "tokens", status: "ok", message: "Token file valid" };
1044
+ } catch {
1045
+ return { name: "tokens", status: "error", message: "Token file is not valid JSON" };
1046
+ }
1047
+ }
1048
+ function checkGlobalCss(cwd) {
1049
+ const configPath = resolve(cwd, "reactscope.config.json");
1050
+ let globalCss = [];
1051
+ if (existsSync(configPath)) {
1052
+ try {
1053
+ const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
1054
+ globalCss = cfg.components?.wrappers?.globalCSS ?? [];
1055
+ } catch {
1056
+ }
1057
+ }
1058
+ if (globalCss.length === 0) {
1059
+ return {
1060
+ name: "globalCSS",
1061
+ status: "warn",
1062
+ message: "No globalCSS configured \u2014 Tailwind styles won't apply to renders. Add `components.wrappers.globalCSS` to reactscope.config.json"
1063
+ };
1064
+ }
1065
+ const missing = globalCss.filter((f) => !existsSync(resolve(cwd, f)));
1066
+ if (missing.length > 0) {
1067
+ return {
1068
+ name: "globalCSS",
1069
+ status: "error",
1070
+ message: `globalCSS file(s) not found: ${missing.join(", ")}`
1071
+ };
1072
+ }
1073
+ return {
1074
+ name: "globalCSS",
1075
+ status: "ok",
1076
+ message: `${globalCss.length} globalCSS file(s) present`
1077
+ };
1078
+ }
1079
+ function checkManifest(cwd) {
1080
+ const manifestPath = resolve(cwd, ".reactscope", "manifest.json");
1081
+ if (!existsSync(manifestPath)) {
1082
+ return {
1083
+ name: "manifest",
1084
+ status: "warn",
1085
+ message: "Manifest not found \u2014 run `scope manifest generate`"
1086
+ };
1087
+ }
1088
+ const manifestMtime = statSync(manifestPath).mtimeMs;
1089
+ const sourceDir = resolve(cwd, "src");
1090
+ const sourceFiles = collectSourceFiles(sourceDir);
1091
+ const stale = sourceFiles.filter((f) => statSync(f).mtimeMs > manifestMtime);
1092
+ if (stale.length > 0) {
1093
+ return {
1094
+ name: "manifest",
1095
+ status: "warn",
1096
+ message: `Manifest may be stale \u2014 ${stale.length} source file(s) modified since last generate. Run \`scope manifest generate\``
1097
+ };
1098
+ }
1099
+ return { name: "manifest", status: "ok", message: "Manifest present and up to date" };
1100
+ }
1101
+ var ICONS = { ok: "\u2713", warn: "!", error: "\u2717" };
1102
+ function formatCheck(check) {
1103
+ return ` [${ICONS[check.status]}] ${check.name.padEnd(12)} ${check.message}`;
1104
+ }
1105
+ function createDoctorCommand() {
1106
+ return new Command("doctor").description("Check the health of your Scope setup (config, tokens, CSS, manifest)").option("--json", "Emit structured JSON output", false).action((opts) => {
1107
+ const cwd = process.cwd();
1108
+ const checks = [
1109
+ checkConfig(cwd),
1110
+ checkTokens(cwd),
1111
+ checkGlobalCss(cwd),
1112
+ checkManifest(cwd)
1113
+ ];
1114
+ const errors = checks.filter((c) => c.status === "error").length;
1115
+ const warnings = checks.filter((c) => c.status === "warn").length;
1116
+ if (opts.json) {
1117
+ process.stdout.write(
1118
+ `${JSON.stringify({ passed: checks.length - errors - warnings, warnings, errors, checks }, null, 2)}
1119
+ `
1120
+ );
1121
+ if (errors > 0) process.exit(1);
1122
+ return;
1123
+ }
1124
+ process.stdout.write("\nScope Doctor\n");
1125
+ 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");
1126
+ for (const check of checks) process.stdout.write(`${formatCheck(check)}
1127
+ `);
1128
+ 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");
1129
+ if (errors > 0) {
1130
+ process.stdout.write(` ${errors} error(s), ${warnings} warning(s)
1131
+
1132
+ `);
1133
+ process.exit(1);
1134
+ } else if (warnings > 0) {
1135
+ process.stdout.write(` ${warnings} warning(s) \u2014 everything works but could be better
1136
+
1137
+ `);
1138
+ } else {
1139
+ process.stdout.write(" All checks passed!\n\n");
1140
+ }
1141
+ });
1142
+ }
992
1143
  function hasConfigFile(dir, stem) {
993
1144
  if (!existsSync(dir)) return false;
994
1145
  try {
@@ -1199,9 +1350,9 @@ function createRL() {
1199
1350
  });
1200
1351
  }
1201
1352
  async function ask(rl, question) {
1202
- return new Promise((resolve18) => {
1353
+ return new Promise((resolve19) => {
1203
1354
  rl.question(question, (answer) => {
1204
- resolve18(answer.trim());
1355
+ resolve19(answer.trim());
1205
1356
  });
1206
1357
  });
1207
1358
  }
@@ -1276,7 +1427,7 @@ function extractTailwindTokens(tokenSources) {
1276
1427
  }
1277
1428
  }
1278
1429
  if (Object.keys(colorTokens).length > 0) {
1279
- tokens["color"] = colorTokens;
1430
+ tokens.color = colorTokens;
1280
1431
  }
1281
1432
  }
1282
1433
  }
@@ -1289,7 +1440,7 @@ function extractTailwindTokens(tokenSources) {
1289
1440
  for (const [key, val] of Object.entries(spacingValues)) {
1290
1441
  spacingTokens[key] = { value: val, type: "dimension" };
1291
1442
  }
1292
- tokens["spacing"] = spacingTokens;
1443
+ tokens.spacing = spacingTokens;
1293
1444
  }
1294
1445
  }
1295
1446
  const fontFamilyMatch = raw.match(/fontFamily\s*:\s*\{([\s\S]*?)\n\s*\}/);
@@ -1302,7 +1453,7 @@ function extractTailwindTokens(tokenSources) {
1302
1453
  }
1303
1454
  }
1304
1455
  if (Object.keys(fontTokens).length > 0) {
1305
- tokens["font"] = fontTokens;
1456
+ tokens.font = fontTokens;
1306
1457
  }
1307
1458
  }
1308
1459
  const borderRadiusMatch = raw.match(/borderRadius\s*:\s*\{([\s\S]*?)\n\s*\}/);
@@ -1313,7 +1464,7 @@ function extractTailwindTokens(tokenSources) {
1313
1464
  for (const [key, val] of Object.entries(radiusValues)) {
1314
1465
  radiusTokens[key] = { value: val, type: "dimension" };
1315
1466
  }
1316
- tokens["radius"] = radiusTokens;
1467
+ tokens.radius = radiusTokens;
1317
1468
  }
1318
1469
  }
1319
1470
  return Object.keys(tokens).length > 0 ? tokens : null;
@@ -1432,7 +1583,28 @@ async function runInit(options) {
1432
1583
  process.stdout.write(` ${p}
1433
1584
  `);
1434
1585
  }
1435
- process.stdout.write("\n Next steps: run `scope manifest` to scan your components.\n\n");
1586
+ process.stdout.write("\n Scanning components...\n");
1587
+ try {
1588
+ const manifestConfig = {
1589
+ include: config.components.include,
1590
+ rootDir
1591
+ };
1592
+ const manifest = await generateManifest(manifestConfig);
1593
+ const manifestCount = Object.keys(manifest.components).length;
1594
+ const manifestOutPath = join(rootDir, config.output.dir, "manifest.json");
1595
+ mkdirSync(join(rootDir, config.output.dir), { recursive: true });
1596
+ writeFileSync(manifestOutPath, `${JSON.stringify(manifest, null, 2)}
1597
+ `);
1598
+ process.stdout.write(
1599
+ ` Found ${manifestCount} component(s) \u2014 manifest written to ${manifestOutPath}
1600
+ `
1601
+ );
1602
+ } catch {
1603
+ process.stdout.write(
1604
+ " (manifest generate skipped \u2014 run `scope manifest generate` manually)\n"
1605
+ );
1606
+ }
1607
+ process.stdout.write("\n");
1436
1608
  return {
1437
1609
  success: true,
1438
1610
  message: "Project initialised successfully.",
@@ -3346,7 +3518,7 @@ function buildRenderer(filePath, componentName, viewportWidth, viewportHeight, g
3346
3518
  }
3347
3519
  });
3348
3520
  return [...set];
3349
- });
3521
+ }) ?? [];
3350
3522
  const projectCss2 = await getCompiledCssForClasses(rootDir, classes);
3351
3523
  if (projectCss2 != null && projectCss2.length > 0) {
3352
3524
  await page.addStyleTag({ content: projectCss2 });
@@ -3359,49 +3531,147 @@ function buildRenderer(filePath, componentName, viewportWidth, viewportHeight, g
3359
3531
  `Component "${componentName}" rendered with zero bounding box \u2014 it may be invisible or not mounted`
3360
3532
  );
3361
3533
  }
3362
- const PAD = 24;
3363
- const MIN_W = 320;
3364
- const MIN_H = 200;
3534
+ const PAD = 8;
3365
3535
  const clipX = Math.max(0, boundingBox.x - PAD);
3366
3536
  const clipY = Math.max(0, boundingBox.y - PAD);
3367
3537
  const rawW = boundingBox.width + PAD * 2;
3368
3538
  const rawH = boundingBox.height + PAD * 2;
3369
- const clipW = Math.max(rawW, MIN_W);
3370
- const clipH = Math.max(rawH, MIN_H);
3371
- const safeW = Math.min(clipW, viewportWidth - clipX);
3372
- const safeH = Math.min(clipH, viewportHeight - clipY);
3539
+ const safeW = Math.min(rawW, viewportWidth - clipX);
3540
+ const safeH = Math.min(rawH, viewportHeight - clipY);
3373
3541
  const screenshot = await page.screenshot({
3374
3542
  clip: { x: clipX, y: clipY, width: safeW, height: safeH },
3375
3543
  type: "png"
3376
3544
  });
3545
+ const STYLE_PROPS = [
3546
+ "display",
3547
+ "width",
3548
+ "height",
3549
+ "color",
3550
+ "backgroundColor",
3551
+ "fontSize",
3552
+ "fontFamily",
3553
+ "fontWeight",
3554
+ "lineHeight",
3555
+ "padding",
3556
+ "paddingTop",
3557
+ "paddingRight",
3558
+ "paddingBottom",
3559
+ "paddingLeft",
3560
+ "margin",
3561
+ "marginTop",
3562
+ "marginRight",
3563
+ "marginBottom",
3564
+ "marginLeft",
3565
+ "gap",
3566
+ "borderRadius",
3567
+ "borderWidth",
3568
+ "borderColor",
3569
+ "borderStyle",
3570
+ "boxShadow",
3571
+ "opacity",
3572
+ "position",
3573
+ "flexDirection",
3574
+ "alignItems",
3575
+ "justifyContent",
3576
+ "overflow"
3577
+ ];
3578
+ const _domResult = await page.evaluate(
3579
+ (args) => {
3580
+ let count = 0;
3581
+ const styles = {};
3582
+ function captureStyles(el, id, propList) {
3583
+ const computed = window.getComputedStyle(el);
3584
+ const out = {};
3585
+ for (const prop of propList) {
3586
+ const val = computed[prop] ?? "";
3587
+ if (val && val !== "none" && val !== "normal" && val !== "auto") out[prop] = val;
3588
+ }
3589
+ styles[id] = out;
3590
+ }
3591
+ function walk(node) {
3592
+ if (node.nodeType === Node.TEXT_NODE) {
3593
+ return {
3594
+ tag: "#text",
3595
+ attrs: {},
3596
+ text: node.textContent?.trim() ?? "",
3597
+ children: []
3598
+ };
3599
+ }
3600
+ const el = node;
3601
+ const id = count++;
3602
+ captureStyles(el, id, args.props);
3603
+ const attrs = {};
3604
+ for (const attr of Array.from(el.attributes)) {
3605
+ attrs[attr.name] = attr.value;
3606
+ }
3607
+ const children = Array.from(el.childNodes).filter(
3608
+ (n) => n.nodeType === Node.ELEMENT_NODE || n.nodeType === Node.TEXT_NODE && (n.textContent?.trim() ?? "").length > 0
3609
+ ).map(walk);
3610
+ return { tag: el.tagName.toLowerCase(), attrs, nodeId: id, children };
3611
+ }
3612
+ const root = document.querySelector(args.sel);
3613
+ if (!root)
3614
+ return {
3615
+ tree: { tag: "div", attrs: {}, children: [] },
3616
+ elementCount: 0,
3617
+ nodeStyles: {}
3618
+ };
3619
+ return { tree: walk(root), elementCount: count, nodeStyles: styles };
3620
+ },
3621
+ { sel: "[data-reactscope-root] > *", props: STYLE_PROPS }
3622
+ );
3623
+ const domTree = _domResult?.tree ?? { tag: "div", attrs: {}, children: [] };
3624
+ const elementCount = _domResult?.elementCount ?? 0;
3625
+ const nodeStyles = _domResult?.nodeStyles ?? {};
3377
3626
  const computedStyles = {};
3378
- const styles = await page.evaluate((sel) => {
3379
- const el = document.querySelector(sel);
3380
- if (el === null) return {};
3381
- const computed = window.getComputedStyle(el);
3382
- const out = {};
3383
- for (const prop of [
3384
- "display",
3385
- "width",
3386
- "height",
3387
- "color",
3388
- "backgroundColor",
3389
- "fontSize",
3390
- "fontFamily",
3391
- "padding",
3392
- "margin"
3393
- ]) {
3394
- out[prop] = computed.getPropertyValue(prop);
3627
+ if (nodeStyles[0]) computedStyles["[data-reactscope-root] > *"] = nodeStyles[0];
3628
+ for (const [nodeId, styles] of Object.entries(nodeStyles)) {
3629
+ computedStyles[`#node-${nodeId}`] = styles;
3630
+ }
3631
+ const dom = {
3632
+ tree: domTree,
3633
+ elementCount,
3634
+ boundingBox: {
3635
+ x: boundingBox.x,
3636
+ y: boundingBox.y,
3637
+ width: boundingBox.width,
3638
+ height: boundingBox.height
3395
3639
  }
3396
- return out;
3397
- }, "[data-reactscope-root] > *");
3398
- computedStyles["[data-reactscope-root] > *"] = styles;
3640
+ };
3641
+ const a11yInfo = await page.evaluate((sel) => {
3642
+ const wrapper = document.querySelector(sel);
3643
+ const el = wrapper?.firstElementChild ?? wrapper;
3644
+ if (!el) return { role: "generic", name: "" };
3645
+ return {
3646
+ role: el.getAttribute("role") ?? el.tagName.toLowerCase() ?? "generic",
3647
+ name: el.getAttribute("aria-label") ?? el.getAttribute("aria-labelledby") ?? el.textContent?.trim().slice(0, 100) ?? ""
3648
+ };
3649
+ }, "[data-reactscope-root]") ?? {
3650
+ role: "generic",
3651
+ name: ""
3652
+ };
3653
+ const imgViolations = await page.evaluate((sel) => {
3654
+ const container = document.querySelector(sel);
3655
+ if (!container) return [];
3656
+ const issues = [];
3657
+ container.querySelectorAll("img").forEach((img) => {
3658
+ if (!img.alt) issues.push("Image missing accessible name");
3659
+ });
3660
+ return issues;
3661
+ }, "[data-reactscope-root]") ?? [];
3662
+ const accessibility = {
3663
+ role: a11yInfo.role,
3664
+ name: a11yInfo.name,
3665
+ violations: imgViolations
3666
+ };
3399
3667
  return {
3400
3668
  screenshot,
3401
3669
  width: Math.round(safeW),
3402
3670
  height: Math.round(safeH),
3403
3671
  renderTimeMs,
3404
- computedStyles
3672
+ computedStyles,
3673
+ dom,
3674
+ accessibility
3405
3675
  };
3406
3676
  } finally {
3407
3677
  pool.release(slot);
@@ -3482,6 +3752,11 @@ Available: ${available}`
3482
3752
  const wrapperScript = scopeData?.hasWrapper === true ? await buildWrapperScript(scopeData.filePath) : void 0;
3483
3753
  const scenarios = buildScenarioMap(opts, scopeData);
3484
3754
  const globalCssFiles = loadGlobalCssFilesFromConfig(rootDir);
3755
+ if (globalCssFiles.length === 0) {
3756
+ process.stderr.write(
3757
+ "warning: No globalCSS files configured. Tailwind/CSS styles will not be applied to renders.\n Add `components.wrappers.globalCSS` to reactscope.config.json\n"
3758
+ );
3759
+ }
3485
3760
  const renderer = buildRenderer(
3486
3761
  filePath,
3487
3762
  componentName,
@@ -3735,17 +4010,31 @@ function registerRenderAll(renderCmd) {
3735
4010
  process.stderr.write(`Rendering ${total} components (concurrency: ${concurrency})\u2026
3736
4011
  `);
3737
4012
  const results = [];
4013
+ const complianceStylesMap = {};
3738
4014
  let completed = 0;
3739
4015
  const renderOne = async (name) => {
3740
4016
  const descriptor = manifest.components[name];
3741
4017
  if (descriptor === void 0) return;
3742
4018
  const filePath = resolve(rootDir, descriptor.filePath);
3743
4019
  const allCssFiles = loadGlobalCssFilesFromConfig(process.cwd());
3744
- const renderer = buildRenderer(filePath, name, 375, 812, allCssFiles, process.cwd());
4020
+ const scopeData = await loadScopeFileForComponent(filePath);
4021
+ const scenarioEntries = scopeData !== null ? Object.entries(scopeData.scenarios) : [];
4022
+ const defaultEntry = scenarioEntries.find(([k]) => k === "default") ?? scenarioEntries[0];
4023
+ const renderProps = defaultEntry !== void 0 ? defaultEntry[1] : {};
4024
+ const wrapperScript = scopeData?.hasWrapper === true ? await buildWrapperScript(scopeData.filePath) : void 0;
4025
+ const renderer = buildRenderer(
4026
+ filePath,
4027
+ name,
4028
+ 375,
4029
+ 812,
4030
+ allCssFiles,
4031
+ process.cwd(),
4032
+ wrapperScript
4033
+ );
3745
4034
  const outcome = await safeRender(
3746
- () => renderer.renderCell({}, descriptor.complexityClass),
4035
+ () => renderer.renderCell(renderProps, descriptor.complexityClass),
3747
4036
  {
3748
- props: {},
4037
+ props: renderProps,
3749
4038
  sourceLocation: {
3750
4039
  file: descriptor.filePath,
3751
4040
  line: descriptor.loc.start,
@@ -3785,6 +4074,77 @@ function registerRenderAll(renderCmd) {
3785
4074
  writeFileSync(pngPath, result.screenshot);
3786
4075
  const jsonPath = resolve(outputDir, `${name}.json`);
3787
4076
  writeFileSync(jsonPath, JSON.stringify(formatRenderJson(name, {}, result), null, 2));
4077
+ const rawStyles = result.computedStyles["[data-reactscope-root] > *"] ?? {};
4078
+ const compStyles = {
4079
+ colors: {},
4080
+ spacing: {},
4081
+ typography: {},
4082
+ borders: {},
4083
+ shadows: {}
4084
+ };
4085
+ for (const [prop, val] of Object.entries(rawStyles)) {
4086
+ if (!val || val === "none" || val === "") continue;
4087
+ const lower = prop.toLowerCase();
4088
+ if (lower.includes("color") || lower.includes("background")) {
4089
+ compStyles.colors[prop] = val;
4090
+ } else if (lower.includes("padding") || lower.includes("margin") || lower.includes("gap") || lower.includes("width") || lower.includes("height")) {
4091
+ compStyles.spacing[prop] = val;
4092
+ } else if (lower.includes("font") || lower.includes("lineheight") || lower.includes("letterspacing") || lower.includes("texttransform")) {
4093
+ compStyles.typography[prop] = val;
4094
+ } else if (lower.includes("border") || lower.includes("radius") || lower.includes("outline")) {
4095
+ compStyles.borders[prop] = val;
4096
+ } else if (lower.includes("shadow")) {
4097
+ compStyles.shadows[prop] = val;
4098
+ }
4099
+ }
4100
+ complianceStylesMap[name] = compStyles;
4101
+ if (scopeData !== null && Object.keys(scopeData.scenarios).length >= 2) {
4102
+ try {
4103
+ const scenarioEntries2 = Object.entries(scopeData.scenarios);
4104
+ const scenarioAxis = {
4105
+ name: "scenario",
4106
+ values: scenarioEntries2.map(([k]) => k)
4107
+ };
4108
+ const scenarioPropsMap = Object.fromEntries(scenarioEntries2);
4109
+ const matrixRenderer = buildRenderer(
4110
+ filePath,
4111
+ name,
4112
+ 375,
4113
+ 812,
4114
+ allCssFiles,
4115
+ process.cwd(),
4116
+ wrapperScript
4117
+ );
4118
+ const wrappedRenderer = {
4119
+ _satori: matrixRenderer._satori,
4120
+ async renderCell(props, cc) {
4121
+ const scenarioName = props.scenario;
4122
+ const realProps = scenarioName !== void 0 ? scenarioPropsMap[scenarioName] ?? props : props;
4123
+ return matrixRenderer.renderCell(realProps, cc ?? "simple");
4124
+ }
4125
+ };
4126
+ const matrix = new RenderMatrix(wrappedRenderer, [scenarioAxis], {
4127
+ concurrency: 2
4128
+ });
4129
+ const matrixResult = await matrix.render();
4130
+ const matrixCells = matrixResult.cells.map((cell) => ({
4131
+ axisValues: [scenarioEntries2[cell.axisIndices[0] ?? 0]?.[0] ?? ""],
4132
+ screenshot: cell.result.screenshot.toString("base64"),
4133
+ width: cell.result.width,
4134
+ height: cell.result.height,
4135
+ renderTimeMs: cell.result.renderTimeMs
4136
+ }));
4137
+ const existingJson = JSON.parse(readFileSync(jsonPath, "utf-8"));
4138
+ existingJson.cells = matrixCells;
4139
+ existingJson.axisLabels = [scenarioAxis.values];
4140
+ writeFileSync(jsonPath, JSON.stringify(existingJson, null, 2));
4141
+ } catch (matrixErr) {
4142
+ process.stderr.write(
4143
+ ` [warn] Matrix render for ${name} failed: ${matrixErr instanceof Error ? matrixErr.message : String(matrixErr)}
4144
+ `
4145
+ );
4146
+ }
4147
+ }
3788
4148
  if (isTTY()) {
3789
4149
  process.stdout.write(
3790
4150
  `\u2713 ${name} \u2192 ${opts.outputDir}/${name}.png (${result.width}\xD7${result.height}, ${result.renderTimeMs.toFixed(0)}ms)
@@ -3808,6 +4168,14 @@ function registerRenderAll(renderCmd) {
3808
4168
  }
3809
4169
  await Promise.all(workers);
3810
4170
  await shutdownPool3();
4171
+ const compStylesPath = resolve(
4172
+ resolve(process.cwd(), opts.outputDir),
4173
+ "..",
4174
+ "compliance-styles.json"
4175
+ );
4176
+ writeFileSync(compStylesPath, JSON.stringify(complianceStylesMap, null, 2));
4177
+ process.stderr.write(`[scope/render] \u2713 Wrote compliance-styles.json
4178
+ `);
3811
4179
  process.stderr.write("\n");
3812
4180
  const summary = formatSummaryText(results, outputDir);
3813
4181
  process.stderr.write(`${summary}
@@ -4007,12 +4375,12 @@ async function runBaseline(options = {}) {
4007
4375
  mkdirSync(rendersDir, { recursive: true });
4008
4376
  let manifest;
4009
4377
  if (manifestPath !== void 0) {
4010
- const { readFileSync: readFileSync13 } = await import('fs');
4378
+ const { readFileSync: readFileSync14 } = await import('fs');
4011
4379
  const absPath = resolve(rootDir, manifestPath);
4012
4380
  if (!existsSync(absPath)) {
4013
4381
  throw new Error(`Manifest not found at ${absPath}.`);
4014
4382
  }
4015
- manifest = JSON.parse(readFileSync13(absPath, "utf-8"));
4383
+ manifest = JSON.parse(readFileSync14(absPath, "utf-8"));
4016
4384
  process.stderr.write(`Loaded manifest from ${manifestPath}
4017
4385
  `);
4018
4386
  } else {
@@ -6200,6 +6568,7 @@ function createProgram(options = {}) {
6200
6568
  program.addCommand(createInstrumentCommand());
6201
6569
  program.addCommand(createInitCommand());
6202
6570
  program.addCommand(createCiCommand());
6571
+ program.addCommand(createDoctorCommand());
6203
6572
  const existingReportCmd = program.commands.find((c) => c.name() === "report");
6204
6573
  if (existingReportCmd !== void 0) {
6205
6574
  registerBaselineSubCommand(existingReportCmd);
@@ -6210,6 +6579,6 @@ function createProgram(options = {}) {
6210
6579
  return program;
6211
6580
  }
6212
6581
 
6213
- export { CI_EXIT, createCiCommand, createInitCommand, createInstrumentCommand, createManifestCommand, createProgram, createTokensCommand, createTokensExportCommand, formatCiReport, isTTY, matchGlob, resolveTokenFilePath2 as resolveTokenFilePath, runCi, runInit };
6582
+ export { CI_EXIT, createCiCommand, createDoctorCommand, createInitCommand, createInstrumentCommand, createManifestCommand, createProgram, createTokensCommand, createTokensExportCommand, formatCiReport, isTTY, matchGlob, resolveTokenFilePath2 as resolveTokenFilePath, runCi, runInit };
6214
6583
  //# sourceMappingURL=index.js.map
6215
6584
  //# sourceMappingURL=index.js.map