@agent-scope/cli 1.0.2 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/program.ts
4
- import { readFileSync as readFileSync2 } from "fs";
4
+ import { readFileSync as readFileSync3 } from "fs";
5
5
  import { generateTest, loadTrace } from "@agent-scope/playwright";
6
6
  import { Command as Command3 } from "commander";
7
7
 
@@ -325,7 +325,7 @@ function createManifestCommand() {
325
325
 
326
326
  // src/render-commands.ts
327
327
  import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
328
- import { resolve as resolve2 } from "path";
328
+ import { resolve as resolve3 } from "path";
329
329
  import {
330
330
  ALL_CONTEXT_IDS,
331
331
  ALL_STRESS_IDS,
@@ -341,9 +341,9 @@ import { Command as Command2 } from "commander";
341
341
  // src/component-bundler.ts
342
342
  import { dirname } from "path";
343
343
  import * as esbuild from "esbuild";
344
- async function buildComponentHarness(filePath, componentName, props, viewportWidth) {
344
+ async function buildComponentHarness(filePath, componentName, props, viewportWidth, projectCss) {
345
345
  const bundledScript = await bundleComponentToIIFE(filePath, componentName, props);
346
- return wrapInHtml(bundledScript, viewportWidth);
346
+ return wrapInHtml(bundledScript, viewportWidth, projectCss);
347
347
  }
348
348
  async function bundleComponentToIIFE(filePath, componentName, props) {
349
349
  const propsJson = JSON.stringify(props).replace(/<\/script>/gi, "<\\/script>");
@@ -429,7 +429,10 @@ ${msg}`);
429
429
  }
430
430
  return outputFile.text;
431
431
  }
432
- function wrapInHtml(bundledScript, viewportWidth) {
432
+ function wrapInHtml(bundledScript, viewportWidth, projectCss) {
433
+ const projectStyleBlock = projectCss != null && projectCss.length > 0 ? `<style id="scope-project-css">
434
+ ${projectCss.replace(/<\/style>/gi, "<\\/style>")}
435
+ </style>` : "";
433
436
  return `<!DOCTYPE html>
434
437
  <html lang="en">
435
438
  <head>
@@ -440,6 +443,7 @@ function wrapInHtml(bundledScript, viewportWidth) {
440
443
  html, body { margin: 0; padding: 0; background: #fff; font-family: system-ui, sans-serif; }
441
444
  #scope-root { display: inline-block; min-width: 1px; min-height: 1px; }
442
445
  </style>
446
+ ${projectStyleBlock}
443
447
  </head>
444
448
  <body>
445
449
  <div id="scope-root" data-reactscope-root></div>
@@ -608,6 +612,126 @@ function csvEscape(value) {
608
612
  return value;
609
613
  }
610
614
 
615
+ // src/tailwind-css.ts
616
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
617
+ import { createRequire } from "module";
618
+ import { resolve as resolve2 } from "path";
619
+ var CONFIG_FILENAMES = [
620
+ ".reactscope/config.json",
621
+ ".reactscope/config.js",
622
+ ".reactscope/config.mjs"
623
+ ];
624
+ var STYLE_ENTRY_CANDIDATES = [
625
+ "src/index.css",
626
+ "src/globals.css",
627
+ "app/globals.css",
628
+ "app/index.css",
629
+ "styles/index.css",
630
+ "index.css"
631
+ ];
632
+ var TAILWIND_IMPORT = /@import\s+["']tailwindcss["']\s*;?/;
633
+ var compilerCache = null;
634
+ function getCachedBuild(cwd) {
635
+ if (compilerCache !== null && resolve2(compilerCache.cwd) === resolve2(cwd)) {
636
+ return compilerCache.build;
637
+ }
638
+ return null;
639
+ }
640
+ function findStylesEntry(cwd) {
641
+ for (const name of CONFIG_FILENAMES) {
642
+ const p = resolve2(cwd, name);
643
+ if (!existsSync2(p)) continue;
644
+ try {
645
+ if (name.endsWith(".json")) {
646
+ const raw = readFileSync2(p, "utf-8");
647
+ const data = JSON.parse(raw);
648
+ const scope = data.scope;
649
+ const entry = scope?.stylesEntry ?? data.stylesEntry;
650
+ if (typeof entry === "string") {
651
+ const full = resolve2(cwd, entry);
652
+ if (existsSync2(full)) return full;
653
+ }
654
+ }
655
+ } catch {
656
+ }
657
+ }
658
+ const pkgPath = resolve2(cwd, "package.json");
659
+ if (existsSync2(pkgPath)) {
660
+ try {
661
+ const raw = readFileSync2(pkgPath, "utf-8");
662
+ const pkg = JSON.parse(raw);
663
+ const entry = pkg.scope?.stylesEntry;
664
+ if (typeof entry === "string") {
665
+ const full = resolve2(cwd, entry);
666
+ if (existsSync2(full)) return full;
667
+ }
668
+ } catch {
669
+ }
670
+ }
671
+ for (const candidate of STYLE_ENTRY_CANDIDATES) {
672
+ const full = resolve2(cwd, candidate);
673
+ if (existsSync2(full)) {
674
+ try {
675
+ const content = readFileSync2(full, "utf-8");
676
+ if (TAILWIND_IMPORT.test(content)) return full;
677
+ } catch {
678
+ }
679
+ }
680
+ }
681
+ return null;
682
+ }
683
+ async function getTailwindCompiler(cwd) {
684
+ const cached = getCachedBuild(cwd);
685
+ if (cached !== null) return cached;
686
+ const entryPath = findStylesEntry(cwd);
687
+ if (entryPath === null) return null;
688
+ let compile;
689
+ try {
690
+ const require2 = createRequire(resolve2(cwd, "package.json"));
691
+ const tailwind = require2("tailwindcss");
692
+ const fn = tailwind.compile;
693
+ if (typeof fn !== "function") return null;
694
+ compile = fn;
695
+ } catch {
696
+ return null;
697
+ }
698
+ const entryContent = readFileSync2(entryPath, "utf-8");
699
+ const loadStylesheet = async (id, base) => {
700
+ if (id === "tailwindcss") {
701
+ const nodeModules = resolve2(cwd, "node_modules");
702
+ const tailwindCssPath = resolve2(nodeModules, "tailwindcss", "index.css");
703
+ if (!existsSync2(tailwindCssPath)) {
704
+ throw new Error(
705
+ `Tailwind v4: tailwindcss package not found at ${tailwindCssPath}. Install with: npm install tailwindcss`
706
+ );
707
+ }
708
+ const content = readFileSync2(tailwindCssPath, "utf-8");
709
+ return { path: "virtual:tailwindcss/index.css", base, content };
710
+ }
711
+ const full = resolve2(base, id);
712
+ if (existsSync2(full)) {
713
+ const content = readFileSync2(full, "utf-8");
714
+ return { path: full, base: resolve2(full, ".."), content };
715
+ }
716
+ throw new Error(`Tailwind v4: could not load stylesheet: ${id} (base: ${base})`);
717
+ };
718
+ const result = await compile(entryContent, {
719
+ base: cwd,
720
+ from: entryPath,
721
+ loadStylesheet
722
+ });
723
+ const build2 = result.build.bind(result);
724
+ compilerCache = { cwd, build: build2 };
725
+ return build2;
726
+ }
727
+ async function getCompiledCssForClasses(cwd, classes) {
728
+ const build2 = await getTailwindCompiler(cwd);
729
+ if (build2 === null) return null;
730
+ const deduped = [...new Set(classes)].filter(Boolean);
731
+ if (deduped.length === 0) return null;
732
+ return build2(deduped);
733
+ }
734
+
611
735
  // src/render-commands.ts
612
736
  var MANIFEST_PATH2 = ".reactscope/manifest.json";
613
737
  var DEFAULT_OUTPUT_DIR = ".reactscope/renders";
@@ -661,6 +785,20 @@ function buildRenderer(filePath, componentName, viewportWidth, viewportHeight) {
661
785
  if (renderError !== null) {
662
786
  throw new Error(`Component render error: ${renderError}`);
663
787
  }
788
+ const rootDir = process.cwd();
789
+ const classes = await page.evaluate(() => {
790
+ const set = /* @__PURE__ */ new Set();
791
+ document.querySelectorAll("[class]").forEach((el) => {
792
+ for (const c of el.className.split(/\s+/)) {
793
+ if (c) set.add(c);
794
+ }
795
+ });
796
+ return [...set];
797
+ });
798
+ const projectCss = await getCompiledCssForClasses(rootDir, classes);
799
+ if (projectCss != null && projectCss.length > 0) {
800
+ await page.addStyleTag({ content: projectCss });
801
+ }
664
802
  const renderTimeMs = performance.now() - startMs;
665
803
  const rootLocator = page.locator("[data-reactscope-root]");
666
804
  const boundingBox = await rootLocator.boundingBox();
@@ -742,7 +880,7 @@ Available: ${available}`
742
880
  }
743
881
  const { width, height } = parseViewport(opts.viewport);
744
882
  const rootDir = process.cwd();
745
- const filePath = resolve2(rootDir, descriptor.filePath);
883
+ const filePath = resolve3(rootDir, descriptor.filePath);
746
884
  const renderer = buildRenderer(filePath, componentName, width, height);
747
885
  process.stderr.write(
748
886
  `Rendering ${componentName} [${descriptor.complexityClass}] at ${width}\xD7${height}\u2026
@@ -772,7 +910,7 @@ Available: ${available}`
772
910
  }
773
911
  const result = outcome.result;
774
912
  if (opts.output !== void 0) {
775
- const outPath = resolve2(process.cwd(), opts.output);
913
+ const outPath = resolve3(process.cwd(), opts.output);
776
914
  writeFileSync3(outPath, result.screenshot);
777
915
  process.stdout.write(
778
916
  `\u2713 ${componentName} \u2192 ${opts.output} (${result.width}\xD7${result.height}, ${result.renderTimeMs.toFixed(0)}ms)
@@ -786,9 +924,9 @@ Available: ${available}`
786
924
  process.stdout.write(`${JSON.stringify(json, null, 2)}
787
925
  `);
788
926
  } else if (fmt === "file") {
789
- const dir = resolve2(process.cwd(), DEFAULT_OUTPUT_DIR);
927
+ const dir = resolve3(process.cwd(), DEFAULT_OUTPUT_DIR);
790
928
  mkdirSync2(dir, { recursive: true });
791
- const outPath = resolve2(dir, `${componentName}.png`);
929
+ const outPath = resolve3(dir, `${componentName}.png`);
792
930
  writeFileSync3(outPath, result.screenshot);
793
931
  const relPath = `${DEFAULT_OUTPUT_DIR}/${componentName}.png`;
794
932
  process.stdout.write(
@@ -796,9 +934,9 @@ Available: ${available}`
796
934
  `
797
935
  );
798
936
  } else {
799
- const dir = resolve2(process.cwd(), DEFAULT_OUTPUT_DIR);
937
+ const dir = resolve3(process.cwd(), DEFAULT_OUTPUT_DIR);
800
938
  mkdirSync2(dir, { recursive: true });
801
- const outPath = resolve2(dir, `${componentName}.png`);
939
+ const outPath = resolve3(dir, `${componentName}.png`);
802
940
  writeFileSync3(outPath, result.screenshot);
803
941
  const relPath = `${DEFAULT_OUTPUT_DIR}/${componentName}.png`;
804
942
  process.stdout.write(
@@ -834,7 +972,7 @@ Available: ${available}`
834
972
  const concurrency = Math.max(1, parseInt(opts.concurrency, 10) || 8);
835
973
  const { width, height } = { width: 375, height: 812 };
836
974
  const rootDir = process.cwd();
837
- const filePath = resolve2(rootDir, descriptor.filePath);
975
+ const filePath = resolve3(rootDir, descriptor.filePath);
838
976
  const renderer = buildRenderer(filePath, componentName, width, height);
839
977
  const axes = [];
840
978
  if (opts.axes !== void 0) {
@@ -901,7 +1039,7 @@ Available: ${available}`
901
1039
  const { SpriteSheetGenerator } = await import("@agent-scope/render");
902
1040
  const gen = new SpriteSheetGenerator();
903
1041
  const sheet = await gen.generate(result);
904
- const spritePath = resolve2(process.cwd(), opts.sprite);
1042
+ const spritePath = resolve3(process.cwd(), opts.sprite);
905
1043
  writeFileSync3(spritePath, sheet.png);
906
1044
  process.stderr.write(`Sprite sheet saved to ${spritePath}
907
1045
  `);
@@ -911,9 +1049,9 @@ Available: ${available}`
911
1049
  const { SpriteSheetGenerator } = await import("@agent-scope/render");
912
1050
  const gen = new SpriteSheetGenerator();
913
1051
  const sheet = await gen.generate(result);
914
- const dir = resolve2(process.cwd(), DEFAULT_OUTPUT_DIR);
1052
+ const dir = resolve3(process.cwd(), DEFAULT_OUTPUT_DIR);
915
1053
  mkdirSync2(dir, { recursive: true });
916
- const outPath = resolve2(dir, `${componentName}-matrix.png`);
1054
+ const outPath = resolve3(dir, `${componentName}-matrix.png`);
917
1055
  writeFileSync3(outPath, sheet.png);
918
1056
  const relPath = `${DEFAULT_OUTPUT_DIR}/${componentName}-matrix.png`;
919
1057
  process.stdout.write(
@@ -957,7 +1095,7 @@ function registerRenderAll(renderCmd) {
957
1095
  return;
958
1096
  }
959
1097
  const concurrency = Math.max(1, parseInt(opts.concurrency, 10) || 4);
960
- const outputDir = resolve2(process.cwd(), opts.outputDir);
1098
+ const outputDir = resolve3(process.cwd(), opts.outputDir);
961
1099
  mkdirSync2(outputDir, { recursive: true });
962
1100
  const rootDir = process.cwd();
963
1101
  process.stderr.write(`Rendering ${total} components (concurrency: ${concurrency})\u2026
@@ -967,7 +1105,7 @@ function registerRenderAll(renderCmd) {
967
1105
  const renderOne = async (name) => {
968
1106
  const descriptor = manifest.components[name];
969
1107
  if (descriptor === void 0) return;
970
- const filePath = resolve2(rootDir, descriptor.filePath);
1108
+ const filePath = resolve3(rootDir, descriptor.filePath);
971
1109
  const renderer = buildRenderer(filePath, name, 375, 812);
972
1110
  const outcome = await safeRender(
973
1111
  () => renderer.renderCell({}, descriptor.complexityClass),
@@ -990,7 +1128,7 @@ function registerRenderAll(renderCmd) {
990
1128
  success: false,
991
1129
  errorMessage: outcome.error.message
992
1130
  });
993
- const errPath = resolve2(outputDir, `${name}.error.json`);
1131
+ const errPath = resolve3(outputDir, `${name}.error.json`);
994
1132
  writeFileSync3(
995
1133
  errPath,
996
1134
  JSON.stringify(
@@ -1008,9 +1146,9 @@ function registerRenderAll(renderCmd) {
1008
1146
  }
1009
1147
  const result = outcome.result;
1010
1148
  results.push({ name, renderTimeMs: result.renderTimeMs, success: true });
1011
- const pngPath = resolve2(outputDir, `${name}.png`);
1149
+ const pngPath = resolve3(outputDir, `${name}.png`);
1012
1150
  writeFileSync3(pngPath, result.screenshot);
1013
- const jsonPath = resolve2(outputDir, `${name}.json`);
1151
+ const jsonPath = resolve3(outputDir, `${name}.json`);
1014
1152
  writeFileSync3(jsonPath, JSON.stringify(formatRenderJson(name, {}, result), null, 2));
1015
1153
  if (isTTY()) {
1016
1154
  process.stdout.write(
@@ -1436,7 +1574,7 @@ function createProgram(options = {}) {
1436
1574
  }
1437
1575
  );
1438
1576
  program2.command("generate").description("Generate a Playwright test from a Scope trace file").argument("<trace>", "Path to a serialized Scope trace (.json)").option("-o, --output <path>", "Output file path", "scope.spec.ts").option("-d, --description <text>", "Test description").action((tracePath, opts) => {
1439
- const raw = readFileSync2(tracePath, "utf-8");
1577
+ const raw = readFileSync3(tracePath, "utf-8");
1440
1578
  const trace = loadTrace(raw);
1441
1579
  const source = generateTest(trace, {
1442
1580
  description: opts.description,