@agent-scope/cli 1.15.0 → 1.17.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/index.cjs CHANGED
@@ -11,6 +11,8 @@ var module$1 = require('module');
11
11
  var readline = require('readline');
12
12
  var playwright$1 = require('playwright');
13
13
  var playwright = require('@agent-scope/playwright');
14
+ var http = require('http');
15
+ var site = require('@agent-scope/site');
14
16
 
15
17
  function _interopNamespace(e) {
16
18
  if (e && e.__esModule) return e;
@@ -1130,9 +1132,9 @@ function createRL() {
1130
1132
  });
1131
1133
  }
1132
1134
  async function ask(rl, question) {
1133
- return new Promise((resolve15) => {
1135
+ return new Promise((resolve17) => {
1134
1136
  rl.question(question, (answer) => {
1135
- resolve15(answer.trim());
1137
+ resolve17(answer.trim());
1136
1138
  });
1137
1139
  });
1138
1140
  }
@@ -2412,8 +2414,8 @@ Available: ${available}`
2412
2414
  wastedRenders: opts.wastedRenders
2413
2415
  });
2414
2416
  await shutdownPool2();
2415
- const fmt = resolveFormat2(opts.format);
2416
- if (fmt === "json") {
2417
+ const fmt2 = resolveFormat2(opts.format);
2418
+ if (fmt2 === "json") {
2417
2419
  process.stdout.write(`${JSON.stringify(instrumentRoot, null, 2)}
2418
2420
  `);
2419
2421
  } else {
@@ -3132,12 +3134,12 @@ Available: ${available}`
3132
3134
  );
3133
3135
  return;
3134
3136
  }
