@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/cli.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
3
|
// src/program.ts
|
|
4
|
-
import { readFileSync as
|
|
4
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
5
5
|
import { generateTest, loadTrace } from "@agent-scope/playwright";
|
|
6
|
-
import { Command as
|
|
6
|
+
import { Command as Command11 } from "commander";
|
|
7
7
|
|
|
8
8
|
// src/browser.ts
|
|
9
9
|
import { writeFileSync } from "fs";
|
|
@@ -569,10 +569,10 @@ async function getCompiledCssForClasses(cwd, classes) {
|
|
|
569
569
|
return build3(deduped);
|
|
570
570
|
}
|
|
571
571
|
async function compileGlobalCssFile(cssFilePath, cwd) {
|
|
572
|
-
const { existsSync:
|
|
572
|
+
const { existsSync: existsSync16, readFileSync: readFileSync14 } = await import("fs");
|
|
573
573
|
const { createRequire: createRequire3 } = await import("module");
|
|
574
|
-
if (!
|
|
575
|
-
const raw =
|
|
574
|
+
if (!existsSync16(cssFilePath)) return null;
|
|
575
|
+
const raw = readFileSync14(cssFilePath, "utf-8");
|
|
576
576
|
const needsCompile = /@tailwind|@import\s+['"]tailwindcss/.test(raw);
|
|
577
577
|
if (!needsCompile) {
|
|
578
578
|
return raw;
|
|
@@ -1042,18 +1042,174 @@ function createCiCommand() {
|
|
|
1042
1042
|
);
|
|
1043
1043
|
}
|
|
1044
1044
|
|
|
1045
|
+
// src/doctor-commands.ts
|
|
1046
|
+
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync3, statSync } from "fs";
|
|
1047
|
+
import { join, resolve as resolve3 } from "path";
|
|
1048
|
+
import { Command as Command2 } from "commander";
|
|
1049
|
+
function collectSourceFiles(dir) {
|
|
1050
|
+
if (!existsSync3(dir)) return [];
|
|
1051
|
+
const results = [];
|
|
1052
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
1053
|
+
const full = join(dir, entry.name);
|
|
1054
|
+
if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== ".reactscope") {
|
|
1055
|
+
results.push(...collectSourceFiles(full));
|
|
1056
|
+
} else if (entry.isFile() && /\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
1057
|
+
results.push(full);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
return results;
|
|
1061
|
+
}
|
|
1062
|
+
function checkConfig(cwd) {
|
|
1063
|
+
const configPath = resolve3(cwd, "reactscope.config.json");
|
|
1064
|
+
if (!existsSync3(configPath)) {
|
|
1065
|
+
return {
|
|
1066
|
+
name: "config",
|
|
1067
|
+
status: "error",
|
|
1068
|
+
message: "reactscope.config.json not found \u2014 run `scope init`"
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
try {
|
|
1072
|
+
JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
1073
|
+
return { name: "config", status: "ok", message: "reactscope.config.json valid" };
|
|
1074
|
+
} catch {
|
|
1075
|
+
return { name: "config", status: "error", message: "reactscope.config.json is not valid JSON" };
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
function checkTokens(cwd) {
|
|
1079
|
+
const configPath = resolve3(cwd, "reactscope.config.json");
|
|
1080
|
+
let tokensPath = resolve3(cwd, "reactscope.tokens.json");
|
|
1081
|
+
if (existsSync3(configPath)) {
|
|
1082
|
+
try {
|
|
1083
|
+
const cfg = JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
1084
|
+
if (cfg.tokens?.file) tokensPath = resolve3(cwd, cfg.tokens.file);
|
|
1085
|
+
} catch {
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
if (!existsSync3(tokensPath)) {
|
|
1089
|
+
return {
|
|
1090
|
+
name: "tokens",
|
|
1091
|
+
status: "warn",
|
|
1092
|
+
message: `Token file not found at ${tokensPath} \u2014 run \`scope init\``
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
try {
|
|
1096
|
+
const raw = JSON.parse(readFileSync3(tokensPath, "utf-8"));
|
|
1097
|
+
if (!raw.version) {
|
|
1098
|
+
return { name: "tokens", status: "warn", message: "Token file is missing a `version` field" };
|
|
1099
|
+
}
|
|
1100
|
+
return { name: "tokens", status: "ok", message: "Token file valid" };
|
|
1101
|
+
} catch {
|
|
1102
|
+
return { name: "tokens", status: "error", message: "Token file is not valid JSON" };
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
function checkGlobalCss(cwd) {
|
|
1106
|
+
const configPath = resolve3(cwd, "reactscope.config.json");
|
|
1107
|
+
let globalCss = [];
|
|
1108
|
+
if (existsSync3(configPath)) {
|
|
1109
|
+
try {
|
|
1110
|
+
const cfg = JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
1111
|
+
globalCss = cfg.components?.wrappers?.globalCSS ?? [];
|
|
1112
|
+
} catch {
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
if (globalCss.length === 0) {
|
|
1116
|
+
return {
|
|
1117
|
+
name: "globalCSS",
|
|
1118
|
+
status: "warn",
|
|
1119
|
+
message: "No globalCSS configured \u2014 Tailwind styles won't apply to renders. Add `components.wrappers.globalCSS` to reactscope.config.json"
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
const missing = globalCss.filter((f) => !existsSync3(resolve3(cwd, f)));
|
|
1123
|
+
if (missing.length > 0) {
|
|
1124
|
+
return {
|
|
1125
|
+
name: "globalCSS",
|
|
1126
|
+
status: "error",
|
|
1127
|
+
message: `globalCSS file(s) not found: ${missing.join(", ")}`
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
return {
|
|
1131
|
+
name: "globalCSS",
|
|
1132
|
+
status: "ok",
|
|
1133
|
+
message: `${globalCss.length} globalCSS file(s) present`
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
function checkManifest(cwd) {
|
|
1137
|
+
const manifestPath = resolve3(cwd, ".reactscope", "manifest.json");
|
|
1138
|
+
if (!existsSync3(manifestPath)) {
|
|
1139
|
+
return {
|
|
1140
|
+
name: "manifest",
|
|
1141
|
+
status: "warn",
|
|
1142
|
+
message: "Manifest not found \u2014 run `scope manifest generate`"
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
const manifestMtime = statSync(manifestPath).mtimeMs;
|
|
1146
|
+
const sourceDir = resolve3(cwd, "src");
|
|
1147
|
+
const sourceFiles = collectSourceFiles(sourceDir);
|
|
1148
|
+
const stale = sourceFiles.filter((f) => statSync(f).mtimeMs > manifestMtime);
|
|
1149
|
+
if (stale.length > 0) {
|
|
1150
|
+
return {
|
|
1151
|
+
name: "manifest",
|
|
1152
|
+
status: "warn",
|
|
1153
|
+
message: `Manifest may be stale \u2014 ${stale.length} source file(s) modified since last generate. Run \`scope manifest generate\``
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
return { name: "manifest", status: "ok", message: "Manifest present and up to date" };
|
|
1157
|
+
}
|
|
1158
|
+
var ICONS = { ok: "\u2713", warn: "!", error: "\u2717" };
|
|
1159
|
+
function formatCheck(check) {
|
|
1160
|
+
return ` [${ICONS[check.status]}] ${check.name.padEnd(12)} ${check.message}`;
|
|
1161
|
+
}
|
|
1162
|
+
function createDoctorCommand() {
|
|
1163
|
+
return new Command2("doctor").description("Check the health of your Scope setup (config, tokens, CSS, manifest)").option("--json", "Emit structured JSON output", false).action((opts) => {
|
|
1164
|
+
const cwd = process.cwd();
|
|
1165
|
+
const checks = [
|
|
1166
|
+
checkConfig(cwd),
|
|
1167
|
+
checkTokens(cwd),
|
|
1168
|
+
checkGlobalCss(cwd),
|
|
1169
|
+
checkManifest(cwd)
|
|
1170
|
+
];
|
|
1171
|
+
const errors = checks.filter((c) => c.status === "error").length;
|
|
1172
|
+
const warnings = checks.filter((c) => c.status === "warn").length;
|
|
1173
|
+
if (opts.json) {
|
|
1174
|
+
process.stdout.write(
|
|
1175
|
+
`${JSON.stringify({ passed: checks.length - errors - warnings, warnings, errors, checks }, null, 2)}
|
|
1176
|
+
`
|
|
1177
|
+
);
|
|
1178
|
+
if (errors > 0) process.exit(1);
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
process.stdout.write("\nScope Doctor\n");
|
|
1182
|
+
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");
|
|
1183
|
+
for (const check of checks) process.stdout.write(`${formatCheck(check)}
|
|
1184
|
+
`);
|
|
1185
|
+
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");
|
|
1186
|
+
if (errors > 0) {
|
|
1187
|
+
process.stdout.write(` ${errors} error(s), ${warnings} warning(s)
|
|
1188
|
+
|
|
1189
|
+
`);
|
|
1190
|
+
process.exit(1);
|
|
1191
|
+
} else if (warnings > 0) {
|
|
1192
|
+
process.stdout.write(` ${warnings} warning(s) \u2014 everything works but could be better
|
|
1193
|
+
|
|
1194
|
+
`);
|
|
1195
|
+
} else {
|
|
1196
|
+
process.stdout.write(" All checks passed!\n\n");
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1045
1201
|
// src/init/index.ts
|
|
1046
|
-
import { appendFileSync, existsSync as
|
|
1047
|
-
import { join as
|
|
1202
|
+
import { appendFileSync, existsSync as existsSync5, mkdirSync, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
1203
|
+
import { join as join3 } from "path";
|
|
1048
1204
|
import * as readline from "readline";
|
|
1049
1205
|
|
|
1050
1206
|
// src/init/detect.ts
|
|
1051
|
-
import { existsSync as
|
|
1052
|
-
import { join } from "path";
|
|
1207
|
+
import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
|
|
1208
|
+
import { join as join2 } from "path";
|
|
1053
1209
|
function hasConfigFile(dir, stem) {
|
|
1054
|
-
if (!
|
|
1210
|
+
if (!existsSync4(dir)) return false;
|
|
1055
1211
|
try {
|
|
1056
|
-
const entries =
|
|
1212
|
+
const entries = readdirSync2(dir);
|
|
1057
1213
|
return entries.some((f) => f === stem || f.startsWith(`${stem}.`));
|
|
1058
1214
|
} catch {
|
|
1059
1215
|
return false;
|
|
@@ -1061,7 +1217,7 @@ function hasConfigFile(dir, stem) {
|
|
|
1061
1217
|
}
|
|
1062
1218
|
function readSafe(path) {
|
|
1063
1219
|
try {
|
|
1064
|
-
return
|
|
1220
|
+
return readFileSync4(path, "utf-8");
|
|
1065
1221
|
} catch {
|
|
1066
1222
|
return null;
|
|
1067
1223
|
}
|
|
@@ -1074,15 +1230,15 @@ function detectFramework(rootDir, packageDeps) {
|
|
|
1074
1230
|
return "unknown";
|
|
1075
1231
|
}
|
|
1076
1232
|
function detectPackageManager(rootDir) {
|
|
1077
|
-
if (
|
|
1078
|
-
if (
|
|
1079
|
-
if (
|
|
1080
|
-
if (
|
|
1233
|
+
if (existsSync4(join2(rootDir, "bun.lock"))) return "bun";
|
|
1234
|
+
if (existsSync4(join2(rootDir, "yarn.lock"))) return "yarn";
|
|
1235
|
+
if (existsSync4(join2(rootDir, "pnpm-lock.yaml"))) return "pnpm";
|
|
1236
|
+
if (existsSync4(join2(rootDir, "package-lock.json"))) return "npm";
|
|
1081
1237
|
return "npm";
|
|
1082
1238
|
}
|
|
1083
1239
|
function detectTypeScript(rootDir) {
|
|
1084
|
-
const candidate =
|
|
1085
|
-
if (
|
|
1240
|
+
const candidate = join2(rootDir, "tsconfig.json");
|
|
1241
|
+
if (existsSync4(candidate)) {
|
|
1086
1242
|
return { typescript: true, tsconfigPath: candidate };
|
|
1087
1243
|
}
|
|
1088
1244
|
return { typescript: false, tsconfigPath: null };
|
|
@@ -1094,11 +1250,11 @@ function detectComponentPatterns(rootDir, typescript) {
|
|
|
1094
1250
|
const ext = typescript ? "tsx" : "jsx";
|
|
1095
1251
|
const altExt = typescript ? "jsx" : "jsx";
|
|
1096
1252
|
for (const dir of COMPONENT_DIRS) {
|
|
1097
|
-
const absDir =
|
|
1098
|
-
if (!
|
|
1253
|
+
const absDir = join2(rootDir, dir);
|
|
1254
|
+
if (!existsSync4(absDir)) continue;
|
|
1099
1255
|
let hasComponents = false;
|
|
1100
1256
|
try {
|
|
1101
|
-
const entries =
|
|
1257
|
+
const entries = readdirSync2(absDir, { withFileTypes: true });
|
|
1102
1258
|
hasComponents = entries.some(
|
|
1103
1259
|
(e) => e.isFile() && COMPONENT_EXTS.some((x) => e.name.endsWith(x))
|
|
1104
1260
|
);
|
|
@@ -1106,7 +1262,7 @@ function detectComponentPatterns(rootDir, typescript) {
|
|
|
1106
1262
|
hasComponents = entries.some(
|
|
1107
1263
|
(e) => e.isDirectory() && (() => {
|
|
1108
1264
|
try {
|
|
1109
|
-
return
|
|
1265
|
+
return readdirSync2(join2(absDir, e.name)).some(
|
|
1110
1266
|
(f) => COMPONENT_EXTS.some((x) => f.endsWith(x))
|
|
1111
1267
|
);
|
|
1112
1268
|
} catch {
|
|
@@ -1143,7 +1299,7 @@ var GLOBAL_CSS_CANDIDATES = [
|
|
|
1143
1299
|
"styles/index.css"
|
|
1144
1300
|
];
|
|
1145
1301
|
function detectGlobalCSSFiles(rootDir) {
|
|
1146
|
-
return GLOBAL_CSS_CANDIDATES.filter((rel) =>
|
|
1302
|
+
return GLOBAL_CSS_CANDIDATES.filter((rel) => existsSync4(join2(rootDir, rel)));
|
|
1147
1303
|
}
|
|
1148
1304
|
var TAILWIND_STEMS = ["tailwind.config"];
|
|
1149
1305
|
var CSS_EXTS = [".css", ".scss", ".sass", ".less"];
|
|
@@ -1154,23 +1310,23 @@ function detectTokenSources(rootDir) {
|
|
|
1154
1310
|
for (const stem of TAILWIND_STEMS) {
|
|
1155
1311
|
if (hasConfigFile(rootDir, stem)) {
|
|
1156
1312
|
try {
|
|
1157
|
-
const entries =
|
|
1313
|
+
const entries = readdirSync2(rootDir);
|
|
1158
1314
|
const match = entries.find((f) => f === stem || f.startsWith(`${stem}.`));
|
|
1159
1315
|
if (match) {
|
|
1160
|
-
sources.push({ kind: "tailwind-config", path:
|
|
1316
|
+
sources.push({ kind: "tailwind-config", path: join2(rootDir, match) });
|
|
1161
1317
|
}
|
|
1162
1318
|
} catch {
|
|
1163
1319
|
}
|
|
1164
1320
|
}
|
|
1165
1321
|
}
|
|
1166
|
-
const srcDir =
|
|
1167
|
-
const dirsToScan =
|
|
1322
|
+
const srcDir = join2(rootDir, "src");
|
|
1323
|
+
const dirsToScan = existsSync4(srcDir) ? [srcDir] : [];
|
|
1168
1324
|
for (const scanDir of dirsToScan) {
|
|
1169
1325
|
try {
|
|
1170
|
-
const entries =
|
|
1326
|
+
const entries = readdirSync2(scanDir, { withFileTypes: true });
|
|
1171
1327
|
for (const entry of entries) {
|
|
1172
1328
|
if (entry.isFile() && CSS_EXTS.some((x) => entry.name.endsWith(x))) {
|
|
1173
|
-
const filePath =
|
|
1329
|
+
const filePath = join2(scanDir, entry.name);
|
|
1174
1330
|
const content = readSafe(filePath);
|
|
1175
1331
|
if (content !== null && CSS_CUSTOM_PROPS_RE.test(content)) {
|
|
1176
1332
|
sources.push({ kind: "css-custom-properties", path: filePath });
|
|
@@ -1180,12 +1336,12 @@ function detectTokenSources(rootDir) {
|
|
|
1180
1336
|
} catch {
|
|
1181
1337
|
}
|
|
1182
1338
|
}
|
|
1183
|
-
if (
|
|
1339
|
+
if (existsSync4(srcDir)) {
|
|
1184
1340
|
try {
|
|
1185
|
-
const entries =
|
|
1341
|
+
const entries = readdirSync2(srcDir);
|
|
1186
1342
|
for (const entry of entries) {
|
|
1187
1343
|
if (THEME_SUFFIXES.some((s) => entry.endsWith(s))) {
|
|
1188
|
-
sources.push({ kind: "theme-file", path:
|
|
1344
|
+
sources.push({ kind: "theme-file", path: join2(srcDir, entry) });
|
|
1189
1345
|
}
|
|
1190
1346
|
}
|
|
1191
1347
|
} catch {
|
|
@@ -1194,7 +1350,7 @@ function detectTokenSources(rootDir) {
|
|
|
1194
1350
|
return sources;
|
|
1195
1351
|
}
|
|
1196
1352
|
function detectProject(rootDir) {
|
|
1197
|
-
const pkgPath =
|
|
1353
|
+
const pkgPath = join2(rootDir, "package.json");
|
|
1198
1354
|
let packageDeps = {};
|
|
1199
1355
|
const pkgContent = readSafe(pkgPath);
|
|
1200
1356
|
if (pkgContent !== null) {
|
|
@@ -1225,7 +1381,8 @@ function detectProject(rootDir) {
|
|
|
1225
1381
|
}
|
|
1226
1382
|
|
|
1227
1383
|
// src/init/index.ts
|
|
1228
|
-
import {
|
|
1384
|
+
import { generateManifest as generateManifest2 } from "@agent-scope/manifest";
|
|
1385
|
+
import { Command as Command3 } from "commander";
|
|
1229
1386
|
function buildDefaultConfig(detected, tokenFile, outputDir) {
|
|
1230
1387
|
const include = detected.componentPatterns.length > 0 ? detected.componentPatterns : ["src/**/*.tsx"];
|
|
1231
1388
|
return {
|
|
@@ -1263,9 +1420,9 @@ function createRL() {
|
|
|
1263
1420
|
});
|
|
1264
1421
|
}
|
|
1265
1422
|
async function ask(rl, question) {
|
|
1266
|
-
return new Promise((
|
|
1423
|
+
return new Promise((resolve19) => {
|
|
1267
1424
|
rl.question(question, (answer) => {
|
|
1268
|
-
|
|
1425
|
+
resolve19(answer.trim());
|
|
1269
1426
|
});
|
|
1270
1427
|
});
|
|
1271
1428
|
}
|
|
@@ -1274,9 +1431,9 @@ async function askWithDefault(rl, label, defaultValue) {
|
|
|
1274
1431
|
return answer.length > 0 ? answer : defaultValue;
|
|
1275
1432
|
}
|
|
1276
1433
|
function ensureGitignoreEntry(rootDir, entry) {
|
|
1277
|
-
const gitignorePath =
|
|
1278
|
-
if (
|
|
1279
|
-
const content =
|
|
1434
|
+
const gitignorePath = join3(rootDir, ".gitignore");
|
|
1435
|
+
if (existsSync5(gitignorePath)) {
|
|
1436
|
+
const content = readFileSync5(gitignorePath, "utf-8");
|
|
1280
1437
|
const normalised = entry.replace(/\/$/, "");
|
|
1281
1438
|
const lines = content.split("\n").map((l) => l.trim());
|
|
1282
1439
|
if (lines.includes(entry) || lines.includes(normalised)) {
|
|
@@ -1305,7 +1462,7 @@ function extractTailwindTokens(tokenSources) {
|
|
|
1305
1462
|
return result;
|
|
1306
1463
|
};
|
|
1307
1464
|
var parseBlock = parseBlock2;
|
|
1308
|
-
const raw =
|
|
1465
|
+
const raw = readFileSync5(tailwindSource.path, "utf-8");
|
|
1309
1466
|
const tokens = {};
|
|
1310
1467
|
const colorsKeyIdx = raw.indexOf("colors:");
|
|
1311
1468
|
if (colorsKeyIdx !== -1) {
|
|
@@ -1340,7 +1497,7 @@ function extractTailwindTokens(tokenSources) {
|
|
|
1340
1497
|
}
|
|
1341
1498
|
}
|
|
1342
1499
|
if (Object.keys(colorTokens).length > 0) {
|
|
1343
|
-
tokens
|
|
1500
|
+
tokens.color = colorTokens;
|
|
1344
1501
|
}
|
|
1345
1502
|
}
|
|
1346
1503
|
}
|
|
@@ -1353,7 +1510,7 @@ function extractTailwindTokens(tokenSources) {
|
|
|
1353
1510
|
for (const [key, val] of Object.entries(spacingValues)) {
|
|
1354
1511
|
spacingTokens[key] = { value: val, type: "dimension" };
|
|
1355
1512
|
}
|
|
1356
|
-
tokens
|
|
1513
|
+
tokens.spacing = spacingTokens;
|
|
1357
1514
|
}
|
|
1358
1515
|
}
|
|
1359
1516
|
const fontFamilyMatch = raw.match(/fontFamily\s*:\s*\{([\s\S]*?)\n\s*\}/);
|
|
@@ -1366,7 +1523,7 @@ function extractTailwindTokens(tokenSources) {
|
|
|
1366
1523
|
}
|
|
1367
1524
|
}
|
|
1368
1525
|
if (Object.keys(fontTokens).length > 0) {
|
|
1369
|
-
tokens
|
|
1526
|
+
tokens.font = fontTokens;
|
|
1370
1527
|
}
|
|
1371
1528
|
}
|
|
1372
1529
|
const borderRadiusMatch = raw.match(/borderRadius\s*:\s*\{([\s\S]*?)\n\s*\}/);
|
|
@@ -1377,7 +1534,7 @@ function extractTailwindTokens(tokenSources) {
|
|
|
1377
1534
|
for (const [key, val] of Object.entries(radiusValues)) {
|
|
1378
1535
|
radiusTokens[key] = { value: val, type: "dimension" };
|
|
1379
1536
|
}
|
|
1380
|
-
tokens
|
|
1537
|
+
tokens.radius = radiusTokens;
|
|
1381
1538
|
}
|
|
1382
1539
|
}
|
|
1383
1540
|
return Object.keys(tokens).length > 0 ? tokens : null;
|
|
@@ -1386,14 +1543,14 @@ function extractTailwindTokens(tokenSources) {
|
|
|
1386
1543
|
}
|
|
1387
1544
|
}
|
|
1388
1545
|
function scaffoldConfig(rootDir, config) {
|
|
1389
|
-
const path =
|
|
1546
|
+
const path = join3(rootDir, "reactscope.config.json");
|
|
1390
1547
|
writeFileSync3(path, `${JSON.stringify(config, null, 2)}
|
|
1391
1548
|
`);
|
|
1392
1549
|
return path;
|
|
1393
1550
|
}
|
|
1394
1551
|
function scaffoldTokenFile(rootDir, tokenFile, extractedTokens) {
|
|
1395
|
-
const path =
|
|
1396
|
-
if (!
|
|
1552
|
+
const path = join3(rootDir, tokenFile);
|
|
1553
|
+
if (!existsSync5(path)) {
|
|
1397
1554
|
const stub = {
|
|
1398
1555
|
$schema: "https://raw.githubusercontent.com/FlatFilers/Scope/main/packages/tokens/schema.json",
|
|
1399
1556
|
version: "1.0.0",
|
|
@@ -1409,19 +1566,19 @@ function scaffoldTokenFile(rootDir, tokenFile, extractedTokens) {
|
|
|
1409
1566
|
return path;
|
|
1410
1567
|
}
|
|
1411
1568
|
function scaffoldOutputDir(rootDir, outputDir) {
|
|
1412
|
-
const dirPath =
|
|
1569
|
+
const dirPath = join3(rootDir, outputDir);
|
|
1413
1570
|
mkdirSync(dirPath, { recursive: true });
|
|
1414
|
-
const keepPath =
|
|
1415
|
-
if (!
|
|
1571
|
+
const keepPath = join3(dirPath, ".gitkeep");
|
|
1572
|
+
if (!existsSync5(keepPath)) {
|
|
1416
1573
|
writeFileSync3(keepPath, "");
|
|
1417
1574
|
}
|
|
1418
1575
|
return dirPath;
|
|
1419
1576
|
}
|
|
1420
1577
|
async function runInit(options) {
|
|
1421
1578
|
const rootDir = options.cwd ?? process.cwd();
|
|
1422
|
-
const configPath =
|
|
1579
|
+
const configPath = join3(rootDir, "reactscope.config.json");
|
|
1423
1580
|
const created = [];
|
|
1424
|
-
if (
|
|
1581
|
+
if (existsSync5(configPath) && !options.force) {
|
|
1425
1582
|
const msg = "reactscope.config.json already exists. Run with --force to overwrite.";
|
|
1426
1583
|
process.stderr.write(`\u26A0\uFE0F ${msg}
|
|
1427
1584
|
`);
|
|
@@ -1496,7 +1653,28 @@ async function runInit(options) {
|
|
|
1496
1653
|
process.stdout.write(` ${p}
|
|
1497
1654
|
`);
|
|
1498
1655
|
}
|
|
1499
|
-
process.stdout.write("\n
|
|
1656
|
+
process.stdout.write("\n Scanning components...\n");
|
|
1657
|
+
try {
|
|
1658
|
+
const manifestConfig = {
|
|
1659
|
+
include: config.components.include,
|
|
1660
|
+
rootDir
|
|
1661
|
+
};
|
|
1662
|
+
const manifest = await generateManifest2(manifestConfig);
|
|
1663
|
+
const manifestCount = Object.keys(manifest.components).length;
|
|
1664
|
+
const manifestOutPath = join3(rootDir, config.output.dir, "manifest.json");
|
|
1665
|
+
mkdirSync(join3(rootDir, config.output.dir), { recursive: true });
|
|
1666
|
+
writeFileSync3(manifestOutPath, `${JSON.stringify(manifest, null, 2)}
|
|
1667
|
+
`);
|
|
1668
|
+
process.stdout.write(
|
|
1669
|
+
` Found ${manifestCount} component(s) \u2014 manifest written to ${manifestOutPath}
|
|
1670
|
+
`
|
|
1671
|
+
);
|
|
1672
|
+
} catch {
|
|
1673
|
+
process.stdout.write(
|
|
1674
|
+
" (manifest generate skipped \u2014 run `scope manifest generate` manually)\n"
|
|
1675
|
+
);
|
|
1676
|
+
}
|
|
1677
|
+
process.stdout.write("\n");
|
|
1500
1678
|
return {
|
|
1501
1679
|
success: true,
|
|
1502
1680
|
message: "Project initialised successfully.",
|
|
@@ -1505,7 +1683,7 @@ async function runInit(options) {
|
|
|
1505
1683
|
};
|
|
1506
1684
|
}
|
|
1507
1685
|
function createInitCommand() {
|
|
1508
|
-
return new
|
|
1686
|
+
return new Command3("init").description("Initialise a Scope project \u2014 scaffold reactscope.config.json and friends").option("-y, --yes", "Accept all detected defaults without prompting", false).option("--force", "Overwrite existing reactscope.config.json if present", false).action(async (opts) => {
|
|
1509
1687
|
try {
|
|
1510
1688
|
const result = await runInit({ yes: opts.yes, force: opts.force });
|
|
1511
1689
|
if (!result.success && !result.skipped) {
|
|
@@ -1520,24 +1698,24 @@ function createInitCommand() {
|
|
|
1520
1698
|
}
|
|
1521
1699
|
|
|
1522
1700
|
// src/instrument/renders.ts
|
|
1523
|
-
import { resolve as
|
|
1701
|
+
import { resolve as resolve8 } from "path";
|
|
1524
1702
|
import { getBrowserEntryScript as getBrowserEntryScript5 } from "@agent-scope/playwright";
|
|
1525
1703
|
import { BrowserPool as BrowserPool2 } from "@agent-scope/render";
|
|
1526
|
-
import { Command as
|
|
1704
|
+
import { Command as Command6 } from "commander";
|
|
1527
1705
|
|
|
1528
1706
|
// src/manifest-commands.ts
|
|
1529
|
-
import { existsSync as
|
|
1530
|
-
import { resolve as
|
|
1531
|
-
import { generateManifest as
|
|
1532
|
-
import { Command as
|
|
1707
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
1708
|
+
import { resolve as resolve4 } from "path";
|
|
1709
|
+
import { generateManifest as generateManifest3 } from "@agent-scope/manifest";
|
|
1710
|
+
import { Command as Command4 } from "commander";
|
|
1533
1711
|
var MANIFEST_PATH = ".reactscope/manifest.json";
|
|
1534
1712
|
function loadManifest(manifestPath = MANIFEST_PATH) {
|
|
1535
|
-
const absPath =
|
|
1536
|
-
if (!
|
|
1713
|
+
const absPath = resolve4(process.cwd(), manifestPath);
|
|
1714
|
+
if (!existsSync6(absPath)) {
|
|
1537
1715
|
throw new Error(`Manifest not found at ${absPath}.
|
|
1538
1716
|
Run \`scope manifest generate\` first.`);
|
|
1539
1717
|
}
|
|
1540
|
-
const raw =
|
|
1718
|
+
const raw = readFileSync6(absPath, "utf-8");
|
|
1541
1719
|
return JSON.parse(raw);
|
|
1542
1720
|
}
|
|
1543
1721
|
function resolveFormat(formatFlag) {
|
|
@@ -1686,13 +1864,13 @@ function registerGenerate(manifestCmd) {
|
|
|
1686
1864
|
"Generate the component manifest from source and write to .reactscope/manifest.json"
|
|
1687
1865
|
).option("--root <path>", "Project root directory (default: cwd)").option("--output <path>", "Output path for manifest.json", MANIFEST_PATH).option("--include <globs>", "Comma-separated glob patterns to include").option("--exclude <globs>", "Comma-separated glob patterns to exclude").action(async (opts) => {
|
|
1688
1866
|
try {
|
|
1689
|
-
const rootDir =
|
|
1690
|
-
const outputPath =
|
|
1867
|
+
const rootDir = resolve4(process.cwd(), opts.root ?? ".");
|
|
1868
|
+
const outputPath = resolve4(process.cwd(), opts.output);
|
|
1691
1869
|
const include = opts.include?.split(",").map((s) => s.trim());
|
|
1692
1870
|
const exclude = opts.exclude?.split(",").map((s) => s.trim());
|
|
1693
1871
|
process.stderr.write(`Scanning ${rootDir} for React components...
|
|
1694
1872
|
`);
|
|
1695
|
-
const manifest = await
|
|
1873
|
+
const manifest = await generateManifest3({
|
|
1696
1874
|
rootDir,
|
|
1697
1875
|
...include !== void 0 && { include },
|
|
1698
1876
|
...exclude !== void 0 && { exclude }
|
|
@@ -1701,7 +1879,7 @@ function registerGenerate(manifestCmd) {
|
|
|
1701
1879
|
process.stderr.write(`Found ${componentCount} components.
|
|
1702
1880
|
`);
|
|
1703
1881
|
const outputDir = outputPath.replace(/\/[^/]+$/, "");
|
|
1704
|
-
if (!
|
|
1882
|
+
if (!existsSync6(outputDir)) {
|
|
1705
1883
|
mkdirSync2(outputDir, { recursive: true });
|
|
1706
1884
|
}
|
|
1707
1885
|
writeFileSync4(outputPath, JSON.stringify(manifest, null, 2), "utf-8");
|
|
@@ -1717,7 +1895,7 @@ function registerGenerate(manifestCmd) {
|
|
|
1717
1895
|
});
|
|
1718
1896
|
}
|
|
1719
1897
|
function createManifestCommand() {
|
|
1720
|
-
const manifestCmd = new
|
|
1898
|
+
const manifestCmd = new Command4("manifest").description(
|
|
1721
1899
|
"Query and explore the component manifest"
|
|
1722
1900
|
);
|
|
1723
1901
|
registerList(manifestCmd);
|
|
@@ -1728,7 +1906,7 @@ function createManifestCommand() {
|
|
|
1728
1906
|
}
|
|
1729
1907
|
|
|
1730
1908
|
// src/instrument/hooks.ts
|
|
1731
|
-
import { resolve as
|
|
1909
|
+
import { resolve as resolve5 } from "path";
|
|
1732
1910
|
import { getBrowserEntryScript as getBrowserEntryScript2 } from "@agent-scope/playwright";
|
|
1733
1911
|
import { Command as Cmd } from "commander";
|
|
1734
1912
|
import { chromium as chromium2 } from "playwright";
|
|
@@ -2060,7 +2238,7 @@ Available: ${available}`
|
|
|
2060
2238
|
throw new Error(`Invalid props JSON: ${opts.props}`);
|
|
2061
2239
|
}
|
|
2062
2240
|
const rootDir = process.cwd();
|
|
2063
|
-
const filePath =
|
|
2241
|
+
const filePath = resolve5(rootDir, descriptor.filePath);
|
|
2064
2242
|
process.stderr.write(`Instrumenting hooks for ${componentName}\u2026
|
|
2065
2243
|
`);
|
|
2066
2244
|
const result = await runHooksProfiling(componentName, filePath, props);
|
|
@@ -2088,7 +2266,7 @@ Available: ${available}`
|
|
|
2088
2266
|
}
|
|
2089
2267
|
|
|
2090
2268
|
// src/instrument/profile.ts
|
|
2091
|
-
import { resolve as
|
|
2269
|
+
import { resolve as resolve6 } from "path";
|
|
2092
2270
|
import { getBrowserEntryScript as getBrowserEntryScript3 } from "@agent-scope/playwright";
|
|
2093
2271
|
import { Command as Cmd2 } from "commander";
|
|
2094
2272
|
import { chromium as chromium3 } from "playwright";
|
|
@@ -2363,7 +2541,7 @@ Available: ${available}`
|
|
|
2363
2541
|
throw new Error(`Invalid interaction JSON: ${opts.interaction}`);
|
|
2364
2542
|
}
|
|
2365
2543
|
const rootDir = process.cwd();
|
|
2366
|
-
const filePath =
|
|
2544
|
+
const filePath = resolve6(rootDir, descriptor.filePath);
|
|
2367
2545
|
process.stderr.write(`Profiling interaction for ${componentName}\u2026
|
|
2368
2546
|
`);
|
|
2369
2547
|
const result = await runInteractionProfile(componentName, filePath, props, interaction);
|
|
@@ -2391,9 +2569,9 @@ Available: ${available}`
|
|
|
2391
2569
|
}
|
|
2392
2570
|
|
|
2393
2571
|
// src/instrument/tree.ts
|
|
2394
|
-
import { resolve as
|
|
2572
|
+
import { resolve as resolve7 } from "path";
|
|
2395
2573
|
import { getBrowserEntryScript as getBrowserEntryScript4 } from "@agent-scope/playwright";
|
|
2396
|
-
import { Command as
|
|
2574
|
+
import { Command as Command5 } from "commander";
|
|
2397
2575
|
import { chromium as chromium4 } from "playwright";
|
|
2398
2576
|
var MANIFEST_PATH4 = ".reactscope/manifest.json";
|
|
2399
2577
|
var DEFAULT_VIEWPORT_WIDTH = 375;
|
|
@@ -2674,7 +2852,7 @@ async function runInstrumentTree(options) {
|
|
|
2674
2852
|
}
|
|
2675
2853
|
}
|
|
2676
2854
|
function createInstrumentTreeCommand() {
|
|
2677
|
-
return new
|
|
2855
|
+
return new Command5("tree").description("Render a component via BrowserPool and output a structured instrumentation tree").argument("<component>", "Component name to instrument (must exist in the manifest)").option("--sort-by <field>", "Sort nodes by field: renderCount | depth").option("--limit <n>", "Limit output to the first N nodes (depth-first)").option("--uses-context <name>", "Filter to components that use a specific context").option("--provider-depth", "Annotate each node with its context-provider nesting depth", false).option(
|
|
2678
2856
|
"--wasted-renders",
|
|
2679
2857
|
"Filter to components with wasted renders (no prop/state/context changes, not memoized)",
|
|
2680
2858
|
false
|
|
@@ -2698,7 +2876,7 @@ Available: ${available}`
|
|
|
2698
2876
|
}
|
|
2699
2877
|
}
|
|
2700
2878
|
const rootDir = process.cwd();
|
|
2701
|
-
const filePath =
|
|
2879
|
+
const filePath = resolve7(rootDir, descriptor.filePath);
|
|
2702
2880
|
process.stderr.write(`Instrumenting ${componentName}\u2026
|
|
2703
2881
|
`);
|
|
2704
2882
|
const instrumentRoot = await runInstrumentTree({
|
|
@@ -3069,7 +3247,7 @@ Available: ${available}`
|
|
|
3069
3247
|
);
|
|
3070
3248
|
}
|
|
3071
3249
|
const rootDir = process.cwd();
|
|
3072
|
-
const filePath =
|
|
3250
|
+
const filePath = resolve8(rootDir, descriptor.filePath);
|
|
3073
3251
|
const preScript = getBrowserEntryScript5() + "\n" + buildInstrumentationScript();
|
|
3074
3252
|
const htmlHarness = await buildComponentHarness(
|
|
3075
3253
|
filePath,
|
|
@@ -3159,7 +3337,7 @@ function formatRendersTable(result) {
|
|
|
3159
3337
|
return lines.join("\n");
|
|
3160
3338
|
}
|
|
3161
3339
|
function createInstrumentRendersCommand() {
|
|
3162
|
-
return new
|
|
3340
|
+
return new Command6("renders").description("Trace re-render causality chains for a component during an interaction sequence").argument("<component>", "Component name to instrument (must be in manifest)").option(
|
|
3163
3341
|
"--interaction <json>",
|
|
3164
3342
|
`Interaction sequence JSON, e.g. '[{"action":"click","target":"button"}]'`,
|
|
3165
3343
|
"[]"
|
|
@@ -3204,7 +3382,7 @@ function createInstrumentRendersCommand() {
|
|
|
3204
3382
|
);
|
|
3205
3383
|
}
|
|
3206
3384
|
function createInstrumentCommand() {
|
|
3207
|
-
const instrumentCmd = new
|
|
3385
|
+
const instrumentCmd = new Command6("instrument").description(
|
|
3208
3386
|
"Structured instrumentation commands for React component analysis"
|
|
3209
3387
|
);
|
|
3210
3388
|
instrumentCmd.addCommand(createInstrumentRendersCommand());
|
|
@@ -3215,8 +3393,8 @@ function createInstrumentCommand() {
|
|
|
3215
3393
|
}
|
|
3216
3394
|
|
|
3217
3395
|
// src/render-commands.ts
|
|
3218
|
-
import { existsSync as
|
|
3219
|
-
import { resolve as
|
|
3396
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
3397
|
+
import { resolve as resolve10 } from "path";
|
|
3220
3398
|
import {
|
|
3221
3399
|
ALL_CONTEXT_IDS,
|
|
3222
3400
|
ALL_STRESS_IDS,
|
|
@@ -3227,13 +3405,13 @@ import {
|
|
|
3227
3405
|
safeRender as safeRender2,
|
|
3228
3406
|
stressAxis
|
|
3229
3407
|
} from "@agent-scope/render";
|
|
3230
|
-
import { Command as
|
|
3408
|
+
import { Command as Command7 } from "commander";
|
|
3231
3409
|
|
|
3232
3410
|
// src/scope-file.ts
|
|
3233
|
-
import { existsSync as
|
|
3411
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, rmSync } from "fs";
|
|
3234
3412
|
import { createRequire as createRequire2 } from "module";
|
|
3235
3413
|
import { tmpdir } from "os";
|
|
3236
|
-
import { dirname as dirname2, join as
|
|
3414
|
+
import { dirname as dirname2, join as join4, resolve as resolve9 } from "path";
|
|
3237
3415
|
import * as esbuild2 from "esbuild";
|
|
3238
3416
|
var SCOPE_EXTENSIONS = [".scope.tsx", ".scope.ts", ".scope.jsx", ".scope.js"];
|
|
3239
3417
|
function findScopeFile(componentFilePath) {
|
|
@@ -3241,15 +3419,15 @@ function findScopeFile(componentFilePath) {
|
|
|
3241
3419
|
const stem = componentFilePath.replace(/\.(tsx?|jsx?)$/, "");
|
|
3242
3420
|
const baseName = stem.slice(dir.length + 1);
|
|
3243
3421
|
for (const ext of SCOPE_EXTENSIONS) {
|
|
3244
|
-
const candidate =
|
|
3245
|
-
if (
|
|
3422
|
+
const candidate = join4(dir, `${baseName}${ext}`);
|
|
3423
|
+
if (existsSync7(candidate)) return candidate;
|
|
3246
3424
|
}
|
|
3247
3425
|
return null;
|
|
3248
3426
|
}
|
|
3249
3427
|
async function loadScopeFile(scopeFilePath) {
|
|
3250
|
-
const tmpDir =
|
|
3428
|
+
const tmpDir = join4(tmpdir(), `scope-file-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
3251
3429
|
mkdirSync3(tmpDir, { recursive: true });
|
|
3252
|
-
const outFile =
|
|
3430
|
+
const outFile = join4(tmpDir, "scope-file.cjs");
|
|
3253
3431
|
try {
|
|
3254
3432
|
const result = await esbuild2.build({
|
|
3255
3433
|
entryPoints: [scopeFilePath],
|
|
@@ -3274,7 +3452,7 @@ async function loadScopeFile(scopeFilePath) {
|
|
|
3274
3452
|
${msg}`);
|
|
3275
3453
|
}
|
|
3276
3454
|
const req = createRequire2(import.meta.url);
|
|
3277
|
-
delete req.cache[
|
|
3455
|
+
delete req.cache[resolve9(outFile)];
|
|
3278
3456
|
const mod = req(outFile);
|
|
3279
3457
|
const scenarios = extractScenarios(mod, scopeFilePath);
|
|
3280
3458
|
const hasWrapper = typeof mod.wrapper === "function" || typeof mod.default?.wrapper === "function";
|
|
@@ -3352,10 +3530,10 @@ ${msg}`);
|
|
|
3352
3530
|
|
|
3353
3531
|
// src/render-commands.ts
|
|
3354
3532
|
function loadGlobalCssFilesFromConfig(cwd) {
|
|
3355
|
-
const configPath =
|
|
3356
|
-
if (!
|
|
3533
|
+
const configPath = resolve10(cwd, "reactscope.config.json");
|
|
3534
|
+
if (!existsSync8(configPath)) return [];
|
|
3357
3535
|
try {
|
|
3358
|
-
const raw =
|
|
3536
|
+
const raw = readFileSync7(configPath, "utf-8");
|
|
3359
3537
|
const cfg = JSON.parse(raw);
|
|
3360
3538
|
return cfg.components?.wrappers?.globalCSS ?? [];
|
|
3361
3539
|
} catch {
|
|
@@ -3426,7 +3604,7 @@ function buildRenderer(filePath, componentName, viewportWidth, viewportHeight, g
|
|
|
3426
3604
|
}
|
|
3427
3605
|
});
|
|
3428
3606
|
return [...set];
|
|
3429
|
-
});
|
|
3607
|
+
}) ?? [];
|
|
3430
3608
|
const projectCss2 = await getCompiledCssForClasses(rootDir, classes);
|
|
3431
3609
|
if (projectCss2 != null && projectCss2.length > 0) {
|
|
3432
3610
|
await page.addStyleTag({ content: projectCss2 });
|
|
@@ -3439,49 +3617,147 @@ function buildRenderer(filePath, componentName, viewportWidth, viewportHeight, g
|
|
|
3439
3617
|
`Component "${componentName}" rendered with zero bounding box \u2014 it may be invisible or not mounted`
|
|
3440
3618
|
);
|
|
3441
3619
|
}
|
|
3442
|
-
const PAD =
|
|
3443
|
-
const MIN_W = 320;
|
|
3444
|
-
const MIN_H = 200;
|
|
3620
|
+
const PAD = 8;
|
|
3445
3621
|
const clipX = Math.max(0, boundingBox.x - PAD);
|
|
3446
3622
|
const clipY = Math.max(0, boundingBox.y - PAD);
|
|
3447
3623
|
const rawW = boundingBox.width + PAD * 2;
|
|
3448
3624
|
const rawH = boundingBox.height + PAD * 2;
|
|
3449
|
-
const
|
|
3450
|
-
const
|
|
3451
|
-
const safeW = Math.min(clipW, viewportWidth - clipX);
|
|
3452
|
-
const safeH = Math.min(clipH, viewportHeight - clipY);
|
|
3625
|
+
const safeW = Math.min(rawW, viewportWidth - clipX);
|
|
3626
|
+
const safeH = Math.min(rawH, viewportHeight - clipY);
|
|
3453
3627
|
const screenshot = await page.screenshot({
|
|
3454
3628
|
clip: { x: clipX, y: clipY, width: safeW, height: safeH },
|
|
3455
3629
|
type: "png"
|
|
3456
3630
|
});
|
|
3631
|
+
const STYLE_PROPS = [
|
|
3632
|
+
"display",
|
|
3633
|
+
"width",
|
|
3634
|
+
"height",
|
|
3635
|
+
"color",
|
|
3636
|
+
"backgroundColor",
|
|
3637
|
+
"fontSize",
|
|
3638
|
+
"fontFamily",
|
|
3639
|
+
"fontWeight",
|
|
3640
|
+
"lineHeight",
|
|
3641
|
+
"padding",
|
|
3642
|
+
"paddingTop",
|
|
3643
|
+
"paddingRight",
|
|
3644
|
+
"paddingBottom",
|
|
3645
|
+
"paddingLeft",
|
|
3646
|
+
"margin",
|
|
3647
|
+
"marginTop",
|
|
3648
|
+
"marginRight",
|
|
3649
|
+
"marginBottom",
|
|
3650
|
+
"marginLeft",
|
|
3651
|
+
"gap",
|
|
3652
|
+
"borderRadius",
|
|
3653
|
+
"borderWidth",
|
|
3654
|
+
"borderColor",
|
|
3655
|
+
"borderStyle",
|
|
3656
|
+
"boxShadow",
|
|
3657
|
+
"opacity",
|
|
3658
|
+
"position",
|
|
3659
|
+
"flexDirection",
|
|
3660
|
+
"alignItems",
|
|
3661
|
+
"justifyContent",
|
|
3662
|
+
"overflow"
|
|
3663
|
+
];
|
|
3664
|
+
const _domResult = await page.evaluate(
|
|
3665
|
+
(args) => {
|
|
3666
|
+
let count = 0;
|
|
3667
|
+
const styles = {};
|
|
3668
|
+
function captureStyles(el, id, propList) {
|
|
3669
|
+
const computed = window.getComputedStyle(el);
|
|
3670
|
+
const out = {};
|
|
3671
|
+
for (const prop of propList) {
|
|
3672
|
+
const val = computed[prop] ?? "";
|
|
3673
|
+
if (val && val !== "none" && val !== "normal" && val !== "auto") out[prop] = val;
|
|
3674
|
+
}
|
|
3675
|
+
styles[id] = out;
|
|
3676
|
+
}
|
|
3677
|
+
function walk(node) {
|
|
3678
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
3679
|
+
return {
|
|
3680
|
+
tag: "#text",
|
|
3681
|
+
attrs: {},
|
|
3682
|
+
text: node.textContent?.trim() ?? "",
|
|
3683
|
+
children: []
|
|
3684
|
+
};
|
|
3685
|
+
}
|
|
3686
|
+
const el = node;
|
|
3687
|
+
const id = count++;
|
|
3688
|
+
captureStyles(el, id, args.props);
|
|
3689
|
+
const attrs = {};
|
|
3690
|
+
for (const attr of Array.from(el.attributes)) {
|
|
3691
|
+
attrs[attr.name] = attr.value;
|
|
3692
|
+
}
|
|
3693
|
+
const children = Array.from(el.childNodes).filter(
|
|
3694
|
+
(n) => n.nodeType === Node.ELEMENT_NODE || n.nodeType === Node.TEXT_NODE && (n.textContent?.trim() ?? "").length > 0
|
|
3695
|
+
).map(walk);
|
|
3696
|
+
return { tag: el.tagName.toLowerCase(), attrs, nodeId: id, children };
|
|
3697
|
+
}
|
|
3698
|
+
const root = document.querySelector(args.sel);
|
|
3699
|
+
if (!root)
|
|
3700
|
+
return {
|
|
3701
|
+
tree: { tag: "div", attrs: {}, children: [] },
|
|
3702
|
+
elementCount: 0,
|
|
3703
|
+
nodeStyles: {}
|
|
3704
|
+
};
|
|
3705
|
+
return { tree: walk(root), elementCount: count, nodeStyles: styles };
|
|
3706
|
+
},
|
|
3707
|
+
{ sel: "[data-reactscope-root] > *", props: STYLE_PROPS }
|
|
3708
|
+
);
|
|
3709
|
+
const domTree = _domResult?.tree ?? { tag: "div", attrs: {}, children: [] };
|
|
3710
|
+
const elementCount = _domResult?.elementCount ?? 0;
|
|
3711
|
+
const nodeStyles = _domResult?.nodeStyles ?? {};
|
|
3457
3712
|
const computedStyles = {};
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
"fontFamily",
|
|
3471
|
-
"padding",
|
|
3472
|
-
"margin"
|
|
3473
|
-
]) {
|
|
3474
|
-
out[prop] = computed.getPropertyValue(prop);
|
|
3713
|
+
if (nodeStyles[0]) computedStyles["[data-reactscope-root] > *"] = nodeStyles[0];
|
|
3714
|
+
for (const [nodeId, styles] of Object.entries(nodeStyles)) {
|
|
3715
|
+
computedStyles[`#node-${nodeId}`] = styles;
|
|
3716
|
+
}
|
|
3717
|
+
const dom = {
|
|
3718
|
+
tree: domTree,
|
|
3719
|
+
elementCount,
|
|
3720
|
+
boundingBox: {
|
|
3721
|
+
x: boundingBox.x,
|
|
3722
|
+
y: boundingBox.y,
|
|
3723
|
+
width: boundingBox.width,
|
|
3724
|
+
height: boundingBox.height
|
|
3475
3725
|
}
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3726
|
+
};
|
|
3727
|
+
const a11yInfo = await page.evaluate((sel) => {
|
|
3728
|
+
const wrapper = document.querySelector(sel);
|
|
3729
|
+
const el = wrapper?.firstElementChild ?? wrapper;
|
|
3730
|
+
if (!el) return { role: "generic", name: "" };
|
|
3731
|
+
return {
|
|
3732
|
+
role: el.getAttribute("role") ?? el.tagName.toLowerCase() ?? "generic",
|
|
3733
|
+
name: el.getAttribute("aria-label") ?? el.getAttribute("aria-labelledby") ?? el.textContent?.trim().slice(0, 100) ?? ""
|
|
3734
|
+
};
|
|
3735
|
+
}, "[data-reactscope-root]") ?? {
|
|
3736
|
+
role: "generic",
|
|
3737
|
+
name: ""
|
|
3738
|
+
};
|
|
3739
|
+
const imgViolations = await page.evaluate((sel) => {
|
|
3740
|
+
const container = document.querySelector(sel);
|
|
3741
|
+
if (!container) return [];
|
|
3742
|
+
const issues = [];
|
|
3743
|
+
container.querySelectorAll("img").forEach((img) => {
|
|
3744
|
+
if (!img.alt) issues.push("Image missing accessible name");
|
|
3745
|
+
});
|
|
3746
|
+
return issues;
|
|
3747
|
+
}, "[data-reactscope-root]") ?? [];
|
|
3748
|
+
const accessibility = {
|
|
3749
|
+
role: a11yInfo.role,
|
|
3750
|
+
name: a11yInfo.name,
|
|
3751
|
+
violations: imgViolations
|
|
3752
|
+
};
|
|
3479
3753
|
return {
|
|
3480
3754
|
screenshot,
|
|
3481
3755
|
width: Math.round(safeW),
|
|
3482
3756
|
height: Math.round(safeH),
|
|
3483
3757
|
renderTimeMs,
|
|
3484
|
-
computedStyles
|
|
3758
|
+
computedStyles,
|
|
3759
|
+
dom,
|
|
3760
|
+
accessibility
|
|
3485
3761
|
};
|
|
3486
3762
|
} finally {
|
|
3487
3763
|
pool.release(slot);
|
|
@@ -3557,11 +3833,16 @@ Available: ${available}`
|
|
|
3557
3833
|
}
|
|
3558
3834
|
const { width, height } = parseViewport(opts.viewport);
|
|
3559
3835
|
const rootDir = process.cwd();
|
|
3560
|
-
const filePath =
|
|
3836
|
+
const filePath = resolve10(rootDir, descriptor.filePath);
|
|
3561
3837
|
const scopeData = await loadScopeFileForComponent(filePath);
|
|
3562
3838
|
const wrapperScript = scopeData?.hasWrapper === true ? await buildWrapperScript(scopeData.filePath) : void 0;
|
|
3563
3839
|
const scenarios = buildScenarioMap(opts, scopeData);
|
|
3564
3840
|
const globalCssFiles = loadGlobalCssFilesFromConfig(rootDir);
|
|
3841
|
+
if (globalCssFiles.length === 0) {
|
|
3842
|
+
process.stderr.write(
|
|
3843
|
+
"warning: No globalCSS files configured. Tailwind/CSS styles will not be applied to renders.\n Add `components.wrappers.globalCSS` to reactscope.config.json\n"
|
|
3844
|
+
);
|
|
3845
|
+
}
|
|
3565
3846
|
const renderer = buildRenderer(
|
|
3566
3847
|
filePath,
|
|
3567
3848
|
componentName,
|
|
@@ -3605,7 +3886,7 @@ Available: ${available}`
|
|
|
3605
3886
|
const result = outcome.result;
|
|
3606
3887
|
const outFileName = isNamed ? `${componentName}-${scenarioName}.png` : `${componentName}.png`;
|
|
3607
3888
|
if (opts.output !== void 0 && !isNamed) {
|
|
3608
|
-
const outPath =
|
|
3889
|
+
const outPath = resolve10(process.cwd(), opts.output);
|
|
3609
3890
|
writeFileSync5(outPath, result.screenshot);
|
|
3610
3891
|
process.stdout.write(
|
|
3611
3892
|
`\u2713 ${label} \u2192 ${opts.output} (${result.width}\xD7${result.height}, ${result.renderTimeMs.toFixed(0)}ms)
|
|
@@ -3616,9 +3897,9 @@ Available: ${available}`
|
|
|
3616
3897
|
process.stdout.write(`${JSON.stringify(json, null, 2)}
|
|
3617
3898
|
`);
|
|
3618
3899
|
} else {
|
|
3619
|
-
const dir =
|
|
3900
|
+
const dir = resolve10(process.cwd(), DEFAULT_OUTPUT_DIR);
|
|
3620
3901
|
mkdirSync4(dir, { recursive: true });
|
|
3621
|
-
const outPath =
|
|
3902
|
+
const outPath = resolve10(dir, outFileName);
|
|
3622
3903
|
writeFileSync5(outPath, result.screenshot);
|
|
3623
3904
|
const relPath = `${DEFAULT_OUTPUT_DIR}/${outFileName}`;
|
|
3624
3905
|
process.stdout.write(
|
|
@@ -3660,7 +3941,7 @@ Available: ${available}`
|
|
|
3660
3941
|
const concurrency = Math.max(1, parseInt(opts.concurrency, 10) || 8);
|
|
3661
3942
|
const { width, height } = { width: 375, height: 812 };
|
|
3662
3943
|
const rootDir = process.cwd();
|
|
3663
|
-
const filePath =
|
|
3944
|
+
const filePath = resolve10(rootDir, descriptor.filePath);
|
|
3664
3945
|
const matrixCssFiles = loadGlobalCssFilesFromConfig(rootDir);
|
|
3665
3946
|
const renderer = buildRenderer(
|
|
3666
3947
|
filePath,
|
|
@@ -3753,7 +4034,7 @@ Available: ${available}`
|
|
|
3753
4034
|
const { SpriteSheetGenerator: SpriteSheetGenerator2 } = await import("@agent-scope/render");
|
|
3754
4035
|
const gen = new SpriteSheetGenerator2();
|
|
3755
4036
|
const sheet = await gen.generate(result);
|
|
3756
|
-
const spritePath =
|
|
4037
|
+
const spritePath = resolve10(process.cwd(), opts.sprite);
|
|
3757
4038
|
writeFileSync5(spritePath, sheet.png);
|
|
3758
4039
|
process.stderr.write(`Sprite sheet saved to ${spritePath}
|
|
3759
4040
|
`);
|
|
@@ -3763,9 +4044,9 @@ Available: ${available}`
|
|
|
3763
4044
|
const { SpriteSheetGenerator: SpriteSheetGenerator2 } = await import("@agent-scope/render");
|
|
3764
4045
|
const gen = new SpriteSheetGenerator2();
|
|
3765
4046
|
const sheet = await gen.generate(result);
|
|
3766
|
-
const dir =
|
|
4047
|
+
const dir = resolve10(process.cwd(), DEFAULT_OUTPUT_DIR);
|
|
3767
4048
|
mkdirSync4(dir, { recursive: true });
|
|
3768
|
-
const outPath =
|
|
4049
|
+
const outPath = resolve10(dir, `${componentName}-matrix.png`);
|
|
3769
4050
|
writeFileSync5(outPath, sheet.png);
|
|
3770
4051
|
const relPath = `${DEFAULT_OUTPUT_DIR}/${componentName}-matrix.png`;
|
|
3771
4052
|
process.stdout.write(
|
|
@@ -3809,23 +4090,37 @@ function registerRenderAll(renderCmd) {
|
|
|
3809
4090
|
return;
|
|
3810
4091
|
}
|
|
3811
4092
|
const concurrency = Math.max(1, parseInt(opts.concurrency, 10) || 4);
|
|
3812
|
-
const outputDir =
|
|
4093
|
+
const outputDir = resolve10(process.cwd(), opts.outputDir);
|
|
3813
4094
|
mkdirSync4(outputDir, { recursive: true });
|
|
3814
4095
|
const rootDir = process.cwd();
|
|
3815
4096
|
process.stderr.write(`Rendering ${total} components (concurrency: ${concurrency})\u2026
|
|
3816
4097
|
`);
|
|
3817
4098
|
const results = [];
|
|
4099
|
+
const complianceStylesMap = {};
|
|
3818
4100
|
let completed = 0;
|
|
3819
4101
|
const renderOne = async (name) => {
|
|
3820
4102
|
const descriptor = manifest.components[name];
|
|
3821
4103
|
if (descriptor === void 0) return;
|
|
3822
|
-
const filePath =
|
|
4104
|
+
const filePath = resolve10(rootDir, descriptor.filePath);
|
|
3823
4105
|
const allCssFiles = loadGlobalCssFilesFromConfig(process.cwd());
|
|
3824
|
-
const
|
|
4106
|
+
const scopeData = await loadScopeFileForComponent(filePath);
|
|
4107
|
+
const scenarioEntries = scopeData !== null ? Object.entries(scopeData.scenarios) : [];
|
|
4108
|
+
const defaultEntry = scenarioEntries.find(([k]) => k === "default") ?? scenarioEntries[0];
|
|
4109
|
+
const renderProps = defaultEntry !== void 0 ? defaultEntry[1] : {};
|
|
4110
|
+
const wrapperScript = scopeData?.hasWrapper === true ? await buildWrapperScript(scopeData.filePath) : void 0;
|
|
4111
|
+
const renderer = buildRenderer(
|
|
4112
|
+
filePath,
|
|
4113
|
+
name,
|
|
4114
|
+
375,
|
|
4115
|
+
812,
|
|
4116
|
+
allCssFiles,
|
|
4117
|
+
process.cwd(),
|
|
4118
|
+
wrapperScript
|
|
4119
|
+
);
|
|
3825
4120
|
const outcome = await safeRender2(
|
|
3826
|
-
() => renderer.renderCell(
|
|
4121
|
+
() => renderer.renderCell(renderProps, descriptor.complexityClass),
|
|
3827
4122
|
{
|
|
3828
|
-
props:
|
|
4123
|
+
props: renderProps,
|
|
3829
4124
|
sourceLocation: {
|
|
3830
4125
|
file: descriptor.filePath,
|
|
3831
4126
|
line: descriptor.loc.start,
|
|
@@ -3843,7 +4138,7 @@ function registerRenderAll(renderCmd) {
|
|
|
3843
4138
|
success: false,
|
|
3844
4139
|
errorMessage: outcome.error.message
|
|
3845
4140
|
});
|
|
3846
|
-
const errPath =
|
|
4141
|
+
const errPath = resolve10(outputDir, `${name}.error.json`);
|
|
3847
4142
|
writeFileSync5(
|
|
3848
4143
|
errPath,
|
|
3849
4144
|
JSON.stringify(
|
|
@@ -3861,10 +4156,81 @@ function registerRenderAll(renderCmd) {
|
|
|
3861
4156
|
}
|
|
3862
4157
|
const result = outcome.result;
|
|
3863
4158
|
results.push({ name, renderTimeMs: result.renderTimeMs, success: true });
|
|
3864
|
-
const pngPath =
|
|
4159
|
+
const pngPath = resolve10(outputDir, `${name}.png`);
|
|
3865
4160
|
writeFileSync5(pngPath, result.screenshot);
|
|
3866
|
-
const jsonPath =
|
|
4161
|
+
const jsonPath = resolve10(outputDir, `${name}.json`);
|
|
3867
4162
|
writeFileSync5(jsonPath, JSON.stringify(formatRenderJson(name, {}, result), null, 2));
|
|
4163
|
+
const rawStyles = result.computedStyles["[data-reactscope-root] > *"] ?? {};
|
|
4164
|
+
const compStyles = {
|
|
4165
|
+
colors: {},
|
|
4166
|
+
spacing: {},
|
|
4167
|
+
typography: {},
|
|
4168
|
+
borders: {},
|
|
4169
|
+
shadows: {}
|
|
4170
|
+
};
|
|
4171
|
+
for (const [prop, val] of Object.entries(rawStyles)) {
|
|
4172
|
+
if (!val || val === "none" || val === "") continue;
|
|
4173
|
+
const lower = prop.toLowerCase();
|
|
4174
|
+
if (lower.includes("color") || lower.includes("background")) {
|
|
4175
|
+
compStyles.colors[prop] = val;
|
|
4176
|
+
} else if (lower.includes("padding") || lower.includes("margin") || lower.includes("gap") || lower.includes("width") || lower.includes("height")) {
|
|
4177
|
+
compStyles.spacing[prop] = val;
|
|
4178
|
+
} else if (lower.includes("font") || lower.includes("lineheight") || lower.includes("letterspacing") || lower.includes("texttransform")) {
|
|
4179
|
+
compStyles.typography[prop] = val;
|
|
4180
|
+
} else if (lower.includes("border") || lower.includes("radius") || lower.includes("outline")) {
|
|
4181
|
+
compStyles.borders[prop] = val;
|
|
4182
|
+
} else if (lower.includes("shadow")) {
|
|
4183
|
+
compStyles.shadows[prop] = val;
|
|
4184
|
+
}
|
|
4185
|
+
}
|
|
4186
|
+
complianceStylesMap[name] = compStyles;
|
|
4187
|
+
if (scopeData !== null && Object.keys(scopeData.scenarios).length >= 2) {
|
|
4188
|
+
try {
|
|
4189
|
+
const scenarioEntries2 = Object.entries(scopeData.scenarios);
|
|
4190
|
+
const scenarioAxis = {
|
|
4191
|
+
name: "scenario",
|
|
4192
|
+
values: scenarioEntries2.map(([k]) => k)
|
|
4193
|
+
};
|
|
4194
|
+
const scenarioPropsMap = Object.fromEntries(scenarioEntries2);
|
|
4195
|
+
const matrixRenderer = buildRenderer(
|
|
4196
|
+
filePath,
|
|
4197
|
+
name,
|
|
4198
|
+
375,
|
|
4199
|
+
812,
|
|
4200
|
+
allCssFiles,
|
|
4201
|
+
process.cwd(),
|
|
4202
|
+
wrapperScript
|
|
4203
|
+
);
|
|
4204
|
+
const wrappedRenderer = {
|
|
4205
|
+
_satori: matrixRenderer._satori,
|
|
4206
|
+
async renderCell(props, cc) {
|
|
4207
|
+
const scenarioName = props.scenario;
|
|
4208
|
+
const realProps = scenarioName !== void 0 ? scenarioPropsMap[scenarioName] ?? props : props;
|
|
4209
|
+
return matrixRenderer.renderCell(realProps, cc ?? "simple");
|
|
4210
|
+
}
|
|
4211
|
+
};
|
|
4212
|
+
const matrix = new RenderMatrix(wrappedRenderer, [scenarioAxis], {
|
|
4213
|
+
concurrency: 2
|
|
4214
|
+
});
|
|
4215
|
+
const matrixResult = await matrix.render();
|
|
4216
|
+
const matrixCells = matrixResult.cells.map((cell) => ({
|
|
4217
|
+
axisValues: [scenarioEntries2[cell.axisIndices[0] ?? 0]?.[0] ?? ""],
|
|
4218
|
+
screenshot: cell.result.screenshot.toString("base64"),
|
|
4219
|
+
width: cell.result.width,
|
|
4220
|
+
height: cell.result.height,
|
|
4221
|
+
renderTimeMs: cell.result.renderTimeMs
|
|
4222
|
+
}));
|
|
4223
|
+
const existingJson = JSON.parse(readFileSync7(jsonPath, "utf-8"));
|
|
4224
|
+
existingJson.cells = matrixCells;
|
|
4225
|
+
existingJson.axisLabels = [scenarioAxis.values];
|
|
4226
|
+
writeFileSync5(jsonPath, JSON.stringify(existingJson, null, 2));
|
|
4227
|
+
} catch (matrixErr) {
|
|
4228
|
+
process.stderr.write(
|
|
4229
|
+
` [warn] Matrix render for ${name} failed: ${matrixErr instanceof Error ? matrixErr.message : String(matrixErr)}
|
|
4230
|
+
`
|
|
4231
|
+
);
|
|
4232
|
+
}
|
|
4233
|
+
}
|
|
3868
4234
|
if (isTTY()) {
|
|
3869
4235
|
process.stdout.write(
|
|
3870
4236
|
`\u2713 ${name} \u2192 ${opts.outputDir}/${name}.png (${result.width}\xD7${result.height}, ${result.renderTimeMs.toFixed(0)}ms)
|
|
@@ -3888,6 +4254,14 @@ function registerRenderAll(renderCmd) {
|
|
|
3888
4254
|
}
|
|
3889
4255
|
await Promise.all(workers);
|
|
3890
4256
|
await shutdownPool3();
|
|
4257
|
+
const compStylesPath = resolve10(
|
|
4258
|
+
resolve10(process.cwd(), opts.outputDir),
|
|
4259
|
+
"..",
|
|
4260
|
+
"compliance-styles.json"
|
|
4261
|
+
);
|
|
4262
|
+
writeFileSync5(compStylesPath, JSON.stringify(complianceStylesMap, null, 2));
|
|
4263
|
+
process.stderr.write(`[scope/render] \u2713 Wrote compliance-styles.json
|
|
4264
|
+
`);
|
|
3891
4265
|
process.stderr.write("\n");
|
|
3892
4266
|
const summary = formatSummaryText(results, outputDir);
|
|
3893
4267
|
process.stderr.write(`${summary}
|
|
@@ -3926,7 +4300,7 @@ function resolveMatrixFormat(formatFlag, spriteAlreadyWritten) {
|
|
|
3926
4300
|
return "json";
|
|
3927
4301
|
}
|
|
3928
4302
|
function createRenderCommand() {
|
|
3929
|
-
const renderCmd = new
|
|
4303
|
+
const renderCmd = new Command7("render").description(
|
|
3930
4304
|
"Render components to PNG or JSON via esbuild + BrowserPool"
|
|
3931
4305
|
);
|
|
3932
4306
|
registerRenderSingle(renderCmd);
|
|
@@ -3936,9 +4310,9 @@ function createRenderCommand() {
|
|
|
3936
4310
|
}
|
|
3937
4311
|
|
|
3938
4312
|
// src/report/baseline.ts
|
|
3939
|
-
import { existsSync as
|
|
3940
|
-
import { resolve as
|
|
3941
|
-
import { generateManifest as
|
|
4313
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
4314
|
+
import { resolve as resolve11 } from "path";
|
|
4315
|
+
import { generateManifest as generateManifest4 } from "@agent-scope/manifest";
|
|
3942
4316
|
import { BrowserPool as BrowserPool4, safeRender as safeRender3 } from "@agent-scope/render";
|
|
3943
4317
|
import { ComplianceEngine as ComplianceEngine2, TokenResolver as TokenResolver2 } from "@agent-scope/tokens";
|
|
3944
4318
|
var DEFAULT_BASELINE_DIR = ".reactscope/baseline";
|
|
@@ -4086,30 +4460,30 @@ async function runBaseline(options = {}) {
|
|
|
4086
4460
|
} = options;
|
|
4087
4461
|
const startTime = performance.now();
|
|
4088
4462
|
const rootDir = process.cwd();
|
|
4089
|
-
const baselineDir =
|
|
4090
|
-
const rendersDir =
|
|
4091
|
-
if (
|
|
4463
|
+
const baselineDir = resolve11(rootDir, outputDir);
|
|
4464
|
+
const rendersDir = resolve11(baselineDir, "renders");
|
|
4465
|
+
if (existsSync9(baselineDir)) {
|
|
4092
4466
|
rmSync2(baselineDir, { recursive: true, force: true });
|
|
4093
4467
|
}
|
|
4094
4468
|
mkdirSync5(rendersDir, { recursive: true });
|
|
4095
4469
|
let manifest;
|
|
4096
4470
|
if (manifestPath !== void 0) {
|
|
4097
|
-
const { readFileSync:
|
|
4098
|
-
const absPath =
|
|
4099
|
-
if (!
|
|
4471
|
+
const { readFileSync: readFileSync14 } = await import("fs");
|
|
4472
|
+
const absPath = resolve11(rootDir, manifestPath);
|
|
4473
|
+
if (!existsSync9(absPath)) {
|
|
4100
4474
|
throw new Error(`Manifest not found at ${absPath}.`);
|
|
4101
4475
|
}
|
|
4102
|
-
manifest = JSON.parse(
|
|
4476
|
+
manifest = JSON.parse(readFileSync14(absPath, "utf-8"));
|
|
4103
4477
|
process.stderr.write(`Loaded manifest from ${manifestPath}
|
|
4104
4478
|
`);
|
|
4105
4479
|
} else {
|
|
4106
4480
|
process.stderr.write("Scanning for React components\u2026\n");
|
|
4107
|
-
manifest = await
|
|
4481
|
+
manifest = await generateManifest4({ rootDir });
|
|
4108
4482
|
const count = Object.keys(manifest.components).length;
|
|
4109
4483
|
process.stderr.write(`Found ${count} components.
|
|
4110
4484
|
`);
|
|
4111
4485
|
}
|
|
4112
|
-
writeFileSync6(
|
|
4486
|
+
writeFileSync6(resolve11(baselineDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
|
|
4113
4487
|
let componentNames = Object.keys(manifest.components);
|
|
4114
4488
|
if (componentsGlob !== void 0) {
|
|
4115
4489
|
componentNames = componentNames.filter((name) => matchGlob(componentsGlob, name));
|
|
@@ -4130,7 +4504,7 @@ async function runBaseline(options = {}) {
|
|
|
4130
4504
|
auditedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4131
4505
|
};
|
|
4132
4506
|
writeFileSync6(
|
|
4133
|
-
|
|
4507
|
+
resolve11(baselineDir, "compliance.json"),
|
|
4134
4508
|
JSON.stringify(emptyReport, null, 2),
|
|
4135
4509
|
"utf-8"
|
|
4136
4510
|
);
|
|
@@ -4151,7 +4525,7 @@ async function runBaseline(options = {}) {
|
|
|
4151
4525
|
const renderOne = async (name) => {
|
|
4152
4526
|
const descriptor = manifest.components[name];
|
|
4153
4527
|
if (descriptor === void 0) return;
|
|
4154
|
-
const filePath =
|
|
4528
|
+
const filePath = resolve11(rootDir, descriptor.filePath);
|
|
4155
4529
|
const outcome = await safeRender3(
|
|
4156
4530
|
() => renderComponent2(filePath, name, {}, viewportWidth, viewportHeight),
|
|
4157
4531
|
{
|
|
@@ -4170,7 +4544,7 @@ async function runBaseline(options = {}) {
|
|
|
4170
4544
|
}
|
|
4171
4545
|
if (outcome.crashed) {
|
|
4172
4546
|
failureCount++;
|
|
4173
|
-
const errPath =
|
|
4547
|
+
const errPath = resolve11(rendersDir, `${name}.error.json`);
|
|
4174
4548
|
writeFileSync6(
|
|
4175
4549
|
errPath,
|
|
4176
4550
|
JSON.stringify(
|
|
@@ -4188,10 +4562,10 @@ async function runBaseline(options = {}) {
|
|
|
4188
4562
|
return;
|
|
4189
4563
|
}
|
|
4190
4564
|
const result = outcome.result;
|
|
4191
|
-
writeFileSync6(
|
|
4565
|
+
writeFileSync6(resolve11(rendersDir, `${name}.png`), result.screenshot);
|
|
4192
4566
|
const jsonOutput = formatRenderJson(name, {}, result);
|
|
4193
4567
|
writeFileSync6(
|
|
4194
|
-
|
|
4568
|
+
resolve11(rendersDir, `${name}.json`),
|
|
4195
4569
|
JSON.stringify(jsonOutput, null, 2),
|
|
4196
4570
|
"utf-8"
|
|
4197
4571
|
);
|
|
@@ -4219,7 +4593,7 @@ async function runBaseline(options = {}) {
|
|
|
4219
4593
|
const engine = new ComplianceEngine2(resolver);
|
|
4220
4594
|
const batchReport = engine.auditBatch(computedStylesMap);
|
|
4221
4595
|
writeFileSync6(
|
|
4222
|
-
|
|
4596
|
+
resolve11(baselineDir, "compliance.json"),
|
|
4223
4597
|
JSON.stringify(batchReport, null, 2),
|
|
4224
4598
|
"utf-8"
|
|
4225
4599
|
);
|
|
@@ -4262,22 +4636,22 @@ function registerBaselineSubCommand(reportCmd) {
|
|
|
4262
4636
|
}
|
|
4263
4637
|
|
|
4264
4638
|
// src/report/diff.ts
|
|
4265
|
-
import { existsSync as
|
|
4266
|
-
import { resolve as
|
|
4267
|
-
import { generateManifest as
|
|
4639
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
4640
|
+
import { resolve as resolve12 } from "path";
|
|
4641
|
+
import { generateManifest as generateManifest5 } from "@agent-scope/manifest";
|
|
4268
4642
|
import { BrowserPool as BrowserPool5, safeRender as safeRender4 } from "@agent-scope/render";
|
|
4269
4643
|
import { ComplianceEngine as ComplianceEngine3, TokenResolver as TokenResolver3 } from "@agent-scope/tokens";
|
|
4270
4644
|
var DEFAULT_BASELINE_DIR2 = ".reactscope/baseline";
|
|
4271
4645
|
function loadBaselineCompliance(baselineDir) {
|
|
4272
|
-
const compliancePath =
|
|
4273
|
-
if (!
|
|
4274
|
-
const raw = JSON.parse(
|
|
4646
|
+
const compliancePath = resolve12(baselineDir, "compliance.json");
|
|
4647
|
+
if (!existsSync10(compliancePath)) return null;
|
|
4648
|
+
const raw = JSON.parse(readFileSync8(compliancePath, "utf-8"));
|
|
4275
4649
|
return raw;
|
|
4276
4650
|
}
|
|
4277
4651
|
function loadBaselineRenderJson2(baselineDir, componentName) {
|
|
4278
|
-
const jsonPath =
|
|
4279
|
-
if (!
|
|
4280
|
-
return JSON.parse(
|
|
4652
|
+
const jsonPath = resolve12(baselineDir, "renders", `${componentName}.json`);
|
|
4653
|
+
if (!existsSync10(jsonPath)) return null;
|
|
4654
|
+
return JSON.parse(readFileSync8(jsonPath, "utf-8"));
|
|
4281
4655
|
}
|
|
4282
4656
|
var _pool5 = null;
|
|
4283
4657
|
async function getPool5(viewportWidth, viewportHeight) {
|
|
@@ -4444,19 +4818,19 @@ async function runDiff(options = {}) {
|
|
|
4444
4818
|
} = options;
|
|
4445
4819
|
const startTime = performance.now();
|
|
4446
4820
|
const rootDir = process.cwd();
|
|
4447
|
-
const baselineDir =
|
|
4448
|
-
if (!
|
|
4821
|
+
const baselineDir = resolve12(rootDir, baselineDirRaw);
|
|
4822
|
+
if (!existsSync10(baselineDir)) {
|
|
4449
4823
|
throw new Error(
|
|
4450
4824
|
`Baseline directory not found at "${baselineDir}". Run \`scope report baseline\` first to create a baseline snapshot.`
|
|
4451
4825
|
);
|
|
4452
4826
|
}
|
|
4453
|
-
const baselineManifestPath =
|
|
4454
|
-
if (!
|
|
4827
|
+
const baselineManifestPath = resolve12(baselineDir, "manifest.json");
|
|
4828
|
+
if (!existsSync10(baselineManifestPath)) {
|
|
4455
4829
|
throw new Error(
|
|
4456
4830
|
`Baseline manifest.json not found at "${baselineManifestPath}". The baseline directory may be incomplete \u2014 re-run \`scope report baseline\`.`
|
|
4457
4831
|
);
|
|
4458
4832
|
}
|
|
4459
|
-
const baselineManifest = JSON.parse(
|
|
4833
|
+
const baselineManifest = JSON.parse(readFileSync8(baselineManifestPath, "utf-8"));
|
|
4460
4834
|
const baselineCompliance = loadBaselineCompliance(baselineDir);
|
|
4461
4835
|
const baselineComponentNames = new Set(Object.keys(baselineManifest.components));
|
|
4462
4836
|
process.stderr.write(
|
|
@@ -4465,16 +4839,16 @@ async function runDiff(options = {}) {
|
|
|
4465
4839
|
);
|
|
4466
4840
|
let currentManifest;
|
|
4467
4841
|
if (manifestPath !== void 0) {
|
|
4468
|
-
const absPath =
|
|
4469
|
-
if (!
|
|
4842
|
+
const absPath = resolve12(rootDir, manifestPath);
|
|
4843
|
+
if (!existsSync10(absPath)) {
|
|
4470
4844
|
throw new Error(`Manifest not found at "${absPath}".`);
|
|
4471
4845
|
}
|
|
4472
|
-
currentManifest = JSON.parse(
|
|
4846
|
+
currentManifest = JSON.parse(readFileSync8(absPath, "utf-8"));
|
|
4473
4847
|
process.stderr.write(`Loaded manifest from ${manifestPath}
|
|
4474
4848
|
`);
|
|
4475
4849
|
} else {
|
|
4476
4850
|
process.stderr.write("Scanning for React components\u2026\n");
|
|
4477
|
-
currentManifest = await
|
|
4851
|
+
currentManifest = await generateManifest5({ rootDir });
|
|
4478
4852
|
const count = Object.keys(currentManifest.components).length;
|
|
4479
4853
|
process.stderr.write(`Found ${count} components.
|
|
4480
4854
|
`);
|
|
@@ -4502,7 +4876,7 @@ async function runDiff(options = {}) {
|
|
|
4502
4876
|
const renderOne = async (name) => {
|
|
4503
4877
|
const descriptor = currentManifest.components[name];
|
|
4504
4878
|
if (descriptor === void 0) return;
|
|
4505
|
-
const filePath =
|
|
4879
|
+
const filePath = resolve12(rootDir, descriptor.filePath);
|
|
4506
4880
|
const outcome = await safeRender4(
|
|
4507
4881
|
() => renderComponent3(filePath, name, {}, viewportWidth, viewportHeight),
|
|
4508
4882
|
{
|
|
@@ -4742,8 +5116,8 @@ function registerDiffSubCommand(reportCmd) {
|
|
|
4742
5116
|
}
|
|
4743
5117
|
|
|
4744
5118
|
// src/report/pr-comment.ts
|
|
4745
|
-
import { existsSync as
|
|
4746
|
-
import { resolve as
|
|
5119
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
5120
|
+
import { resolve as resolve13 } from "path";
|
|
4747
5121
|
var STATUS_BADGE = {
|
|
4748
5122
|
added: "\u2705 added",
|
|
4749
5123
|
removed: "\u{1F5D1}\uFE0F removed",
|
|
@@ -4826,13 +5200,13 @@ function formatPrComment(diff) {
|
|
|
4826
5200
|
return lines.join("\n");
|
|
4827
5201
|
}
|
|
4828
5202
|
function loadDiffResult(filePath) {
|
|
4829
|
-
const abs =
|
|
4830
|
-
if (!
|
|
5203
|
+
const abs = resolve13(filePath);
|
|
5204
|
+
if (!existsSync11(abs)) {
|
|
4831
5205
|
throw new Error(`DiffResult file not found: ${abs}`);
|
|
4832
5206
|
}
|
|
4833
5207
|
let raw;
|
|
4834
5208
|
try {
|
|
4835
|
-
raw =
|
|
5209
|
+
raw = readFileSync9(abs, "utf-8");
|
|
4836
5210
|
} catch (err) {
|
|
4837
5211
|
throw new Error(
|
|
4838
5212
|
`Failed to read DiffResult file: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -4859,7 +5233,7 @@ function registerPrCommentSubCommand(reportCmd) {
|
|
|
4859
5233
|
const diff = loadDiffResult(opts.input);
|
|
4860
5234
|
const comment = formatPrComment(diff);
|
|
4861
5235
|
if (opts.output !== void 0) {
|
|
4862
|
-
writeFileSync8(
|
|
5236
|
+
writeFileSync8(resolve13(opts.output), comment, "utf-8");
|
|
4863
5237
|
process.stderr.write(`PR comment written to ${opts.output}
|
|
4864
5238
|
`);
|
|
4865
5239
|
} else {
|
|
@@ -5154,11 +5528,11 @@ function buildStructuredReport(report) {
|
|
|
5154
5528
|
}
|
|
5155
5529
|
|
|
5156
5530
|
// src/site-commands.ts
|
|
5157
|
-
import { createReadStream, existsSync as
|
|
5531
|
+
import { createReadStream, existsSync as existsSync12, statSync as statSync2 } from "fs";
|
|
5158
5532
|
import { createServer } from "http";
|
|
5159
|
-
import { extname, join as
|
|
5533
|
+
import { extname, join as join5, resolve as resolve14 } from "path";
|
|
5160
5534
|
import { buildSite } from "@agent-scope/site";
|
|
5161
|
-
import { Command as
|
|
5535
|
+
import { Command as Command8 } from "commander";
|
|
5162
5536
|
var MIME_TYPES = {
|
|
5163
5537
|
".html": "text/html; charset=utf-8",
|
|
5164
5538
|
".css": "text/css; charset=utf-8",
|
|
@@ -5174,16 +5548,16 @@ function registerBuild(siteCmd) {
|
|
|
5174
5548
|
siteCmd.command("build").description("Build a static HTML gallery from .reactscope/ output").option("-i, --input <path>", "Path to .reactscope input directory", ".reactscope").option("-o, --output <path>", "Output directory for generated site", ".reactscope/site").option("--base-path <path>", "Base URL path prefix for subdirectory deployment", "/").option("--compliance <path>", "Path to compliance batch report JSON").option("--title <text>", "Site title", "Scope \u2014 Component Gallery").action(
|
|
5175
5549
|
async (opts) => {
|
|
5176
5550
|
try {
|
|
5177
|
-
const inputDir =
|
|
5178
|
-
const outputDir =
|
|
5179
|
-
if (!
|
|
5551
|
+
const inputDir = resolve14(process.cwd(), opts.input);
|
|
5552
|
+
const outputDir = resolve14(process.cwd(), opts.output);
|
|
5553
|
+
if (!existsSync12(inputDir)) {
|
|
5180
5554
|
throw new Error(
|
|
5181
5555
|
`Input directory not found: ${inputDir}
|
|
5182
5556
|
Run \`scope manifest generate\` and \`scope render\` first.`
|
|
5183
5557
|
);
|
|
5184
5558
|
}
|
|
5185
|
-
const manifestPath =
|
|
5186
|
-
if (!
|
|
5559
|
+
const manifestPath = join5(inputDir, "manifest.json");
|
|
5560
|
+
if (!existsSync12(manifestPath)) {
|
|
5187
5561
|
throw new Error(
|
|
5188
5562
|
`Manifest not found at ${manifestPath}
|
|
5189
5563
|
Run \`scope manifest generate\` first.`
|
|
@@ -5196,7 +5570,7 @@ Run \`scope manifest generate\` first.`
|
|
|
5196
5570
|
outputDir,
|
|
5197
5571
|
basePath: opts.basePath,
|
|
5198
5572
|
...opts.compliance !== void 0 && {
|
|
5199
|
-
compliancePath:
|
|
5573
|
+
compliancePath: resolve14(process.cwd(), opts.compliance)
|
|
5200
5574
|
},
|
|
5201
5575
|
title: opts.title
|
|
5202
5576
|
});
|
|
@@ -5219,8 +5593,8 @@ function registerServe(siteCmd) {
|
|
|
5219
5593
|
if (Number.isNaN(port) || port < 1 || port > 65535) {
|
|
5220
5594
|
throw new Error(`Invalid port: ${opts.port}`);
|
|
5221
5595
|
}
|
|
5222
|
-
const serveDir =
|
|
5223
|
-
if (!
|
|
5596
|
+
const serveDir = resolve14(process.cwd(), opts.dir);
|
|
5597
|
+
if (!existsSync12(serveDir)) {
|
|
5224
5598
|
throw new Error(
|
|
5225
5599
|
`Serve directory not found: ${serveDir}
|
|
5226
5600
|
Run \`scope site build\` first.`
|
|
@@ -5229,13 +5603,13 @@ Run \`scope site build\` first.`
|
|
|
5229
5603
|
const server = createServer((req, res) => {
|
|
5230
5604
|
const rawUrl = req.url ?? "/";
|
|
5231
5605
|
const urlPath = decodeURIComponent(rawUrl.split("?")[0] ?? "/");
|
|
5232
|
-
const filePath =
|
|
5606
|
+
const filePath = join5(serveDir, urlPath.endsWith("/") ? `${urlPath}index.html` : urlPath);
|
|
5233
5607
|
if (!filePath.startsWith(serveDir)) {
|
|
5234
5608
|
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
5235
5609
|
res.end("Forbidden");
|
|
5236
5610
|
return;
|
|
5237
5611
|
}
|
|
5238
|
-
if (
|
|
5612
|
+
if (existsSync12(filePath) && statSync2(filePath).isFile()) {
|
|
5239
5613
|
const ext = extname(filePath).toLowerCase();
|
|
5240
5614
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
5241
5615
|
res.writeHead(200, { "Content-Type": contentType });
|
|
@@ -5243,7 +5617,7 @@ Run \`scope site build\` first.`
|
|
|
5243
5617
|
return;
|
|
5244
5618
|
}
|
|
5245
5619
|
const htmlPath = `${filePath}.html`;
|
|
5246
|
-
if (
|
|
5620
|
+
if (existsSync12(htmlPath) && statSync2(htmlPath).isFile()) {
|
|
5247
5621
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
5248
5622
|
createReadStream(htmlPath).pipe(res);
|
|
5249
5623
|
return;
|
|
@@ -5276,7 +5650,7 @@ Run \`scope site build\` first.`
|
|
|
5276
5650
|
});
|
|
5277
5651
|
}
|
|
5278
5652
|
function createSiteCommand() {
|
|
5279
|
-
const siteCmd = new
|
|
5653
|
+
const siteCmd = new Command8("site").description(
|
|
5280
5654
|
"Build and serve the static component gallery site"
|
|
5281
5655
|
);
|
|
5282
5656
|
registerBuild(siteCmd);
|
|
@@ -5285,8 +5659,8 @@ function createSiteCommand() {
|
|
|
5285
5659
|
}
|
|
5286
5660
|
|
|
5287
5661
|
// src/tokens/commands.ts
|
|
5288
|
-
import { existsSync as
|
|
5289
|
-
import { resolve as
|
|
5662
|
+
import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
|
|
5663
|
+
import { resolve as resolve18 } from "path";
|
|
5290
5664
|
import {
|
|
5291
5665
|
parseTokenFileSync as parseTokenFileSync2,
|
|
5292
5666
|
TokenParseError,
|
|
@@ -5294,26 +5668,26 @@ import {
|
|
|
5294
5668
|
TokenValidationError,
|
|
5295
5669
|
validateTokenFile
|
|
5296
5670
|
} from "@agent-scope/tokens";
|
|
5297
|
-
import { Command as
|
|
5671
|
+
import { Command as Command10 } from "commander";
|
|
5298
5672
|
|
|
5299
5673
|
// src/tokens/compliance.ts
|
|
5300
|
-
import { existsSync as
|
|
5301
|
-
import { resolve as
|
|
5674
|
+
import { existsSync as existsSync13, readFileSync as readFileSync10 } from "fs";
|
|
5675
|
+
import { resolve as resolve15 } from "path";
|
|
5302
5676
|
import {
|
|
5303
5677
|
ComplianceEngine as ComplianceEngine4,
|
|
5304
5678
|
TokenResolver as TokenResolver4
|
|
5305
5679
|
} from "@agent-scope/tokens";
|
|
5306
5680
|
var DEFAULT_STYLES_PATH = ".reactscope/compliance-styles.json";
|
|
5307
5681
|
function loadStylesFile(stylesPath) {
|
|
5308
|
-
const absPath =
|
|
5309
|
-
if (!
|
|
5682
|
+
const absPath = resolve15(process.cwd(), stylesPath);
|
|
5683
|
+
if (!existsSync13(absPath)) {
|
|
5310
5684
|
throw new Error(
|
|
5311
5685
|
`Compliance styles file not found at ${absPath}.
|
|
5312
5686
|
Run \`scope render all\` first to generate component styles, or use --styles to specify a path.
|
|
5313
5687
|
Expected format: { "ComponentName": { colors: {}, spacing: {}, typography: {}, borders: {}, shadows: {} } }`
|
|
5314
5688
|
);
|
|
5315
5689
|
}
|
|
5316
|
-
const raw =
|
|
5690
|
+
const raw = readFileSync10(absPath, "utf-8");
|
|
5317
5691
|
let parsed;
|
|
5318
5692
|
try {
|
|
5319
5693
|
parsed = JSON.parse(raw);
|
|
@@ -5473,38 +5847,38 @@ function registerCompliance(tokensCmd) {
|
|
|
5473
5847
|
}
|
|
5474
5848
|
|
|
5475
5849
|
// src/tokens/export.ts
|
|
5476
|
-
import { existsSync as
|
|
5477
|
-
import { resolve as
|
|
5850
|
+
import { existsSync as existsSync14, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
|
|
5851
|
+
import { resolve as resolve16 } from "path";
|
|
5478
5852
|
import {
|
|
5479
5853
|
exportTokens,
|
|
5480
5854
|
parseTokenFileSync,
|
|
5481
5855
|
ThemeResolver,
|
|
5482
5856
|
TokenResolver as TokenResolver5
|
|
5483
5857
|
} from "@agent-scope/tokens";
|
|
5484
|
-
import { Command as
|
|
5858
|
+
import { Command as Command9 } from "commander";
|
|
5485
5859
|
var DEFAULT_TOKEN_FILE = "reactscope.tokens.json";
|
|
5486
5860
|
var CONFIG_FILE = "reactscope.config.json";
|
|
5487
5861
|
var SUPPORTED_FORMATS = ["css", "ts", "scss", "tailwind", "flat-json", "figma"];
|
|
5488
5862
|
function resolveTokenFilePath2(fileFlag) {
|
|
5489
5863
|
if (fileFlag !== void 0) {
|
|
5490
|
-
return
|
|
5864
|
+
return resolve16(process.cwd(), fileFlag);
|
|
5491
5865
|
}
|
|
5492
|
-
const configPath =
|
|
5493
|
-
if (
|
|
5866
|
+
const configPath = resolve16(process.cwd(), CONFIG_FILE);
|
|
5867
|
+
if (existsSync14(configPath)) {
|
|
5494
5868
|
try {
|
|
5495
|
-
const raw =
|
|
5869
|
+
const raw = readFileSync11(configPath, "utf-8");
|
|
5496
5870
|
const config = JSON.parse(raw);
|
|
5497
5871
|
if (typeof config === "object" && config !== null && "tokens" in config && typeof config.tokens === "object" && config.tokens !== null && typeof config.tokens?.file === "string") {
|
|
5498
5872
|
const file = config.tokens.file;
|
|
5499
|
-
return
|
|
5873
|
+
return resolve16(process.cwd(), file);
|
|
5500
5874
|
}
|
|
5501
5875
|
} catch {
|
|
5502
5876
|
}
|
|
5503
5877
|
}
|
|
5504
|
-
return
|
|
5878
|
+
return resolve16(process.cwd(), DEFAULT_TOKEN_FILE);
|
|
5505
5879
|
}
|
|
5506
5880
|
function createTokensExportCommand() {
|
|
5507
|
-
return new
|
|
5881
|
+
return new Command9("export").description("Export design tokens to a downstream format").requiredOption("--format <fmt>", `Output format: ${SUPPORTED_FORMATS.join(", ")}`).option("--file <path>", "Path to token file (overrides config)").option("--out <path>", "Write output to file instead of stdout").option("--prefix <prefix>", "CSS/SCSS: prefix for variable names (e.g. 'scope')").option("--selector <selector>", "CSS: custom root selector (default: ':root')").option(
|
|
5508
5882
|
"--theme <name>",
|
|
5509
5883
|
"Include theme overrides for the named theme (applies to css, ts, scss, tailwind, figma)"
|
|
5510
5884
|
).action(
|
|
@@ -5530,13 +5904,13 @@ Supported formats: ${SUPPORTED_FORMATS.join(", ")}
|
|
|
5530
5904
|
const format = opts.format;
|
|
5531
5905
|
try {
|
|
5532
5906
|
const filePath = resolveTokenFilePath2(opts.file);
|
|
5533
|
-
if (!
|
|
5907
|
+
if (!existsSync14(filePath)) {
|
|
5534
5908
|
throw new Error(
|
|
5535
5909
|
`Token file not found at ${filePath}.
|
|
5536
5910
|
Create a reactscope.tokens.json file or use --file to specify a path.`
|
|
5537
5911
|
);
|
|
5538
5912
|
}
|
|
5539
|
-
const raw =
|
|
5913
|
+
const raw = readFileSync11(filePath, "utf-8");
|
|
5540
5914
|
const { tokens, rawFile } = parseTokenFileSync(raw);
|
|
5541
5915
|
let themesMap;
|
|
5542
5916
|
if (opts.theme !== void 0) {
|
|
@@ -5575,7 +5949,7 @@ Available themes: ${themeNames.join(", ")}`
|
|
|
5575
5949
|
themes: themesMap
|
|
5576
5950
|
});
|
|
5577
5951
|
if (opts.out !== void 0) {
|
|
5578
|
-
const outPath =
|
|
5952
|
+
const outPath = resolve16(process.cwd(), opts.out);
|
|
5579
5953
|
writeFileSync9(outPath, output, "utf-8");
|
|
5580
5954
|
process.stderr.write(`Exported ${tokens.length} tokens to ${outPath}
|
|
5581
5955
|
`);
|
|
@@ -5691,7 +6065,7 @@ ${formatImpactSummary(report)}
|
|
|
5691
6065
|
|
|
5692
6066
|
// src/tokens/preview.ts
|
|
5693
6067
|
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync10 } from "fs";
|
|
5694
|
-
import { resolve as
|
|
6068
|
+
import { resolve as resolve17 } from "path";
|
|
5695
6069
|
import { BrowserPool as BrowserPool6, SpriteSheetGenerator } from "@agent-scope/render";
|
|
5696
6070
|
import { ComplianceEngine as ComplianceEngine6, ImpactAnalyzer as ImpactAnalyzer2, TokenResolver as TokenResolver7 } from "@agent-scope/tokens";
|
|
5697
6071
|
var DEFAULT_STYLES_PATH3 = ".reactscope/compliance-styles.json";
|
|
@@ -5872,8 +6246,8 @@ function registerPreview(tokensCmd) {
|
|
|
5872
6246
|
});
|
|
5873
6247
|
const spriteResult = await generator.generate(matrixResult);
|
|
5874
6248
|
const tokenLabel = tokenPath.replace(/\./g, "-");
|
|
5875
|
-
const outputPath = opts.output ??
|
|
5876
|
-
const outputDir =
|
|
6249
|
+
const outputPath = opts.output ?? resolve17(process.cwd(), DEFAULT_OUTPUT_DIR2, `preview-${tokenLabel}.png`);
|
|
6250
|
+
const outputDir = resolve17(outputPath, "..");
|
|
5877
6251
|
mkdirSync6(outputDir, { recursive: true });
|
|
5878
6252
|
writeFileSync10(outputPath, spriteResult.png);
|
|
5879
6253
|
const useJson = opts.format === "json" || opts.format !== "text" && !isTTY();
|
|
@@ -5934,30 +6308,30 @@ function buildTable2(headers, rows) {
|
|
|
5934
6308
|
}
|
|
5935
6309
|
function resolveTokenFilePath(fileFlag) {
|
|
5936
6310
|
if (fileFlag !== void 0) {
|
|
5937
|
-
return
|
|
6311
|
+
return resolve18(process.cwd(), fileFlag);
|
|
5938
6312
|
}
|
|
5939
|
-
const configPath =
|
|
5940
|
-
if (
|
|
6313
|
+
const configPath = resolve18(process.cwd(), CONFIG_FILE2);
|
|
6314
|
+
if (existsSync15(configPath)) {
|
|
5941
6315
|
try {
|
|
5942
|
-
const raw =
|
|
6316
|
+
const raw = readFileSync12(configPath, "utf-8");
|
|
5943
6317
|
const config = JSON.parse(raw);
|
|
5944
6318
|
if (typeof config === "object" && config !== null && "tokens" in config && typeof config.tokens === "object" && config.tokens !== null && typeof config.tokens?.file === "string") {
|
|
5945
6319
|
const file = config.tokens.file;
|
|
5946
|
-
return
|
|
6320
|
+
return resolve18(process.cwd(), file);
|
|
5947
6321
|
}
|
|
5948
6322
|
} catch {
|
|
5949
6323
|
}
|
|
5950
6324
|
}
|
|
5951
|
-
return
|
|
6325
|
+
return resolve18(process.cwd(), DEFAULT_TOKEN_FILE2);
|
|
5952
6326
|
}
|
|
5953
6327
|
function loadTokens(absPath) {
|
|
5954
|
-
if (!
|
|
6328
|
+
if (!existsSync15(absPath)) {
|
|
5955
6329
|
throw new Error(
|
|
5956
6330
|
`Token file not found at ${absPath}.
|
|
5957
6331
|
Create a reactscope.tokens.json file or use --file to specify a path.`
|
|
5958
6332
|
);
|
|
5959
6333
|
}
|
|
5960
|
-
const raw =
|
|
6334
|
+
const raw = readFileSync12(absPath, "utf-8");
|
|
5961
6335
|
return parseTokenFileSync2(raw);
|
|
5962
6336
|
}
|
|
5963
6337
|
function getRawValue(node, segments) {
|
|
@@ -6171,13 +6545,13 @@ function registerValidate(tokensCmd) {
|
|
|
6171
6545
|
).option("--file <path>", "Path to token file (overrides config)").option("--format <fmt>", "Output format: json or text (default: auto-detect)").action((opts) => {
|
|
6172
6546
|
try {
|
|
6173
6547
|
const filePath = resolveTokenFilePath(opts.file);
|
|
6174
|
-
if (!
|
|
6548
|
+
if (!existsSync15(filePath)) {
|
|
6175
6549
|
throw new Error(
|
|
6176
6550
|
`Token file not found at ${filePath}.
|
|
6177
6551
|
Create a reactscope.tokens.json file or use --file to specify a path.`
|
|
6178
6552
|
);
|
|
6179
6553
|
}
|
|
6180
|
-
const raw =
|
|
6554
|
+
const raw = readFileSync12(filePath, "utf-8");
|
|
6181
6555
|
const useJson = opts.format === "json" || opts.format !== "text" && !isTTY2();
|
|
6182
6556
|
const errors = [];
|
|
6183
6557
|
let parsed;
|
|
@@ -6245,7 +6619,7 @@ function outputValidationResult(filePath, errors, useJson) {
|
|
|
6245
6619
|
}
|
|
6246
6620
|
}
|
|
6247
6621
|
function createTokensCommand() {
|
|
6248
|
-
const tokensCmd = new
|
|
6622
|
+
const tokensCmd = new Command10("tokens").description(
|
|
6249
6623
|
"Query and validate design tokens from a reactscope.tokens.json file"
|
|
6250
6624
|
);
|
|
6251
6625
|
registerGet2(tokensCmd);
|
|
@@ -6262,7 +6636,7 @@ function createTokensCommand() {
|
|
|
6262
6636
|
|
|
6263
6637
|
// src/program.ts
|
|
6264
6638
|
function createProgram(options = {}) {
|
|
6265
|
-
const program2 = new
|
|
6639
|
+
const program2 = new Command11("scope").version(options.version ?? "0.1.0").description("Scope \u2014 React instrumentation toolkit");
|
|
6266
6640
|
program2.command("capture <url>").description("Capture a React component tree from a live URL and output as JSON").option("-o, --output <path>", "Write JSON to file instead of stdout").option("--pretty", "Pretty-print JSON output (default: minified)", false).option("--timeout <ms>", "Max wait time for React to mount (ms)", "10000").option("--wait <ms>", "Additional wait after page load before capture (ms)", "0").action(
|
|
6267
6641
|
async (url, opts) => {
|
|
6268
6642
|
try {
|
|
@@ -6335,7 +6709,7 @@ function createProgram(options = {}) {
|
|
|
6335
6709
|
}
|
|
6336
6710
|
);
|
|
6337
6711
|
program2.command("generate").description("Generate a Playwright test from a Scope trace file").argument("<trace>", "Path to a serialized Scope trace (.json)").option("-o, --output <path>", "Output file path", "scope.spec.ts").option("-d, --description <text>", "Test description").action((tracePath, opts) => {
|
|
6338
|
-
const raw =
|
|
6712
|
+
const raw = readFileSync13(tracePath, "utf-8");
|
|
6339
6713
|
const trace = loadTrace(raw);
|
|
6340
6714
|
const source = generateTest(trace, {
|
|
6341
6715
|
description: opts.description,
|
|
@@ -6350,6 +6724,7 @@ function createProgram(options = {}) {
|
|
|
6350
6724
|
program2.addCommand(createInstrumentCommand());
|
|
6351
6725
|
program2.addCommand(createInitCommand());
|
|
6352
6726
|
program2.addCommand(createCiCommand());
|
|
6727
|
+
program2.addCommand(createDoctorCommand());
|
|
6353
6728
|
const existingReportCmd = program2.commands.find((c) => c.name() === "report");
|
|
6354
6729
|
if (existingReportCmd !== void 0) {
|
|
6355
6730
|
registerBaselineSubCommand(existingReportCmd);
|