@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.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,
|
|
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:
|
|
520
|
+
const { existsSync: existsSync16, readFileSync: readFileSync14 } = await import('fs');
|
|
521
521
|
const { createRequire: createRequire3 } = await import('module');
|
|
522
|
-
if (!
|
|
523
|
-
const raw =
|
|
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((
|
|
1353
|
+
return new Promise((resolve19) => {
|
|
1203
1354
|
rl.question(question, (answer) => {
|
|
1204
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
3370
|
-
const
|
|
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
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
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
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
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
|
|
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(
|
|
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:
|
|
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(
|
|
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
|