@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.cjs
CHANGED
|
@@ -6,14 +6,16 @@ var manifest = require('@agent-scope/manifest');
|
|
|
6
6
|
var render = require('@agent-scope/render');
|
|
7
7
|
var tokens = require('@agent-scope/tokens');
|
|
8
8
|
var commander = require('commander');
|
|
9
|
-
var
|
|
9
|
+
var esbuild2 = require('esbuild');
|
|
10
10
|
var module$1 = require('module');
|
|
11
11
|
var readline = require('readline');
|
|
12
12
|
var playwright = require('@agent-scope/playwright');
|
|
13
13
|
var playwright$1 = require('playwright');
|
|
14
|
+
var os = require('os');
|
|
14
15
|
var http = require('http');
|
|
15
16
|
var site = require('@agent-scope/site');
|
|
16
17
|
|
|
18
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
17
19
|
function _interopNamespace(e) {
|
|
18
20
|
if (e && e.__esModule) return e;
|
|
19
21
|
var n = Object.create(null);
|
|
@@ -32,13 +34,13 @@ function _interopNamespace(e) {
|
|
|
32
34
|
return Object.freeze(n);
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
var
|
|
37
|
+
var esbuild2__namespace = /*#__PURE__*/_interopNamespace(esbuild2);
|
|
36
38
|
var readline__namespace = /*#__PURE__*/_interopNamespace(readline);
|
|
37
39
|
|
|
38
40
|
// src/ci/commands.ts
|
|
39
|
-
async function buildComponentHarness(filePath, componentName, props, viewportWidth, projectCss) {
|
|
41
|
+
async function buildComponentHarness(filePath, componentName, props, viewportWidth, projectCss, wrapperScript) {
|
|
40
42
|
const bundledScript = await bundleComponentToIIFE(filePath, componentName, props);
|
|
41
|
-
return wrapInHtml(bundledScript, viewportWidth, projectCss);
|
|
43
|
+
return wrapInHtml(bundledScript, viewportWidth, projectCss, wrapperScript);
|
|
42
44
|
}
|
|
43
45
|
async function bundleComponentToIIFE(filePath, componentName, props) {
|
|
44
46
|
const propsJson = JSON.stringify(props).replace(/<\/script>/gi, "<\\/script>");
|
|
@@ -74,7 +76,12 @@ import { createElement } from "react";
|
|
|
74
76
|
window.__SCOPE_RENDER_COMPLETE__ = true;
|
|
75
77
|
return;
|
|
76
78
|
}
|
|
77
|
-
|
|
79
|
+
// If a scope file wrapper was injected, use it to wrap the component
|
|
80
|
+
var Wrapper = (window).__SCOPE_WRAPPER__;
|
|
81
|
+
var element = Wrapper
|
|
82
|
+
? createElement(Wrapper, null, createElement(Component, props))
|
|
83
|
+
: createElement(Component, props);
|
|
84
|
+
createRoot(rootEl).render(element);
|
|
78
85
|
// Use requestAnimationFrame to let React flush the render
|
|
79
86
|
requestAnimationFrame(function() {
|
|
80
87
|
window.__SCOPE_RENDER_COMPLETE__ = true;
|
|
@@ -86,7 +93,7 @@ import { createElement } from "react";
|
|
|
86
93
|
})();
|
|
87
94
|
`
|
|
88
95
|
);
|
|
89
|
-
const result = await
|
|
96
|
+
const result = await esbuild2__namespace.build({
|
|
90
97
|
stdin: {
|
|
91
98
|
contents: wrapperCode,
|
|
92
99
|
// Resolve relative imports (within the component's dir)
|
|
@@ -124,10 +131,11 @@ ${msg}`);
|
|
|
124
131
|
}
|
|
125
132
|
return outputFile.text;
|
|
126
133
|
}
|
|
127
|
-
function wrapInHtml(bundledScript, viewportWidth, projectCss) {
|
|
134
|
+
function wrapInHtml(bundledScript, viewportWidth, projectCss, wrapperScript) {
|
|
128
135
|
const projectStyleBlock = projectCss != null && projectCss.length > 0 ? `<style id="scope-project-css">
|
|
129
136
|
${projectCss.replace(/<\/style>/gi, "<\\/style>")}
|
|
130
137
|
</style>` : "";
|
|
138
|
+
const wrapperScriptBlock = wrapperScript != null && wrapperScript.length > 0 ? `<script id="scope-wrapper-script">${wrapperScript}</script>` : "";
|
|
131
139
|
return `<!DOCTYPE html>
|
|
132
140
|
<html lang="en">
|
|
133
141
|
<head>
|
|
@@ -142,6 +150,7 @@ ${projectCss.replace(/<\/style>/gi, "<\\/style>")}
|
|
|
142
150
|
</head>
|
|
143
151
|
<body>
|
|
144
152
|
<div id="scope-root" data-reactscope-root></div>
|
|
153
|
+
${wrapperScriptBlock}
|
|
145
154
|
<script>${bundledScript}</script>
|
|
146
155
|
</body>
|
|
147
156
|
</html>`;
|
|
@@ -505,16 +514,16 @@ async function getTailwindCompiler(cwd) {
|
|
|
505
514
|
from: entryPath,
|
|
506
515
|
loadStylesheet
|
|
507
516
|
});
|
|
508
|
-
const
|
|
509
|
-
compilerCache = { cwd, build:
|
|
510
|
-
return
|
|
517
|
+
const build3 = result.build.bind(result);
|
|
518
|
+
compilerCache = { cwd, build: build3 };
|
|
519
|
+
return build3;
|
|
511
520
|
}
|
|
512
521
|
async function getCompiledCssForClasses(cwd, classes) {
|
|
513
|
-
const
|
|
514
|
-
if (
|
|
522
|
+
const build3 = await getTailwindCompiler(cwd);
|
|
523
|
+
if (build3 === null) return null;
|
|
515
524
|
const deduped = [...new Set(classes)].filter(Boolean);
|
|
516
525
|
if (deduped.length === 0) return null;
|
|
517
|
-
return
|
|
526
|
+
return build3(deduped);
|
|
518
527
|
}
|
|
519
528
|
|
|
520
529
|
// src/ci/commands.ts
|
|
@@ -1132,9 +1141,9 @@ function createRL() {
|
|
|
1132
1141
|
});
|
|
1133
1142
|
}
|
|
1134
1143
|
async function ask(rl, question) {
|
|
1135
|
-
return new Promise((
|
|
1144
|
+
return new Promise((resolve18) => {
|
|
1136
1145
|
rl.question(question, (answer) => {
|
|
1137
|
-
|
|
1146
|
+
resolve18(answer.trim());
|
|
1138
1147
|
});
|
|
1139
1148
|
});
|
|
1140
1149
|
}
|
|
@@ -1706,8 +1715,15 @@ async function runHooksProfiling(componentName, filePath, props) {
|
|
|
1706
1715
|
try {
|
|
1707
1716
|
const context = await browser.newContext();
|
|
1708
1717
|
const page = await context.newPage();
|
|
1709
|
-
|
|
1710
|
-
const htmlHarness = await buildComponentHarness(
|
|
1718
|
+
const scopeRuntime = playwright.getBrowserEntryScript();
|
|
1719
|
+
const htmlHarness = await buildComponentHarness(
|
|
1720
|
+
filePath,
|
|
1721
|
+
componentName,
|
|
1722
|
+
props,
|
|
1723
|
+
1280,
|
|
1724
|
+
void 0,
|
|
1725
|
+
scopeRuntime
|
|
1726
|
+
);
|
|
1711
1727
|
await page.setContent(htmlHarness, { waitUntil: "load" });
|
|
1712
1728
|
await page.waitForFunction(
|
|
1713
1729
|
() => {
|
|
@@ -1966,7 +1982,15 @@ async function runInteractionProfile(componentName, filePath, props, interaction
|
|
|
1966
1982
|
try {
|
|
1967
1983
|
const context = await browser.newContext();
|
|
1968
1984
|
const page = await context.newPage();
|
|
1969
|
-
const
|
|
1985
|
+
const scopeRuntime = playwright.getBrowserEntryScript();
|
|
1986
|
+
const htmlHarness = await buildComponentHarness(
|
|
1987
|
+
filePath,
|
|
1988
|
+
componentName,
|
|
1989
|
+
props,
|
|
1990
|
+
1280,
|
|
1991
|
+
void 0,
|
|
1992
|
+
scopeRuntime
|
|
1993
|
+
);
|
|
1970
1994
|
await page.setContent(htmlHarness, { waitUntil: "load" });
|
|
1971
1995
|
await page.waitForFunction(
|
|
1972
1996
|
() => {
|
|
@@ -2305,12 +2329,14 @@ async function runInstrumentTree(options) {
|
|
|
2305
2329
|
viewport: { width: DEFAULT_VIEWPORT_WIDTH, height: DEFAULT_VIEWPORT_HEIGHT }
|
|
2306
2330
|
});
|
|
2307
2331
|
const page = await context.newPage();
|
|
2308
|
-
|
|
2332
|
+
const scopeRuntime = playwright.getBrowserEntryScript();
|
|
2309
2333
|
const htmlHarness = await buildComponentHarness(
|
|
2310
2334
|
filePath,
|
|
2311
2335
|
componentName,
|
|
2312
2336
|
{},
|
|
2313
|
-
DEFAULT_VIEWPORT_WIDTH
|
|
2337
|
+
DEFAULT_VIEWPORT_WIDTH,
|
|
2338
|
+
void 0,
|
|
2339
|
+
scopeRuntime
|
|
2314
2340
|
);
|
|
2315
2341
|
await page.setContent(htmlHarness, { waitUntil: "load" });
|
|
2316
2342
|
await page.waitForFunction(
|
|
@@ -2756,13 +2782,20 @@ Available: ${available}`
|
|
|
2756
2782
|
}
|
|
2757
2783
|
const rootDir = process.cwd();
|
|
2758
2784
|
const filePath = path.resolve(rootDir, descriptor.filePath);
|
|
2759
|
-
const
|
|
2785
|
+
const preScript = playwright.getBrowserEntryScript() + "\n" + buildInstrumentationScript();
|
|
2786
|
+
const htmlHarness = await buildComponentHarness(
|
|
2787
|
+
filePath,
|
|
2788
|
+
options.componentName,
|
|
2789
|
+
{},
|
|
2790
|
+
1280,
|
|
2791
|
+
void 0,
|
|
2792
|
+
preScript
|
|
2793
|
+
);
|
|
2760
2794
|
const pool = await getPool2();
|
|
2761
2795
|
const slot = await pool.acquire();
|
|
2762
2796
|
const { page } = slot;
|
|
2763
2797
|
const startMs = performance.now();
|
|
2764
2798
|
try {
|
|
2765
|
-
await page.addInitScript(buildInstrumentationScript());
|
|
2766
2799
|
await page.setContent(htmlHarness, { waitUntil: "load" });
|
|
2767
2800
|
await page.waitForFunction(
|
|
2768
2801
|
() => window.__SCOPE_RENDER_COMPLETE__ === true,
|
|
@@ -2932,6 +2965,122 @@ function writeReportToFile(report, outputPath, pretty) {
|
|
|
2932
2965
|
const json = pretty ? JSON.stringify(report, null, 2) : JSON.stringify(report);
|
|
2933
2966
|
fs.writeFileSync(outputPath, json, "utf-8");
|
|
2934
2967
|
}
|
|
2968
|
+
var SCOPE_EXTENSIONS = [".scope.tsx", ".scope.ts", ".scope.jsx", ".scope.js"];
|
|
2969
|
+
function findScopeFile(componentFilePath) {
|
|
2970
|
+
const dir = path.dirname(componentFilePath);
|
|
2971
|
+
const stem = componentFilePath.replace(/\.(tsx?|jsx?)$/, "");
|
|
2972
|
+
const baseName = stem.slice(dir.length + 1);
|
|
2973
|
+
for (const ext of SCOPE_EXTENSIONS) {
|
|
2974
|
+
const candidate = path.join(dir, `${baseName}${ext}`);
|
|
2975
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
2976
|
+
}
|
|
2977
|
+
return null;
|
|
2978
|
+
}
|
|
2979
|
+
async function loadScopeFile(scopeFilePath) {
|
|
2980
|
+
const tmpDir = path.join(os.tmpdir(), `scope-file-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
2981
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
2982
|
+
const outFile = path.join(tmpDir, "scope-file.cjs");
|
|
2983
|
+
try {
|
|
2984
|
+
const result = await esbuild2__namespace.build({
|
|
2985
|
+
entryPoints: [scopeFilePath],
|
|
2986
|
+
bundle: true,
|
|
2987
|
+
format: "cjs",
|
|
2988
|
+
platform: "node",
|
|
2989
|
+
target: "node18",
|
|
2990
|
+
outfile: outFile,
|
|
2991
|
+
write: true,
|
|
2992
|
+
jsx: "automatic",
|
|
2993
|
+
jsxImportSource: "react",
|
|
2994
|
+
// Externalize React — we don't need to execute JSX, just extract plain data
|
|
2995
|
+
external: ["react", "react-dom", "react/jsx-runtime"],
|
|
2996
|
+
define: {
|
|
2997
|
+
"process.env.NODE_ENV": '"development"'
|
|
2998
|
+
},
|
|
2999
|
+
logLevel: "silent"
|
|
3000
|
+
});
|
|
3001
|
+
if (result.errors.length > 0) {
|
|
3002
|
+
const msg = result.errors.map((e) => `${e.text}${e.location ? ` (${e.location.file}:${e.location.line})` : ""}`).join("\n");
|
|
3003
|
+
throw new Error(`Failed to bundle scope file ${scopeFilePath}:
|
|
3004
|
+
${msg}`);
|
|
3005
|
+
}
|
|
3006
|
+
const req = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
3007
|
+
delete req.cache[path.resolve(outFile)];
|
|
3008
|
+
const mod = req(outFile);
|
|
3009
|
+
const scenarios = extractScenarios(mod, scopeFilePath);
|
|
3010
|
+
const hasWrapper = typeof mod.wrapper === "function" || typeof mod.default?.wrapper === "function";
|
|
3011
|
+
return { filePath: scopeFilePath, scenarios, hasWrapper };
|
|
3012
|
+
} finally {
|
|
3013
|
+
try {
|
|
3014
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
3015
|
+
} catch {
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
async function loadScopeFileForComponent(componentFilePath) {
|
|
3020
|
+
const scopeFilePath = findScopeFile(componentFilePath);
|
|
3021
|
+
if (scopeFilePath === null) return null;
|
|
3022
|
+
return loadScopeFile(scopeFilePath);
|
|
3023
|
+
}
|
|
3024
|
+
function extractScenarios(mod, filePath) {
|
|
3025
|
+
const raw = mod.scenarios ?? mod.default?.scenarios;
|
|
3026
|
+
if (raw === void 0) return {};
|
|
3027
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
3028
|
+
console.warn(`[scope] ${filePath}: "scenarios" export is not a plain object \u2014 ignoring.`);
|
|
3029
|
+
return {};
|
|
3030
|
+
}
|
|
3031
|
+
const result = {};
|
|
3032
|
+
for (const [name, props] of Object.entries(raw)) {
|
|
3033
|
+
if (typeof props !== "object" || props === null || Array.isArray(props)) {
|
|
3034
|
+
console.warn(`[scope] ${filePath}: scenario "${name}" is not a plain object \u2014 skipping.`);
|
|
3035
|
+
continue;
|
|
3036
|
+
}
|
|
3037
|
+
result[name] = props;
|
|
3038
|
+
}
|
|
3039
|
+
return result;
|
|
3040
|
+
}
|
|
3041
|
+
async function buildWrapperScript(scopeFilePath) {
|
|
3042
|
+
const wrapperEntry = (
|
|
3043
|
+
/* ts */
|
|
3044
|
+
`
|
|
3045
|
+
import * as __scopeMod from ${JSON.stringify(scopeFilePath)};
|
|
3046
|
+
// Expose the wrapper on window so the harness can access it
|
|
3047
|
+
var wrapper =
|
|
3048
|
+
__scopeMod.wrapper ??
|
|
3049
|
+
(__scopeMod.default && __scopeMod.default.wrapper) ??
|
|
3050
|
+
null;
|
|
3051
|
+
window.__SCOPE_WRAPPER__ = wrapper;
|
|
3052
|
+
`
|
|
3053
|
+
);
|
|
3054
|
+
const result = await esbuild2__namespace.build({
|
|
3055
|
+
stdin: {
|
|
3056
|
+
contents: wrapperEntry,
|
|
3057
|
+
resolveDir: path.dirname(scopeFilePath),
|
|
3058
|
+
loader: "tsx",
|
|
3059
|
+
sourcefile: "__scope_wrapper_entry__.tsx"
|
|
3060
|
+
},
|
|
3061
|
+
bundle: true,
|
|
3062
|
+
format: "iife",
|
|
3063
|
+
platform: "browser",
|
|
3064
|
+
target: "es2020",
|
|
3065
|
+
write: false,
|
|
3066
|
+
jsx: "automatic",
|
|
3067
|
+
jsxImportSource: "react",
|
|
3068
|
+
external: [],
|
|
3069
|
+
define: {
|
|
3070
|
+
"process.env.NODE_ENV": '"development"',
|
|
3071
|
+
global: "globalThis"
|
|
3072
|
+
},
|
|
3073
|
+
logLevel: "silent"
|
|
3074
|
+
});
|
|
3075
|
+
if (result.errors.length > 0) {
|
|
3076
|
+
const msg = result.errors.map((e) => `${e.text}${e.location ? ` (${e.location.file}:${e.location.line})` : ""}`).join("\n");
|
|
3077
|
+
throw new Error(`Failed to build wrapper script from ${scopeFilePath}:
|
|
3078
|
+
${msg}`);
|
|
3079
|
+
}
|
|
3080
|
+
return result.outputFiles?.[0]?.text ?? "";
|
|
3081
|
+
}
|
|
3082
|
+
|
|
3083
|
+
// src/render-commands.ts
|
|
2935
3084
|
var MANIFEST_PATH6 = ".reactscope/manifest.json";
|
|
2936
3085
|
var DEFAULT_OUTPUT_DIR = ".reactscope/renders";
|
|
2937
3086
|
var _pool3 = null;
|
|
@@ -2952,7 +3101,7 @@ async function shutdownPool3() {
|
|
|
2952
3101
|
_pool3 = null;
|
|
2953
3102
|
}
|
|
2954
3103
|
}
|
|
2955
|
-
function buildRenderer(filePath, componentName, viewportWidth, viewportHeight) {
|
|
3104
|
+
function buildRenderer(filePath, componentName, viewportWidth, viewportHeight, wrapperScript) {
|
|
2956
3105
|
const satori = new render.SatoriRenderer({
|
|
2957
3106
|
defaultViewport: { width: viewportWidth, height: viewportHeight }
|
|
2958
3107
|
});
|
|
@@ -2965,7 +3114,10 @@ function buildRenderer(filePath, componentName, viewportWidth, viewportHeight) {
|
|
|
2965
3114
|
filePath,
|
|
2966
3115
|
componentName,
|
|
2967
3116
|
props,
|
|
2968
|
-
viewportWidth
|
|
3117
|
+
viewportWidth,
|
|
3118
|
+
void 0,
|
|
3119
|
+
// projectCss (handled separately)
|
|
3120
|
+
wrapperScript
|
|
2969
3121
|
);
|
|
2970
3122
|
const slot = await pool.acquire();
|
|
2971
3123
|
const { page } = slot;
|
|
@@ -3056,8 +3208,37 @@ function buildRenderer(filePath, componentName, viewportWidth, viewportHeight) {
|
|
|
3056
3208
|
}
|
|
3057
3209
|
};
|
|
3058
3210
|
}
|
|
3211
|
+
function buildScenarioMap(opts, scopeData) {
|
|
3212
|
+
if (opts.scenario !== void 0) {
|
|
3213
|
+
if (scopeData === null) {
|
|
3214
|
+
throw new Error(`--scenario "${opts.scenario}" requires a .scope file next to the component`);
|
|
3215
|
+
}
|
|
3216
|
+
const props = scopeData.scenarios[opts.scenario];
|
|
3217
|
+
if (props === void 0) {
|
|
3218
|
+
const available = Object.keys(scopeData.scenarios).join(", ") || "(none)";
|
|
3219
|
+
throw new Error(
|
|
3220
|
+
`Scenario "${opts.scenario}" not found in scope file.
|
|
3221
|
+
Available: ${available}`
|
|
3222
|
+
);
|
|
3223
|
+
}
|
|
3224
|
+
return { [opts.scenario]: props };
|
|
3225
|
+
}
|
|
3226
|
+
if (opts.props !== void 0) {
|
|
3227
|
+
let parsed;
|
|
3228
|
+
try {
|
|
3229
|
+
parsed = JSON.parse(opts.props);
|
|
3230
|
+
} catch {
|
|
3231
|
+
throw new Error(`Invalid props JSON: ${opts.props}`);
|
|
3232
|
+
}
|
|
3233
|
+
return { __default__: parsed };
|
|
3234
|
+
}
|
|
3235
|
+
if (scopeData !== null && Object.keys(scopeData.scenarios).length > 0) {
|
|
3236
|
+
return scopeData.scenarios;
|
|
3237
|
+
}
|
|
3238
|
+
return { __default__: {} };
|
|
3239
|
+
}
|
|
3059
3240
|
function registerRenderSingle(renderCmd) {
|
|
3060
|
-
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(
|
|
3241
|
+
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(
|
|
3061
3242
|
async (componentName, opts) => {
|
|
3062
3243
|
try {
|
|
3063
3244
|
const manifest = loadManifest(opts.manifest);
|
|
@@ -3069,80 +3250,71 @@ function registerRenderSingle(renderCmd) {
|
|
|
3069
3250
|
Available: ${available}`
|
|
3070
3251
|
);
|
|
3071
3252
|
}
|
|
3072
|
-
let props = {};
|
|
3073
|
-
if (opts.props !== void 0) {
|
|
3074
|
-
try {
|
|
3075
|
-
props = JSON.parse(opts.props);
|
|
3076
|
-
} catch {
|
|
3077
|
-
throw new Error(`Invalid props JSON: ${opts.props}`);
|
|
3078
|
-
}
|
|
3079
|
-
}
|
|
3080
3253
|
const { width, height } = parseViewport(opts.viewport);
|
|
3081
3254
|
const rootDir = process.cwd();
|
|
3082
3255
|
const filePath = path.resolve(rootDir, descriptor.filePath);
|
|
3083
|
-
const
|
|
3256
|
+
const scopeData = await loadScopeFileForComponent(filePath);
|
|
3257
|
+
const wrapperScript = scopeData?.hasWrapper === true ? await buildWrapperScript(scopeData.filePath) : void 0;
|
|
3258
|
+
const scenarios = buildScenarioMap(opts, scopeData);
|
|
3259
|
+
const renderer = buildRenderer(filePath, componentName, width, height, wrapperScript);
|
|
3084
3260
|
process.stderr.write(
|
|
3085
3261
|
`Rendering ${componentName} [${descriptor.complexityClass}] at ${width}\xD7${height}\u2026
|
|
3086
3262
|
`
|
|
3087
3263
|
);
|
|
3088
|
-
const
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3264
|
+
const fmt2 = resolveSingleFormat(opts.format);
|
|
3265
|
+
let anyFailed = false;
|
|
3266
|
+
for (const [scenarioName, props] of Object.entries(scenarios)) {
|
|
3267
|
+
const isNamed = scenarioName !== "__default__";
|
|
3268
|
+
const label = isNamed ? `${componentName}:${scenarioName}` : componentName;
|
|
3269
|
+
const outcome = await render.safeRender(
|
|
3270
|
+
() => renderer.renderCell(props, descriptor.complexityClass),
|
|
3271
|
+
{
|
|
3272
|
+
props,
|
|
3273
|
+
sourceLocation: {
|
|
3274
|
+
file: descriptor.filePath,
|
|
3275
|
+
line: descriptor.loc.start,
|
|
3276
|
+
column: 0
|
|
3277
|
+
}
|
|
3096
3278
|
}
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
if (outcome.crashed) {
|
|
3101
|
-
process.stderr.write(`\u2717 Render failed: ${outcome.error.message}
|
|
3279
|
+
);
|
|
3280
|
+
if (outcome.crashed) {
|
|
3281
|
+
process.stderr.write(`\u2717 ${label} render failed: ${outcome.error.message}
|
|
3102
3282
|
`);
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3283
|
+
const hintList = outcome.error.heuristicFlags.join(", ");
|
|
3284
|
+
if (hintList.length > 0) {
|
|
3285
|
+
process.stderr.write(` Hints: ${hintList}
|
|
3106
3286
|
`);
|
|
3287
|
+
}
|
|
3288
|
+
anyFailed = true;
|
|
3289
|
+
continue;
|
|
3107
3290
|
}
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
`\u2713 ${componentName} \u2192 ${opts.output} (${result.width}\xD7${result.height}, ${result.renderTimeMs.toFixed(0)}ms)
|
|
3291
|
+
const result = outcome.result;
|
|
3292
|
+
const outFileName = isNamed ? `${componentName}-${scenarioName}.png` : `${componentName}.png`;
|
|
3293
|
+
if (opts.output !== void 0 && !isNamed) {
|
|
3294
|
+
const outPath = path.resolve(process.cwd(), opts.output);
|
|
3295
|
+
fs.writeFileSync(outPath, result.screenshot);
|
|
3296
|
+
process.stdout.write(
|
|
3297
|
+
`\u2713 ${label} \u2192 ${opts.output} (${result.width}\xD7${result.height}, ${result.renderTimeMs.toFixed(0)}ms)
|
|
3116
3298
|
`
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
if (fmt2 === "json") {
|
|
3122
|
-
const json = formatRenderJson(componentName, props, result);
|
|
3123
|
-
process.stdout.write(`${JSON.stringify(json, null, 2)}
|
|
3299
|
+
);
|
|
3300
|
+
} else if (fmt2 === "json") {
|
|
3301
|
+
const json = formatRenderJson(label, props, result);
|
|
3302
|
+
process.stdout.write(`${JSON.stringify(json, null, 2)}
|
|
3124
3303
|
`);
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
`
|
|
3134
|
-
);
|
|
3135
|
-
} else {
|
|
3136
|
-
const dir = path.resolve(process.cwd(), DEFAULT_OUTPUT_DIR);
|
|
3137
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
3138
|
-
const outPath = path.resolve(dir, `${componentName}.png`);
|
|
3139
|
-
fs.writeFileSync(outPath, result.screenshot);
|
|
3140
|
-
const relPath = `${DEFAULT_OUTPUT_DIR}/${componentName}.png`;
|
|
3141
|
-
process.stdout.write(
|
|
3142
|
-
`\u2713 ${componentName} \u2192 ${relPath} (${result.width}\xD7${result.height}, ${result.renderTimeMs.toFixed(0)}ms)
|
|
3304
|
+
} else {
|
|
3305
|
+
const dir = path.resolve(process.cwd(), DEFAULT_OUTPUT_DIR);
|
|
3306
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
3307
|
+
const outPath = path.resolve(dir, outFileName);
|
|
3308
|
+
fs.writeFileSync(outPath, result.screenshot);
|
|
3309
|
+
const relPath = `${DEFAULT_OUTPUT_DIR}/${outFileName}`;
|
|
3310
|
+
process.stdout.write(
|
|
3311
|
+
`\u2713 ${label} \u2192 ${relPath} (${result.width}\xD7${result.height}, ${result.renderTimeMs.toFixed(0)}ms)
|
|
3143
3312
|
`
|
|
3144
|
-
|
|
3313
|
+
);
|
|
3314
|
+
}
|
|
3145
3315
|
}
|
|
3316
|
+
await shutdownPool3();
|
|
3317
|
+
if (anyFailed) process.exit(1);
|
|
3146
3318
|
} catch (err) {
|
|
3147
3319
|
await shutdownPool3();
|
|
3148
3320
|
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|