3135
- const fmt = resolveSingleFormat(opts.format);
3136
- if (fmt === "json") {
3137
+ const fmt2 = resolveSingleFormat(opts.format);
3138
+ if (fmt2 === "json") {
3137
3139
  const json = formatRenderJson(componentName, props, result);
3138
3140
  process.stdout.write(`${JSON.stringify(json, null, 2)}
3139
3141
  `);
3140
- } else if (fmt === "file") {
3142
+ } else if (fmt2 === "file") {
3141
3143
  const dir = path.resolve(process.cwd(), DEFAULT_OUTPUT_DIR);
3142
3144
  fs.mkdirSync(dir, { recursive: true });
3143
3145
  const outPath = path.resolve(dir, `${componentName}.png`);
@@ -3258,8 +3260,8 @@ Available: ${available}`
3258
3260
  process.stderr.write(`Sprite sheet saved to ${spritePath}
3259
3261
  `);
3260
3262
  }
3261
- const fmt = resolveMatrixFormat(opts.format, opts.sprite !== void 0);
3262
- if (fmt === "file") {
3263
+ const fmt2 = resolveMatrixFormat(opts.format, opts.sprite !== void 0);
3264
+ if (fmt2 === "file") {
3263
3265
  const { SpriteSheetGenerator: SpriteSheetGenerator2 } = await import('@agent-scope/render');
3264
3266
  const gen = new SpriteSheetGenerator2();
3265
3267
  const sheet = await gen.generate(result);
@@ -3272,10 +3274,10 @@ Available: ${available}`
3272
3274
  `\u2713 ${componentName} matrix (${result.stats.totalCells} cells) \u2192 ${relPath} (${result.stats.wallClockTimeMs.toFixed(0)}ms total)
3273
3275
  `
3274
3276
  );
3275
- } else if (fmt === "json") {
3277
+ } else if (fmt2 === "json") {
3276
3278
  process.stdout.write(`${JSON.stringify(formatMatrixJson(result), null, 2)}
3277
3279
  `);
3278
- } else if (fmt === "png") {
3280
+ } else if (fmt2 === "png") {
3279
3281
  if (opts.sprite !== void 0) {
3280
3282
  } else {
3281
3283
  const { SpriteSheetGenerator: SpriteSheetGenerator2 } = await import('@agent-scope/render');
@@ -3283,9 +3285,9 @@ Available: ${available}`
3283
3285
  const sheet = await gen.generate(result);
3284
3286
  process.stdout.write(sheet.png);
3285
3287
  }
3286
- } else if (fmt === "html") {
3288
+ } else if (fmt2 === "html") {
3287
3289
  process.stdout.write(formatMatrixHtml(componentName, result));
3288
- } else if (fmt === "csv") {
3290
+ } else if (fmt2 === "csv") {
3289
3291
  process.stdout.write(formatMatrixCsv(componentName, result));
3290
3292
  }
3291
3293
  } catch (err) {
@@ -3586,12 +3588,12 @@ async function runBaseline(options = {}) {
3586
3588
  fs.mkdirSync(rendersDir, { recursive: true });
3587
3589
  let manifest$1;
3588
3590
  if (manifestPath !== void 0) {
3589
- const { readFileSync: readFileSync11 } = await import('fs');
3591
+ const { readFileSync: readFileSync12 } = await import('fs');
3590
3592
  const absPath = path.resolve(rootDir, manifestPath);
3591
3593
  if (!fs.existsSync(absPath)) {
3592
3594
  throw new Error(`Manifest not found at ${absPath}.`);
3593
3595
  }
3594
- manifest$1 = JSON.parse(readFileSync11(absPath, "utf-8"));
3596
+ manifest$1 = JSON.parse(readFileSync12(absPath, "utf-8"));
3595
3597
  process.stderr.write(`Loaded manifest from ${manifestPath}
3596
3598
  `);
3597
3599
  } else {
@@ -4225,6 +4227,135 @@ function registerDiffSubCommand(reportCmd) {
4225
4227
  }
4226
4228
  );
4227
4229
  }
4230
+ var STATUS_BADGE = {
4231
+ added: "\u2705 added",
4232
+ removed: "\u{1F5D1}\uFE0F removed",
4233
+ unchanged: "\u2014 unchanged",
4234
+ compliance_regressed: "\u274C regressed",
4235
+ compliance_improved: "\u{1F4C8} improved",
4236
+ size_changed: "\u{1F4D0} resized"
4237
+ };
4238
+ function fmt(n) {
4239
+ return `${(n * 100).toFixed(1)}%`;
4240
+ }
4241
+ function fmtDelta(delta) {
4242
+ if (delta === null) return "\u2014";
4243
+ const sign = delta >= 0 ? "+" : "";
4244
+ return `${sign}${(delta * 100).toFixed(1)}%`;
4245
+ }
4246
+ function fmtDimensions(d) {
4247
+ if (d === null) return "\u2014";
4248
+ return `${d.width} \xD7 ${d.height}`;
4249
+ }
4250
+ function complianceDeltaArrow(diff) {
4251
+ const delta = diff.currentAggregateCompliance - diff.baselineAggregateCompliance;
4252
+ if (Math.abs(delta) < 5e-4) return `\u2192 ${fmt(diff.currentAggregateCompliance)} (no change)`;
4253
+ const arrow = delta > 0 ? "\u2191" : "\u2193";
4254
+ return `${arrow} ${fmtDelta(delta)}`;
4255
+ }
4256
+ function formatPrComment(diff) {
4257
+ const { summary, components } = diff;
4258
+ const lines = [];
4259
+ const hasRegressions = diff.hasRegressions;
4260
+ const headerEmoji = hasRegressions ? "\u26A0\uFE0F" : "\u2705";
4261
+ lines.push(`## ${headerEmoji} Scope Report`);
4262
+ lines.push("");
4263
+ lines.push("| Metric | Value |");
4264
+ lines.push("|---|---|");
4265
+ lines.push(`| Baseline compliance | ${fmt(diff.baselineAggregateCompliance)} |`);
4266
+ lines.push(`| Current compliance | ${fmt(diff.currentAggregateCompliance)} |`);
4267
+ lines.push(`| Delta | ${complianceDeltaArrow(diff)} |`);
4268
+ lines.push(
4269
+ `| Components | ${summary.total} total \xB7 ${summary.added} added \xB7 ${summary.removed} removed \xB7 ${summary.complianceRegressed} regressed |`
4270
+ );
4271
+ if (summary.renderFailed > 0) {
4272
+ lines.push(`| Render failures | ${summary.renderFailed} |`);
4273
+ }
4274
+ lines.push("");
4275
+ const changed = components.filter((c) => c.status !== "unchanged");
4276
+ const unchanged = components.filter((c) => c.status === "unchanged");
4277
+ if (changed.length > 0) {
4278
+ lines.push("### Changes");
4279
+ lines.push("");
4280
+ lines.push("| Component | Status | Compliance \u0394 | Dimensions |");
4281
+ lines.push("|---|---|---|---|");
4282
+ for (const c of changed) {
4283
+ const badge = STATUS_BADGE[c.status];
4284
+ const delta = fmtDelta(c.complianceDelta);
4285
+ const dims = fmtDimensions(c.currentDimensions ?? c.baselineDimensions);
4286
+ lines.push(`| \`${c.name}\` | ${badge} | ${delta} | ${dims} |`);
4287
+ }
4288
+ lines.push("");
4289
+ }
4290
+ if (unchanged.length > 0) {
4291
+ lines.push(
4292
+ `<details><summary>${unchanged.length} unchanged component${unchanged.length === 1 ? "" : "s"}</summary>`
4293
+ );
4294
+ lines.push("");
4295
+ lines.push("| Component | Compliance |");
4296
+ lines.push("|---|---|");
4297
+ for (const c of unchanged) {
4298
+ lines.push(
4299
+ `| \`${c.name}\` | ${c.currentCompliance !== null ? fmt(c.currentCompliance) : "\u2014"} |`
4300
+ );
4301
+ }
4302
+ lines.push("");
4303
+ lines.push("</details>");
4304
+ lines.push("");
4305
+ }
4306
+ lines.push(
4307
+ `> Generated by [Scope](https://github.com/FlatFilers/Scope) \xB7 diffed at ${diff.diffedAt}`
4308
+ );
4309
+ return lines.join("\n");
4310
+ }
4311
+ function loadDiffResult(filePath) {
4312
+ const abs = path.resolve(filePath);
4313
+ if (!fs.existsSync(abs)) {
4314
+ throw new Error(`DiffResult file not found: ${abs}`);
4315
+ }
4316
+ let raw;
4317
+ try {
4318
+ raw = fs.readFileSync(abs, "utf-8");
4319
+ } catch (err) {
4320
+ throw new Error(
4321
+ `Failed to read DiffResult file: ${err instanceof Error ? err.message : String(err)}`
4322
+ );
4323
+ }
4324
+ let parsed;
4325
+ try {
4326
+ parsed = JSON.parse(raw);
4327
+ } catch {
4328
+ throw new Error(`DiffResult file is not valid JSON: ${abs}`);
4329
+ }
4330
+ if (typeof parsed !== "object" || parsed === null || !("diffedAt" in parsed) || !("components" in parsed) || !("summary" in parsed)) {
4331
+ throw new Error(
4332
+ `DiffResult file does not match expected shape (missing diffedAt/components/summary): ${abs}`
4333
+ );
4334
+ }
4335
+ return parsed;
4336
+ }
4337
+ function registerPrCommentSubCommand(reportCmd) {
4338
+ reportCmd.command("pr-comment").description(
4339
+ "Format a DiffResult JSON file as a GitHub PR comment (Markdown, written to stdout)"
4340
+ ).requiredOption("-i, --input <path>", "Path to DiffResult JSON (from scope report diff --json)").option("-o, --output <path>", "Write comment to file instead of stdout").action(async (opts) => {
4341
+ try {
4342
+ const diff = loadDiffResult(opts.input);
4343
+ const comment = formatPrComment(diff);
4344
+ if (opts.output !== void 0) {
4345
+ fs.writeFileSync(path.resolve(opts.output), comment, "utf-8");
4346
+ process.stderr.write(`PR comment written to ${opts.output}
4347
+ `);
4348
+ } else {
4349
+ process.stdout.write(`${comment}
4350
+ `);
4351
+ }
4352
+ } catch (err) {
4353
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
4354
+ `);
4355
+ process.exit(1);
4356
+ }
4357
+ });
4358
+ }
4228
4359
 
4229
4360
  // src/tree-formatter.ts
4230
4361
  var BRANCH2 = "\u251C\u2500\u2500 ";
@@ -4503,6 +4634,130 @@ function buildStructuredReport(report) {
4503
4634
  route: report.route?.pattern ?? null
4504
4635
  };
4505
4636
  }
4637
+ var MIME_TYPES = {
4638
+ ".html": "text/html; charset=utf-8",
4639
+ ".css": "text/css; charset=utf-8",
4640
+ ".js": "application/javascript; charset=utf-8",
4641
+ ".json": "application/json; charset=utf-8",
4642
+ ".png": "image/png",
4643
+ ".jpg": "image/jpeg",
4644
+ ".jpeg": "image/jpeg",
4645
+ ".svg": "image/svg+xml",
4646
+ ".ico": "image/x-icon"
4647
+ };
4648
+ function registerBuild(siteCmd) {
4649
+ 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(
4650
+ async (opts) => {
4651
+ try {
4652
+ const inputDir = path.resolve(process.cwd(), opts.input);
4653
+ const outputDir = path.resolve(process.cwd(), opts.output);
4654
+ if (!fs.existsSync(inputDir)) {
4655
+ throw new Error(
4656
+ `Input directory not found: ${inputDir}
4657
+ Run \`scope manifest generate\` and \`scope render\` first.`
4658
+ );
4659
+ }
4660
+ const manifestPath = path.join(inputDir, "manifest.json");
4661
+ if (!fs.existsSync(manifestPath)) {
4662
+ throw new Error(
4663
+ `Manifest not found at ${manifestPath}
4664
+ Run \`scope manifest generate\` first.`
4665
+ );
4666
+ }
4667
+ process.stderr.write(`Building site from ${inputDir}\u2026
4668
+ `);
4669
+ await site.buildSite({
4670
+ inputDir,
4671
+ outputDir,
4672
+ basePath: opts.basePath,
4673
+ ...opts.compliance !== void 0 && {
4674
+ compliancePath: path.resolve(process.cwd(), opts.compliance)
4675
+ },
4676
+ title: opts.title
4677
+ });
4678
+ process.stderr.write(`Site written to ${outputDir}
4679
+ `);
4680
+ process.stdout.write(`${outputDir}
4681
+ `);
4682
+ } catch (err) {
4683
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
4684
+ `);
4685
+ process.exit(1);
4686
+ }
4687
+ }
4688
+ );
4689
+ }
4690
+ function registerServe(siteCmd) {
4691
+ 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) => {
4692
+ try {
4693
+ const port = Number.parseInt(opts.port, 10);
4694
+ if (Number.isNaN(port) || port < 1 || port > 65535) {
4695
+ throw new Error(`Invalid port: ${opts.port}`);
4696
+ }
4697
+ const serveDir = path.resolve(process.cwd(), opts.dir);
4698
+ if (!fs.existsSync(serveDir)) {
4699
+ throw new Error(
4700
+ `Serve directory not found: ${serveDir}
4701
+ Run \`scope site build\` first.`
4702
+ );
4703
+ }
4704
+ const server = http.createServer((req, res) => {
4705
+ const rawUrl = req.url ?? "/";
4706
+ const urlPath = decodeURIComponent(rawUrl.split("?")[0] ?? "/");
4707
+ const filePath = path.join(serveDir, urlPath.endsWith("/") ? `${urlPath}index.html` : urlPath);
4708
+ if (!filePath.startsWith(serveDir)) {
4709
+ res.writeHead(403, { "Content-Type": "text/plain" });
4710
+ res.end("Forbidden");
4711
+ return;
4712
+ }
4713
+ if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
4714
+ const ext = path.extname(filePath).toLowerCase();
4715
+ const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
4716
+ res.writeHead(200, { "Content-Type": contentType });
4717
+ fs.createReadStream(filePath).pipe(res);
4718
+ return;
4719
+ }
4720
+ const htmlPath = `${filePath}.html`;
4721
+ if (fs.existsSync(htmlPath) && fs.statSync(htmlPath).isFile()) {
4722
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
4723
+ fs.createReadStream(htmlPath).pipe(res);
4724
+ return;
4725
+ }
4726
+ res.writeHead(404, { "Content-Type": "text/plain" });
4727
+ res.end(`Not found: ${urlPath}`);
4728
+ });
4729
+ server.listen(port, () => {
4730
+ process.stderr.write(`Scope site running at http://localhost:${port}
4731
+ `);
4732
+ process.stderr.write(`Serving ${serveDir}
4733
+ `);
4734
+ process.stderr.write("Press Ctrl+C to stop.\n");
4735
+ });
4736
+ server.on("error", (err) => {
4737
+ if (err.code === "EADDRINUSE") {
4738
+ process.stderr.write(`Error: Port ${port} is already in use.
4739
+ `);
4740
+ } else {
4741
+ process.stderr.write(`Server error: ${err.message}
4742
+ `);
4743
+ }
4744
+ process.exit(1);
4745
+ });
4746
+ } catch (err) {
4747
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
4748
+ `);
4749
+ process.exit(1);
4750
+ }
4751
+ });
4752
+ }
4753
+ function createSiteCommand() {
4754
+ const siteCmd = new commander.Command("site").description(
4755
+ "Build and serve the static component gallery site"
4756
+ );
4757
+ registerBuild(siteCmd);
4758
+ registerServe(siteCmd);
4759
+ return siteCmd;
4760
+ }
4506
4761
  var DEFAULT_STYLES_PATH = ".reactscope/compliance-styles.json";
4507
4762
  function loadStylesFile(stylesPath) {
4508
4763
  const absPath = path.resolve(process.cwd(), stylesPath);
@@ -5520,7 +5775,9 @@ function createProgram(options = {}) {
5520
5775
  if (existingReportCmd !== void 0) {
5521
5776
  registerBaselineSubCommand(existingReportCmd);
5522
5777
  registerDiffSubCommand(existingReportCmd);
5778
+ registerPrCommentSubCommand(existingReportCmd);
5523
5779
  }
5780
+ program.addCommand(createSiteCommand());
5524
5781
  return program;
5525
5782
  }
5526
5783