@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.cjs
CHANGED
|
@@ -118,6 +118,21 @@ import { createElement } from "react";
|
|
|
118
118
|
// Suppress "React must be in scope" warnings from old JSX (we use automatic)
|
|
119
119
|
banner: {
|
|
120
120
|
js: "/* @agent-scope/cli component harness */"
|
|
121
|
+
},
|
|
122
|
+
// CSS imports (e.g. `import './styles.css'`) are handled at the page level via
|
|
123
|
+
// globalCSS injection. Tell esbuild to treat CSS files as empty modules so
|
|
124
|
+
// components that import CSS directly (e.g. App.tsx) don't error during bundling.
|
|
125
|
+
loader: {
|
|
126
|
+
".css": "empty",
|
|
127
|
+
".svg": "dataurl",
|
|
128
|
+
".png": "dataurl",
|
|
129
|
+
".jpg": "dataurl",
|
|
130
|
+
".jpeg": "dataurl",
|
|
131
|
+
".gif": "dataurl",
|
|
132
|
+
".webp": "dataurl",
|
|
133
|
+
".ttf": "dataurl",
|
|
134
|
+
".woff": "dataurl",
|
|
135
|
+
".woff2": "dataurl"
|
|
121
136
|
}
|
|
122
137
|
});
|
|
123
138
|
if (result.errors.length > 0) {
|
|
@@ -525,6 +540,57 @@ async function getCompiledCssForClasses(cwd, classes) {
|
|
|
525
540
|
if (deduped.length === 0) return null;
|
|
526
541
|
return build3(deduped);
|
|
527
542
|
}
|
|
543
|
+
async function compileGlobalCssFile(cssFilePath, cwd) {
|
|
544
|
+
const { existsSync: existsSync15, readFileSync: readFileSync13 } = await import('fs');
|
|
545
|
+
const { createRequire: createRequire3 } = await import('module');
|
|
546
|
+
if (!existsSync15(cssFilePath)) return null;
|
|
547
|
+
const raw = readFileSync13(cssFilePath, "utf-8");
|
|
548
|
+
const needsCompile = /@tailwind|@import\s+['"]tailwindcss/.test(raw);
|
|
549
|
+
if (!needsCompile) {
|
|
550
|
+
return raw;
|
|
551
|
+
}
|
|
552
|
+
try {
|
|
553
|
+
const require2 = createRequire3(path.resolve(cwd, "package.json"));
|
|
554
|
+
let postcss;
|
|
555
|
+
let twPlugin;
|
|
556
|
+
try {
|
|
557
|
+
postcss = require2("postcss");
|
|
558
|
+
twPlugin = require2("tailwindcss");
|
|
559
|
+
} catch {
|
|
560
|
+
return raw;
|
|
561
|
+
}
|
|
562
|
+
let autoprefixerPlugin;
|
|
563
|
+
try {
|
|
564
|
+
autoprefixerPlugin = require2("autoprefixer");
|
|
565
|
+
} catch {
|
|
566
|
+
autoprefixerPlugin = null;
|
|
567
|
+
}
|
|
568
|
+
const plugins = autoprefixerPlugin ? [twPlugin, autoprefixerPlugin] : [twPlugin];
|
|
569
|
+
const result = await postcss(plugins).process(raw, {
|
|
570
|
+
from: cssFilePath,
|
|
571
|
+
to: cssFilePath
|
|
572
|
+
});
|
|
573
|
+
return result.css;
|
|
574
|
+
} catch (err) {
|
|
575
|
+
process.stderr.write(
|
|
576
|
+
`[scope/render] Warning: CSS compilation failed for ${cssFilePath}: ${err instanceof Error ? err.message : String(err)}
|
|
577
|
+
`
|
|
578
|
+
);
|
|
579
|
+
return raw;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
async function loadGlobalCss(globalCssFiles, cwd) {
|
|
583
|
+
if (globalCssFiles.length === 0) return null;
|
|
584
|
+
const parts = [];
|
|
585
|
+
for (const relPath of globalCssFiles) {
|
|
586
|
+
const absPath = path.resolve(cwd, relPath);
|
|
587
|
+
const css = await compileGlobalCssFile(absPath, cwd);
|
|
588
|
+
if (css !== null && css.trim().length > 0) {
|
|
589
|
+
parts.push(css);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return parts.length > 0 ? parts.join("\n") : null;
|
|
593
|
+
}
|
|
528
594
|
|
|
529
595
|
// src/ci/commands.ts
|
|
530
596
|
var CI_EXIT = {
|
|
@@ -1028,6 +1094,20 @@ function detectComponentPatterns(rootDir, typescript) {
|
|
|
1028
1094
|
}
|
|
1029
1095
|
return unique;
|
|
1030
1096
|
}
|
|
1097
|
+
var GLOBAL_CSS_CANDIDATES = [
|
|
1098
|
+
"src/styles.css",
|
|
1099
|
+
"src/index.css",
|
|
1100
|
+
"src/global.css",
|
|
1101
|
+
"src/globals.css",
|
|
1102
|
+
"src/app.css",
|
|
1103
|
+
"src/main.css",
|
|
1104
|
+
"styles/globals.css",
|
|
1105
|
+
"styles/global.css",
|
|
1106
|
+
"styles/index.css"
|
|
1107
|
+
];
|
|
1108
|
+
function detectGlobalCSSFiles(rootDir) {
|
|
1109
|
+
return GLOBAL_CSS_CANDIDATES.filter((rel) => fs.existsSync(path.join(rootDir, rel)));
|
|
1110
|
+
}
|
|
1031
1111
|
var TAILWIND_STEMS = ["tailwind.config"];
|
|
1032
1112
|
var CSS_EXTS = [".css", ".scss", ".sass", ".less"];
|
|
1033
1113
|
var THEME_SUFFIXES = [".theme.ts", ".theme.js", ".theme.tsx"];
|
|
@@ -1095,13 +1175,15 @@ function detectProject(rootDir) {
|
|
|
1095
1175
|
const packageManager = detectPackageManager(rootDir);
|
|
1096
1176
|
const componentPatterns = detectComponentPatterns(rootDir, typescript);
|
|
1097
1177
|
const tokenSources = detectTokenSources(rootDir);
|
|
1178
|
+
const globalCSSFiles = detectGlobalCSSFiles(rootDir);
|
|
1098
1179
|
return {
|
|
1099
1180
|
framework,
|
|
1100
1181
|
typescript,
|
|
1101
1182
|
tsconfigPath,
|
|
1102
1183
|
componentPatterns,
|
|
1103
1184
|
tokenSources,
|
|
1104
|
-
packageManager
|
|
1185
|
+
packageManager,
|
|
1186
|
+
globalCSSFiles
|
|
1105
1187
|
};
|
|
1106
1188
|
}
|
|
1107
1189
|
function buildDefaultConfig(detected, tokenFile, outputDir) {
|
|
@@ -1110,7 +1192,7 @@ function buildDefaultConfig(detected, tokenFile, outputDir) {
|
|
|
1110
1192
|
components: {
|
|
1111
1193
|
include,
|
|
1112
1194
|
exclude: ["**/*.test.tsx", "**/*.stories.tsx"],
|
|
1113
|
-
wrappers: { providers: [], globalCSS: [] }
|
|
1195
|
+
wrappers: { providers: [], globalCSS: detected.globalCSSFiles ?? [] }
|
|
1114
1196
|
},
|
|
1115
1197
|
render: {
|
|
1116
1198
|
viewport: { default: { width: 1280, height: 800 } },
|
|
@@ -1168,18 +1250,118 @@ function ensureGitignoreEntry(rootDir, entry) {
|
|
|
1168
1250
|
`);
|
|
1169
1251
|
}
|
|
1170
1252
|
}
|
|
1253
|
+
function extractTailwindTokens(tokenSources) {
|
|
1254
|
+
const tailwindSource = tokenSources.find((s) => s.kind === "tailwind-config");
|
|
1255
|
+
if (!tailwindSource) return null;
|
|
1256
|
+
try {
|
|
1257
|
+
let parseBlock2 = function(block) {
|
|
1258
|
+
const result = {};
|
|
1259
|
+
const lineRe = /['"]?(\w[\w.-]*|\d+)['"]?\s*:\s*['"]?(#[0-9a-fA-F]{3,8}|\d+(?:px|rem|em|%)|[\w-]+(?:\/[\w]+)?)['"]?/g;
|
|
1260
|
+
for (const m of block.matchAll(lineRe)) {
|
|
1261
|
+
if (m[1] !== void 0 && m[2] !== void 0) {
|
|
1262
|
+
result[m[1]] = m[2];
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
return result;
|
|
1266
|
+
};
|
|
1267
|
+
var parseBlock = parseBlock2;
|
|
1268
|
+
const raw = fs.readFileSync(tailwindSource.path, "utf-8");
|
|
1269
|
+
const tokens = {};
|
|
1270
|
+
const colorsKeyIdx = raw.indexOf("colors:");
|
|
1271
|
+
if (colorsKeyIdx !== -1) {
|
|
1272
|
+
const colorsBraceStart = raw.indexOf("{", colorsKeyIdx);
|
|
1273
|
+
if (colorsBraceStart !== -1) {
|
|
1274
|
+
let colorDepth = 0;
|
|
1275
|
+
let colorsBraceEnd = -1;
|
|
1276
|
+
for (let ci = colorsBraceStart; ci < raw.length; ci++) {
|
|
1277
|
+
if (raw[ci] === "{") colorDepth++;
|
|
1278
|
+
else if (raw[ci] === "}") {
|
|
1279
|
+
colorDepth--;
|
|
1280
|
+
if (colorDepth === 0) {
|
|
1281
|
+
colorsBraceEnd = ci;
|
|
1282
|
+
break;
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
if (colorsBraceEnd > colorsBraceStart) {
|
|
1287
|
+
const colorSection = raw.slice(colorsBraceStart + 1, colorsBraceEnd);
|
|
1288
|
+
const scaleRe = /(\w+)\s*:\s*\{([^}]+)\}/g;
|
|
1289
|
+
const colorTokens = {};
|
|
1290
|
+
for (const sm of colorSection.matchAll(scaleRe)) {
|
|
1291
|
+
if (sm[1] === void 0 || sm[2] === void 0) continue;
|
|
1292
|
+
const scaleName = sm[1];
|
|
1293
|
+
const scaleValues = parseBlock2(sm[2]);
|
|
1294
|
+
if (Object.keys(scaleValues).length > 0) {
|
|
1295
|
+
const scaleTokens = {};
|
|
1296
|
+
for (const [step, hex] of Object.entries(scaleValues)) {
|
|
1297
|
+
scaleTokens[step] = { value: hex, type: "color" };
|
|
1298
|
+
}
|
|
1299
|
+
colorTokens[scaleName] = scaleTokens;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
if (Object.keys(colorTokens).length > 0) {
|
|
1303
|
+
tokens["color"] = colorTokens;
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
const spacingMatch = raw.match(/spacing\s*:\s*\{([\s\S]*?)\n\s*\}/);
|
|
1309
|
+
if (spacingMatch?.[1] !== void 0) {
|
|
1310
|
+
const spacingValues = parseBlock2(spacingMatch[1]);
|
|
1311
|
+
if (Object.keys(spacingValues).length > 0) {
|
|
1312
|
+
const spacingTokens = {};
|
|
1313
|
+
for (const [key, val] of Object.entries(spacingValues)) {
|
|
1314
|
+
spacingTokens[key] = { value: val, type: "dimension" };
|
|
1315
|
+
}
|
|
1316
|
+
tokens["spacing"] = spacingTokens;
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
const fontFamilyMatch = raw.match(/fontFamily\s*:\s*\{([\s\S]*?)\n\s*\}/);
|
|
1320
|
+
if (fontFamilyMatch?.[1] !== void 0) {
|
|
1321
|
+
const fontFamilyRe = /(\w+)\s*:\s*\[\s*['"]([^'"]+)['"]/g;
|
|
1322
|
+
const fontTokens = {};
|
|
1323
|
+
for (const fm of fontFamilyMatch[1].matchAll(fontFamilyRe)) {
|
|
1324
|
+
if (fm[1] !== void 0 && fm[2] !== void 0) {
|
|
1325
|
+
fontTokens[fm[1]] = { value: fm[2], type: "fontFamily" };
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
if (Object.keys(fontTokens).length > 0) {
|
|
1329
|
+
tokens["font"] = fontTokens;
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
const borderRadiusMatch = raw.match(/borderRadius\s*:\s*\{([\s\S]*?)\n\s*\}/);
|
|
1333
|
+
if (borderRadiusMatch?.[1] !== void 0) {
|
|
1334
|
+
const radiusValues = parseBlock2(borderRadiusMatch[1]);
|
|
1335
|
+
if (Object.keys(radiusValues).length > 0) {
|
|
1336
|
+
const radiusTokens = {};
|
|
1337
|
+
for (const [key, val] of Object.entries(radiusValues)) {
|
|
1338
|
+
radiusTokens[key] = { value: val, type: "dimension" };
|
|
1339
|
+
}
|
|
1340
|
+
tokens["radius"] = radiusTokens;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
return Object.keys(tokens).length > 0 ? tokens : null;
|
|
1344
|
+
} catch {
|
|
1345
|
+
return null;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1171
1348
|
function scaffoldConfig(rootDir, config) {
|
|
1172
1349
|
const path$1 = path.join(rootDir, "reactscope.config.json");
|
|
1173
1350
|
fs.writeFileSync(path$1, `${JSON.stringify(config, null, 2)}
|
|
1174
1351
|
`);
|
|
1175
1352
|
return path$1;
|
|
1176
1353
|
}
|
|
1177
|
-
function scaffoldTokenFile(rootDir, tokenFile) {
|
|
1354
|
+
function scaffoldTokenFile(rootDir, tokenFile, extractedTokens) {
|
|
1178
1355
|
const path$1 = path.join(rootDir, tokenFile);
|
|
1179
1356
|
if (!fs.existsSync(path$1)) {
|
|
1180
1357
|
const stub = {
|
|
1181
1358
|
$schema: "https://raw.githubusercontent.com/FlatFilers/Scope/main/packages/tokens/schema.json",
|
|
1182
|
-
|
|
1359
|
+
version: "1.0.0",
|
|
1360
|
+
meta: {
|
|
1361
|
+
name: "Design Tokens",
|
|
1362
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
1363
|
+
},
|
|
1364
|
+
tokens: extractedTokens ?? {}
|
|
1183
1365
|
};
|
|
1184
1366
|
fs.writeFileSync(path$1, `${JSON.stringify(stub, null, 2)}
|
|
1185
1367
|
`);
|
|
@@ -1257,7 +1439,13 @@ async function runInit(options) {
|
|
|
1257
1439
|
}
|
|
1258
1440
|
const cfgPath = scaffoldConfig(rootDir, config);
|
|
1259
1441
|
created.push(cfgPath);
|
|
1260
|
-
const
|
|
1442
|
+
const extractedTokens = extractTailwindTokens(detected.tokenSources);
|
|
1443
|
+
if (extractedTokens !== null) {
|
|
1444
|
+
const tokenGroupCount = Object.keys(extractedTokens).length;
|
|
1445
|
+
process.stdout.write(` Extracted ${tokenGroupCount} token group(s) from Tailwind config
|
|
1446
|
+
`);
|
|
1447
|
+
}
|
|
1448
|
+
const tokPath = scaffoldTokenFile(rootDir, config.tokens.file, extractedTokens ?? void 0);
|
|
1261
1449
|
created.push(tokPath);
|
|
1262
1450
|
const outDirPath = scaffoldOutputDir(rootDir, config.output.dir);
|
|
1263
1451
|
created.push(outDirPath);
|
|
@@ -1357,7 +1545,10 @@ Available: ${available}${hint}`
|
|
|
1357
1545
|
});
|
|
1358
1546
|
}
|
|
1359
1547
|
function registerQuery(manifestCmd) {
|
|
1360
|
-
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(
|
|
1548
|
+
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(
|
|
1549
|
+
"--has-prop <spec>",
|
|
1550
|
+
"Find components with a prop matching name or name:type (e.g. 'loading' or 'variant:union')"
|
|
1551
|
+
).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(
|
|
1361
1552
|
(opts) => {
|
|
1362
1553
|
try {
|
|
1363
1554
|
const manifest = loadManifest(opts.manifest);
|
|
@@ -1368,9 +1559,11 @@ function registerQuery(manifestCmd) {
|
|
|
1368
1559
|
if (opts.complexity !== void 0) queryParts.push(`complexity=${opts.complexity}`);
|
|
1369
1560
|
if (opts.sideEffects) queryParts.push("side-effects");
|
|
1370
1561
|
if (opts.hasFetch) queryParts.push("has-fetch");
|
|
1562
|
+
if (opts.hasProp !== void 0) queryParts.push(`has-prop=${opts.hasProp}`);
|
|
1563
|
+
if (opts.composedBy !== void 0) queryParts.push(`composed-by=${opts.composedBy}`);
|
|
1371
1564
|
if (queryParts.length === 0) {
|
|
1372
1565
|
process.stderr.write(
|
|
1373
|
-
"No query flags specified. Use --context, --hook, --complexity, --side-effects,
|
|
1566
|
+
"No query flags specified. Use --context, --hook, --complexity, --side-effects, --has-fetch, --has-prop, or --composed-by.\n"
|
|
1374
1567
|
);
|
|
1375
1568
|
process.exit(1);
|
|
1376
1569
|
}
|
|
@@ -1397,6 +1590,27 @@ function registerQuery(manifestCmd) {
|
|
|
1397
1590
|
if (opts.hasFetch) {
|
|
1398
1591
|
entries = entries.filter(([, d]) => d.sideEffects.fetches.length > 0);
|
|
1399
1592
|
}
|
|
1593
|
+
if (opts.hasProp !== void 0) {
|
|
1594
|
+
const spec = opts.hasProp;
|
|
1595
|
+
const colonIdx = spec.indexOf(":");
|
|
1596
|
+
const propName = colonIdx >= 0 ? spec.slice(0, colonIdx) : spec;
|
|
1597
|
+
const propType = colonIdx >= 0 ? spec.slice(colonIdx + 1) : void 0;
|
|
1598
|
+
entries = entries.filter(([, d]) => {
|
|
1599
|
+
const props = d.props;
|
|
1600
|
+
if (!props || !(propName in props)) return false;
|
|
1601
|
+
if (propType !== void 0) {
|
|
1602
|
+
return props[propName]?.type === propType;
|
|
1603
|
+
}
|
|
1604
|
+
return true;
|
|
1605
|
+
});
|
|
1606
|
+
}
|
|
1607
|
+
if (opts.composedBy !== void 0) {
|
|
1608
|
+
const targetName = opts.composedBy;
|
|
1609
|
+
entries = entries.filter(([, d]) => {
|
|
1610
|
+
const composedBy = d.composedBy;
|
|
1611
|
+
return composedBy !== void 0 && composedBy.includes(targetName);
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1400
1614
|
const rows = entries.map(([name, d]) => ({
|
|
1401
1615
|
name,
|
|
1402
1616
|
file: d.filePath,
|
|
@@ -3081,6 +3295,17 @@ ${msg}`);
|
|
|
3081
3295
|
}
|
|
3082
3296
|
|
|
3083
3297
|
// src/render-commands.ts
|
|
3298
|
+
function loadGlobalCssFilesFromConfig(cwd) {
|
|
3299
|
+
const configPath = path.resolve(cwd, "reactscope.config.json");
|
|
3300
|
+
if (!fs.existsSync(configPath)) return [];
|
|
3301
|
+
try {
|
|
3302
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
3303
|
+
const cfg = JSON.parse(raw);
|
|
3304
|
+
return cfg.components?.wrappers?.globalCSS ?? [];
|
|
3305
|
+
} catch {
|
|
3306
|
+
return [];
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3084
3309
|
var MANIFEST_PATH6 = ".reactscope/manifest.json";
|
|
3085
3310
|
var DEFAULT_OUTPUT_DIR = ".reactscope/renders";
|
|
3086
3311
|
var _pool3 = null;
|
|
@@ -3101,7 +3326,7 @@ async function shutdownPool3() {
|
|
|
3101
3326
|
_pool3 = null;
|
|
3102
3327
|
}
|
|
3103
3328
|
}
|
|
3104
|
-
function buildRenderer(filePath, componentName, viewportWidth, viewportHeight, wrapperScript) {
|
|
3329
|
+
function buildRenderer(filePath, componentName, viewportWidth, viewportHeight, globalCssFiles = [], projectCwd = process.cwd(), wrapperScript) {
|
|
3105
3330
|
const satori = new render.SatoriRenderer({
|
|
3106
3331
|
defaultViewport: { width: viewportWidth, height: viewportHeight }
|
|
3107
3332
|
});
|
|
@@ -3110,13 +3335,13 @@ function buildRenderer(filePath, componentName, viewportWidth, viewportHeight, w
|
|
|
3110
3335
|
async renderCell(props, _complexityClass) {
|
|
3111
3336
|
const startMs = performance.now();
|
|
3112
3337
|
const pool = await getPool3(viewportWidth, viewportHeight);
|
|
3338
|
+
const projectCss = await loadGlobalCss(globalCssFiles, projectCwd);
|
|
3113
3339
|
const htmlHarness = await buildComponentHarness(
|
|
3114
3340
|
filePath,
|
|
3115
3341
|
componentName,
|
|
3116
3342
|
props,
|
|
3117
3343
|
viewportWidth,
|
|
3118
|
-
void 0,
|
|
3119
|
-
// projectCss (handled separately)
|
|
3344
|
+
projectCss ?? void 0,
|
|
3120
3345
|
wrapperScript
|
|
3121
3346
|
);
|
|
3122
3347
|
const slot = await pool.acquire();
|
|
@@ -3146,9 +3371,9 @@ function buildRenderer(filePath, componentName, viewportWidth, viewportHeight, w
|
|
|
3146
3371
|
});
|
|
3147
3372
|
return [...set];
|
|
3148
3373
|
});
|
|
3149
|
-
const
|
|
3150
|
-
if (
|
|
3151
|
-
await page.addStyleTag({ content:
|
|
3374
|
+
const projectCss2 = await getCompiledCssForClasses(rootDir, classes);
|
|
3375
|
+
if (projectCss2 != null && projectCss2.length > 0) {
|
|
3376
|
+
await page.addStyleTag({ content: projectCss2 });
|
|
3152
3377
|
}
|
|
3153
3378
|
const renderTimeMs = performance.now() - startMs;
|
|
3154
3379
|
const rootLocator = page.locator("[data-reactscope-root]");
|
|
@@ -3250,26 +3475,59 @@ function registerRenderSingle(renderCmd) {
|
|
|
3250
3475
|
Available: ${available}`
|
|
3251
3476
|
);
|
|
3252
3477
|
}
|
|
3478
|
+
let props = {};
|
|
3479
|
+
if (opts.props !== void 0) {
|
|
3480
|
+
try {
|
|
3481
|
+
props = JSON.parse(opts.props);
|
|
3482
|
+
} catch {
|
|
3483
|
+
throw new Error(`Invalid props JSON: ${opts.props}`);
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
if (descriptor.props !== void 0) {
|
|
3487
|
+
const propDefs = descriptor.props;
|
|
3488
|
+
for (const [propName, propDef] of Object.entries(propDefs)) {
|
|
3489
|
+
if (propName in props) continue;
|
|
3490
|
+
if (!propDef.required && propDef.default !== void 0) continue;
|
|
3491
|
+
if (propDef.type === "node" || propDef.type === "string") {
|
|
3492
|
+
props[propName] = propName === "children" ? componentName : propName;
|
|
3493
|
+
} else if (propDef.type === "union" && propDef.values && propDef.values.length > 0) {
|
|
3494
|
+
props[propName] = propDef.values[0];
|
|
3495
|
+
} else if (propDef.type === "boolean") {
|
|
3496
|
+
props[propName] = false;
|
|
3497
|
+
} else if (propDef.type === "number") {
|
|
3498
|
+
props[propName] = 0;
|
|
3499
|
+
}
|
|
3500
|
+
}
|
|
3501
|
+
}
|
|
3253
3502
|
const { width, height } = parseViewport(opts.viewport);
|
|
3254
3503
|
const rootDir = process.cwd();
|
|
3255
3504
|
const filePath = path.resolve(rootDir, descriptor.filePath);
|
|
3256
3505
|
const scopeData = await loadScopeFileForComponent(filePath);
|
|
3257
3506
|
const wrapperScript = scopeData?.hasWrapper === true ? await buildWrapperScript(scopeData.filePath) : void 0;
|
|
3258
3507
|
const scenarios = buildScenarioMap(opts, scopeData);
|
|
3259
|
-
const
|
|
3508
|
+
const globalCssFiles = loadGlobalCssFilesFromConfig(rootDir);
|
|
3509
|
+
const renderer = buildRenderer(
|
|
3510
|
+
filePath,
|
|
3511
|
+
componentName,
|
|
3512
|
+
width,
|
|
3513
|
+
height,
|
|
3514
|
+
globalCssFiles,
|
|
3515
|
+
rootDir,
|
|
3516
|
+
wrapperScript
|
|
3517
|
+
);
|
|
3260
3518
|
process.stderr.write(
|
|
3261
3519
|
`Rendering ${componentName} [${descriptor.complexityClass}] at ${width}\xD7${height}\u2026
|
|
3262
3520
|
`
|
|
3263
3521
|
);
|
|
3264
3522
|
const fmt2 = resolveSingleFormat(opts.format);
|
|
3265
3523
|
let anyFailed = false;
|
|
3266
|
-
for (const [scenarioName,
|
|
3524
|
+
for (const [scenarioName, props2] of Object.entries(scenarios)) {
|
|
3267
3525
|
const isNamed = scenarioName !== "__default__";
|
|
3268
3526
|
const label = isNamed ? `${componentName}:${scenarioName}` : componentName;
|
|
3269
3527
|
const outcome = await render.safeRender(
|
|
3270
|
-
() => renderer.renderCell(
|
|
3528
|
+
() => renderer.renderCell(props2, descriptor.complexityClass),
|
|
3271
3529
|
{
|
|
3272
|
-
props,
|
|
3530
|
+
props: props2,
|
|
3273
3531
|
sourceLocation: {
|
|
3274
3532
|
file: descriptor.filePath,
|
|
3275
3533
|
line: descriptor.loc.start,
|
|
@@ -3298,7 +3556,7 @@ Available: ${available}`
|
|
|
3298
3556
|
`
|
|
3299
3557
|
);
|
|
3300
3558
|
} else if (fmt2 === "json") {
|
|
3301
|
-
const json = formatRenderJson(label,
|
|
3559
|
+
const json = formatRenderJson(label, props2, result);
|
|
3302
3560
|
process.stdout.write(`${JSON.stringify(json, null, 2)}
|
|
3303
3561
|
`);
|
|
3304
3562
|
} else {
|
|
@@ -3325,7 +3583,10 @@ Available: ${available}`
|
|
|
3325
3583
|
);
|
|
3326
3584
|
}
|
|
3327
3585
|
function registerRenderMatrix(renderCmd) {
|
|
3328
|
-
renderCmd.command("matrix <component>").description("Render a component across a matrix of prop axes").option(
|
|
3586
|
+
renderCmd.command("matrix <component>").description("Render a component across a matrix of prop axes").option(
|
|
3587
|
+
"--axes <spec>",
|
|
3588
|
+
`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"]}'`
|
|
3589
|
+
).option(
|
|
3329
3590
|
"--contexts <ids>",
|
|
3330
3591
|
"Composition context IDs, comma-separated (e.g. centered,rtl,sidebar)"
|
|
3331
3592
|
).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(
|
|
@@ -3344,21 +3605,47 @@ Available: ${available}`
|
|
|
3344
3605
|
const { width, height } = { width: 375, height: 812 };
|
|
3345
3606
|
const rootDir = process.cwd();
|
|
3346
3607
|
const filePath = path.resolve(rootDir, descriptor.filePath);
|
|
3347
|
-
const
|
|
3608
|
+
const matrixCssFiles = loadGlobalCssFilesFromConfig(rootDir);
|
|
3609
|
+
const renderer = buildRenderer(
|
|
3610
|
+
filePath,
|
|
3611
|
+
componentName,
|
|
3612
|
+
width,
|
|
3613
|
+
height,
|
|
3614
|
+
matrixCssFiles,
|
|
3615
|
+
rootDir
|
|
3616
|
+
);
|
|
3348
3617
|
const axes = [];
|
|
3349
3618
|
if (opts.axes !== void 0) {
|
|
3350
|
-
const
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3619
|
+
const axesRaw = opts.axes.trim();
|
|
3620
|
+
if (axesRaw.startsWith("{")) {
|
|
3621
|
+
let parsed;
|
|
3622
|
+
try {
|
|
3623
|
+
parsed = JSON.parse(axesRaw);
|
|
3624
|
+
} catch {
|
|
3625
|
+
throw new Error(`Invalid JSON in --axes: ${axesRaw}`);
|
|
3355
3626
|
}
|
|
3356
|
-
const name
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3627
|
+
for (const [name, vals] of Object.entries(parsed)) {
|
|
3628
|
+
if (!Array.isArray(vals)) {
|
|
3629
|
+
throw new Error(`Axis "${name}" must be an array of values in JSON format`);
|
|
3630
|
+
}
|
|
3631
|
+
axes.push({ name, values: vals.map(String) });
|
|
3632
|
+
}
|
|
3633
|
+
} else {
|
|
3634
|
+
const axisSpecs = axesRaw.split(/\s+/);
|
|
3635
|
+
for (const spec of axisSpecs) {
|
|
3636
|
+
const colonIdx = spec.indexOf(":");
|
|
3637
|
+
if (colonIdx < 0) {
|
|
3638
|
+
throw new Error(
|
|
3639
|
+
`Invalid axis spec "${spec}". Expected format: name:val1,val2,...`
|
|
3640
|
+
);
|
|
3641
|
+
}
|
|
3642
|
+
const name = spec.slice(0, colonIdx);
|
|
3643
|
+
const values = spec.slice(colonIdx + 1).split(",").map((v) => v.trim());
|
|
3644
|
+
if (name.length === 0 || values.length === 0) {
|
|
3645
|
+
throw new Error(`Invalid axis spec "${spec}"`);
|
|
3646
|
+
}
|
|
3647
|
+
axes.push({ name, values });
|
|
3360
3648
|
}
|
|
3361
|
-
axes.push({ name, values });
|
|
3362
3649
|
}
|
|
3363
3650
|
}
|
|
3364
3651
|
if (opts.contexts !== void 0) {
|
|
@@ -3477,7 +3764,8 @@ function registerRenderAll(renderCmd) {
|
|
|
3477
3764
|
const descriptor = manifest.components[name];
|
|
3478
3765
|
if (descriptor === void 0) return;
|
|
3479
3766
|
const filePath = path.resolve(rootDir, descriptor.filePath);
|
|
3480
|
-
const
|
|
3767
|
+
const allCssFiles = loadGlobalCssFilesFromConfig(process.cwd());
|
|
3768
|
+
const renderer = buildRenderer(filePath, name, 375, 812, allCssFiles, process.cwd());
|
|
3481
3769
|
const outcome = await render.safeRender(
|
|
3482
3770
|
() => renderer.renderCell({}, descriptor.complexityClass),
|
|
3483
3771
|
{
|
|
@@ -3743,12 +4031,12 @@ async function runBaseline(options = {}) {
|
|
|
3743
4031
|
fs.mkdirSync(rendersDir, { recursive: true });
|
|
3744
4032
|
let manifest$1;
|
|
3745
4033
|
if (manifestPath !== void 0) {
|
|
3746
|
-
const { readFileSync:
|
|
4034
|
+
const { readFileSync: readFileSync13 } = await import('fs');
|
|
3747
4035
|
const absPath = path.resolve(rootDir, manifestPath);
|
|
3748
4036
|
if (!fs.existsSync(absPath)) {
|
|
3749
4037
|
throw new Error(`Manifest not found at ${absPath}.`);
|
|
3750
4038
|
}
|
|
3751
|
-
manifest$1 = JSON.parse(
|
|
4039
|
+
manifest$1 = JSON.parse(readFileSync13(absPath, "utf-8"));
|
|
3752
4040
|
process.stderr.write(`Loaded manifest from ${manifestPath}
|
|
3753
4041
|
`);
|
|
3754
4042
|
} else {
|
|
@@ -5109,10 +5397,20 @@ function createTokensExportCommand() {
|
|
|
5109
5397
|
).action(
|
|
5110
5398
|
(opts) => {
|
|
5111
5399
|
if (!SUPPORTED_FORMATS.includes(opts.format)) {
|
|
5400
|
+
const FORMAT_ALIASES = {
|
|
5401
|
+
json: "flat-json",
|
|
5402
|
+
"json-flat": "flat-json",
|
|
5403
|
+
javascript: "ts",
|
|
5404
|
+
js: "ts",
|
|
5405
|
+
sass: "scss",
|
|
5406
|
+
tw: "tailwind"
|
|
5407
|
+
};
|
|
5408
|
+
const hint = FORMAT_ALIASES[opts.format.toLowerCase()];
|
|
5112
5409
|
process.stderr.write(
|
|
5113
5410
|
`Error: unsupported format "${opts.format}".
|
|
5114
5411
|
Supported formats: ${SUPPORTED_FORMATS.join(", ")}
|
|
5115
|
-
`
|
|
5412
|
+
` + (hint ? `Did you mean "${hint}"?
|
|
5413
|
+
` : "")
|
|
5116
5414
|
);
|
|
5117
5415
|
process.exit(1);
|
|
5118
5416
|
}
|