@agent-scope/cli 1.9.0 → 1.10.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 +346 -16
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +325 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +327 -4
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.cjs
CHANGED
|
@@ -228,9 +228,9 @@ function createRL() {
|
|
|
228
228
|
});
|
|
229
229
|
}
|
|
230
230
|
async function ask(rl, question) {
|
|
231
|
-
return new Promise((
|
|
231
|
+
return new Promise((resolve7) => {
|
|
232
232
|
rl.question(question, (answer) => {
|
|
233
|
-
|
|
233
|
+
resolve7(answer.trim());
|
|
234
234
|
});
|
|
235
235
|
});
|
|
236
236
|
}
|
|
@@ -2020,6 +2020,325 @@ function createRenderCommand() {
|
|
|
2020
2020
|
registerRenderAll(renderCmd);
|
|
2021
2021
|
return renderCmd;
|
|
2022
2022
|
}
|
|
2023
|
+
var DEFAULT_BASELINE_DIR = ".reactscope/baseline";
|
|
2024
|
+
var _pool3 = null;
|
|
2025
|
+
async function getPool3(viewportWidth, viewportHeight) {
|
|
2026
|
+
if (_pool3 === null) {
|
|
2027
|
+
_pool3 = new render.BrowserPool({
|
|
2028
|
+
size: { browsers: 1, pagesPerBrowser: 4 },
|
|
2029
|
+
viewportWidth,
|
|
2030
|
+
viewportHeight
|
|
2031
|
+
});
|
|
2032
|
+
await _pool3.init();
|
|
2033
|
+
}
|
|
2034
|
+
return _pool3;
|
|
2035
|
+
}
|
|
2036
|
+
async function shutdownPool3() {
|
|
2037
|
+
if (_pool3 !== null) {
|
|
2038
|
+
await _pool3.close();
|
|
2039
|
+
_pool3 = null;
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
async function renderComponent(filePath, componentName, props, viewportWidth, viewportHeight) {
|
|
2043
|
+
const pool = await getPool3(viewportWidth, viewportHeight);
|
|
2044
|
+
const htmlHarness = await buildComponentHarness(filePath, componentName, props, viewportWidth);
|
|
2045
|
+
const slot = await pool.acquire();
|
|
2046
|
+
const { page } = slot;
|
|
2047
|
+
try {
|
|
2048
|
+
await page.setContent(htmlHarness, { waitUntil: "load" });
|
|
2049
|
+
await page.waitForFunction(
|
|
2050
|
+
() => {
|
|
2051
|
+
const w = window;
|
|
2052
|
+
return w.__SCOPE_RENDER_COMPLETE__ === true;
|
|
2053
|
+
},
|
|
2054
|
+
{ timeout: 15e3 }
|
|
2055
|
+
);
|
|
2056
|
+
const renderError = await page.evaluate(() => {
|
|
2057
|
+
return window.__SCOPE_RENDER_ERROR__ ?? null;
|
|
2058
|
+
});
|
|
2059
|
+
if (renderError !== null) {
|
|
2060
|
+
throw new Error(`Component render error: ${renderError}`);
|
|
2061
|
+
}
|
|
2062
|
+
const rootDir = process.cwd();
|
|
2063
|
+
const classes = await page.evaluate(() => {
|
|
2064
|
+
const set = /* @__PURE__ */ new Set();
|
|
2065
|
+
document.querySelectorAll("[class]").forEach((el) => {
|
|
2066
|
+
for (const c of el.className.split(/\s+/)) {
|
|
2067
|
+
if (c) set.add(c);
|
|
2068
|
+
}
|
|
2069
|
+
});
|
|
2070
|
+
return [...set];
|
|
2071
|
+
});
|
|
2072
|
+
const projectCss = await getCompiledCssForClasses(rootDir, classes);
|
|
2073
|
+
if (projectCss != null && projectCss.length > 0) {
|
|
2074
|
+
await page.addStyleTag({ content: projectCss });
|
|
2075
|
+
}
|
|
2076
|
+
const startMs = performance.now();
|
|
2077
|
+
const rootLocator = page.locator("[data-reactscope-root]");
|
|
2078
|
+
const boundingBox = await rootLocator.boundingBox();
|
|
2079
|
+
if (boundingBox === null || boundingBox.width === 0 || boundingBox.height === 0) {
|
|
2080
|
+
throw new Error(
|
|
2081
|
+
`Component "${componentName}" rendered with zero bounding box \u2014 it may be invisible or not mounted`
|
|
2082
|
+
);
|
|
2083
|
+
}
|
|
2084
|
+
const PAD = 24;
|
|
2085
|
+
const MIN_W = 320;
|
|
2086
|
+
const MIN_H = 200;
|
|
2087
|
+
const clipX = Math.max(0, boundingBox.x - PAD);
|
|
2088
|
+
const clipY = Math.max(0, boundingBox.y - PAD);
|
|
2089
|
+
const rawW = boundingBox.width + PAD * 2;
|
|
2090
|
+
const rawH = boundingBox.height + PAD * 2;
|
|
2091
|
+
const clipW = Math.max(rawW, MIN_W);
|
|
2092
|
+
const clipH = Math.max(rawH, MIN_H);
|
|
2093
|
+
const safeW = Math.min(clipW, viewportWidth - clipX);
|
|
2094
|
+
const safeH = Math.min(clipH, viewportHeight - clipY);
|
|
2095
|
+
const screenshot = await page.screenshot({
|
|
2096
|
+
clip: { x: clipX, y: clipY, width: safeW, height: safeH },
|
|
2097
|
+
type: "png"
|
|
2098
|
+
});
|
|
2099
|
+
const computedStylesRaw = {};
|
|
2100
|
+
const styles = await page.evaluate((sel) => {
|
|
2101
|
+
const el = document.querySelector(sel);
|
|
2102
|
+
if (el === null) return {};
|
|
2103
|
+
const computed = window.getComputedStyle(el);
|
|
2104
|
+
const out = {};
|
|
2105
|
+
for (const prop of [
|
|
2106
|
+
"display",
|
|
2107
|
+
"width",
|
|
2108
|
+
"height",
|
|
2109
|
+
"color",
|
|
2110
|
+
"backgroundColor",
|
|
2111
|
+
"fontSize",
|
|
2112
|
+
"fontFamily",
|
|
2113
|
+
"padding",
|
|
2114
|
+
"margin"
|
|
2115
|
+
]) {
|
|
2116
|
+
out[prop] = computed.getPropertyValue(prop);
|
|
2117
|
+
}
|
|
2118
|
+
return out;
|
|
2119
|
+
}, "[data-reactscope-root] > *");
|
|
2120
|
+
computedStylesRaw["[data-reactscope-root] > *"] = styles;
|
|
2121
|
+
const renderTimeMs = performance.now() - startMs;
|
|
2122
|
+
return {
|
|
2123
|
+
screenshot,
|
|
2124
|
+
width: Math.round(safeW),
|
|
2125
|
+
height: Math.round(safeH),
|
|
2126
|
+
renderTimeMs,
|
|
2127
|
+
computedStyles: computedStylesRaw
|
|
2128
|
+
};
|
|
2129
|
+
} finally {
|
|
2130
|
+
pool.release(slot);
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
function extractComputedStyles(computedStylesRaw) {
|
|
2134
|
+
const flat = {};
|
|
2135
|
+
for (const styles of Object.values(computedStylesRaw)) {
|
|
2136
|
+
Object.assign(flat, styles);
|
|
2137
|
+
}
|
|
2138
|
+
const colors = {};
|
|
2139
|
+
const spacing = {};
|
|
2140
|
+
const typography = {};
|
|
2141
|
+
const borders = {};
|
|
2142
|
+
const shadows = {};
|
|
2143
|
+
for (const [prop, value] of Object.entries(flat)) {
|
|
2144
|
+
if (prop === "color" || prop === "backgroundColor") {
|
|
2145
|
+
colors[prop] = value;
|
|
2146
|
+
} else if (prop === "padding" || prop === "margin") {
|
|
2147
|
+
spacing[prop] = value;
|
|
2148
|
+
} else if (prop === "fontSize" || prop === "fontFamily" || prop === "fontWeight" || prop === "lineHeight") {
|
|
2149
|
+
typography[prop] = value;
|
|
2150
|
+
} else if (prop === "borderRadius" || prop === "borderWidth") {
|
|
2151
|
+
borders[prop] = value;
|
|
2152
|
+
} else if (prop === "boxShadow") {
|
|
2153
|
+
shadows[prop] = value;
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
return { colors, spacing, typography, borders, shadows };
|
|
2157
|
+
}
|
|
2158
|
+
async function runBaseline(options = {}) {
|
|
2159
|
+
const {
|
|
2160
|
+
outputDir = DEFAULT_BASELINE_DIR,
|
|
2161
|
+
componentsGlob,
|
|
2162
|
+
manifestPath,
|
|
2163
|
+
viewportWidth = 375,
|
|
2164
|
+
viewportHeight = 812
|
|
2165
|
+
} = options;
|
|
2166
|
+
const startTime = performance.now();
|
|
2167
|
+
const rootDir = process.cwd();
|
|
2168
|
+
const baselineDir = path.resolve(rootDir, outputDir);
|
|
2169
|
+
const rendersDir = path.resolve(baselineDir, "renders");
|
|
2170
|
+
if (fs.existsSync(baselineDir)) {
|
|
2171
|
+
fs.rmSync(baselineDir, { recursive: true, force: true });
|
|
2172
|
+
}
|
|
2173
|
+
fs.mkdirSync(rendersDir, { recursive: true });
|
|
2174
|
+
let manifest$1;
|
|
2175
|
+
if (manifestPath !== void 0) {
|
|
2176
|
+
const { readFileSync: readFileSync7 } = await import('fs');
|
|
2177
|
+
const absPath = path.resolve(rootDir, manifestPath);
|
|
2178
|
+
if (!fs.existsSync(absPath)) {
|
|
2179
|
+
throw new Error(`Manifest not found at ${absPath}.`);
|
|
2180
|
+
}
|
|
2181
|
+
manifest$1 = JSON.parse(readFileSync7(absPath, "utf-8"));
|
|
2182
|
+
process.stderr.write(`Loaded manifest from ${manifestPath}
|
|
2183
|
+
`);
|
|
2184
|
+
} else {
|
|
2185
|
+
process.stderr.write("Scanning for React components\u2026\n");
|
|
2186
|
+
manifest$1 = await manifest.generateManifest({ rootDir });
|
|
2187
|
+
const count = Object.keys(manifest$1.components).length;
|
|
2188
|
+
process.stderr.write(`Found ${count} components.
|
|
2189
|
+
`);
|
|
2190
|
+
}
|
|
2191
|
+
fs.writeFileSync(path.resolve(baselineDir, "manifest.json"), JSON.stringify(manifest$1, null, 2), "utf-8");
|
|
2192
|
+
let componentNames = Object.keys(manifest$1.components);
|
|
2193
|
+
if (componentsGlob !== void 0) {
|
|
2194
|
+
componentNames = componentNames.filter((name) => matchGlob(componentsGlob, name));
|
|
2195
|
+
process.stderr.write(
|
|
2196
|
+
`Filtered to ${componentNames.length} components matching "${componentsGlob}".
|
|
2197
|
+
`
|
|
2198
|
+
);
|
|
2199
|
+
}
|
|
2200
|
+
const total = componentNames.length;
|
|
2201
|
+
if (total === 0) {
|
|
2202
|
+
process.stderr.write("No components to baseline.\n");
|
|
2203
|
+
const emptyReport = {
|
|
2204
|
+
components: {},
|
|
2205
|
+
totalProperties: 0,
|
|
2206
|
+
totalOnSystem: 0,
|
|
2207
|
+
totalOffSystem: 0,
|
|
2208
|
+
aggregateCompliance: 1,
|
|
2209
|
+
auditedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2210
|
+
};
|
|
2211
|
+
fs.writeFileSync(
|
|
2212
|
+
path.resolve(baselineDir, "compliance.json"),
|
|
2213
|
+
JSON.stringify(emptyReport, null, 2),
|
|
2214
|
+
"utf-8"
|
|
2215
|
+
);
|
|
2216
|
+
return {
|
|
2217
|
+
baselineDir,
|
|
2218
|
+
componentCount: 0,
|
|
2219
|
+
failureCount: 0,
|
|
2220
|
+
wallClockMs: performance.now() - startTime
|
|
2221
|
+
};
|
|
2222
|
+
}
|
|
2223
|
+
process.stderr.write(`Rendering ${total} components\u2026
|
|
2224
|
+
`);
|
|
2225
|
+
const computedStylesMap = /* @__PURE__ */ new Map();
|
|
2226
|
+
let completed = 0;
|
|
2227
|
+
let failureCount = 0;
|
|
2228
|
+
const CONCURRENCY = 4;
|
|
2229
|
+
let nextIdx = 0;
|
|
2230
|
+
const renderOne = async (name) => {
|
|
2231
|
+
const descriptor = manifest$1.components[name];
|
|
2232
|
+
if (descriptor === void 0) return;
|
|
2233
|
+
const filePath = path.resolve(rootDir, descriptor.filePath);
|
|
2234
|
+
const outcome = await render.safeRender(
|
|
2235
|
+
() => renderComponent(filePath, name, {}, viewportWidth, viewportHeight),
|
|
2236
|
+
{
|
|
2237
|
+
props: {},
|
|
2238
|
+
sourceLocation: {
|
|
2239
|
+
file: descriptor.filePath,
|
|
2240
|
+
line: descriptor.loc.start,
|
|
2241
|
+
column: 0
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
);
|
|
2245
|
+
completed++;
|
|
2246
|
+
const pct = Math.round(completed / total * 100);
|
|
2247
|
+
if (isTTY()) {
|
|
2248
|
+
process.stderr.write(`${renderProgressBar(completed, total, name, pct)}\r`);
|
|
2249
|
+
}
|
|
2250
|
+
if (outcome.crashed) {
|
|
2251
|
+
failureCount++;
|
|
2252
|
+
const errPath = path.resolve(rendersDir, `${name}.error.json`);
|
|
2253
|
+
fs.writeFileSync(
|
|
2254
|
+
errPath,
|
|
2255
|
+
JSON.stringify(
|
|
2256
|
+
{
|
|
2257
|
+
component: name,
|
|
2258
|
+
errorMessage: outcome.error.message,
|
|
2259
|
+
heuristicFlags: outcome.error.heuristicFlags,
|
|
2260
|
+
propsAtCrash: outcome.error.propsAtCrash
|
|
2261
|
+
},
|
|
2262
|
+
null,
|
|
2263
|
+
2
|
|
2264
|
+
),
|
|
2265
|
+
"utf-8"
|
|
2266
|
+
);
|
|
2267
|
+
return;
|
|
2268
|
+
}
|
|
2269
|
+
const result = outcome.result;
|
|
2270
|
+
fs.writeFileSync(path.resolve(rendersDir, `${name}.png`), result.screenshot);
|
|
2271
|
+
const jsonOutput = formatRenderJson(name, {}, result);
|
|
2272
|
+
fs.writeFileSync(
|
|
2273
|
+
path.resolve(rendersDir, `${name}.json`),
|
|
2274
|
+
JSON.stringify(jsonOutput, null, 2),
|
|
2275
|
+
"utf-8"
|
|
2276
|
+
);
|
|
2277
|
+
computedStylesMap.set(name, extractComputedStyles(result.computedStyles));
|
|
2278
|
+
};
|
|
2279
|
+
const worker = async () => {
|
|
2280
|
+
while (nextIdx < componentNames.length) {
|
|
2281
|
+
const i = nextIdx++;
|
|
2282
|
+
const name = componentNames[i];
|
|
2283
|
+
if (name !== void 0) {
|
|
2284
|
+
await renderOne(name);
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
};
|
|
2288
|
+
const workers = [];
|
|
2289
|
+
for (let w = 0; w < Math.min(CONCURRENCY, total); w++) {
|
|
2290
|
+
workers.push(worker());
|
|
2291
|
+
}
|
|
2292
|
+
await Promise.all(workers);
|
|
2293
|
+
await shutdownPool3();
|
|
2294
|
+
if (isTTY()) {
|
|
2295
|
+
process.stderr.write("\n");
|
|
2296
|
+
}
|
|
2297
|
+
const resolver = new tokens.TokenResolver([]);
|
|
2298
|
+
const engine = new tokens.ComplianceEngine(resolver);
|
|
2299
|
+
const batchReport = engine.auditBatch(computedStylesMap);
|
|
2300
|
+
fs.writeFileSync(
|
|
2301
|
+
path.resolve(baselineDir, "compliance.json"),
|
|
2302
|
+
JSON.stringify(batchReport, null, 2),
|
|
2303
|
+
"utf-8"
|
|
2304
|
+
);
|
|
2305
|
+
const wallClockMs = performance.now() - startTime;
|
|
2306
|
+
const successCount = total - failureCount;
|
|
2307
|
+
process.stderr.write(
|
|
2308
|
+
`
|
|
2309
|
+
Baseline complete: ${successCount}/${total} components rendered` + (failureCount > 0 ? ` (${failureCount} failed)` : "") + ` in ${(wallClockMs / 1e3).toFixed(1)}s
|
|
2310
|
+
`
|
|
2311
|
+
);
|
|
2312
|
+
process.stderr.write(`Snapshot saved to ${baselineDir}
|
|
2313
|
+
`);
|
|
2314
|
+
return { baselineDir, componentCount: total, failureCount, wallClockMs };
|
|
2315
|
+
}
|
|
2316
|
+
function registerBaselineSubCommand(reportCmd) {
|
|
2317
|
+
reportCmd.command("baseline").description("Capture a baseline snapshot (manifest + renders + compliance) for later diffing").option(
|
|
2318
|
+
"-o, --output <dir>",
|
|
2319
|
+
"Output directory for the baseline snapshot",
|
|
2320
|
+
DEFAULT_BASELINE_DIR
|
|
2321
|
+
).option("--components <glob>", "Glob pattern to baseline a subset of components").option("--manifest <path>", "Path to an existing manifest.json to use instead of regenerating").option("--viewport <WxH>", "Viewport size, e.g. 1280x720", "375x812").action(
|
|
2322
|
+
async (opts) => {
|
|
2323
|
+
try {
|
|
2324
|
+
const [wStr, hStr] = opts.viewport.split("x");
|
|
2325
|
+
const viewportWidth = Number.parseInt(wStr ?? "375", 10);
|
|
2326
|
+
const viewportHeight = Number.parseInt(hStr ?? "812", 10);
|
|
2327
|
+
await runBaseline({
|
|
2328
|
+
outputDir: opts.output,
|
|
2329
|
+
componentsGlob: opts.components,
|
|
2330
|
+
manifestPath: opts.manifest,
|
|
2331
|
+
viewportWidth,
|
|
2332
|
+
viewportHeight
|
|
2333
|
+
});
|
|
2334
|
+
} catch (err) {
|
|
2335
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
2336
|
+
`);
|
|
2337
|
+
process.exit(1);
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
);
|
|
2341
|
+
}
|
|
2023
2342
|
|
|
2024
2343
|
// src/tree-formatter.ts
|
|
2025
2344
|
var BRANCH = "\u251C\u2500\u2500 ";
|
|
@@ -2730,6 +3049,10 @@ function createProgram(options = {}) {
|
|
|
2730
3049
|
program.addCommand(createTokensCommand());
|
|
2731
3050
|
program.addCommand(createInstrumentCommand());
|
|
2732
3051
|
program.addCommand(createInitCommand());
|
|
3052
|
+
const existingReportCmd = program.commands.find((c) => c.name() === "report");
|
|
3053
|
+
if (existingReportCmd !== void 0) {
|
|
3054
|
+
registerBaselineSubCommand(existingReportCmd);
|
|
3055
|
+
}
|
|
2733
3056
|
return program;
|
|
2734
3057
|
}
|
|
2735
3058
|
|