@agent-scope/cli 1.16.0 → 1.17.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/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { existsSync, writeFileSync, readFileSync, mkdirSync, appendFileSync, readdirSync, rmSync } from 'fs';
2
- import { resolve, join, dirname } from 'path';
1
+ import { existsSync, writeFileSync, readFileSync, mkdirSync, appendFileSync, readdirSync, rmSync, statSync, createReadStream } from 'fs';
2
+ import { resolve, join, extname, dirname } from 'path';
3
3
  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';
@@ -7,8 +7,10 @@ import { Command } from 'commander';
7
7
  import * as esbuild from 'esbuild';
8
8
  import { createRequire } from 'module';
9
9
  import * as readline from 'readline';
10
- import { chromium } from 'playwright';
11
10
  import { loadTrace, generateTest, getBrowserEntryScript } from '@agent-scope/playwright';
11
+ import { chromium } from 'playwright';
12
+ import { createServer } from 'http';
13
+ import { buildSite } from '@agent-scope/site';
12
14
 
13
15
  // src/ci/commands.ts
14
16
  async function buildComponentHarness(filePath, componentName, props, viewportWidth, projectCss) {
@@ -1107,9 +1109,9 @@ function createRL() {
1107
1109
  });
1108
1110
  }
1109
1111
  async function ask(rl, question) {
1110
- return new Promise((resolve16) => {
1112
+ return new Promise((resolve17) => {
1111
1113
  rl.question(question, (answer) => {
1112
- resolve16(answer.trim());
1114
+ resolve17(answer.trim());
1113
1115
  });
1114
1116
  });
1115
1117
  }
@@ -1681,6 +1683,7 @@ async function runHooksProfiling(componentName, filePath, props) {
1681
1683
  try {
1682
1684
  const context = await browser.newContext();
1683
1685
  const page = await context.newPage();
1686
+ await page.addInitScript({ content: getBrowserEntryScript() });
1684
1687
  const htmlHarness = await buildComponentHarness(filePath, componentName, props, 1280);
1685
1688
  await page.setContent(htmlHarness, { waitUntil: "load" });
1686
1689
  await page.waitForFunction(
@@ -2064,24 +2067,6 @@ Available: ${available}`
2064
2067
  var MANIFEST_PATH4 = ".reactscope/manifest.json";
2065
2068
  var DEFAULT_VIEWPORT_WIDTH = 375;
2066
2069
  var DEFAULT_VIEWPORT_HEIGHT = 812;
2067
- var _pool2 = null;
2068
- async function getPool2() {
2069
- if (_pool2 === null) {
2070
- _pool2 = new BrowserPool({
2071
- size: { browsers: 1, pagesPerBrowser: 1 },
2072
- viewportWidth: DEFAULT_VIEWPORT_WIDTH,
2073
- viewportHeight: DEFAULT_VIEWPORT_HEIGHT
2074
- });
2075
- await _pool2.init();
2076
- }
2077
- return _pool2;
2078
- }
2079
- async function shutdownPool2() {
2080
- if (_pool2 !== null) {
2081
- await _pool2.close();
2082
- _pool2 = null;
2083
- }
2084
- }
2085
2070
  function mapNodeType(node) {
2086
2071
  if (node.type === "forward_ref") return "forwardRef";
2087
2072
  if (node.type === "host") return "host";
@@ -2291,10 +2276,12 @@ function formatInstrumentTree(root, showProviderDepth = false) {
2291
2276
  }
2292
2277
  async function runInstrumentTree(options) {
2293
2278
  const { componentName, filePath } = options;
2294
- const pool = await getPool2();
2295
- const slot = await pool.acquire();
2296
- const { page } = slot;
2279
+ const browser = await chromium.launch({ headless: true });
2297
2280
  try {
2281
+ const context = await browser.newContext({
2282
+ viewport: { width: DEFAULT_VIEWPORT_WIDTH, height: DEFAULT_VIEWPORT_HEIGHT }
2283
+ });
2284
+ const page = await context.newPage();
2298
2285
  await page.addInitScript({ content: getBrowserEntryScript() });
2299
2286
  const htmlHarness = await buildComponentHarness(
2300
2287
  filePath,
@@ -2348,7 +2335,7 @@ async function runInstrumentTree(options) {
2348
2335
  }
2349
2336
  return instrumentRoot;
2350
2337
  } finally {
2351
- pool.release(slot);
2338
+ await browser.close();
2352
2339
  }
2353
2340
  }
2354
2341
  function createInstrumentTreeCommand() {
@@ -2388,7 +2375,6 @@ Available: ${available}`
2388
2375
  providerDepth: opts.providerDepth,
2389
2376
  wastedRenders: opts.wastedRenders
2390
2377
  });
2391
- await shutdownPool2();
2392
2378
  const fmt2 = resolveFormat2(opts.format);
2393
2379
  if (fmt2 === "json") {
2394
2380
  process.stdout.write(`${JSON.stringify(instrumentRoot, null, 2)}
@@ -2399,7 +2385,6 @@ Available: ${available}`
2399
2385
  `);
2400
2386
  }
2401
2387
  } catch (err) {
2402
- await shutdownPool2();
2403
2388
  process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
2404
2389
  `);
2405
2390
  process.exit(1);
@@ -2717,22 +2702,22 @@ async function replayInteraction2(page, steps) {
2717
2702
  }
2718
2703
  }
2719
2704
  }
2720
- var _pool3 = null;
2721
- async function getPool3() {
2722
- if (_pool3 === null) {
2723
- _pool3 = new BrowserPool({
2705
+ var _pool2 = null;
2706
+ async function getPool2() {
2707
+ if (_pool2 === null) {
2708
+ _pool2 = new BrowserPool({
2724
2709
  size: { browsers: 1, pagesPerBrowser: 2 },
2725
2710
  viewportWidth: 1280,
2726
2711
  viewportHeight: 800
2727
2712
  });
2728
- await _pool3.init();
2713
+ await _pool2.init();
2729
2714
  }
2730
- return _pool3;
2715
+ return _pool2;
2731
2716
  }
2732
- async function shutdownPool3() {
2733
- if (_pool3 !== null) {
2734
- await _pool3.close();
2735
- _pool3 = null;
2717
+ async function shutdownPool2() {
2718
+ if (_pool2 !== null) {
2719
+ await _pool2.close();
2720
+ _pool2 = null;
2736
2721
  }
2737
2722
  }
2738
2723
  async function analyzeRenders(options) {
@@ -2749,7 +2734,7 @@ Available: ${available}`
2749
2734
  const rootDir = process.cwd();
2750
2735
  const filePath = resolve(rootDir, descriptor.filePath);
2751
2736
  const htmlHarness = await buildComponentHarness(filePath, options.componentName, {}, 1280);
2752
- const pool = await getPool3();
2737
+ const pool = await getPool2();
2753
2738
  const slot = await pool.acquire();
2754
2739
  const { page } = slot;
2755
2740
  const startMs = performance.now();
@@ -2857,7 +2842,7 @@ function createInstrumentRendersCommand() {
2857
2842
  interaction,
2858
2843
  manifestPath: opts.manifest
2859
2844
  });
2860
- await shutdownPool3();
2845
+ await shutdownPool2();
2861
2846
  if (opts.json || !isTTY()) {
2862
2847
  process.stdout.write(`${JSON.stringify(result, null, 2)}
2863
2848
  `);
@@ -2866,7 +2851,7 @@ function createInstrumentRendersCommand() {
2866
2851
  `);
2867
2852
  }
2868
2853
  } catch (err) {
2869
- await shutdownPool3();
2854
+ await shutdownPool2();
2870
2855
  process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
2871
2856
  `);
2872
2857
  process.exit(1);
@@ -2926,22 +2911,22 @@ function writeReportToFile(report, outputPath, pretty) {
2926
2911
  }
2927
2912
  var MANIFEST_PATH6 = ".reactscope/manifest.json";
2928
2913
  var DEFAULT_OUTPUT_DIR = ".reactscope/renders";
2929
- var _pool4 = null;
2930
- async function getPool4(viewportWidth, viewportHeight) {
2931
- if (_pool4 === null) {
2932
- _pool4 = new BrowserPool({
2914
+ var _pool3 = null;
2915
+ async function getPool3(viewportWidth, viewportHeight) {
2916
+ if (_pool3 === null) {
2917
+ _pool3 = new BrowserPool({
2933
2918
  size: { browsers: 1, pagesPerBrowser: 4 },
2934
2919
  viewportWidth,
2935
2920
  viewportHeight
2936
2921
  });
2937
- await _pool4.init();
2922
+ await _pool3.init();
2938
2923
  }
2939
- return _pool4;
2924
+ return _pool3;
2940
2925
  }
2941
- async function shutdownPool4() {
2942
- if (_pool4 !== null) {
2943
- await _pool4.close();
2944
- _pool4 = null;
2926
+ async function shutdownPool3() {
2927
+ if (_pool3 !== null) {
2928
+ await _pool3.close();
2929
+ _pool3 = null;
2945
2930
  }
2946
2931
  }
2947
2932
  function buildRenderer(filePath, componentName, viewportWidth, viewportHeight) {
@@ -2952,7 +2937,7 @@ function buildRenderer(filePath, componentName, viewportWidth, viewportHeight) {
2952
2937
  _satori: satori,
2953
2938
  async renderCell(props, _complexityClass) {
2954
2939
  const startMs = performance.now();
2955
- const pool = await getPool4(viewportWidth, viewportHeight);
2940
+ const pool = await getPool3(viewportWidth, viewportHeight);
2956
2941
  const htmlHarness = await buildComponentHarness(
2957
2942
  filePath,
2958
2943
  componentName,
@@ -3088,7 +3073,7 @@ Available: ${available}`
3088
3073
  }
3089
3074
  }
3090
3075
  );
3091
- await shutdownPool4();
3076
+ await shutdownPool3();
3092
3077
  if (outcome.crashed) {
3093
3078
  process.stderr.write(`\u2717 Render failed: ${outcome.error.message}
3094
3079
  `);
@@ -3136,7 +3121,7 @@ Available: ${available}`
3136
3121
  );
3137
3122
  }
3138
3123
  } catch (err) {
3139
- await shutdownPool4();
3124
+ await shutdownPool3();
3140
3125
  process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
3141
3126
  `);
3142
3127
  process.exit(1);
@@ -3221,7 +3206,7 @@ Available: ${available}`
3221
3206
  concurrency
3222
3207
  });
3223
3208
  const result = await matrix.render();
3224
- await shutdownPool4();
3209
+ await shutdownPool3();
3225
3210
  process.stderr.write(
3226
3211
  `Done. ${result.stats.totalCells} cells, avg ${result.stats.avgRenderTimeMs.toFixed(1)}ms
3227
3212
  `
@@ -3266,7 +3251,7 @@ Available: ${available}`
3266
3251
  process.stdout.write(formatMatrixCsv(componentName, result));
3267
3252
  }
3268
3253
  } catch (err) {
3269
- await shutdownPool4();
3254
+ await shutdownPool3();
3270
3255
  process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
3271
3256
  `);
3272
3257
  process.exit(1);
@@ -3363,13 +3348,13 @@ function registerRenderAll(renderCmd) {
3363
3348
  workers.push(worker());
3364
3349
  }
3365
3350
  await Promise.all(workers);
3366
- await shutdownPool4();
3351
+ await shutdownPool3();
3367
3352
  process.stderr.write("\n");
3368
3353
  const summary = formatSummaryText(results, outputDir);
3369
3354
  process.stderr.write(`${summary}
3370
3355
  `);
3371
3356
  } catch (err) {
3372
- await shutdownPool4();
3357
+ await shutdownPool3();
3373
3358
  process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
3374
3359
  `);
3375
3360
  process.exit(1);
@@ -3411,26 +3396,26 @@ function createRenderCommand() {
3411
3396
  return renderCmd;
3412
3397
  }
3413
3398
  var DEFAULT_BASELINE_DIR = ".reactscope/baseline";
3414
- var _pool5 = null;
3415
- async function getPool5(viewportWidth, viewportHeight) {
3416
- if (_pool5 === null) {
3417
- _pool5 = new BrowserPool({
3399
+ var _pool4 = null;
3400
+ async function getPool4(viewportWidth, viewportHeight) {
3401
+ if (_pool4 === null) {
3402
+ _pool4 = new BrowserPool({
3418
3403
  size: { browsers: 1, pagesPerBrowser: 4 },
3419
3404
  viewportWidth,
3420
3405
  viewportHeight
3421
3406
  });
3422
- await _pool5.init();
3407
+ await _pool4.init();
3423
3408
  }
3424
- return _pool5;
3409
+ return _pool4;
3425
3410
  }
3426
- async function shutdownPool5() {
3427
- if (_pool5 !== null) {
3428
- await _pool5.close();
3429
- _pool5 = null;
3411
+ async function shutdownPool4() {
3412
+ if (_pool4 !== null) {
3413
+ await _pool4.close();
3414
+ _pool4 = null;
3430
3415
  }
3431
3416
  }
3432
3417
  async function renderComponent2(filePath, componentName, props, viewportWidth, viewportHeight) {
3433
- const pool = await getPool5(viewportWidth, viewportHeight);
3418
+ const pool = await getPool4(viewportWidth, viewportHeight);
3434
3419
  const htmlHarness = await buildComponentHarness(filePath, componentName, props, viewportWidth);
3435
3420
  const slot = await pool.acquire();
3436
3421
  const { page } = slot;
@@ -3680,7 +3665,7 @@ async function runBaseline(options = {}) {
3680
3665
  workers.push(worker());
3681
3666
  }
3682
3667
  await Promise.all(workers);
3683
- await shutdownPool5();
3668
+ await shutdownPool4();
3684
3669
  if (isTTY()) {
3685
3670
  process.stderr.write("\n");
3686
3671
  }
@@ -3741,26 +3726,26 @@ function loadBaselineRenderJson2(baselineDir, componentName) {
3741
3726
  if (!existsSync(jsonPath)) return null;
3742
3727
  return JSON.parse(readFileSync(jsonPath, "utf-8"));
3743
3728
  }
3744
- var _pool6 = null;
3745
- async function getPool6(viewportWidth, viewportHeight) {
3746
- if (_pool6 === null) {
3747
- _pool6 = new BrowserPool({
3729
+ var _pool5 = null;
3730
+ async function getPool5(viewportWidth, viewportHeight) {
3731
+ if (_pool5 === null) {
3732
+ _pool5 = new BrowserPool({
3748
3733
  size: { browsers: 1, pagesPerBrowser: 4 },
3749
3734
  viewportWidth,
3750
3735
  viewportHeight
3751
3736
  });
3752
- await _pool6.init();
3737
+ await _pool5.init();
3753
3738
  }
3754
- return _pool6;
3739
+ return _pool5;
3755
3740
  }
3756
- async function shutdownPool6() {
3757
- if (_pool6 !== null) {
3758
- await _pool6.close();
3759
- _pool6 = null;
3741
+ async function shutdownPool5() {
3742
+ if (_pool5 !== null) {
3743
+ await _pool5.close();
3744
+ _pool5 = null;
3760
3745
  }
3761
3746
  }
3762
3747
  async function renderComponent3(filePath, componentName, props, viewportWidth, viewportHeight) {
3763
- const pool = await getPool6(viewportWidth, viewportHeight);
3748
+ const pool = await getPool5(viewportWidth, viewportHeight);
3764
3749
  const htmlHarness = await buildComponentHarness(filePath, componentName, props, viewportWidth);
3765
3750
  const slot = await pool.acquire();
3766
3751
  const { page } = slot;
@@ -4009,7 +3994,7 @@ async function runDiff(options = {}) {
4009
3994
  }
4010
3995
  await Promise.all(workers);
4011
3996
  }
4012
- await shutdownPool6();
3997
+ await shutdownPool5();
4013
3998
  if (isTTY() && total > 0) {
4014
3999
  process.stderr.write("\n");
4015
4000
  }
@@ -4609,6 +4594,130 @@ function buildStructuredReport(report) {
4609
4594
  route: report.route?.pattern ?? null
4610
4595
  };
4611
4596
  }
4597
+ var MIME_TYPES = {
4598
+ ".html": "text/html; charset=utf-8",
4599
+ ".css": "text/css; charset=utf-8",
4600
+ ".js": "application/javascript; charset=utf-8",
4601
+ ".json": "application/json; charset=utf-8",
4602
+ ".png": "image/png",
4603
+ ".jpg": "image/jpeg",
4604
+ ".jpeg": "image/jpeg",
4605
+ ".svg": "image/svg+xml",
4606
+ ".ico": "image/x-icon"
4607
+ };
4608
+ function registerBuild(siteCmd) {
4609
+ siteCmd.command("build").description("Build a static HTML gallery from .reactscope/ output").option("-i, --input <path>", "Path to .reactscope input directory", ".reactscope").option("-o, --output <path>", "Output directory for generated site", ".reactscope/site").option("--base-path <path>", "Base URL path prefix for subdirectory deployment", "/").option("--compliance <path>", "Path to compliance batch report JSON").option("--title <text>", "Site title", "Scope \u2014 Component Gallery").action(
4610
+ async (opts) => {
4611
+ try {
4612
+ const inputDir = resolve(process.cwd(), opts.input);
4613
+ const outputDir = resolve(process.cwd(), opts.output);
4614
+ if (!existsSync(inputDir)) {
4615
+ throw new Error(
4616
+ `Input directory not found: ${inputDir}
4617
+ Run \`scope manifest generate\` and \`scope render\` first.`
4618
+ );
4619
+ }
4620
+ const manifestPath = join(inputDir, "manifest.json");
4621
+ if (!existsSync(manifestPath)) {
4622
+ throw new Error(
4623
+ `Manifest not found at ${manifestPath}
4624
+ Run \`scope manifest generate\` first.`
4625
+ );
4626
+ }
4627
+ process.stderr.write(`Building site from ${inputDir}\u2026
4628
+ `);
4629
+ await buildSite({
4630
+ inputDir,
4631
+ outputDir,
4632
+ basePath: opts.basePath,
4633
+ ...opts.compliance !== void 0 && {
4634
+ compliancePath: resolve(process.cwd(), opts.compliance)
4635
+ },
4636
+ title: opts.title
4637
+ });
4638
+ process.stderr.write(`Site written to ${outputDir}
4639
+ `);
4640
+ process.stdout.write(`${outputDir}
4641
+ `);
4642
+ } catch (err) {
4643
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
4644
+ `);
4645
+ process.exit(1);
4646
+ }
4647
+ }
4648
+ );
4649
+ }
4650
+ function registerServe(siteCmd) {
4651
+ siteCmd.command("serve").description("Serve the built static site locally").option("-p, --port <number>", "Port to listen on", "3000").option("-d, --dir <path>", "Directory to serve", ".reactscope/site").action((opts) => {
4652
+ try {
4653
+ const port = Number.parseInt(opts.port, 10);
4654
+ if (Number.isNaN(port) || port < 1 || port > 65535) {
4655
+ throw new Error(`Invalid port: ${opts.port}`);
4656
+ }
4657
+ const serveDir = resolve(process.cwd(), opts.dir);
4658
+ if (!existsSync(serveDir)) {
4659
+ throw new Error(
4660
+ `Serve directory not found: ${serveDir}
4661
+ Run \`scope site build\` first.`
4662
+ );
4663
+ }
4664
+ const server = createServer((req, res) => {
4665
+ const rawUrl = req.url ?? "/";
4666
+ const urlPath = decodeURIComponent(rawUrl.split("?")[0] ?? "/");
4667
+ const filePath = join(serveDir, urlPath.endsWith("/") ? `${urlPath}index.html` : urlPath);
4668
+ if (!filePath.startsWith(serveDir)) {
4669
+ res.writeHead(403, { "Content-Type": "text/plain" });
4670
+ res.end("Forbidden");
4671
+ return;
4672
+ }
4673
+ if (existsSync(filePath) && statSync(filePath).isFile()) {
4674
+ const ext = extname(filePath).toLowerCase();
4675
+ const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
4676
+ res.writeHead(200, { "Content-Type": contentType });
4677
+ createReadStream(filePath).pipe(res);
4678
+ return;
4679
+ }
4680
+ const htmlPath = `${filePath}.html`;
4681
+ if (existsSync(htmlPath) && statSync(htmlPath).isFile()) {
4682
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
4683
+ createReadStream(htmlPath).pipe(res);
4684
+ return;
4685
+ }
4686
+ res.writeHead(404, { "Content-Type": "text/plain" });
4687
+ res.end(`Not found: ${urlPath}`);
4688
+ });
4689
+ server.listen(port, () => {
4690
+ process.stderr.write(`Scope site running at http://localhost:${port}
4691
+ `);
4692
+ process.stderr.write(`Serving ${serveDir}
4693
+ `);
4694
+ process.stderr.write("Press Ctrl+C to stop.\n");
4695
+ });
4696
+ server.on("error", (err) => {
4697
+ if (err.code === "EADDRINUSE") {
4698
+ process.stderr.write(`Error: Port ${port} is already in use.
4699
+ `);
4700
+ } else {
4701
+ process.stderr.write(`Server error: ${err.message}
4702
+ `);
4703
+ }
4704
+ process.exit(1);
4705
+ });
4706
+ } catch (err) {
4707
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
4708
+ `);
4709
+ process.exit(1);
4710
+ }
4711
+ });
4712
+ }
4713
+ function createSiteCommand() {
4714
+ const siteCmd = new Command("site").description(
4715
+ "Build and serve the static component gallery site"
4716
+ );
4717
+ registerBuild(siteCmd);
4718
+ registerServe(siteCmd);
4719
+ return siteCmd;
4720
+ }
4612
4721
  var DEFAULT_STYLES_PATH = ".reactscope/compliance-styles.json";
4613
4722
  function loadStylesFile(stylesPath) {
4614
4723
  const absPath = resolve(process.cwd(), stylesPath);
@@ -5628,6 +5737,7 @@ function createProgram(options = {}) {
5628
5737
  registerDiffSubCommand(existingReportCmd);
5629
5738
  registerPrCommentSubCommand(existingReportCmd);
5630
5739
  }
5740
+ program.addCommand(createSiteCommand());
5631
5741
  return program;
5632
5742
  }
5633
5743