@agent-scope/cli 1.17.2 → 1.18.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 +349 -170
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +256 -84
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +254 -83
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -4,18 +4,19 @@ import { generateManifest } from '@agent-scope/manifest';
|
|
|
4
4
|
import { SpriteSheetGenerator, safeRender, BrowserPool, ALL_CONTEXT_IDS, contextAxis, stressAxis, ALL_STRESS_IDS, RenderMatrix, SatoriRenderer } from '@agent-scope/render';
|
|
5
5
|
import { TokenResolver, ComplianceEngine, parseTokenFileSync, ThemeResolver, exportTokens, validateTokenFile, TokenValidationError, TokenParseError, ImpactAnalyzer } from '@agent-scope/tokens';
|
|
6
6
|
import { Command } from 'commander';
|
|
7
|
-
import * as
|
|
7
|
+
import * as esbuild2 from 'esbuild';
|
|
8
8
|
import { createRequire } from 'module';
|
|
9
9
|
import * as readline from 'readline';
|
|
10
10
|
import { loadTrace, generateTest, getBrowserEntryScript } from '@agent-scope/playwright';
|
|
11
11
|
import { chromium } from 'playwright';
|
|
12
|
+
import { tmpdir } from 'os';
|
|
12
13
|
import { createServer } from 'http';
|
|
13
14
|
import { buildSite } from '@agent-scope/site';
|
|
14
15
|
|
|
15
16
|
// src/ci/commands.ts
|
|
16
|
-
async function buildComponentHarness(filePath, componentName, props, viewportWidth, projectCss) {
|
|
17
|
+
async function buildComponentHarness(filePath, componentName, props, viewportWidth, projectCss, wrapperScript) {
|
|
17
18
|
const bundledScript = await bundleComponentToIIFE(filePath, componentName, props);
|
|
18
|
-
return wrapInHtml(bundledScript, viewportWidth, projectCss);
|
|
19
|
+
return wrapInHtml(bundledScript, viewportWidth, projectCss, wrapperScript);
|
|
19
20
|
}
|
|
20
21
|
async function bundleComponentToIIFE(filePath, componentName, props) {
|
|
21
22
|
const propsJson = JSON.stringify(props).replace(/<\/script>/gi, "<\\/script>");
|
|
@@ -51,7 +52,12 @@ import { createElement } from "react";
|
|
|
51
52
|
window.__SCOPE_RENDER_COMPLETE__ = true;
|
|
52
53
|
return;
|
|
53
54
|
}
|
|
54
|
-
|
|
55
|
+
// If a scope file wrapper was injected, use it to wrap the component
|
|
56
|
+
var Wrapper = (window).__SCOPE_WRAPPER__;
|
|
57
|
+
var element = Wrapper
|
|
58
|
+
? createElement(Wrapper, null, createElement(Component, props))
|
|
59
|
+
: createElement(Component, props);
|
|
60
|
+
createRoot(rootEl).render(element);
|
|
55
61
|
// Use requestAnimationFrame to let React flush the render
|
|
56
62
|
requestAnimationFrame(function() {
|
|
57
63
|
window.__SCOPE_RENDER_COMPLETE__ = true;
|
|
@@ -63,7 +69,7 @@ import { createElement } from "react";
|
|
|
63
69
|
})();
|
|
64
70
|
`
|
|
65
71
|
);
|
|
66
|
-
const result = await
|
|
72
|
+
const result = await esbuild2.build({
|
|
67
73
|
stdin: {
|
|
68
74
|
contents: wrapperCode,
|
|
69
75
|
// Resolve relative imports (within the component's dir)
|
|
@@ -101,10 +107,11 @@ ${msg}`);
|
|
|
101
107
|
}
|
|
102
108
|
return outputFile.text;
|
|
103
109
|
}
|
|
104
|
-
function wrapInHtml(bundledScript, viewportWidth, projectCss) {
|
|
110
|
+
function wrapInHtml(bundledScript, viewportWidth, projectCss, wrapperScript) {
|
|
105
111
|
const projectStyleBlock = projectCss != null && projectCss.length > 0 ? `<style id="scope-project-css">
|
|
106
112
|
${projectCss.replace(/<\/style>/gi, "<\\/style>")}
|
|
107
113
|
</style>` : "";
|
|
114
|
+
const wrapperScriptBlock = wrapperScript != null && wrapperScript.length > 0 ? `<script id="scope-wrapper-script">${wrapperScript}</script>` : "";
|
|
108
115
|
return `<!DOCTYPE html>
|
|
109
116
|
<html lang="en">
|
|
110
117
|
<head>
|
|
@@ -119,6 +126,7 @@ ${projectCss.replace(/<\/style>/gi, "<\\/style>")}
|
|
|
119
126
|
</head>
|
|
120
127
|
<body>
|
|
121
128
|
<div id="scope-root" data-reactscope-root></div>
|
|
129
|
+
${wrapperScriptBlock}
|
|
122
130
|
<script>${bundledScript}</script>
|
|
123
131
|
</body>
|
|
124
132
|
</html>`;
|
|
@@ -482,16 +490,16 @@ async function getTailwindCompiler(cwd) {
|
|
|
482
490
|
from: entryPath,
|
|
483
491
|
loadStylesheet
|
|
484
492
|
});
|
|
485
|
-
const
|
|
486
|
-
compilerCache = { cwd, build:
|
|
487
|
-
return
|
|
493
|
+
const build3 = result.build.bind(result);
|
|
494
|
+
compilerCache = { cwd, build: build3 };
|
|
495
|
+
return build3;
|
|
488
496
|
}
|
|
489
497
|
async function getCompiledCssForClasses(cwd, classes) {
|
|
490
|
-
const
|
|
491
|
-
if (
|
|
498
|
+
const build3 = await getTailwindCompiler(cwd);
|
|
499
|
+
if (build3 === null) return null;
|
|
492
500
|
const deduped = [...new Set(classes)].filter(Boolean);
|
|
493
501
|
if (deduped.length === 0) return null;
|
|
494
|
-
return
|
|
502
|
+
return build3(deduped);
|
|
495
503
|
}
|
|
496
504
|
|
|
497
505
|
// src/ci/commands.ts
|
|
@@ -1109,9 +1117,9 @@ function createRL() {
|
|
|
1109
1117
|
});
|
|
1110
1118
|
}
|
|
1111
1119
|
async function ask(rl, question) {
|
|
1112
|
-
return new Promise((
|
|
1120
|
+
return new Promise((resolve18) => {
|
|
1113
1121
|
rl.question(question, (answer) => {
|
|
1114
|
-
|
|
1122
|
+
resolve18(answer.trim());
|
|
1115
1123
|
});
|
|
1116
1124
|
});
|
|
1117
1125
|
}
|
|
@@ -1683,8 +1691,15 @@ async function runHooksProfiling(componentName, filePath, props) {
|
|
|
1683
1691
|
try {
|
|
1684
1692
|
const context = await browser.newContext();
|
|
1685
1693
|
const page = await context.newPage();
|
|
1686
|
-
|
|
1687
|
-
const htmlHarness = await buildComponentHarness(
|
|
1694
|
+
const scopeRuntime = getBrowserEntryScript();
|
|
1695
|
+
const htmlHarness = await buildComponentHarness(
|
|
1696
|
+
filePath,
|
|
1697
|
+
componentName,
|
|
1698
|
+
props,
|
|
1699
|
+
1280,
|
|
1700
|
+
void 0,
|
|
1701
|
+
scopeRuntime
|
|
1702
|
+
);
|
|
1688
1703
|
await page.setContent(htmlHarness, { waitUntil: "load" });
|
|
1689
1704
|
await page.waitForFunction(
|
|
1690
1705
|
() => {
|
|
@@ -1943,7 +1958,15 @@ async function runInteractionProfile(componentName, filePath, props, interaction
|
|
|
1943
1958
|
try {
|
|
1944
1959
|
const context = await browser.newContext();
|
|
1945
1960
|
const page = await context.newPage();
|
|
1946
|
-
const
|
|
1961
|
+
const scopeRuntime = getBrowserEntryScript();
|
|
1962
|
+
const htmlHarness = await buildComponentHarness(
|
|
1963
|
+
filePath,
|
|
1964
|
+
componentName,
|
|
1965
|
+
props,
|
|
1966
|
+
1280,
|
|
1967
|
+
void 0,
|
|
1968
|
+
scopeRuntime
|
|
1969
|
+
);
|
|
1947
1970
|
await page.setContent(htmlHarness, { waitUntil: "load" });
|
|
1948
1971
|
await page.waitForFunction(
|
|
1949
1972
|
() => {
|
|
@@ -2282,12 +2305,14 @@ async function runInstrumentTree(options) {
|
|
|
2282
2305
|
viewport: { width: DEFAULT_VIEWPORT_WIDTH, height: DEFAULT_VIEWPORT_HEIGHT }
|
|
2283
2306
|
});
|
|
2284
2307
|
const page = await context.newPage();
|
|
2285
|
-
|
|
2308
|
+
const scopeRuntime = getBrowserEntryScript();
|
|
2286
2309
|
const htmlHarness = await buildComponentHarness(
|
|
2287
2310
|
filePath,
|
|
2288
2311
|
componentName,
|
|
2289
2312
|
{},
|
|
2290
|
-
DEFAULT_VIEWPORT_WIDTH
|
|
2313
|
+
DEFAULT_VIEWPORT_WIDTH,
|
|
2314
|
+
void 0,
|
|
2315
|
+
scopeRuntime
|
|
2291
2316
|
);
|
|
2292
2317
|
await page.setContent(htmlHarness, { waitUntil: "load" });
|
|
2293
2318
|
await page.waitForFunction(
|
|
@@ -2733,13 +2758,20 @@ Available: ${available}`
|
|
|
2733
2758
|
}
|
|
2734
2759
|
const rootDir = process.cwd();
|
|
2735
2760
|
const filePath = resolve(rootDir, descriptor.filePath);
|
|
2736
|
-
const
|
|
2761
|
+
const preScript = getBrowserEntryScript() + "\n" + buildInstrumentationScript();
|
|
2762
|
+
const htmlHarness = await buildComponentHarness(
|
|
2763
|
+
filePath,
|
|
2764
|
+
options.componentName,
|
|
2765
|
+
{},
|
|
2766
|
+
1280,
|
|
2767
|
+
void 0,
|
|
2768
|
+
preScript
|
|
2769
|
+
);
|
|
2737
2770
|
const pool = await getPool2();
|
|
2738
2771
|
const slot = await pool.acquire();
|
|
2739
2772
|
const { page } = slot;
|
|
2740
2773
|
const startMs = performance.now();
|
|
2741
2774
|
try {
|
|
2742
|
-
await page.addInitScript(buildInstrumentationScript());
|
|
2743
2775
|
await page.setContent(htmlHarness, { waitUntil: "load" });
|
|
2744
2776
|
await page.waitForFunction(
|
|
2745
2777
|
() => window.__SCOPE_RENDER_COMPLETE__ === true,
|
|
@@ -2909,6 +2941,122 @@ function writeReportToFile(report, outputPath, pretty) {
|
|
|
2909
2941
|
const json = pretty ? JSON.stringify(report, null, 2) : JSON.stringify(report);
|
|
2910
2942
|
writeFileSync(outputPath, json, "utf-8");
|
|
2911
2943
|
}
|
|
2944
|
+
var SCOPE_EXTENSIONS = [".scope.tsx", ".scope.ts", ".scope.jsx", ".scope.js"];
|
|
2945
|
+
function findScopeFile(componentFilePath) {
|
|
2946
|
+
const dir = dirname(componentFilePath);
|
|
2947
|
+
const stem = componentFilePath.replace(/\.(tsx?|jsx?)$/, "");
|
|
2948
|
+
const baseName = stem.slice(dir.length + 1);
|
|
2949
|
+
for (const ext of SCOPE_EXTENSIONS) {
|
|
2950
|
+
const candidate = join(dir, `${baseName}${ext}`);
|
|
2951
|
+
if (existsSync(candidate)) return candidate;
|
|
2952
|
+
}
|
|
2953
|
+
return null;
|
|
2954
|
+
}
|
|
2955
|
+
async function loadScopeFile(scopeFilePath) {
|
|
2956
|
+
const tmpDir = join(tmpdir(), `scope-file-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
2957
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
2958
|
+
const outFile = join(tmpDir, "scope-file.cjs");
|
|
2959
|
+
try {
|
|
2960
|
+
const result = await esbuild2.build({
|
|
2961
|
+
entryPoints: [scopeFilePath],
|
|
2962
|
+
bundle: true,
|
|
2963
|
+
format: "cjs",
|
|
2964
|
+
platform: "node",
|
|
2965
|
+
target: "node18",
|
|
2966
|
+
outfile: outFile,
|
|
2967
|
+
write: true,
|
|
2968
|
+
jsx: "automatic",
|
|
2969
|
+
jsxImportSource: "react",
|
|
2970
|
+
// Externalize React — we don't need to execute JSX, just extract plain data
|
|
2971
|
+
external: ["react", "react-dom", "react/jsx-runtime"],
|
|
2972
|
+
define: {
|
|
2973
|
+
"process.env.NODE_ENV": '"development"'
|
|
2974
|
+
},
|
|
2975
|
+
logLevel: "silent"
|
|
2976
|
+
});
|
|
2977
|
+
if (result.errors.length > 0) {
|
|
2978
|
+
const msg = result.errors.map((e) => `${e.text}${e.location ? ` (${e.location.file}:${e.location.line})` : ""}`).join("\n");
|
|
2979
|
+
throw new Error(`Failed to bundle scope file ${scopeFilePath}:
|
|
2980
|
+
${msg}`);
|
|
2981
|
+
}
|
|
2982
|
+
const req = createRequire(import.meta.url);
|
|
2983
|
+
delete req.cache[resolve(outFile)];
|
|
2984
|
+
const mod = req(outFile);
|
|
2985
|
+
const scenarios = extractScenarios(mod, scopeFilePath);
|
|
2986
|
+
const hasWrapper = typeof mod.wrapper === "function" || typeof mod.default?.wrapper === "function";
|
|
2987
|
+
return { filePath: scopeFilePath, scenarios, hasWrapper };
|
|
2988
|
+
} finally {
|
|
2989
|
+
try {
|
|
2990
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
2991
|
+
} catch {
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
async function loadScopeFileForComponent(componentFilePath) {
|
|
2996
|
+
const scopeFilePath = findScopeFile(componentFilePath);
|
|
2997
|
+
if (scopeFilePath === null) return null;
|
|
2998
|
+
return loadScopeFile(scopeFilePath);
|
|
2999
|
+
}
|
|
3000
|
+
function extractScenarios(mod, filePath) {
|
|
3001
|
+
const raw = mod.scenarios ?? mod.default?.scenarios;
|
|
3002
|
+
if (raw === void 0) return {};
|
|
3003
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
3004
|
+
console.warn(`[scope] ${filePath}: "scenarios" export is not a plain object \u2014 ignoring.`);
|
|
3005
|
+
return {};
|
|
3006
|
+
}
|
|
3007
|
+
const result = {};
|
|
3008
|
+
for (const [name, props] of Object.entries(raw)) {
|
|
3009
|
+
if (typeof props !== "object" || props === null || Array.isArray(props)) {
|
|
3010
|
+
console.warn(`[scope] ${filePath}: scenario "${name}" is not a plain object \u2014 skipping.`);
|
|
3011
|
+
continue;
|
|
3012
|
+
}
|
|
3013
|
+
result[name] = props;
|
|
3014
|
+
}
|
|
3015
|
+
return result;
|
|
3016
|
+
}
|
|
3017
|
+
async function buildWrapperScript(scopeFilePath) {
|
|
3018
|
+
const wrapperEntry = (
|
|
3019
|
+
/* ts */
|
|
3020
|
+
`
|
|
3021
|
+
import * as __scopeMod from ${JSON.stringify(scopeFilePath)};
|
|
3022
|
+
// Expose the wrapper on window so the harness can access it
|
|
3023
|
+
var wrapper =
|
|
3024
|
+
__scopeMod.wrapper ??
|
|
3025
|
+
(__scopeMod.default && __scopeMod.default.wrapper) ??
|
|
3026
|
+
null;
|
|
3027
|
+
window.__SCOPE_WRAPPER__ = wrapper;
|
|
3028
|
+
`
|
|
3029
|
+
);
|
|
3030
|
+
const result = await esbuild2.build({
|
|
3031
|
+
stdin: {
|
|
3032
|
+
contents: wrapperEntry,
|
|
3033
|
+
resolveDir: dirname(scopeFilePath),
|
|
3034
|
+
loader: "tsx",
|
|
3035
|
+
sourcefile: "__scope_wrapper_entry__.tsx"
|
|
3036
|
+
},
|
|
3037
|
+
bundle: true,
|
|
3038
|
+
format: "iife",
|
|
3039
|
+
platform: "browser",
|
|
3040
|
+
target: "es2020",
|
|
3041
|
+
write: false,
|
|
3042
|
+
jsx: "automatic",
|
|
3043
|
+
jsxImportSource: "react",
|
|
3044
|
+
external: [],
|
|
3045
|
+
define: {
|
|
3046
|
+
"process.env.NODE_ENV": '"development"',
|
|
3047
|
+
global: "globalThis"
|
|
3048
|
+
},
|
|
3049
|
+
logLevel: "silent"
|
|
3050
|
+
});
|
|
3051
|
+
if (result.errors.length > 0) {
|
|
3052
|
+
const msg = result.errors.map((e) => `${e.text}${e.location ? ` (${e.location.file}:${e.location.line})` : ""}`).join("\n");
|
|
3053
|
+
throw new Error(`Failed to build wrapper script from ${scopeFilePath}:
|
|
3054
|
+
${msg}`);
|
|
3055
|
+
}
|
|
3056
|
+
return result.outputFiles?.[0]?.text ?? "";
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
// src/render-commands.ts
|
|
2912
3060
|
var MANIFEST_PATH6 = ".reactscope/manifest.json";
|
|
2913
3061
|
var DEFAULT_OUTPUT_DIR = ".reactscope/renders";
|
|
2914
3062
|
var _pool3 = null;
|
|
@@ -2929,7 +3077,7 @@ async function shutdownPool3() {
|
|
|
2929
3077
|
_pool3 = null;
|
|
2930
3078
|
}
|
|
2931
3079
|
}
|
|
2932
|
-
function buildRenderer(filePath, componentName, viewportWidth, viewportHeight) {
|
|
3080
|
+
function buildRenderer(filePath, componentName, viewportWidth, viewportHeight, wrapperScript) {
|
|
2933
3081
|
const satori = new SatoriRenderer({
|
|
2934
3082
|
defaultViewport: { width: viewportWidth, height: viewportHeight }
|
|
2935
3083
|
});
|
|
@@ -2942,7 +3090,10 @@ function buildRenderer(filePath, componentName, viewportWidth, viewportHeight) {
|
|
|
2942
3090
|
filePath,
|
|
2943
3091
|
componentName,
|
|
2944
3092
|
props,
|
|
2945
|
-
viewportWidth
|
|
3093
|
+
viewportWidth,
|
|
3094
|
+
void 0,
|
|
3095
|
+
// projectCss (handled separately)
|
|
3096
|
+
wrapperScript
|
|
2946
3097
|
);
|
|
2947
3098
|
const slot = await pool.acquire();
|
|
2948
3099
|
const { page } = slot;
|
|
@@ -3033,8 +3184,37 @@ function buildRenderer(filePath, componentName, viewportWidth, viewportHeight) {
|
|
|
3033
3184
|
}
|
|
3034
3185
|
};
|
|
3035
3186
|
}
|
|
3187
|
+
function buildScenarioMap(opts, scopeData) {
|
|
3188
|
+
if (opts.scenario !== void 0) {
|
|
3189
|
+
if (scopeData === null) {
|
|
3190
|
+
throw new Error(`--scenario "${opts.scenario}" requires a .scope file next to the component`);
|
|
3191
|
+
}
|
|
3192
|
+
const props = scopeData.scenarios[opts.scenario];
|
|
3193
|
+
if (props === void 0) {
|
|
3194
|
+
const available = Object.keys(scopeData.scenarios).join(", ") || "(none)";
|
|
3195
|
+
throw new Error(
|
|
3196
|
+
`Scenario "${opts.scenario}" not found in scope file.
|
|
3197
|
+
Available: ${available}`
|
|
3198
|
+
);
|
|
3199
|
+
}
|
|
3200
|
+
return { [opts.scenario]: props };
|
|
3201
|
+
}
|
|
3202
|
+
if (opts.props !== void 0) {
|
|
3203
|
+
let parsed;
|
|
3204
|
+
try {
|
|
3205
|
+
parsed = JSON.parse(opts.props);
|
|
3206
|
+
} catch {
|
|
3207
|
+
throw new Error(`Invalid props JSON: ${opts.props}`);
|
|
3208
|
+
}
|
|
3209
|
+
return { __default__: parsed };
|
|
3210
|
+
}
|
|
3211
|
+
if (scopeData !== null && Object.keys(scopeData.scenarios).length > 0) {
|
|
3212
|
+
return scopeData.scenarios;
|
|
3213
|
+
}
|
|
3214
|
+
return { __default__: {} };
|
|
3215
|
+
}
|
|
3036
3216
|
function registerRenderSingle(renderCmd) {
|
|
3037
|
-
renderCmd.command("component <component>", { isDefault: true }).description("Render a single component to PNG or JSON").option("--props <json>", `Inline props JSON, e.g. '{"variant":"primary"}'`).option("--viewport <WxH>", "Viewport size e.g. 1280x720", "375x812").option("--theme <name>", "Theme name from the token system").option("-o, --output <path>", "Write PNG to file instead of stdout").option("--format <fmt>", "Output format: png or json (default: auto)").option("--manifest <path>", "Path to manifest.json", MANIFEST_PATH6).action(
|
|
3217
|
+
renderCmd.command("component <component>", { isDefault: true }).description("Render a single component to PNG or JSON").option("--props <json>", `Inline props JSON, e.g. '{"variant":"primary"}'`).option("--scenario <name>", "Run a named scenario from the component's .scope file").option("--viewport <WxH>", "Viewport size e.g. 1280x720", "375x812").option("--theme <name>", "Theme name from the token system").option("-o, --output <path>", "Write PNG to file instead of stdout").option("--format <fmt>", "Output format: png or json (default: auto)").option("--manifest <path>", "Path to manifest.json", MANIFEST_PATH6).action(
|
|
3038
3218
|
async (componentName, opts) => {
|
|
3039
3219
|
try {
|
|
3040
3220
|
const manifest = loadManifest(opts.manifest);
|
|
@@ -3046,80 +3226,71 @@ function registerRenderSingle(renderCmd) {
|
|
|
3046
3226
|
Available: ${available}`
|
|
3047
3227
|
);
|
|
3048
3228
|
}
|
|
3049
|
-
let props = {};
|
|
3050
|
-
if (opts.props !== void 0) {
|
|
3051
|
-
try {
|
|
3052
|
-
props = JSON.parse(opts.props);
|
|
3053
|
-
} catch {
|
|
3054
|
-
throw new Error(`Invalid props JSON: ${opts.props}`);
|
|
3055
|
-
}
|
|
3056
|
-
}
|
|
3057
3229
|
const { width, height } = parseViewport(opts.viewport);
|
|
3058
3230
|
const rootDir = process.cwd();
|
|
3059
3231
|
const filePath = resolve(rootDir, descriptor.filePath);
|
|
3060
|
-
const
|
|
3232
|
+
const scopeData = await loadScopeFileForComponent(filePath);
|
|
3233
|
+
const wrapperScript = scopeData?.hasWrapper === true ? await buildWrapperScript(scopeData.filePath) : void 0;
|
|
3234
|
+
const scenarios = buildScenarioMap(opts, scopeData);
|
|
3235
|
+
const renderer = buildRenderer(filePath, componentName, width, height, wrapperScript);
|
|
3061
3236
|
process.stderr.write(
|
|
3062
3237
|
`Rendering ${componentName} [${descriptor.complexityClass}] at ${width}\xD7${height}\u2026
|
|
3063
3238
|
`
|
|
3064
3239
|
);
|
|
3065
|
-
const
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3240
|
+
const fmt2 = resolveSingleFormat(opts.format);
|
|
3241
|
+
let anyFailed = false;
|
|
3242
|
+
for (const [scenarioName, props] of Object.entries(scenarios)) {
|
|
3243
|
+
const isNamed = scenarioName !== "__default__";
|
|
3244
|
+
const label = isNamed ? `${componentName}:${scenarioName}` : componentName;
|
|
3245
|
+
const outcome = await safeRender(
|
|
3246
|
+
() => renderer.renderCell(props, descriptor.complexityClass),
|
|
3247
|
+
{
|
|
3248
|
+
props,
|
|
3249
|
+
sourceLocation: {
|
|
3250
|
+
file: descriptor.filePath,
|
|
3251
|
+
line: descriptor.loc.start,
|
|
3252
|
+
column: 0
|
|
3253
|
+
}
|
|
3073
3254
|
}
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
if (outcome.crashed) {
|
|
3078
|
-
process.stderr.write(`\u2717 Render failed: ${outcome.error.message}
|
|
3255
|
+
);
|
|
3256
|
+
if (outcome.crashed) {
|
|
3257
|
+
process.stderr.write(`\u2717 ${label} render failed: ${outcome.error.message}
|
|
3079
3258
|
`);
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3259
|
+
const hintList = outcome.error.heuristicFlags.join(", ");
|
|
3260
|
+
if (hintList.length > 0) {
|
|
3261
|
+
process.stderr.write(` Hints: ${hintList}
|
|
3083
3262
|
`);
|
|
3263
|
+
}
|
|
3264
|
+
anyFailed = true;
|
|
3265
|
+
continue;
|
|
3084
3266
|
}
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
`\u2713 ${componentName} \u2192 ${opts.output} (${result.width}\xD7${result.height}, ${result.renderTimeMs.toFixed(0)}ms)
|
|
3267
|
+
const result = outcome.result;
|
|
3268
|
+
const outFileName = isNamed ? `${componentName}-${scenarioName}.png` : `${componentName}.png`;
|
|
3269
|
+
if (opts.output !== void 0 && !isNamed) {
|
|
3270
|
+
const outPath = resolve(process.cwd(), opts.output);
|
|
3271
|
+
writeFileSync(outPath, result.screenshot);
|
|
3272
|
+
process.stdout.write(
|
|
3273
|
+
`\u2713 ${label} \u2192 ${opts.output} (${result.width}\xD7${result.height}, ${result.renderTimeMs.toFixed(0)}ms)
|
|
3093
3274
|
`
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
if (fmt2 === "json") {
|
|
3099
|
-
const json = formatRenderJson(componentName, props, result);
|
|
3100
|
-
process.stdout.write(`${JSON.stringify(json, null, 2)}
|
|
3275
|
+
);
|
|
3276
|
+
} else if (fmt2 === "json") {
|
|
3277
|
+
const json = formatRenderJson(label, props, result);
|
|
3278
|
+
process.stdout.write(`${JSON.stringify(json, null, 2)}
|
|
3101
3279
|
`);
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
`
|
|
3111
|
-
);
|
|
3112
|
-
} else {
|
|
3113
|
-
const dir = resolve(process.cwd(), DEFAULT_OUTPUT_DIR);
|
|
3114
|
-
mkdirSync(dir, { recursive: true });
|
|
3115
|
-
const outPath = resolve(dir, `${componentName}.png`);
|
|
3116
|
-
writeFileSync(outPath, result.screenshot);
|
|
3117
|
-
const relPath = `${DEFAULT_OUTPUT_DIR}/${componentName}.png`;
|
|
3118
|
-
process.stdout.write(
|
|
3119
|
-
`\u2713 ${componentName} \u2192 ${relPath} (${result.width}\xD7${result.height}, ${result.renderTimeMs.toFixed(0)}ms)
|
|
3280
|
+
} else {
|
|
3281
|
+
const dir = resolve(process.cwd(), DEFAULT_OUTPUT_DIR);
|
|
3282
|
+
mkdirSync(dir, { recursive: true });
|
|
3283
|
+
const outPath = resolve(dir, outFileName);
|
|
3284
|
+
writeFileSync(outPath, result.screenshot);
|
|
3285
|
+
const relPath = `${DEFAULT_OUTPUT_DIR}/${outFileName}`;
|
|
3286
|
+
process.stdout.write(
|
|
3287
|
+
`\u2713 ${label} \u2192 ${relPath} (${result.width}\xD7${result.height}, ${result.renderTimeMs.toFixed(0)}ms)
|
|
3120
3288
|
`
|
|
3121
|
-
|
|
3289
|
+
);
|
|
3290
|
+
}
|
|
3122
3291
|
}
|
|
3292
|
+
await shutdownPool3();
|
|
3293
|
+
if (anyFailed) process.exit(1);
|
|
3123
3294
|
} catch (err) {
|
|
3124
3295
|
await shutdownPool3();
|
|
3125
3296
|
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|