@agent-scope/cli 1.18.0 → 1.18.1
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 +373 -75
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +332 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +332 -34
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -94,6 +94,21 @@ import { createElement } from "react";
|
|
|
94
94
|
// Suppress "React must be in scope" warnings from old JSX (we use automatic)
|
|
95
95
|
banner: {
|
|
96
96
|
js: "/* @agent-scope/cli component harness */"
|
|
97
|
+
},
|
|
98
|
+
// CSS imports (e.g. `import './styles.css'`) are handled at the page level via
|
|
99
|
+
// globalCSS injection. Tell esbuild to treat CSS files as empty modules so
|
|
100
|
+
// components that import CSS directly (e.g. App.tsx) don't error during bundling.
|
|
101
|
+
loader: {
|
|
102
|
+
".css": "empty",
|
|
103
|
+
".svg": "dataurl",
|
|
104
|
+
".png": "dataurl",
|
|
105
|
+
".jpg": "dataurl",
|
|
106
|
+
".jpeg": "dataurl",
|
|
107
|
+
".gif": "dataurl",
|
|
108
|
+
".webp": "dataurl",
|
|
109
|
+
".ttf": "dataurl",
|
|
110
|
+
".woff": "dataurl",
|
|
111
|
+
".woff2": "dataurl"
|
|
97
112
|
}
|
|
98
113
|
});
|
|
99
114
|
if (result.errors.length > 0) {
|
|
@@ -501,6 +516,57 @@ async function getCompiledCssForClasses(cwd, classes) {
|
|
|
501
516
|
if (deduped.length === 0) return null;
|
|
502
517
|
return build3(deduped);
|
|
503
518
|
}
|
|
519
|
+
async function compileGlobalCssFile(cssFilePath, cwd) {
|
|
520
|
+
const { existsSync: existsSync15, readFileSync: readFileSync13 } = await import('fs');
|
|
521
|
+
const { createRequire: createRequire3 } = await import('module');
|
|
522
|
+
if (!existsSync15(cssFilePath)) return null;
|
|
523
|
+
const raw = readFileSync13(cssFilePath, "utf-8");
|
|
524
|
+
const needsCompile = /@tailwind|@import\s+['"]tailwindcss/.test(raw);
|
|
525
|
+
if (!needsCompile) {
|
|
526
|
+
return raw;
|
|
527
|
+
}
|
|
528
|
+
try {
|
|
529
|
+
const require2 = createRequire3(resolve(cwd, "package.json"));
|
|
530
|
+
let postcss;
|
|
531
|
+
let twPlugin;
|
|
532
|
+
try {
|
|
533
|
+
postcss = require2("postcss");
|
|
534
|
+
twPlugin = require2("tailwindcss");
|
|
535
|
+
} catch {
|
|
536
|
+
return raw;
|
|
537
|
+
}
|
|
538
|
+
let autoprefixerPlugin;
|
|
539
|
+
try {
|
|
540
|
+
autoprefixerPlugin = require2("autoprefixer");
|
|
541
|
+
} catch {
|
|
542
|
+
autoprefixerPlugin = null;
|
|
543
|
+
}
|
|
544
|
+
const plugins = autoprefixerPlugin ? [twPlugin, autoprefixerPlugin] : [twPlugin];
|
|
545
|
+
const result = await postcss(plugins).process(raw, {
|
|
546
|
+
from: cssFilePath,
|
|
547
|
+
to: cssFilePath
|
|
548
|
+
});
|
|
549
|
+
return result.css;
|
|
550
|
+
} catch (err) {
|
|
551
|
+
process.stderr.write(
|
|
552
|
+
`[scope/render] Warning: CSS compilation failed for ${cssFilePath}: ${err instanceof Error ? err.message : String(err)}
|
|
553
|
+
`
|
|
554
|
+
);
|
|
555
|
+
return raw;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
async function loadGlobalCss(globalCssFiles, cwd) {
|
|
559
|
+
if (globalCssFiles.length === 0) return null;
|
|
560
|
+
const parts = [];
|
|
561
|
+
for (const relPath of globalCssFiles) {
|
|
562
|
+
const absPath = resolve(cwd, relPath);
|
|
563
|
+
const css = await compileGlobalCssFile(absPath, cwd);
|
|
564
|
+
if (css !== null && css.trim().length > 0) {
|
|
565
|
+
parts.push(css);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return parts.length > 0 ? parts.join("\n") : null;
|
|
569
|
+
}
|
|
504
570
|
|
|
505
571
|
// src/ci/commands.ts
|
|
506
572
|
var CI_EXIT = {
|
|
@@ -1004,6 +1070,20 @@ function detectComponentPatterns(rootDir, typescript) {
|
|
|
1004
1070
|
}
|
|
1005
1071
|
return unique;
|
|
1006
1072
|
}
|
|
1073
|
+
var GLOBAL_CSS_CANDIDATES = [
|
|
1074
|
+
"src/styles.css",
|
|
1075
|
+
"src/index.css",
|
|
1076
|
+
"src/global.css",
|
|
1077
|
+
"src/globals.css",
|
|
1078
|
+
"src/app.css",
|
|
1079
|
+
"src/main.css",
|
|
1080
|
+
"styles/globals.css",
|
|
1081
|
+
"styles/global.css",
|
|
1082
|
+
"styles/index.css"
|
|
1083
|
+
];
|
|
1084
|
+
function detectGlobalCSSFiles(rootDir) {
|
|
1085
|
+
return GLOBAL_CSS_CANDIDATES.filter((rel) => existsSync(join(rootDir, rel)));
|
|
1086
|
+
}
|
|
1007
1087
|
var TAILWIND_STEMS = ["tailwind.config"];
|
|
1008
1088
|
var CSS_EXTS = [".css", ".scss", ".sass", ".less"];
|
|
1009
1089
|
var THEME_SUFFIXES = [".theme.ts", ".theme.js", ".theme.tsx"];
|
|
@@ -1071,13 +1151,15 @@ function detectProject(rootDir) {
|
|
|
1071
1151
|
const packageManager = detectPackageManager(rootDir);
|
|
1072
1152
|
const componentPatterns = detectComponentPatterns(rootDir, typescript);
|
|
1073
1153
|
const tokenSources = detectTokenSources(rootDir);
|
|
1154
|
+
const globalCSSFiles = detectGlobalCSSFiles(rootDir);
|
|
1074
1155
|
return {
|
|
1075
1156
|
framework,
|
|
1076
1157
|
typescript,
|
|
1077
1158
|
tsconfigPath,
|
|
1078
1159
|
componentPatterns,
|
|
1079
1160
|
tokenSources,
|
|
1080
|
-
packageManager
|
|
1161
|
+
packageManager,
|
|
1162
|
+
globalCSSFiles
|
|
1081
1163
|
};
|
|
1082
1164
|
}
|
|
1083
1165
|
function buildDefaultConfig(detected, tokenFile, outputDir) {
|
|
@@ -1086,7 +1168,7 @@ function buildDefaultConfig(detected, tokenFile, outputDir) {
|
|
|
1086
1168
|
components: {
|
|
1087
1169
|
include,
|
|
1088
1170
|
exclude: ["**/*.test.tsx", "**/*.stories.tsx"],
|
|
1089
|
-
wrappers: { providers: [], globalCSS: [] }
|
|
1171
|
+
wrappers: { providers: [], globalCSS: detected.globalCSSFiles ?? [] }
|
|
1090
1172
|
},
|
|
1091
1173
|
render: {
|
|
1092
1174
|
viewport: { default: { width: 1280, height: 800 } },
|
|
@@ -1144,18 +1226,118 @@ function ensureGitignoreEntry(rootDir, entry) {
|
|
|
1144
1226
|
`);
|
|
1145
1227
|
}
|
|
1146
1228
|
}
|
|
1229
|
+
function extractTailwindTokens(tokenSources) {
|
|
1230
|
+
const tailwindSource = tokenSources.find((s) => s.kind === "tailwind-config");
|
|
1231
|
+
if (!tailwindSource) return null;
|
|
1232
|
+
try {
|
|
1233
|
+
let parseBlock2 = function(block) {
|
|
1234
|
+
const result = {};
|
|
1235
|
+
const lineRe = /['"]?(\w[\w.-]*|\d+)['"]?\s*:\s*['"]?(#[0-9a-fA-F]{3,8}|\d+(?:px|rem|em|%)|[\w-]+(?:\/[\w]+)?)['"]?/g;
|
|
1236
|
+
for (const m of block.matchAll(lineRe)) {
|
|
1237
|
+
if (m[1] !== void 0 && m[2] !== void 0) {
|
|
1238
|
+
result[m[1]] = m[2];
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
return result;
|
|
1242
|
+
};
|
|
1243
|
+
var parseBlock = parseBlock2;
|
|
1244
|
+
const raw = readFileSync(tailwindSource.path, "utf-8");
|
|
1245
|
+
const tokens = {};
|
|
1246
|
+
const colorsKeyIdx = raw.indexOf("colors:");
|
|
1247
|
+
if (colorsKeyIdx !== -1) {
|
|
1248
|
+
const colorsBraceStart = raw.indexOf("{", colorsKeyIdx);
|
|
1249
|
+
if (colorsBraceStart !== -1) {
|
|
1250
|
+
let colorDepth = 0;
|
|
1251
|
+
let colorsBraceEnd = -1;
|
|
1252
|
+
for (let ci = colorsBraceStart; ci < raw.length; ci++) {
|
|
1253
|
+
if (raw[ci] === "{") colorDepth++;
|
|
1254
|
+
else if (raw[ci] === "}") {
|
|
1255
|
+
colorDepth--;
|
|
1256
|
+
if (colorDepth === 0) {
|
|
1257
|
+
colorsBraceEnd = ci;
|
|
1258
|
+
break;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
if (colorsBraceEnd > colorsBraceStart) {
|
|
1263
|
+
const colorSection = raw.slice(colorsBraceStart + 1, colorsBraceEnd);
|
|
1264
|
+
const scaleRe = /(\w+)\s*:\s*\{([^}]+)\}/g;
|
|
1265
|
+
const colorTokens = {};
|
|
1266
|
+
for (const sm of colorSection.matchAll(scaleRe)) {
|
|
1267
|
+
if (sm[1] === void 0 || sm[2] === void 0) continue;
|
|
1268
|
+
const scaleName = sm[1];
|
|
1269
|
+
const scaleValues = parseBlock2(sm[2]);
|
|
1270
|
+
if (Object.keys(scaleValues).length > 0) {
|
|
1271
|
+
const scaleTokens = {};
|
|
1272
|
+
for (const [step, hex] of Object.entries(scaleValues)) {
|
|
1273
|
+
scaleTokens[step] = { value: hex, type: "color" };
|
|
1274
|
+
}
|
|
1275
|
+
colorTokens[scaleName] = scaleTokens;
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
if (Object.keys(colorTokens).length > 0) {
|
|
1279
|
+
tokens["color"] = colorTokens;
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
const spacingMatch = raw.match(/spacing\s*:\s*\{([\s\S]*?)\n\s*\}/);
|
|
1285
|
+
if (spacingMatch?.[1] !== void 0) {
|
|
1286
|
+
const spacingValues = parseBlock2(spacingMatch[1]);
|
|
1287
|
+
if (Object.keys(spacingValues).length > 0) {
|
|
1288
|
+
const spacingTokens = {};
|
|
1289
|
+
for (const [key, val] of Object.entries(spacingValues)) {
|
|
1290
|
+
spacingTokens[key] = { value: val, type: "dimension" };
|
|
1291
|
+
}
|
|
1292
|
+
tokens["spacing"] = spacingTokens;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
const fontFamilyMatch = raw.match(/fontFamily\s*:\s*\{([\s\S]*?)\n\s*\}/);
|
|
1296
|
+
if (fontFamilyMatch?.[1] !== void 0) {
|
|
1297
|
+
const fontFamilyRe = /(\w+)\s*:\s*\[\s*['"]([^'"]+)['"]/g;
|
|
1298
|
+
const fontTokens = {};
|
|
1299
|
+
for (const fm of fontFamilyMatch[1].matchAll(fontFamilyRe)) {
|
|
1300
|
+
if (fm[1] !== void 0 && fm[2] !== void 0) {
|
|
1301
|
+
fontTokens[fm[1]] = { value: fm[2], type: "fontFamily" };
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
if (Object.keys(fontTokens).length > 0) {
|
|
1305
|
+
tokens["font"] = fontTokens;
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
const borderRadiusMatch = raw.match(/borderRadius\s*:\s*\{([\s\S]*?)\n\s*\}/);
|
|
1309
|
+
if (borderRadiusMatch?.[1] !== void 0) {
|
|
1310
|
+
const radiusValues = parseBlock2(borderRadiusMatch[1]);
|
|
1311
|
+
if (Object.keys(radiusValues).length > 0) {
|
|
1312
|
+
const radiusTokens = {};
|
|
1313
|
+
for (const [key, val] of Object.entries(radiusValues)) {
|
|
1314
|
+
radiusTokens[key] = { value: val, type: "dimension" };
|
|
1315
|
+
}
|
|
1316
|
+
tokens["radius"] = radiusTokens;
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
return Object.keys(tokens).length > 0 ? tokens : null;
|
|
1320
|
+
} catch {
|
|
1321
|
+
return null;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1147
1324
|
function scaffoldConfig(rootDir, config) {
|
|
1148
1325
|
const path = join(rootDir, "reactscope.config.json");
|
|
1149
1326
|
writeFileSync(path, `${JSON.stringify(config, null, 2)}
|
|
1150
1327
|
`);
|
|
1151
1328
|
return path;
|
|
1152
1329
|
}
|
|
1153
|
-
function scaffoldTokenFile(rootDir, tokenFile) {
|
|
1330
|
+
function scaffoldTokenFile(rootDir, tokenFile, extractedTokens) {
|
|
1154
1331
|
const path = join(rootDir, tokenFile);
|
|
1155
1332
|
if (!existsSync(path)) {
|
|
1156
1333
|
const stub = {
|
|
1157
1334
|
$schema: "https://raw.githubusercontent.com/FlatFilers/Scope/main/packages/tokens/schema.json",
|
|
1158
|
-
|
|
1335
|
+
version: "1.0.0",
|
|
1336
|
+
meta: {
|
|
1337
|
+
name: "Design Tokens",
|
|
1338
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
1339
|
+
},
|
|
1340
|
+
tokens: extractedTokens ?? {}
|
|
1159
1341
|
};
|
|
1160
1342
|
writeFileSync(path, `${JSON.stringify(stub, null, 2)}
|
|
1161
1343
|
`);
|
|
@@ -1233,7 +1415,13 @@ async function runInit(options) {
|
|
|
1233
1415
|
}
|
|
1234
1416
|
const cfgPath = scaffoldConfig(rootDir, config);
|
|
1235
1417
|
created.push(cfgPath);
|
|
1236
|
-
const
|
|
1418
|
+
const extractedTokens = extractTailwindTokens(detected.tokenSources);
|
|
1419
|
+
if (extractedTokens !== null) {
|
|
1420
|
+
const tokenGroupCount = Object.keys(extractedTokens).length;
|
|
1421
|
+
process.stdout.write(` Extracted ${tokenGroupCount} token group(s) from Tailwind config
|
|
1422
|
+
`);
|
|
1423
|
+
}
|
|
1424
|
+
const tokPath = scaffoldTokenFile(rootDir, config.tokens.file, extractedTokens ?? void 0);
|
|
1237
1425
|
created.push(tokPath);
|
|
1238
1426
|
const outDirPath = scaffoldOutputDir(rootDir, config.output.dir);
|
|
1239
1427
|
created.push(outDirPath);
|
|
@@ -1333,7 +1521,10 @@ Available: ${available}${hint}`
|
|
|
1333
1521
|
});
|
|
1334
1522
|
}
|
|
1335
1523
|
function registerQuery(manifestCmd) {
|
|
1336
|
-
manifestCmd.command("query").description("Query components by attributes").option("--context <name>", "Find components consuming a context").option("--hook <name>", "Find components using a specific hook").option("--complexity <class>", "Filter by complexity class: simple or complex").option("--side-effects", "Find components with any side effects", false).option("--has-fetch", "Find components with fetch calls", false).option(
|
|
1524
|
+
manifestCmd.command("query").description("Query components by attributes").option("--context <name>", "Find components consuming a context").option("--hook <name>", "Find components using a specific hook").option("--complexity <class>", "Filter by complexity class: simple or complex").option("--side-effects", "Find components with any side effects", false).option("--has-fetch", "Find components with fetch calls", false).option(
|
|
1525
|
+
"--has-prop <spec>",
|
|
1526
|
+
"Find components with a prop matching name or name:type (e.g. 'loading' or 'variant:union')"
|
|
1527
|
+
).option("--composed-by <name>", "Find components that compose the named component").option("--format <fmt>", "Output format: json or table (default: auto-detect)").option("--manifest <path>", "Path to manifest.json", MANIFEST_PATH).action(
|
|
1337
1528
|
(opts) => {
|
|
1338
1529
|
try {
|
|
1339
1530
|
const manifest = loadManifest(opts.manifest);
|
|
@@ -1344,9 +1535,11 @@ function registerQuery(manifestCmd) {
|
|
|
1344
1535
|
if (opts.complexity !== void 0) queryParts.push(`complexity=${opts.complexity}`);
|
|
1345
1536
|
if (opts.sideEffects) queryParts.push("side-effects");
|
|
1346
1537
|
if (opts.hasFetch) queryParts.push("has-fetch");
|
|
1538
|
+
if (opts.hasProp !== void 0) queryParts.push(`has-prop=${opts.hasProp}`);
|
|
1539
|
+
if (opts.composedBy !== void 0) queryParts.push(`composed-by=${opts.composedBy}`);
|
|
1347
1540
|
if (queryParts.length === 0) {
|
|
1348
1541
|
process.stderr.write(
|
|
1349
|
-
"No query flags specified. Use --context, --hook, --complexity, --side-effects,
|
|
1542
|
+
"No query flags specified. Use --context, --hook, --complexity, --side-effects, --has-fetch, --has-prop, or --composed-by.\n"
|
|
1350
1543
|
);
|
|
1351
1544
|
process.exit(1);
|
|
1352
1545
|
}
|
|
@@ -1373,6 +1566,27 @@ function registerQuery(manifestCmd) {
|
|
|
1373
1566
|
if (opts.hasFetch) {
|
|
1374
1567
|
entries = entries.filter(([, d]) => d.sideEffects.fetches.length > 0);
|
|
1375
1568
|
}
|
|
1569
|
+
if (opts.hasProp !== void 0) {
|
|
1570
|
+
const spec = opts.hasProp;
|
|
1571
|
+
const colonIdx = spec.indexOf(":");
|
|
1572
|
+
const propName = colonIdx >= 0 ? spec.slice(0, colonIdx) : spec;
|
|
1573
|
+
const propType = colonIdx >= 0 ? spec.slice(colonIdx + 1) : void 0;
|
|
1574
|
+
entries = entries.filter(([, d]) => {
|
|
1575
|
+
const props = d.props;
|
|
1576
|
+
if (!props || !(propName in props)) return false;
|
|
1577
|
+
if (propType !== void 0) {
|
|
1578
|
+
return props[propName]?.type === propType;
|
|
1579
|
+
}
|
|
1580
|
+
return true;
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1583
|
+
if (opts.composedBy !== void 0) {
|
|
1584
|
+
const targetName = opts.composedBy;
|
|
1585
|
+
entries = entries.filter(([, d]) => {
|
|
1586
|
+
const composedBy = d.composedBy;
|
|
1587
|
+
return composedBy !== void 0 && composedBy.includes(targetName);
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1376
1590
|
const rows = entries.map(([name, d]) => ({
|
|
1377
1591
|
name,
|
|
1378
1592
|
file: d.filePath,
|
|
@@ -3057,6 +3271,17 @@ ${msg}`);
|
|
|
3057
3271
|
}
|
|
3058
3272
|
|
|
3059
3273
|
// src/render-commands.ts
|
|
3274
|
+
function loadGlobalCssFilesFromConfig(cwd) {
|
|
3275
|
+
const configPath = resolve(cwd, "reactscope.config.json");
|
|
3276
|
+
if (!existsSync(configPath)) return [];
|
|
3277
|
+
try {
|
|
3278
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
3279
|
+
const cfg = JSON.parse(raw);
|
|
3280
|
+
return cfg.components?.wrappers?.globalCSS ?? [];
|
|
3281
|
+
} catch {
|
|
3282
|
+
return [];
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3060
3285
|
var MANIFEST_PATH6 = ".reactscope/manifest.json";
|
|
3061
3286
|
var DEFAULT_OUTPUT_DIR = ".reactscope/renders";
|
|
3062
3287
|
var _pool3 = null;
|
|
@@ -3077,7 +3302,7 @@ async function shutdownPool3() {
|
|
|
3077
3302
|
_pool3 = null;
|
|
3078
3303
|
}
|
|
3079
3304
|
}
|
|
3080
|
-
function buildRenderer(filePath, componentName, viewportWidth, viewportHeight, wrapperScript) {
|
|
3305
|
+
function buildRenderer(filePath, componentName, viewportWidth, viewportHeight, globalCssFiles = [], projectCwd = process.cwd(), wrapperScript) {
|
|
3081
3306
|
const satori = new SatoriRenderer({
|
|
3082
3307
|
defaultViewport: { width: viewportWidth, height: viewportHeight }
|
|
3083
3308
|
});
|
|
@@ -3086,13 +3311,13 @@ function buildRenderer(filePath, componentName, viewportWidth, viewportHeight, w
|
|
|
3086
3311
|
async renderCell(props, _complexityClass) {
|
|
3087
3312
|
const startMs = performance.now();
|
|
3088
3313
|
const pool = await getPool3(viewportWidth, viewportHeight);
|
|
3314
|
+
const projectCss = await loadGlobalCss(globalCssFiles, projectCwd);
|
|
3089
3315
|
const htmlHarness = await buildComponentHarness(
|
|
3090
3316
|
filePath,
|
|
3091
3317
|
componentName,
|
|
3092
3318
|
props,
|
|
3093
3319
|
viewportWidth,
|
|
3094
|
-
void 0,
|
|
3095
|
-
// projectCss (handled separately)
|
|
3320
|
+
projectCss ?? void 0,
|
|
3096
3321
|
wrapperScript
|
|
3097
3322
|
);
|
|
3098
3323
|
const slot = await pool.acquire();
|
|
@@ -3122,9 +3347,9 @@ function buildRenderer(filePath, componentName, viewportWidth, viewportHeight, w
|
|
|
3122
3347
|
});
|
|
3123
3348
|
return [...set];
|
|
3124
3349
|
});
|
|
3125
|
-
const
|
|
3126
|
-
if (
|
|
3127
|
-
await page.addStyleTag({ content:
|
|
3350
|
+
const projectCss2 = await getCompiledCssForClasses(rootDir, classes);
|
|
3351
|
+
if (projectCss2 != null && projectCss2.length > 0) {
|
|
3352
|
+
await page.addStyleTag({ content: projectCss2 });
|
|
3128
3353
|
}
|
|
3129
3354
|
const renderTimeMs = performance.now() - startMs;
|
|
3130
3355
|
const rootLocator = page.locator("[data-reactscope-root]");
|
|
@@ -3226,26 +3451,59 @@ function registerRenderSingle(renderCmd) {
|
|
|
3226
3451
|
Available: ${available}`
|
|
3227
3452
|
);
|
|
3228
3453
|
}
|
|
3454
|
+
let props = {};
|
|
3455
|
+
if (opts.props !== void 0) {
|
|
3456
|
+
try {
|
|
3457
|
+
props = JSON.parse(opts.props);
|
|
3458
|
+
} catch {
|
|
3459
|
+
throw new Error(`Invalid props JSON: ${opts.props}`);
|
|
3460
|
+
}
|
|
3461
|
+
}
|
|
3462
|
+
if (descriptor.props !== void 0) {
|
|
3463
|
+
const propDefs = descriptor.props;
|
|
3464
|
+
for (const [propName, propDef] of Object.entries(propDefs)) {
|
|
3465
|
+
if (propName in props) continue;
|
|
3466
|
+
if (!propDef.required && propDef.default !== void 0) continue;
|
|
3467
|
+
if (propDef.type === "node" || propDef.type === "string") {
|
|
3468
|
+
props[propName] = propName === "children" ? componentName : propName;
|
|
3469
|
+
} else if (propDef.type === "union" && propDef.values && propDef.values.length > 0) {
|
|
3470
|
+
props[propName] = propDef.values[0];
|
|
3471
|
+
} else if (propDef.type === "boolean") {
|
|
3472
|
+
props[propName] = false;
|
|
3473
|
+
} else if (propDef.type === "number") {
|
|
3474
|
+
props[propName] = 0;
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3229
3478
|
const { width, height } = parseViewport(opts.viewport);
|
|
3230
3479
|
const rootDir = process.cwd();
|
|
3231
3480
|
const filePath = resolve(rootDir, descriptor.filePath);
|
|
3232
3481
|
const scopeData = await loadScopeFileForComponent(filePath);
|
|
3233
3482
|
const wrapperScript = scopeData?.hasWrapper === true ? await buildWrapperScript(scopeData.filePath) : void 0;
|
|
3234
3483
|
const scenarios = buildScenarioMap(opts, scopeData);
|
|
3235
|
-
const
|
|
3484
|
+
const globalCssFiles = loadGlobalCssFilesFromConfig(rootDir);
|
|
3485
|
+
const renderer = buildRenderer(
|
|
3486
|
+
filePath,
|
|
3487
|
+
componentName,
|
|
3488
|
+
width,
|
|
3489
|
+
height,
|
|
3490
|
+
globalCssFiles,
|
|
3491
|
+
rootDir,
|
|
3492
|
+
wrapperScript
|
|
3493
|
+
);
|
|
3236
3494
|
process.stderr.write(
|
|
3237
3495
|
`Rendering ${componentName} [${descriptor.complexityClass}] at ${width}\xD7${height}\u2026
|
|
3238
3496
|
`
|
|
3239
3497
|
);
|
|
3240
3498
|
const fmt2 = resolveSingleFormat(opts.format);
|
|
3241
3499
|
let anyFailed = false;
|
|
3242
|
-
for (const [scenarioName,
|
|
3500
|
+
for (const [scenarioName, props2] of Object.entries(scenarios)) {
|
|
3243
3501
|
const isNamed = scenarioName !== "__default__";
|
|
3244
3502
|
const label = isNamed ? `${componentName}:${scenarioName}` : componentName;
|
|
3245
3503
|
const outcome = await safeRender(
|
|
3246
|
-
() => renderer.renderCell(
|
|
3504
|
+
() => renderer.renderCell(props2, descriptor.complexityClass),
|
|
3247
3505
|
{
|
|
3248
|
-
props,
|
|
3506
|
+
props: props2,
|
|
3249
3507
|
sourceLocation: {
|
|
3250
3508
|
file: descriptor.filePath,
|
|
3251
3509
|
line: descriptor.loc.start,
|
|
@@ -3274,7 +3532,7 @@ Available: ${available}`
|
|
|
3274
3532
|
`
|
|
3275
3533
|
);
|
|
3276
3534
|
} else if (fmt2 === "json") {
|
|
3277
|
-
const json = formatRenderJson(label,
|
|
3535
|
+
const json = formatRenderJson(label, props2, result);
|
|
3278
3536
|
process.stdout.write(`${JSON.stringify(json, null, 2)}
|
|
3279
3537
|
`);
|
|
3280
3538
|
} else {
|
|
@@ -3301,7 +3559,10 @@ Available: ${available}`
|
|
|
3301
3559
|
);
|
|
3302
3560
|
}
|
|
3303
3561
|
function registerRenderMatrix(renderCmd) {
|
|
3304
|
-
renderCmd.command("matrix <component>").description("Render a component across a matrix of prop axes").option(
|
|
3562
|
+
renderCmd.command("matrix <component>").description("Render a component across a matrix of prop axes").option(
|
|
3563
|
+
"--axes <spec>",
|
|
3564
|
+
`Axis definitions: key:v1,v2 space-separated OR JSON object e.g. 'variant:primary,ghost size:sm,lg' or '{"variant":["primary","ghost"],"size":["sm","lg"]}'`
|
|
3565
|
+
).option(
|
|
3305
3566
|
"--contexts <ids>",
|
|
3306
3567
|
"Composition context IDs, comma-separated (e.g. centered,rtl,sidebar)"
|
|
3307
3568
|
).option("--stress <ids>", "Stress preset IDs, comma-separated (e.g. text.long,text.unicode)").option("--sprite <path>", "Write sprite sheet PNG to file").option("--format <fmt>", "Output format: json|png|html|csv (default: auto)").option("--concurrency <n>", "Max parallel renders", "8").option("--manifest <path>", "Path to manifest.json", MANIFEST_PATH6).action(
|
|
@@ -3320,21 +3581,47 @@ Available: ${available}`
|
|
|
3320
3581
|
const { width, height } = { width: 375, height: 812 };
|
|
3321
3582
|
const rootDir = process.cwd();
|
|
3322
3583
|
const filePath = resolve(rootDir, descriptor.filePath);
|
|
3323
|
-
const
|
|
3584
|
+
const matrixCssFiles = loadGlobalCssFilesFromConfig(rootDir);
|
|
3585
|
+
const renderer = buildRenderer(
|
|
3586
|
+
filePath,
|
|
3587
|
+
componentName,
|
|
3588
|
+
width,
|
|
3589
|
+
height,
|
|
3590
|
+
matrixCssFiles,
|
|
3591
|
+
rootDir
|
|
3592
|
+
);
|
|
3324
3593
|
const axes = [];
|
|
3325
3594
|
if (opts.axes !== void 0) {
|
|
3326
|
-
const
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3595
|
+
const axesRaw = opts.axes.trim();
|
|
3596
|
+
if (axesRaw.startsWith("{")) {
|
|
3597
|
+
let parsed;
|
|
3598
|
+
try {
|
|
3599
|
+
parsed = JSON.parse(axesRaw);
|
|
3600
|
+
} catch {
|
|
3601
|
+
throw new Error(`Invalid JSON in --axes: ${axesRaw}`);
|
|
3331
3602
|
}
|
|
3332
|
-
const name
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3603
|
+
for (const [name, vals] of Object.entries(parsed)) {
|
|
3604
|
+
if (!Array.isArray(vals)) {
|
|
3605
|
+
throw new Error(`Axis "${name}" must be an array of values in JSON format`);
|
|
3606
|
+
}
|
|
3607
|
+
axes.push({ name, values: vals.map(String) });
|
|
3608
|
+
}
|
|
3609
|
+
} else {
|
|
3610
|
+
const axisSpecs = axesRaw.split(/\s+/);
|
|
3611
|
+
for (const spec of axisSpecs) {
|
|
3612
|
+
const colonIdx = spec.indexOf(":");
|
|
3613
|
+
if (colonIdx < 0) {
|
|
3614
|
+
throw new Error(
|
|
3615
|
+
`Invalid axis spec "${spec}". Expected format: name:val1,val2,...`
|
|
3616
|
+
);
|
|
3617
|
+
}
|
|
3618
|
+
const name = spec.slice(0, colonIdx);
|
|
3619
|
+
const values = spec.slice(colonIdx + 1).split(",").map((v) => v.trim());
|
|
3620
|
+
if (name.length === 0 || values.length === 0) {
|
|
3621
|
+
throw new Error(`Invalid axis spec "${spec}"`);
|
|
3622
|
+
}
|
|
3623
|
+
axes.push({ name, values });
|
|
3336
3624
|
}
|
|
3337
|
-
axes.push({ name, values });
|
|
3338
3625
|
}
|
|
3339
3626
|
}
|
|
3340
3627
|
if (opts.contexts !== void 0) {
|
|
@@ -3453,7 +3740,8 @@ function registerRenderAll(renderCmd) {
|
|
|
3453
3740
|
const descriptor = manifest.components[name];
|
|
3454
3741
|
if (descriptor === void 0) return;
|
|
3455
3742
|
const filePath = resolve(rootDir, descriptor.filePath);
|
|
3456
|
-
const
|
|
3743
|
+
const allCssFiles = loadGlobalCssFilesFromConfig(process.cwd());
|
|
3744
|
+
const renderer = buildRenderer(filePath, name, 375, 812, allCssFiles, process.cwd());
|
|
3457
3745
|
const outcome = await safeRender(
|
|
3458
3746
|
() => renderer.renderCell({}, descriptor.complexityClass),
|
|
3459
3747
|
{
|
|
@@ -3719,12 +4007,12 @@ async function runBaseline(options = {}) {
|
|
|
3719
4007
|
mkdirSync(rendersDir, { recursive: true });
|
|
3720
4008
|
let manifest;
|
|
3721
4009
|
if (manifestPath !== void 0) {
|
|
3722
|
-
const { readFileSync:
|
|
4010
|
+
const { readFileSync: readFileSync13 } = await import('fs');
|
|
3723
4011
|
const absPath = resolve(rootDir, manifestPath);
|
|
3724
4012
|
if (!existsSync(absPath)) {
|
|
3725
4013
|
throw new Error(`Manifest not found at ${absPath}.`);
|
|
3726
4014
|
}
|
|
3727
|
-
manifest = JSON.parse(
|
|
4015
|
+
manifest = JSON.parse(readFileSync13(absPath, "utf-8"));
|
|
3728
4016
|
process.stderr.write(`Loaded manifest from ${manifestPath}
|
|
3729
4017
|
`);
|
|
3730
4018
|
} else {
|
|
@@ -5085,10 +5373,20 @@ function createTokensExportCommand() {
|
|
|
5085
5373
|
).action(
|
|
5086
5374
|
(opts) => {
|
|
5087
5375
|
if (!SUPPORTED_FORMATS.includes(opts.format)) {
|
|
5376
|
+
const FORMAT_ALIASES = {
|
|
5377
|
+
json: "flat-json",
|
|
5378
|
+
"json-flat": "flat-json",
|
|
5379
|
+
javascript: "ts",
|
|
5380
|
+
js: "ts",
|
|
5381
|
+
sass: "scss",
|
|
5382
|
+
tw: "tailwind"
|
|
5383
|
+
};
|
|
5384
|
+
const hint = FORMAT_ALIASES[opts.format.toLowerCase()];
|
|
5088
5385
|
process.stderr.write(
|
|
5089
5386
|
`Error: unsupported format "${opts.format}".
|
|
5090
5387
|
Supported formats: ${SUPPORTED_FORMATS.join(", ")}
|
|
5091
|
-
`
|
|
5388
|
+
` + (hint ? `Did you mean "${hint}"?
|
|
5389
|
+
` : "")
|
|
5092
5390
|
);
|
|
5093
5391
|
process.exit(1);
|
|
5094
5392
|
}
|