@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 +159 -21
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +134 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +134 -2
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
927
|
+
const dir = resolve3(process.cwd(), DEFAULT_OUTPUT_DIR);
|
|
790
928
|
mkdirSync2(dir, { recursive: true });
|
|
791
|
-
const outPath =
|
|
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 =
|
|
937
|
+
const dir = resolve3(process.cwd(), DEFAULT_OUTPUT_DIR);
|
|
800
938
|
mkdirSync2(dir, { recursive: true });
|
|
801
|
-
const outPath =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1052
|
+
const dir = resolve3(process.cwd(), DEFAULT_OUTPUT_DIR);
|
|
915
1053
|
mkdirSync2(dir, { recursive: true });
|
|
916
|
-
const outPath =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1149
|
+
const pngPath = resolve3(outputDir, `${name}.png`);
|
|
1012
1150
|
writeFileSync3(pngPath, result.screenshot);
|
|
1013
|
-
const jsonPath =
|
|
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 =
|
|
1577
|
+
const raw = readFileSync3(tracePath, "utf-8");
|
|
1440
1578
|
const trace = loadTrace(raw);
|
|
1441
1579
|
const source = generateTest(trace, {
|
|
1442
1580
|
description: opts.description,
|