@agent-scope/cli 1.11.0 → 1.12.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 +490 -126
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +416 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +416 -60
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/program.ts
|
|
4
4
|
import { readFileSync as readFileSync7 } from "fs";
|
|
5
5
|
import { generateTest, loadTrace } from "@agent-scope/playwright";
|
|
6
|
-
import { Command as
|
|
6
|
+
import { Command as Command8 } from "commander";
|
|
7
7
|
|
|
8
8
|
// src/browser.ts
|
|
9
9
|
import { writeFileSync } from "fs";
|
|
@@ -255,9 +255,9 @@ function createRL() {
|
|
|
255
255
|
});
|
|
256
256
|
}
|
|
257
257
|
async function ask(rl, question) {
|
|
258
|
-
return new Promise((
|
|
258
|
+
return new Promise((resolve11) => {
|
|
259
259
|
rl.question(question, (answer) => {
|
|
260
|
-
|
|
260
|
+
resolve11(answer.trim());
|
|
261
261
|
});
|
|
262
262
|
});
|
|
263
263
|
}
|
|
@@ -406,9 +406,9 @@ function createInitCommand() {
|
|
|
406
406
|
}
|
|
407
407
|
|
|
408
408
|
// src/instrument/renders.ts
|
|
409
|
-
import { resolve as
|
|
410
|
-
import { BrowserPool } from "@agent-scope/render";
|
|
411
|
-
import { Command as
|
|
409
|
+
import { resolve as resolve5 } from "path";
|
|
410
|
+
import { BrowserPool as BrowserPool2 } from "@agent-scope/render";
|
|
411
|
+
import { Command as Command4 } from "commander";
|
|
412
412
|
|
|
413
413
|
// src/component-bundler.ts
|
|
414
414
|
import { dirname } from "path";
|
|
@@ -1602,8 +1602,371 @@ Available: ${available}`
|
|
|
1602
1602
|
return cmd;
|
|
1603
1603
|
}
|
|
1604
1604
|
|
|
1605
|
-
// src/instrument/
|
|
1605
|
+
// src/instrument/tree.ts
|
|
1606
|
+
import { resolve as resolve4 } from "path";
|
|
1607
|
+
import { getBrowserEntryScript as getBrowserEntryScript2 } from "@agent-scope/playwright";
|
|
1608
|
+
import { BrowserPool } from "@agent-scope/render";
|
|
1609
|
+
import { Command as Command3 } from "commander";
|
|
1606
1610
|
var MANIFEST_PATH4 = ".reactscope/manifest.json";
|
|
1611
|
+
var DEFAULT_VIEWPORT_WIDTH = 375;
|
|
1612
|
+
var DEFAULT_VIEWPORT_HEIGHT = 812;
|
|
1613
|
+
var _pool = null;
|
|
1614
|
+
async function getPool() {
|
|
1615
|
+
if (_pool === null) {
|
|
1616
|
+
_pool = new BrowserPool({
|
|
1617
|
+
size: { browsers: 1, pagesPerBrowser: 1 },
|
|
1618
|
+
viewportWidth: DEFAULT_VIEWPORT_WIDTH,
|
|
1619
|
+
viewportHeight: DEFAULT_VIEWPORT_HEIGHT
|
|
1620
|
+
});
|
|
1621
|
+
await _pool.init();
|
|
1622
|
+
}
|
|
1623
|
+
return _pool;
|
|
1624
|
+
}
|
|
1625
|
+
async function shutdownPool() {
|
|
1626
|
+
if (_pool !== null) {
|
|
1627
|
+
await _pool.close();
|
|
1628
|
+
_pool = null;
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
function mapNodeType(node) {
|
|
1632
|
+
if (node.type === "forward_ref") return "forwardRef";
|
|
1633
|
+
if (node.type === "host") return "host";
|
|
1634
|
+
const name = node.name;
|
|
1635
|
+
if (name.endsWith(".Provider") || name === "Provider") return "context.provider";
|
|
1636
|
+
if (name.endsWith(".Consumer") || name === "Consumer") return "context.consumer";
|
|
1637
|
+
return node.type;
|
|
1638
|
+
}
|
|
1639
|
+
function flattenSerializedValue(sv) {
|
|
1640
|
+
if (sv === null || sv === void 0) return null;
|
|
1641
|
+
const v = sv;
|
|
1642
|
+
switch (v.type) {
|
|
1643
|
+
case "null":
|
|
1644
|
+
case "undefined":
|
|
1645
|
+
return null;
|
|
1646
|
+
case "string":
|
|
1647
|
+
case "number":
|
|
1648
|
+
case "boolean":
|
|
1649
|
+
return v.value;
|
|
1650
|
+
case "object": {
|
|
1651
|
+
if (!Array.isArray(v.entries)) return {};
|
|
1652
|
+
const result = {};
|
|
1653
|
+
for (const entry of v.entries) {
|
|
1654
|
+
result[entry.key] = flattenSerializedValue(entry.value);
|
|
1655
|
+
}
|
|
1656
|
+
return result;
|
|
1657
|
+
}
|
|
1658
|
+
case "array": {
|
|
1659
|
+
if (!Array.isArray(v.items)) return [];
|
|
1660
|
+
return v.items.map(flattenSerializedValue);
|
|
1661
|
+
}
|
|
1662
|
+
case "function":
|
|
1663
|
+
return "[Function]";
|
|
1664
|
+
case "symbol":
|
|
1665
|
+
return `[Symbol: ${v.description ?? ""}]`;
|
|
1666
|
+
case "circular":
|
|
1667
|
+
return "[Circular]";
|
|
1668
|
+
case "truncated":
|
|
1669
|
+
return `[Truncated: ${v.preview ?? ""}]`;
|
|
1670
|
+
default:
|
|
1671
|
+
return v.preview ?? null;
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
function flattenHookState(hooks) {
|
|
1675
|
+
const result = {};
|
|
1676
|
+
for (let i = 0; i < hooks.length; i++) {
|
|
1677
|
+
const hook = hooks[i];
|
|
1678
|
+
if (hook === void 0) continue;
|
|
1679
|
+
const key = hook.name !== null && hook.name !== void 0 ? hook.name : `${hook.type}[${i}]`;
|
|
1680
|
+
result[key] = flattenSerializedValue(hook.value);
|
|
1681
|
+
}
|
|
1682
|
+
return result;
|
|
1683
|
+
}
|
|
1684
|
+
function extractContextNames(contexts) {
|
|
1685
|
+
const names = contexts.map((c) => c.contextName ?? "Unknown").filter((name, idx, arr) => arr.indexOf(name) === idx);
|
|
1686
|
+
return names;
|
|
1687
|
+
}
|
|
1688
|
+
function anyContextChanged(contexts) {
|
|
1689
|
+
return contexts.some((c) => c.didTriggerRender);
|
|
1690
|
+
}
|
|
1691
|
+
function convertToInstrumentNode(node, depth = 0) {
|
|
1692
|
+
const contexts = extractContextNames(node.context);
|
|
1693
|
+
const contextChanged = anyContextChanged(node.context);
|
|
1694
|
+
const state = flattenHookState(node.state);
|
|
1695
|
+
const propsFlat = flattenSerializedValue(node.props);
|
|
1696
|
+
const props = propsFlat !== null && typeof propsFlat === "object" && !Array.isArray(propsFlat) ? propsFlat : {};
|
|
1697
|
+
return {
|
|
1698
|
+
component: node.name,
|
|
1699
|
+
type: mapNodeType(node),
|
|
1700
|
+
renderCount: node.renderCount,
|
|
1701
|
+
lastRenderDuration: node.renderDuration,
|
|
1702
|
+
memoized: node.type === "memo",
|
|
1703
|
+
// memoSkipped requires tracking bail-outs across commits — not available from
|
|
1704
|
+
// a single-shot capture. Defaulted to 0.
|
|
1705
|
+
memoSkipped: 0,
|
|
1706
|
+
props,
|
|
1707
|
+
// propsChanged is not tracked in a single-shot capture — would need a diff
|
|
1708
|
+
// between two renders. Defaulted to false.
|
|
1709
|
+
propsChanged: false,
|
|
1710
|
+
state,
|
|
1711
|
+
stateChanged: false,
|
|
1712
|
+
contextChanged,
|
|
1713
|
+
contexts,
|
|
1714
|
+
depth,
|
|
1715
|
+
children: node.children.map((child) => convertToInstrumentNode(child, depth + 1))
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
function filterByContext(node, contextName) {
|
|
1719
|
+
const filteredChildren = node.children.map((child) => filterByContext(child, contextName)).filter((c) => c !== null);
|
|
1720
|
+
const selfMatches = node.contexts.some((c) => c.toLowerCase() === contextName.toLowerCase());
|
|
1721
|
+
if (!selfMatches && filteredChildren.length === 0) return null;
|
|
1722
|
+
return { ...node, children: filteredChildren };
|
|
1723
|
+
}
|
|
1724
|
+
function filterWastedRenders(node) {
|
|
1725
|
+
const filteredChildren = node.children.map((child) => filterWastedRenders(child)).filter((c) => c !== null);
|
|
1726
|
+
const isWasted = !node.propsChanged && !node.stateChanged && !node.contextChanged && !node.memoized && node.renderCount > 1;
|
|
1727
|
+
if (!isWasted && filteredChildren.length === 0) return null;
|
|
1728
|
+
return { ...node, children: filteredChildren };
|
|
1729
|
+
}
|
|
1730
|
+
function sortTree(node, sortBy) {
|
|
1731
|
+
const sortedChildren = node.children.map((child) => sortTree(child, sortBy)).sort((a, b) => {
|
|
1732
|
+
if (sortBy === "renderCount") return b.renderCount - a.renderCount;
|
|
1733
|
+
return a.depth - b.depth;
|
|
1734
|
+
});
|
|
1735
|
+
return { ...node, children: sortedChildren };
|
|
1736
|
+
}
|
|
1737
|
+
function annotateProviderDepth(node, providerDepth = 0) {
|
|
1738
|
+
const isProvider = node.type === "context.provider";
|
|
1739
|
+
const childProviderDepth = isProvider ? providerDepth + 1 : providerDepth;
|
|
1740
|
+
return {
|
|
1741
|
+
...node,
|
|
1742
|
+
_providerDepth: providerDepth,
|
|
1743
|
+
children: node.children.map((child) => annotateProviderDepth(child, childProviderDepth))
|
|
1744
|
+
};
|
|
1745
|
+
}
|
|
1746
|
+
function limitNodes(root, limit) {
|
|
1747
|
+
let remaining = limit;
|
|
1748
|
+
const clip = (node) => {
|
|
1749
|
+
if (remaining <= 0) return null;
|
|
1750
|
+
remaining--;
|
|
1751
|
+
const clippedChildren = [];
|
|
1752
|
+
for (const child of node.children) {
|
|
1753
|
+
const clipped = clip(child);
|
|
1754
|
+
if (clipped !== null) clippedChildren.push(clipped);
|
|
1755
|
+
}
|
|
1756
|
+
return { ...node, children: clippedChildren };
|
|
1757
|
+
};
|
|
1758
|
+
return clip(root) ?? root;
|
|
1759
|
+
}
|
|
1760
|
+
var BRANCH = "\u251C\u2500\u2500 ";
|
|
1761
|
+
var LAST_BRANCH = "\u2514\u2500\u2500 ";
|
|
1762
|
+
var VERTICAL = "\u2502 ";
|
|
1763
|
+
var EMPTY = " ";
|
|
1764
|
+
function buildTTYLabel(node, showProviderDepth) {
|
|
1765
|
+
const parts = [node.component];
|
|
1766
|
+
switch (node.type) {
|
|
1767
|
+
case "memo":
|
|
1768
|
+
parts.push("[memo]");
|
|
1769
|
+
break;
|
|
1770
|
+
case "forwardRef":
|
|
1771
|
+
parts.push("[forwardRef]");
|
|
1772
|
+
break;
|
|
1773
|
+
case "class":
|
|
1774
|
+
parts.push("[class]");
|
|
1775
|
+
break;
|
|
1776
|
+
case "context.provider":
|
|
1777
|
+
parts.push("[provider]");
|
|
1778
|
+
break;
|
|
1779
|
+
case "context.consumer":
|
|
1780
|
+
parts.push("[consumer]");
|
|
1781
|
+
break;
|
|
1782
|
+
default:
|
|
1783
|
+
break;
|
|
1784
|
+
}
|
|
1785
|
+
if (node.renderCount > 0) {
|
|
1786
|
+
const durationStr = node.lastRenderDuration > 0 ? ` ${node.lastRenderDuration.toFixed(2)}ms` : "";
|
|
1787
|
+
parts.push(`(renders:${node.renderCount}${durationStr})`);
|
|
1788
|
+
}
|
|
1789
|
+
if (node.contexts.length > 0) {
|
|
1790
|
+
parts.push(`[ctx:${node.contexts.join(",")}]`);
|
|
1791
|
+
}
|
|
1792
|
+
if (showProviderDepth) {
|
|
1793
|
+
const pd = node._providerDepth;
|
|
1794
|
+
if (pd !== void 0 && pd > 0) {
|
|
1795
|
+
parts.push(`[pd:${pd}]`);
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
return parts.join(" ");
|
|
1799
|
+
}
|
|
1800
|
+
function renderTTYNode(node, prefix, isLast, showProviderDepth, lines) {
|
|
1801
|
+
if (node.type === "host") {
|
|
1802
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
1803
|
+
const child = node.children[i];
|
|
1804
|
+
if (child !== void 0) {
|
|
1805
|
+
renderTTYNode(child, prefix, i === node.children.length - 1, showProviderDepth, lines);
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
return;
|
|
1809
|
+
}
|
|
1810
|
+
const connector = isLast ? LAST_BRANCH : BRANCH;
|
|
1811
|
+
lines.push(`${prefix}${connector}${buildTTYLabel(node, showProviderDepth)}`);
|
|
1812
|
+
const nextPrefix = prefix + (isLast ? EMPTY : VERTICAL);
|
|
1813
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
1814
|
+
const child = node.children[i];
|
|
1815
|
+
if (child !== void 0) {
|
|
1816
|
+
renderTTYNode(child, nextPrefix, i === node.children.length - 1, showProviderDepth, lines);
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
function formatInstrumentTree(root, showProviderDepth = false) {
|
|
1821
|
+
const lines = [];
|
|
1822
|
+
if (root.type !== "host") {
|
|
1823
|
+
lines.push(buildTTYLabel(root, showProviderDepth));
|
|
1824
|
+
for (let i = 0; i < root.children.length; i++) {
|
|
1825
|
+
const child = root.children[i];
|
|
1826
|
+
if (child !== void 0) {
|
|
1827
|
+
renderTTYNode(child, "", i === root.children.length - 1, showProviderDepth, lines);
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
} else {
|
|
1831
|
+
for (let i = 0; i < root.children.length; i++) {
|
|
1832
|
+
const child = root.children[i];
|
|
1833
|
+
if (child !== void 0) {
|
|
1834
|
+
renderTTYNode(child, "", i === root.children.length - 1, showProviderDepth, lines);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
return lines.join("\n");
|
|
1839
|
+
}
|
|
1840
|
+
async function runInstrumentTree(options) {
|
|
1841
|
+
const { componentName, filePath } = options;
|
|
1842
|
+
const pool = await getPool();
|
|
1843
|
+
const slot = await pool.acquire();
|
|
1844
|
+
const { page } = slot;
|
|
1845
|
+
try {
|
|
1846
|
+
await page.addInitScript({ content: getBrowserEntryScript2() });
|
|
1847
|
+
const htmlHarness = await buildComponentHarness(
|
|
1848
|
+
filePath,
|
|
1849
|
+
componentName,
|
|
1850
|
+
{},
|
|
1851
|
+
DEFAULT_VIEWPORT_WIDTH
|
|
1852
|
+
);
|
|
1853
|
+
await page.setContent(htmlHarness, { waitUntil: "load" });
|
|
1854
|
+
await page.waitForFunction(
|
|
1855
|
+
() => {
|
|
1856
|
+
const w = window;
|
|
1857
|
+
return w.__SCOPE_RENDER_COMPLETE__ === true;
|
|
1858
|
+
},
|
|
1859
|
+
{ timeout: 15e3 }
|
|
1860
|
+
);
|
|
1861
|
+
const renderError = await page.evaluate(
|
|
1862
|
+
() => window.__SCOPE_RENDER_ERROR__ ?? null
|
|
1863
|
+
);
|
|
1864
|
+
if (renderError !== null) {
|
|
1865
|
+
throw new Error(`Component render error: ${renderError}`);
|
|
1866
|
+
}
|
|
1867
|
+
const captureJson = await page.evaluate(async () => {
|
|
1868
|
+
const w = window;
|
|
1869
|
+
if (typeof w.__SCOPE_CAPTURE_JSON__ !== "function") {
|
|
1870
|
+
throw new Error("__SCOPE_CAPTURE_JSON__ not available \u2014 Scope runtime not injected");
|
|
1871
|
+
}
|
|
1872
|
+
return w.__SCOPE_CAPTURE_JSON__({ lightweight: false });
|
|
1873
|
+
});
|
|
1874
|
+
const captureResult = JSON.parse(captureJson);
|
|
1875
|
+
const componentTree = captureResult.tree;
|
|
1876
|
+
if (componentTree === void 0 || componentTree === null) {
|
|
1877
|
+
throw new Error(`No component tree found for "${componentName}"`);
|
|
1878
|
+
}
|
|
1879
|
+
let instrumentRoot = convertToInstrumentNode(componentTree, 0);
|
|
1880
|
+
if (options.usesContext !== void 0) {
|
|
1881
|
+
const filtered = filterByContext(instrumentRoot, options.usesContext);
|
|
1882
|
+
instrumentRoot = filtered !== null ? filtered : { ...instrumentRoot, children: [] };
|
|
1883
|
+
}
|
|
1884
|
+
if (options.wastedRenders === true) {
|
|
1885
|
+
const filtered = filterWastedRenders(instrumentRoot);
|
|
1886
|
+
instrumentRoot = filtered !== null ? filtered : { ...instrumentRoot, children: [] };
|
|
1887
|
+
}
|
|
1888
|
+
if (options.sortBy !== void 0) {
|
|
1889
|
+
instrumentRoot = sortTree(instrumentRoot, options.sortBy);
|
|
1890
|
+
}
|
|
1891
|
+
if (options.providerDepth === true) {
|
|
1892
|
+
instrumentRoot = annotateProviderDepth(instrumentRoot, 0);
|
|
1893
|
+
}
|
|
1894
|
+
if (options.limit !== void 0 && options.limit > 0) {
|
|
1895
|
+
instrumentRoot = limitNodes(instrumentRoot, options.limit);
|
|
1896
|
+
}
|
|
1897
|
+
return instrumentRoot;
|
|
1898
|
+
} finally {
|
|
1899
|
+
pool.release(slot);
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
function createInstrumentTreeCommand() {
|
|
1903
|
+
return new Command3("tree").description("Render a component via BrowserPool and output a structured instrumentation tree").argument("<component>", "Component name to instrument (must exist in the manifest)").option("--sort-by <field>", "Sort nodes by field: renderCount | depth").option("--limit <n>", "Limit output to the first N nodes (depth-first)").option("--uses-context <name>", "Filter to components that use a specific context").option("--provider-depth", "Annotate each node with its context-provider nesting depth", false).option(
|
|
1904
|
+
"--wasted-renders",
|
|
1905
|
+
"Filter to components with wasted renders (no prop/state/context changes, not memoized)",
|
|
1906
|
+
false
|
|
1907
|
+
).option("--format <fmt>", "Output format: json | tree (default: auto)").option("--manifest <path>", "Path to manifest.json", MANIFEST_PATH4).action(async (componentName, opts) => {
|
|
1908
|
+
try {
|
|
1909
|
+
const manifest = loadManifest(opts.manifest);
|
|
1910
|
+
const descriptor = manifest.components[componentName];
|
|
1911
|
+
if (descriptor === void 0) {
|
|
1912
|
+
const available = Object.keys(manifest.components).slice(0, 5).join(", ");
|
|
1913
|
+
throw new Error(
|
|
1914
|
+
`Component "${componentName}" not found in manifest.
|
|
1915
|
+
Available: ${available}`
|
|
1916
|
+
);
|
|
1917
|
+
}
|
|
1918
|
+
if (opts.sortBy !== void 0) {
|
|
1919
|
+
const allowed = ["renderCount", "depth"];
|
|
1920
|
+
if (!allowed.includes(opts.sortBy)) {
|
|
1921
|
+
throw new Error(
|
|
1922
|
+
`Unknown --sort-by value "${opts.sortBy}". Allowed: ${allowed.join(", ")}`
|
|
1923
|
+
);
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
const rootDir = process.cwd();
|
|
1927
|
+
const filePath = resolve4(rootDir, descriptor.filePath);
|
|
1928
|
+
process.stderr.write(`Instrumenting ${componentName}\u2026
|
|
1929
|
+
`);
|
|
1930
|
+
const instrumentRoot = await runInstrumentTree({
|
|
1931
|
+
componentName,
|
|
1932
|
+
filePath,
|
|
1933
|
+
sortBy: opts.sortBy,
|
|
1934
|
+
limit: opts.limit !== void 0 ? Math.max(1, parseInt(opts.limit, 10)) : void 0,
|
|
1935
|
+
usesContext: opts.usesContext,
|
|
1936
|
+
providerDepth: opts.providerDepth,
|
|
1937
|
+
wastedRenders: opts.wastedRenders
|
|
1938
|
+
});
|
|
1939
|
+
await shutdownPool();
|
|
1940
|
+
const fmt = resolveFormat2(opts.format);
|
|
1941
|
+
if (fmt === "json") {
|
|
1942
|
+
process.stdout.write(`${JSON.stringify(instrumentRoot, null, 2)}
|
|
1943
|
+
`);
|
|
1944
|
+
} else {
|
|
1945
|
+
const tree = formatInstrumentTree(instrumentRoot, opts.providerDepth ?? false);
|
|
1946
|
+
process.stdout.write(`${tree}
|
|
1947
|
+
`);
|
|
1948
|
+
}
|
|
1949
|
+
} catch (err) {
|
|
1950
|
+
await shutdownPool();
|
|
1951
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
1952
|
+
`);
|
|
1953
|
+
process.exit(1);
|
|
1954
|
+
}
|
|
1955
|
+
});
|
|
1956
|
+
}
|
|
1957
|
+
function resolveFormat2(formatFlag) {
|
|
1958
|
+
if (formatFlag !== void 0) {
|
|
1959
|
+
const lower = formatFlag.toLowerCase();
|
|
1960
|
+
if (lower !== "json" && lower !== "tree") {
|
|
1961
|
+
throw new Error(`Unknown format "${formatFlag}". Allowed: json, tree`);
|
|
1962
|
+
}
|
|
1963
|
+
return lower;
|
|
1964
|
+
}
|
|
1965
|
+
return isTTY() ? "tree" : "json";
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
// src/instrument/renders.ts
|
|
1969
|
+
var MANIFEST_PATH5 = ".reactscope/manifest.json";
|
|
1607
1970
|
function determineTrigger(event) {
|
|
1608
1971
|
if (event.forceUpdate) return "force_update";
|
|
1609
1972
|
if (event.stateChanged) return "state_change";
|
|
@@ -1904,26 +2267,26 @@ async function replayInteraction2(page, steps) {
|
|
|
1904
2267
|
}
|
|
1905
2268
|
}
|
|
1906
2269
|
}
|
|
1907
|
-
var
|
|
1908
|
-
async function
|
|
1909
|
-
if (
|
|
1910
|
-
|
|
2270
|
+
var _pool2 = null;
|
|
2271
|
+
async function getPool2() {
|
|
2272
|
+
if (_pool2 === null) {
|
|
2273
|
+
_pool2 = new BrowserPool2({
|
|
1911
2274
|
size: { browsers: 1, pagesPerBrowser: 2 },
|
|
1912
2275
|
viewportWidth: 1280,
|
|
1913
2276
|
viewportHeight: 800
|
|
1914
2277
|
});
|
|
1915
|
-
await
|
|
2278
|
+
await _pool2.init();
|
|
1916
2279
|
}
|
|
1917
|
-
return
|
|
2280
|
+
return _pool2;
|
|
1918
2281
|
}
|
|
1919
|
-
async function
|
|
1920
|
-
if (
|
|
1921
|
-
await
|
|
1922
|
-
|
|
2282
|
+
async function shutdownPool2() {
|
|
2283
|
+
if (_pool2 !== null) {
|
|
2284
|
+
await _pool2.close();
|
|
2285
|
+
_pool2 = null;
|
|
1923
2286
|
}
|
|
1924
2287
|
}
|
|
1925
2288
|
async function analyzeRenders(options) {
|
|
1926
|
-
const manifestPath = options.manifestPath ??
|
|
2289
|
+
const manifestPath = options.manifestPath ?? MANIFEST_PATH5;
|
|
1927
2290
|
const manifest = loadManifest(manifestPath);
|
|
1928
2291
|
const descriptor = manifest.components[options.componentName];
|
|
1929
2292
|
if (descriptor === void 0) {
|
|
@@ -1934,9 +2297,9 @@ Available: ${available}`
|
|
|
1934
2297
|
);
|
|
1935
2298
|
}
|
|
1936
2299
|
const rootDir = process.cwd();
|
|
1937
|
-
const filePath =
|
|
2300
|
+
const filePath = resolve5(rootDir, descriptor.filePath);
|
|
1938
2301
|
const htmlHarness = await buildComponentHarness(filePath, options.componentName, {}, 1280);
|
|
1939
|
-
const pool = await
|
|
2302
|
+
const pool = await getPool2();
|
|
1940
2303
|
const slot = await pool.acquire();
|
|
1941
2304
|
const { page } = slot;
|
|
1942
2305
|
const startMs = performance.now();
|
|
@@ -2017,11 +2380,11 @@ function formatRendersTable(result) {
|
|
|
2017
2380
|
return lines.join("\n");
|
|
2018
2381
|
}
|
|
2019
2382
|
function createInstrumentRendersCommand() {
|
|
2020
|
-
return new
|
|
2383
|
+
return new Command4("renders").description("Trace re-render causality chains for a component during an interaction sequence").argument("<component>", "Component name to instrument (must be in manifest)").option(
|
|
2021
2384
|
"--interaction <json>",
|
|
2022
2385
|
`Interaction sequence JSON, e.g. '[{"action":"click","target":"button"}]'`,
|
|
2023
2386
|
"[]"
|
|
2024
|
-
).option("--json", "Output as JSON regardless of TTY", false).option("--manifest <path>", "Path to manifest.json",
|
|
2387
|
+
).option("--json", "Output as JSON regardless of TTY", false).option("--manifest <path>", "Path to manifest.json", MANIFEST_PATH5).action(
|
|
2025
2388
|
async (componentName, opts) => {
|
|
2026
2389
|
let interaction = [];
|
|
2027
2390
|
try {
|
|
@@ -2044,7 +2407,7 @@ function createInstrumentRendersCommand() {
|
|
|
2044
2407
|
interaction,
|
|
2045
2408
|
manifestPath: opts.manifest
|
|
2046
2409
|
});
|
|
2047
|
-
await
|
|
2410
|
+
await shutdownPool2();
|
|
2048
2411
|
if (opts.json || !isTTY()) {
|
|
2049
2412
|
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
2050
2413
|
`);
|
|
@@ -2053,7 +2416,7 @@ function createInstrumentRendersCommand() {
|
|
|
2053
2416
|
`);
|
|
2054
2417
|
}
|
|
2055
2418
|
} catch (err) {
|
|
2056
|
-
await
|
|
2419
|
+
await shutdownPool2();
|
|
2057
2420
|
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
2058
2421
|
`);
|
|
2059
2422
|
process.exit(1);
|
|
@@ -2062,34 +2425,35 @@ function createInstrumentRendersCommand() {
|
|
|
2062
2425
|
);
|
|
2063
2426
|
}
|
|
2064
2427
|
function createInstrumentCommand() {
|
|
2065
|
-
const instrumentCmd = new
|
|
2428
|
+
const instrumentCmd = new Command4("instrument").description(
|
|
2066
2429
|
"Structured instrumentation commands for React component analysis"
|
|
2067
2430
|
);
|
|
2068
2431
|
instrumentCmd.addCommand(createInstrumentRendersCommand());
|
|
2069
2432
|
instrumentCmd.addCommand(createInstrumentHooksCommand());
|
|
2070
2433
|
instrumentCmd.addCommand(createInstrumentProfileCommand());
|
|
2434
|
+
instrumentCmd.addCommand(createInstrumentTreeCommand());
|
|
2071
2435
|
return instrumentCmd;
|
|
2072
2436
|
}
|
|
2073
2437
|
|
|
2074
2438
|
// src/render-commands.ts
|
|
2075
2439
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
2076
|
-
import { resolve as
|
|
2440
|
+
import { resolve as resolve7 } from "path";
|
|
2077
2441
|
import {
|
|
2078
2442
|
ALL_CONTEXT_IDS,
|
|
2079
2443
|
ALL_STRESS_IDS,
|
|
2080
|
-
BrowserPool as
|
|
2444
|
+
BrowserPool as BrowserPool3,
|
|
2081
2445
|
contextAxis,
|
|
2082
2446
|
RenderMatrix,
|
|
2083
2447
|
SatoriRenderer,
|
|
2084
2448
|
safeRender,
|
|
2085
2449
|
stressAxis
|
|
2086
2450
|
} from "@agent-scope/render";
|
|
2087
|
-
import { Command as
|
|
2451
|
+
import { Command as Command5 } from "commander";
|
|
2088
2452
|
|
|
2089
2453
|
// src/tailwind-css.ts
|
|
2090
2454
|
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
2091
2455
|
import { createRequire } from "module";
|
|
2092
|
-
import { resolve as
|
|
2456
|
+
import { resolve as resolve6 } from "path";
|
|
2093
2457
|
var CONFIG_FILENAMES = [
|
|
2094
2458
|
".reactscope/config.json",
|
|
2095
2459
|
".reactscope/config.js",
|
|
@@ -2106,14 +2470,14 @@ var STYLE_ENTRY_CANDIDATES = [
|
|
|
2106
2470
|
var TAILWIND_IMPORT = /@import\s+["']tailwindcss["']\s*;?/;
|
|
2107
2471
|
var compilerCache = null;
|
|
2108
2472
|
function getCachedBuild(cwd) {
|
|
2109
|
-
if (compilerCache !== null &&
|
|
2473
|
+
if (compilerCache !== null && resolve6(compilerCache.cwd) === resolve6(cwd)) {
|
|
2110
2474
|
return compilerCache.build;
|
|
2111
2475
|
}
|
|
2112
2476
|
return null;
|
|
2113
2477
|
}
|
|
2114
2478
|
function findStylesEntry(cwd) {
|
|
2115
2479
|
for (const name of CONFIG_FILENAMES) {
|
|
2116
|
-
const p =
|
|
2480
|
+
const p = resolve6(cwd, name);
|
|
2117
2481
|
if (!existsSync4(p)) continue;
|
|
2118
2482
|
try {
|
|
2119
2483
|
if (name.endsWith(".json")) {
|
|
@@ -2122,28 +2486,28 @@ function findStylesEntry(cwd) {
|
|
|
2122
2486
|
const scope = data.scope;
|
|
2123
2487
|
const entry = scope?.stylesEntry ?? data.stylesEntry;
|
|
2124
2488
|
if (typeof entry === "string") {
|
|
2125
|
-
const full =
|
|
2489
|
+
const full = resolve6(cwd, entry);
|
|
2126
2490
|
if (existsSync4(full)) return full;
|
|
2127
2491
|
}
|
|
2128
2492
|
}
|
|
2129
2493
|
} catch {
|
|
2130
2494
|
}
|
|
2131
2495
|
}
|
|
2132
|
-
const pkgPath =
|
|
2496
|
+
const pkgPath = resolve6(cwd, "package.json");
|
|
2133
2497
|
if (existsSync4(pkgPath)) {
|
|
2134
2498
|
try {
|
|
2135
2499
|
const raw = readFileSync4(pkgPath, "utf-8");
|
|
2136
2500
|
const pkg = JSON.parse(raw);
|
|
2137
2501
|
const entry = pkg.scope?.stylesEntry;
|
|
2138
2502
|
if (typeof entry === "string") {
|
|
2139
|
-
const full =
|
|
2503
|
+
const full = resolve6(cwd, entry);
|
|
2140
2504
|
if (existsSync4(full)) return full;
|
|
2141
2505
|
}
|
|
2142
2506
|
} catch {
|
|
2143
2507
|
}
|
|
2144
2508
|
}
|
|
2145
2509
|
for (const candidate of STYLE_ENTRY_CANDIDATES) {
|
|
2146
|
-
const full =
|
|
2510
|
+
const full = resolve6(cwd, candidate);
|
|
2147
2511
|
if (existsSync4(full)) {
|
|
2148
2512
|
try {
|
|
2149
2513
|
const content = readFileSync4(full, "utf-8");
|
|
@@ -2161,7 +2525,7 @@ async function getTailwindCompiler(cwd) {
|
|
|
2161
2525
|
if (entryPath === null) return null;
|
|
2162
2526
|
let compile;
|
|
2163
2527
|
try {
|
|
2164
|
-
const require2 = createRequire(
|
|
2528
|
+
const require2 = createRequire(resolve6(cwd, "package.json"));
|
|
2165
2529
|
const tailwind = require2("tailwindcss");
|
|
2166
2530
|
const fn = tailwind.compile;
|
|
2167
2531
|
if (typeof fn !== "function") return null;
|
|
@@ -2172,8 +2536,8 @@ async function getTailwindCompiler(cwd) {
|
|
|
2172
2536
|
const entryContent = readFileSync4(entryPath, "utf-8");
|
|
2173
2537
|
const loadStylesheet = async (id, base) => {
|
|
2174
2538
|
if (id === "tailwindcss") {
|
|
2175
|
-
const nodeModules =
|
|
2176
|
-
const tailwindCssPath =
|
|
2539
|
+
const nodeModules = resolve6(cwd, "node_modules");
|
|
2540
|
+
const tailwindCssPath = resolve6(nodeModules, "tailwindcss", "index.css");
|
|
2177
2541
|
if (!existsSync4(tailwindCssPath)) {
|
|
2178
2542
|
throw new Error(
|
|
2179
2543
|
`Tailwind v4: tailwindcss package not found at ${tailwindCssPath}. Install with: npm install tailwindcss`
|
|
@@ -2182,10 +2546,10 @@ async function getTailwindCompiler(cwd) {
|
|
|
2182
2546
|
const content = readFileSync4(tailwindCssPath, "utf-8");
|
|
2183
2547
|
return { path: "virtual:tailwindcss/index.css", base, content };
|
|
2184
2548
|
}
|
|
2185
|
-
const full =
|
|
2549
|
+
const full = resolve6(base, id);
|
|
2186
2550
|
if (existsSync4(full)) {
|
|
2187
2551
|
const content = readFileSync4(full, "utf-8");
|
|
2188
|
-
return { path: full, base:
|
|
2552
|
+
return { path: full, base: resolve6(full, ".."), content };
|
|
2189
2553
|
}
|
|
2190
2554
|
throw new Error(`Tailwind v4: could not load stylesheet: ${id} (base: ${base})`);
|
|
2191
2555
|
};
|
|
@@ -2207,24 +2571,24 @@ async function getCompiledCssForClasses(cwd, classes) {
|
|
|
2207
2571
|
}
|
|
2208
2572
|
|
|
2209
2573
|
// src/render-commands.ts
|
|
2210
|
-
var
|
|
2574
|
+
var MANIFEST_PATH6 = ".reactscope/manifest.json";
|
|
2211
2575
|
var DEFAULT_OUTPUT_DIR = ".reactscope/renders";
|
|
2212
|
-
var
|
|
2213
|
-
async function
|
|
2214
|
-
if (
|
|
2215
|
-
|
|
2576
|
+
var _pool3 = null;
|
|
2577
|
+
async function getPool3(viewportWidth, viewportHeight) {
|
|
2578
|
+
if (_pool3 === null) {
|
|
2579
|
+
_pool3 = new BrowserPool3({
|
|
2216
2580
|
size: { browsers: 1, pagesPerBrowser: 4 },
|
|
2217
2581
|
viewportWidth,
|
|
2218
2582
|
viewportHeight
|
|
2219
2583
|
});
|
|
2220
|
-
await
|
|
2584
|
+
await _pool3.init();
|
|
2221
2585
|
}
|
|
2222
|
-
return
|
|
2586
|
+
return _pool3;
|
|
2223
2587
|
}
|
|
2224
|
-
async function
|
|
2225
|
-
if (
|
|
2226
|
-
await
|
|
2227
|
-
|
|
2588
|
+
async function shutdownPool3() {
|
|
2589
|
+
if (_pool3 !== null) {
|
|
2590
|
+
await _pool3.close();
|
|
2591
|
+
_pool3 = null;
|
|
2228
2592
|
}
|
|
2229
2593
|
}
|
|
2230
2594
|
function buildRenderer(filePath, componentName, viewportWidth, viewportHeight) {
|
|
@@ -2235,7 +2599,7 @@ function buildRenderer(filePath, componentName, viewportWidth, viewportHeight) {
|
|
|
2235
2599
|
_satori: satori,
|
|
2236
2600
|
async renderCell(props, _complexityClass) {
|
|
2237
2601
|
const startMs = performance.now();
|
|
2238
|
-
const pool = await
|
|
2602
|
+
const pool = await getPool3(viewportWidth, viewportHeight);
|
|
2239
2603
|
const htmlHarness = await buildComponentHarness(
|
|
2240
2604
|
filePath,
|
|
2241
2605
|
componentName,
|
|
@@ -2332,7 +2696,7 @@ function buildRenderer(filePath, componentName, viewportWidth, viewportHeight) {
|
|
|
2332
2696
|
};
|
|
2333
2697
|
}
|
|
2334
2698
|
function registerRenderSingle(renderCmd) {
|
|
2335
|
-
renderCmd.command("component <component>", { isDefault: true }).description("Render a single component to PNG or JSON").option("--props <json>", `Inline props JSON, e.g. '{"variant":"primary"}'`).option("--viewport <WxH>", "Viewport size e.g. 1280x720", "375x812").option("--theme <name>", "Theme name from the token system").option("-o, --output <path>", "Write PNG to file instead of stdout").option("--format <fmt>", "Output format: png or json (default: auto)").option("--manifest <path>", "Path to manifest.json",
|
|
2699
|
+
renderCmd.command("component <component>", { isDefault: true }).description("Render a single component to PNG or JSON").option("--props <json>", `Inline props JSON, e.g. '{"variant":"primary"}'`).option("--viewport <WxH>", "Viewport size e.g. 1280x720", "375x812").option("--theme <name>", "Theme name from the token system").option("-o, --output <path>", "Write PNG to file instead of stdout").option("--format <fmt>", "Output format: png or json (default: auto)").option("--manifest <path>", "Path to manifest.json", MANIFEST_PATH6).action(
|
|
2336
2700
|
async (componentName, opts) => {
|
|
2337
2701
|
try {
|
|
2338
2702
|
const manifest = loadManifest(opts.manifest);
|
|
@@ -2354,7 +2718,7 @@ Available: ${available}`
|
|
|
2354
2718
|
}
|
|
2355
2719
|
const { width, height } = parseViewport(opts.viewport);
|
|
2356
2720
|
const rootDir = process.cwd();
|
|
2357
|
-
const filePath =
|
|
2721
|
+
const filePath = resolve7(rootDir, descriptor.filePath);
|
|
2358
2722
|
const renderer = buildRenderer(filePath, componentName, width, height);
|
|
2359
2723
|
process.stderr.write(
|
|
2360
2724
|
`Rendering ${componentName} [${descriptor.complexityClass}] at ${width}\xD7${height}\u2026
|
|
@@ -2371,7 +2735,7 @@ Available: ${available}`
|
|
|
2371
2735
|
}
|
|
2372
2736
|
}
|
|
2373
2737
|
);
|
|
2374
|
-
await
|
|
2738
|
+
await shutdownPool3();
|
|
2375
2739
|
if (outcome.crashed) {
|
|
2376
2740
|
process.stderr.write(`\u2717 Render failed: ${outcome.error.message}
|
|
2377
2741
|
`);
|
|
@@ -2384,7 +2748,7 @@ Available: ${available}`
|
|
|
2384
2748
|
}
|
|
2385
2749
|
const result = outcome.result;
|
|
2386
2750
|
if (opts.output !== void 0) {
|
|
2387
|
-
const outPath =
|
|
2751
|
+
const outPath = resolve7(process.cwd(), opts.output);
|
|
2388
2752
|
writeFileSync4(outPath, result.screenshot);
|
|
2389
2753
|
process.stdout.write(
|
|
2390
2754
|
`\u2713 ${componentName} \u2192 ${opts.output} (${result.width}\xD7${result.height}, ${result.renderTimeMs.toFixed(0)}ms)
|
|
@@ -2398,9 +2762,9 @@ Available: ${available}`
|
|
|
2398
2762
|
process.stdout.write(`${JSON.stringify(json, null, 2)}
|
|
2399
2763
|
`);
|
|
2400
2764
|
} else if (fmt === "file") {
|
|
2401
|
-
const dir =
|
|
2765
|
+
const dir = resolve7(process.cwd(), DEFAULT_OUTPUT_DIR);
|
|
2402
2766
|
mkdirSync3(dir, { recursive: true });
|
|
2403
|
-
const outPath =
|
|
2767
|
+
const outPath = resolve7(dir, `${componentName}.png`);
|
|
2404
2768
|
writeFileSync4(outPath, result.screenshot);
|
|
2405
2769
|
const relPath = `${DEFAULT_OUTPUT_DIR}/${componentName}.png`;
|
|
2406
2770
|
process.stdout.write(
|
|
@@ -2408,9 +2772,9 @@ Available: ${available}`
|
|
|
2408
2772
|
`
|
|
2409
2773
|
);
|
|
2410
2774
|
} else {
|
|
2411
|
-
const dir =
|
|
2775
|
+
const dir = resolve7(process.cwd(), DEFAULT_OUTPUT_DIR);
|
|
2412
2776
|
mkdirSync3(dir, { recursive: true });
|
|
2413
|
-
const outPath =
|
|
2777
|
+
const outPath = resolve7(dir, `${componentName}.png`);
|
|
2414
2778
|
writeFileSync4(outPath, result.screenshot);
|
|
2415
2779
|
const relPath = `${DEFAULT_OUTPUT_DIR}/${componentName}.png`;
|
|
2416
2780
|
process.stdout.write(
|
|
@@ -2419,7 +2783,7 @@ Available: ${available}`
|
|
|
2419
2783
|
);
|
|
2420
2784
|
}
|
|
2421
2785
|
} catch (err) {
|
|
2422
|
-
await
|
|
2786
|
+
await shutdownPool3();
|
|
2423
2787
|
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
2424
2788
|
`);
|
|
2425
2789
|
process.exit(1);
|
|
@@ -2431,7 +2795,7 @@ function registerRenderMatrix(renderCmd) {
|
|
|
2431
2795
|
renderCmd.command("matrix <component>").description("Render a component across a matrix of prop axes").option("--axes <spec>", "Axis definitions e.g. 'variant:primary,secondary size:sm,md,lg'").option(
|
|
2432
2796
|
"--contexts <ids>",
|
|
2433
2797
|
"Composition context IDs, comma-separated (e.g. centered,rtl,sidebar)"
|
|
2434
|
-
).option("--stress <ids>", "Stress preset IDs, comma-separated (e.g. text.long,text.unicode)").option("--sprite <path>", "Write sprite sheet PNG to file").option("--format <fmt>", "Output format: json|png|html|csv (default: auto)").option("--concurrency <n>", "Max parallel renders", "8").option("--manifest <path>", "Path to manifest.json",
|
|
2798
|
+
).option("--stress <ids>", "Stress preset IDs, comma-separated (e.g. text.long,text.unicode)").option("--sprite <path>", "Write sprite sheet PNG to file").option("--format <fmt>", "Output format: json|png|html|csv (default: auto)").option("--concurrency <n>", "Max parallel renders", "8").option("--manifest <path>", "Path to manifest.json", MANIFEST_PATH6).action(
|
|
2435
2799
|
async (componentName, opts) => {
|
|
2436
2800
|
try {
|
|
2437
2801
|
const manifest = loadManifest(opts.manifest);
|
|
@@ -2446,7 +2810,7 @@ Available: ${available}`
|
|
|
2446
2810
|
const concurrency = Math.max(1, parseInt(opts.concurrency, 10) || 8);
|
|
2447
2811
|
const { width, height } = { width: 375, height: 812 };
|
|
2448
2812
|
const rootDir = process.cwd();
|
|
2449
|
-
const filePath =
|
|
2813
|
+
const filePath = resolve7(rootDir, descriptor.filePath);
|
|
2450
2814
|
const renderer = buildRenderer(filePath, componentName, width, height);
|
|
2451
2815
|
const axes = [];
|
|
2452
2816
|
if (opts.axes !== void 0) {
|
|
@@ -2504,7 +2868,7 @@ Available: ${available}`
|
|
|
2504
2868
|
concurrency
|
|
2505
2869
|
});
|
|
2506
2870
|
const result = await matrix.render();
|
|
2507
|
-
await
|
|
2871
|
+
await shutdownPool3();
|
|
2508
2872
|
process.stderr.write(
|
|
2509
2873
|
`Done. ${result.stats.totalCells} cells, avg ${result.stats.avgRenderTimeMs.toFixed(1)}ms
|
|
2510
2874
|
`
|
|
@@ -2513,7 +2877,7 @@ Available: ${available}`
|
|
|
2513
2877
|
const { SpriteSheetGenerator } = await import("@agent-scope/render");
|
|
2514
2878
|
const gen = new SpriteSheetGenerator();
|
|
2515
2879
|
const sheet = await gen.generate(result);
|
|
2516
|
-
const spritePath =
|
|
2880
|
+
const spritePath = resolve7(process.cwd(), opts.sprite);
|
|
2517
2881
|
writeFileSync4(spritePath, sheet.png);
|
|
2518
2882
|
process.stderr.write(`Sprite sheet saved to ${spritePath}
|
|
2519
2883
|
`);
|
|
@@ -2523,9 +2887,9 @@ Available: ${available}`
|
|
|
2523
2887
|
const { SpriteSheetGenerator } = await import("@agent-scope/render");
|
|
2524
2888
|
const gen = new SpriteSheetGenerator();
|
|
2525
2889
|
const sheet = await gen.generate(result);
|
|
2526
|
-
const dir =
|
|
2890
|
+
const dir = resolve7(process.cwd(), DEFAULT_OUTPUT_DIR);
|
|
2527
2891
|
mkdirSync3(dir, { recursive: true });
|
|
2528
|
-
const outPath =
|
|
2892
|
+
const outPath = resolve7(dir, `${componentName}-matrix.png`);
|
|
2529
2893
|
writeFileSync4(outPath, sheet.png);
|
|
2530
2894
|
const relPath = `${DEFAULT_OUTPUT_DIR}/${componentName}-matrix.png`;
|
|
2531
2895
|
process.stdout.write(
|
|
@@ -2549,7 +2913,7 @@ Available: ${available}`
|
|
|
2549
2913
|
process.stdout.write(formatMatrixCsv(componentName, result));
|
|
2550
2914
|
}
|
|
2551
2915
|
} catch (err) {
|
|
2552
|
-
await
|
|
2916
|
+
await shutdownPool3();
|
|
2553
2917
|
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
2554
2918
|
`);
|
|
2555
2919
|
process.exit(1);
|
|
@@ -2558,7 +2922,7 @@ Available: ${available}`
|
|
|
2558
2922
|
);
|
|
2559
2923
|
}
|
|
2560
2924
|
function registerRenderAll(renderCmd) {
|
|
2561
|
-
renderCmd.command("all").description("Render all components from the manifest").option("--concurrency <n>", "Max parallel renders", "4").option("--output-dir <dir>", "Output directory for renders", DEFAULT_OUTPUT_DIR).option("--manifest <path>", "Path to manifest.json",
|
|
2925
|
+
renderCmd.command("all").description("Render all components from the manifest").option("--concurrency <n>", "Max parallel renders", "4").option("--output-dir <dir>", "Output directory for renders", DEFAULT_OUTPUT_DIR).option("--manifest <path>", "Path to manifest.json", MANIFEST_PATH6).option("--format <fmt>", "Output format: json|png (default: png)", "png").action(
|
|
2562
2926
|
async (opts) => {
|
|
2563
2927
|
try {
|
|
2564
2928
|
const manifest = loadManifest(opts.manifest);
|
|
@@ -2569,7 +2933,7 @@ function registerRenderAll(renderCmd) {
|
|
|
2569
2933
|
return;
|
|
2570
2934
|
}
|
|
2571
2935
|
const concurrency = Math.max(1, parseInt(opts.concurrency, 10) || 4);
|
|
2572
|
-
const outputDir =
|
|
2936
|
+
const outputDir = resolve7(process.cwd(), opts.outputDir);
|
|
2573
2937
|
mkdirSync3(outputDir, { recursive: true });
|
|
2574
2938
|
const rootDir = process.cwd();
|
|
2575
2939
|
process.stderr.write(`Rendering ${total} components (concurrency: ${concurrency})\u2026
|
|
@@ -2579,7 +2943,7 @@ function registerRenderAll(renderCmd) {
|
|
|
2579
2943
|
const renderOne = async (name) => {
|
|
2580
2944
|
const descriptor = manifest.components[name];
|
|
2581
2945
|
if (descriptor === void 0) return;
|
|
2582
|
-
const filePath =
|
|
2946
|
+
const filePath = resolve7(rootDir, descriptor.filePath);
|
|
2583
2947
|
const renderer = buildRenderer(filePath, name, 375, 812);
|
|
2584
2948
|
const outcome = await safeRender(
|
|
2585
2949
|
() => renderer.renderCell({}, descriptor.complexityClass),
|
|
@@ -2602,7 +2966,7 @@ function registerRenderAll(renderCmd) {
|
|
|
2602
2966
|
success: false,
|
|
2603
2967
|
errorMessage: outcome.error.message
|
|
2604
2968
|
});
|
|
2605
|
-
const errPath =
|
|
2969
|
+
const errPath = resolve7(outputDir, `${name}.error.json`);
|
|
2606
2970
|
writeFileSync4(
|
|
2607
2971
|
errPath,
|
|
2608
2972
|
JSON.stringify(
|
|
@@ -2620,9 +2984,9 @@ function registerRenderAll(renderCmd) {
|
|
|
2620
2984
|
}
|
|
2621
2985
|
const result = outcome.result;
|
|
2622
2986
|
results.push({ name, renderTimeMs: result.renderTimeMs, success: true });
|
|
2623
|
-
const pngPath =
|
|
2987
|
+
const pngPath = resolve7(outputDir, `${name}.png`);
|
|
2624
2988
|
writeFileSync4(pngPath, result.screenshot);
|
|
2625
|
-
const jsonPath =
|
|
2989
|
+
const jsonPath = resolve7(outputDir, `${name}.json`);
|
|
2626
2990
|
writeFileSync4(jsonPath, JSON.stringify(formatRenderJson(name, {}, result), null, 2));
|
|
2627
2991
|
if (isTTY()) {
|
|
2628
2992
|
process.stdout.write(
|
|
@@ -2646,13 +3010,13 @@ function registerRenderAll(renderCmd) {
|
|
|
2646
3010
|
workers.push(worker());
|
|
2647
3011
|
}
|
|
2648
3012
|
await Promise.all(workers);
|
|
2649
|
-
await
|
|
3013
|
+
await shutdownPool3();
|
|
2650
3014
|
process.stderr.write("\n");
|
|
2651
3015
|
const summary = formatSummaryText(results, outputDir);
|
|
2652
3016
|
process.stderr.write(`${summary}
|
|
2653
3017
|
`);
|
|
2654
3018
|
} catch (err) {
|
|
2655
|
-
await
|
|
3019
|
+
await shutdownPool3();
|
|
2656
3020
|
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
2657
3021
|
`);
|
|
2658
3022
|
process.exit(1);
|
|
@@ -2685,7 +3049,7 @@ function resolveMatrixFormat(formatFlag, spriteAlreadyWritten) {
|
|
|
2685
3049
|
return "json";
|
|
2686
3050
|
}
|
|
2687
3051
|
function createRenderCommand() {
|
|
2688
|
-
const renderCmd = new
|
|
3052
|
+
const renderCmd = new Command5("render").description(
|
|
2689
3053
|
"Render components to PNG or JSON via esbuild + BrowserPool"
|
|
2690
3054
|
);
|
|
2691
3055
|
registerRenderSingle(renderCmd);
|
|
@@ -2696,31 +3060,31 @@ function createRenderCommand() {
|
|
|
2696
3060
|
|
|
2697
3061
|
// src/report/baseline.ts
|
|
2698
3062
|
import { existsSync as existsSync5, mkdirSync as mkdirSync4, rmSync, writeFileSync as writeFileSync5 } from "fs";
|
|
2699
|
-
import { resolve as
|
|
3063
|
+
import { resolve as resolve8 } from "path";
|
|
2700
3064
|
import { generateManifest as generateManifest2 } from "@agent-scope/manifest";
|
|
2701
|
-
import { BrowserPool as
|
|
3065
|
+
import { BrowserPool as BrowserPool4, safeRender as safeRender2 } from "@agent-scope/render";
|
|
2702
3066
|
import { ComplianceEngine, TokenResolver } from "@agent-scope/tokens";
|
|
2703
3067
|
var DEFAULT_BASELINE_DIR = ".reactscope/baseline";
|
|
2704
|
-
var
|
|
2705
|
-
async function
|
|
2706
|
-
if (
|
|
2707
|
-
|
|
3068
|
+
var _pool4 = null;
|
|
3069
|
+
async function getPool4(viewportWidth, viewportHeight) {
|
|
3070
|
+
if (_pool4 === null) {
|
|
3071
|
+
_pool4 = new BrowserPool4({
|
|
2708
3072
|
size: { browsers: 1, pagesPerBrowser: 4 },
|
|
2709
3073
|
viewportWidth,
|
|
2710
3074
|
viewportHeight
|
|
2711
3075
|
});
|
|
2712
|
-
await
|
|
3076
|
+
await _pool4.init();
|
|
2713
3077
|
}
|
|
2714
|
-
return
|
|
3078
|
+
return _pool4;
|
|
2715
3079
|
}
|
|
2716
|
-
async function
|
|
2717
|
-
if (
|
|
2718
|
-
await
|
|
2719
|
-
|
|
3080
|
+
async function shutdownPool4() {
|
|
3081
|
+
if (_pool4 !== null) {
|
|
3082
|
+
await _pool4.close();
|
|
3083
|
+
_pool4 = null;
|
|
2720
3084
|
}
|
|
2721
3085
|
}
|
|
2722
3086
|
async function renderComponent(filePath, componentName, props, viewportWidth, viewportHeight) {
|
|
2723
|
-
const pool = await
|
|
3087
|
+
const pool = await getPool4(viewportWidth, viewportHeight);
|
|
2724
3088
|
const htmlHarness = await buildComponentHarness(filePath, componentName, props, viewportWidth);
|
|
2725
3089
|
const slot = await pool.acquire();
|
|
2726
3090
|
const { page } = slot;
|
|
@@ -2845,8 +3209,8 @@ async function runBaseline(options = {}) {
|
|
|
2845
3209
|
} = options;
|
|
2846
3210
|
const startTime = performance.now();
|
|
2847
3211
|
const rootDir = process.cwd();
|
|
2848
|
-
const baselineDir =
|
|
2849
|
-
const rendersDir =
|
|
3212
|
+
const baselineDir = resolve8(rootDir, outputDir);
|
|
3213
|
+
const rendersDir = resolve8(baselineDir, "renders");
|
|
2850
3214
|
if (existsSync5(baselineDir)) {
|
|
2851
3215
|
rmSync(baselineDir, { recursive: true, force: true });
|
|
2852
3216
|
}
|
|
@@ -2854,7 +3218,7 @@ async function runBaseline(options = {}) {
|
|
|
2854
3218
|
let manifest;
|
|
2855
3219
|
if (manifestPath !== void 0) {
|
|
2856
3220
|
const { readFileSync: readFileSync8 } = await import("fs");
|
|
2857
|
-
const absPath =
|
|
3221
|
+
const absPath = resolve8(rootDir, manifestPath);
|
|
2858
3222
|
if (!existsSync5(absPath)) {
|
|
2859
3223
|
throw new Error(`Manifest not found at ${absPath}.`);
|
|
2860
3224
|
}
|
|
@@ -2868,7 +3232,7 @@ async function runBaseline(options = {}) {
|
|
|
2868
3232
|
process.stderr.write(`Found ${count} components.
|
|
2869
3233
|
`);
|
|
2870
3234
|
}
|
|
2871
|
-
writeFileSync5(
|
|
3235
|
+
writeFileSync5(resolve8(baselineDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
|
|
2872
3236
|
let componentNames = Object.keys(manifest.components);
|
|
2873
3237
|
if (componentsGlob !== void 0) {
|
|
2874
3238
|
componentNames = componentNames.filter((name) => matchGlob(componentsGlob, name));
|
|
@@ -2889,7 +3253,7 @@ async function runBaseline(options = {}) {
|
|
|
2889
3253
|
auditedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2890
3254
|
};
|
|
2891
3255
|
writeFileSync5(
|
|
2892
|
-
|
|
3256
|
+
resolve8(baselineDir, "compliance.json"),
|
|
2893
3257
|
JSON.stringify(emptyReport, null, 2),
|
|
2894
3258
|
"utf-8"
|
|
2895
3259
|
);
|
|
@@ -2910,7 +3274,7 @@ async function runBaseline(options = {}) {
|
|
|
2910
3274
|
const renderOne = async (name) => {
|
|
2911
3275
|
const descriptor = manifest.components[name];
|
|
2912
3276
|
if (descriptor === void 0) return;
|
|
2913
|
-
const filePath =
|
|
3277
|
+
const filePath = resolve8(rootDir, descriptor.filePath);
|
|
2914
3278
|
const outcome = await safeRender2(
|
|
2915
3279
|
() => renderComponent(filePath, name, {}, viewportWidth, viewportHeight),
|
|
2916
3280
|
{
|
|
@@ -2929,7 +3293,7 @@ async function runBaseline(options = {}) {
|
|
|
2929
3293
|
}
|
|
2930
3294
|
if (outcome.crashed) {
|
|
2931
3295
|
failureCount++;
|
|
2932
|
-
const errPath =
|
|
3296
|
+
const errPath = resolve8(rendersDir, `${name}.error.json`);
|
|
2933
3297
|
writeFileSync5(
|
|
2934
3298
|
errPath,
|
|
2935
3299
|
JSON.stringify(
|
|
@@ -2947,10 +3311,10 @@ async function runBaseline(options = {}) {
|
|
|
2947
3311
|
return;
|
|
2948
3312
|
}
|
|
2949
3313
|
const result = outcome.result;
|
|
2950
|
-
writeFileSync5(
|
|
3314
|
+
writeFileSync5(resolve8(rendersDir, `${name}.png`), result.screenshot);
|
|
2951
3315
|
const jsonOutput = formatRenderJson(name, {}, result);
|
|
2952
3316
|
writeFileSync5(
|
|
2953
|
-
|
|
3317
|
+
resolve8(rendersDir, `${name}.json`),
|
|
2954
3318
|
JSON.stringify(jsonOutput, null, 2),
|
|
2955
3319
|
"utf-8"
|
|
2956
3320
|
);
|
|
@@ -2970,7 +3334,7 @@ async function runBaseline(options = {}) {
|
|
|
2970
3334
|
workers.push(worker());
|
|
2971
3335
|
}
|
|
2972
3336
|
await Promise.all(workers);
|
|
2973
|
-
await
|
|
3337
|
+
await shutdownPool4();
|
|
2974
3338
|
if (isTTY()) {
|
|
2975
3339
|
process.stderr.write("\n");
|
|
2976
3340
|
}
|
|
@@ -2978,7 +3342,7 @@ async function runBaseline(options = {}) {
|
|
|
2978
3342
|
const engine = new ComplianceEngine(resolver);
|
|
2979
3343
|
const batchReport = engine.auditBatch(computedStylesMap);
|
|
2980
3344
|
writeFileSync5(
|
|
2981
|
-
|
|
3345
|
+
resolve8(baselineDir, "compliance.json"),
|
|
2982
3346
|
JSON.stringify(batchReport, null, 2),
|
|
2983
3347
|
"utf-8"
|
|
2984
3348
|
);
|
|
@@ -3021,10 +3385,10 @@ function registerBaselineSubCommand(reportCmd) {
|
|
|
3021
3385
|
}
|
|
3022
3386
|
|
|
3023
3387
|
// src/tree-formatter.ts
|
|
3024
|
-
var
|
|
3025
|
-
var
|
|
3026
|
-
var
|
|
3027
|
-
var
|
|
3388
|
+
var BRANCH2 = "\u251C\u2500\u2500 ";
|
|
3389
|
+
var LAST_BRANCH2 = "\u2514\u2500\u2500 ";
|
|
3390
|
+
var VERTICAL2 = "\u2502 ";
|
|
3391
|
+
var EMPTY2 = " ";
|
|
3028
3392
|
function buildLabel(node, options) {
|
|
3029
3393
|
const parts = [node.name];
|
|
3030
3394
|
if (node.type === "memo") {
|
|
@@ -3072,19 +3436,19 @@ function renderNode(node, prefix, isLast, depth, options, lines) {
|
|
|
3072
3436
|
}
|
|
3073
3437
|
return;
|
|
3074
3438
|
}
|
|
3075
|
-
const connector = isLast ?
|
|
3439
|
+
const connector = isLast ? LAST_BRANCH2 : BRANCH2;
|
|
3076
3440
|
const label = buildLabel(node, options);
|
|
3077
3441
|
lines.push(`${prefix}${connector}${label}`);
|
|
3078
3442
|
if (options.maxDepth !== void 0 && depth >= options.maxDepth) {
|
|
3079
3443
|
const childCount = countVisibleDescendants(node, options);
|
|
3080
3444
|
if (childCount > 0) {
|
|
3081
|
-
const nextPrefix2 = prefix + (isLast ?
|
|
3082
|
-
lines.push(`${nextPrefix2}${
|
|
3445
|
+
const nextPrefix2 = prefix + (isLast ? EMPTY2 : VERTICAL2);
|
|
3446
|
+
lines.push(`${nextPrefix2}${LAST_BRANCH2}\u2026 (${childCount} more)`);
|
|
3083
3447
|
}
|
|
3084
3448
|
return;
|
|
3085
3449
|
}
|
|
3086
3450
|
const visibleChildren = getVisibleChildren(node, options);
|
|
3087
|
-
const nextPrefix = prefix + (isLast ?
|
|
3451
|
+
const nextPrefix = prefix + (isLast ? EMPTY2 : VERTICAL2);
|
|
3088
3452
|
for (let i = 0; i < visibleChildren.length; i++) {
|
|
3089
3453
|
const child = visibleChildren[i];
|
|
3090
3454
|
if (child !== void 0) {
|
|
@@ -3126,7 +3490,7 @@ function formatTree(root, options = {}) {
|
|
|
3126
3490
|
if (options.maxDepth === 0) {
|
|
3127
3491
|
const childCount = countVisibleDescendants(root, options);
|
|
3128
3492
|
if (childCount > 0) {
|
|
3129
|
-
lines.push(`${
|
|
3493
|
+
lines.push(`${LAST_BRANCH2}\u2026 (${childCount} more)`);
|
|
3130
3494
|
}
|
|
3131
3495
|
} else {
|
|
3132
3496
|
const visibleChildren = getVisibleChildren(root, options);
|
|
@@ -3301,7 +3665,7 @@ function buildStructuredReport(report) {
|
|
|
3301
3665
|
|
|
3302
3666
|
// src/tokens/commands.ts
|
|
3303
3667
|
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
|
|
3304
|
-
import { resolve as
|
|
3668
|
+
import { resolve as resolve10 } from "path";
|
|
3305
3669
|
import {
|
|
3306
3670
|
parseTokenFileSync as parseTokenFileSync2,
|
|
3307
3671
|
TokenParseError,
|
|
@@ -3309,41 +3673,41 @@ import {
|
|
|
3309
3673
|
TokenValidationError,
|
|
3310
3674
|
validateTokenFile
|
|
3311
3675
|
} from "@agent-scope/tokens";
|
|
3312
|
-
import { Command as
|
|
3676
|
+
import { Command as Command7 } from "commander";
|
|
3313
3677
|
|
|
3314
3678
|
// src/tokens/export.ts
|
|
3315
3679
|
import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
3316
|
-
import { resolve as
|
|
3680
|
+
import { resolve as resolve9 } from "path";
|
|
3317
3681
|
import {
|
|
3318
3682
|
exportTokens,
|
|
3319
3683
|
parseTokenFileSync,
|
|
3320
3684
|
ThemeResolver,
|
|
3321
3685
|
TokenResolver as TokenResolver2
|
|
3322
3686
|
} from "@agent-scope/tokens";
|
|
3323
|
-
import { Command as
|
|
3687
|
+
import { Command as Command6 } from "commander";
|
|
3324
3688
|
var DEFAULT_TOKEN_FILE = "reactscope.tokens.json";
|
|
3325
3689
|
var CONFIG_FILE = "reactscope.config.json";
|
|
3326
3690
|
var SUPPORTED_FORMATS = ["css", "ts", "scss", "tailwind", "flat-json", "figma"];
|
|
3327
3691
|
function resolveTokenFilePath(fileFlag) {
|
|
3328
3692
|
if (fileFlag !== void 0) {
|
|
3329
|
-
return
|
|
3693
|
+
return resolve9(process.cwd(), fileFlag);
|
|
3330
3694
|
}
|
|
3331
|
-
const configPath =
|
|
3695
|
+
const configPath = resolve9(process.cwd(), CONFIG_FILE);
|
|
3332
3696
|
if (existsSync6(configPath)) {
|
|
3333
3697
|
try {
|
|
3334
3698
|
const raw = readFileSync5(configPath, "utf-8");
|
|
3335
3699
|
const config = JSON.parse(raw);
|
|
3336
3700
|
if (typeof config === "object" && config !== null && "tokens" in config && typeof config.tokens === "object" && config.tokens !== null && typeof config.tokens?.file === "string") {
|
|
3337
3701
|
const file = config.tokens.file;
|
|
3338
|
-
return
|
|
3702
|
+
return resolve9(process.cwd(), file);
|
|
3339
3703
|
}
|
|
3340
3704
|
} catch {
|
|
3341
3705
|
}
|
|
3342
3706
|
}
|
|
3343
|
-
return
|
|
3707
|
+
return resolve9(process.cwd(), DEFAULT_TOKEN_FILE);
|
|
3344
3708
|
}
|
|
3345
3709
|
function createTokensExportCommand() {
|
|
3346
|
-
return new
|
|
3710
|
+
return new Command6("export").description("Export design tokens to a downstream format").requiredOption("--format <fmt>", `Output format: ${SUPPORTED_FORMATS.join(", ")}`).option("--file <path>", "Path to token file (overrides config)").option("--out <path>", "Write output to file instead of stdout").option("--prefix <prefix>", "CSS/SCSS: prefix for variable names (e.g. 'scope')").option("--selector <selector>", "CSS: custom root selector (default: ':root')").option(
|
|
3347
3711
|
"--theme <name>",
|
|
3348
3712
|
"Include theme overrides for the named theme (applies to css, ts, scss, tailwind, figma)"
|
|
3349
3713
|
).action(
|
|
@@ -3404,7 +3768,7 @@ Available themes: ${themeNames.join(", ")}`
|
|
|
3404
3768
|
themes: themesMap
|
|
3405
3769
|
});
|
|
3406
3770
|
if (opts.out !== void 0) {
|
|
3407
|
-
const outPath =
|
|
3771
|
+
const outPath = resolve9(process.cwd(), opts.out);
|
|
3408
3772
|
writeFileSync6(outPath, output, "utf-8");
|
|
3409
3773
|
process.stderr.write(`Exported ${tokens.length} tokens to ${outPath}
|
|
3410
3774
|
`);
|
|
@@ -3445,21 +3809,21 @@ function buildTable2(headers, rows) {
|
|
|
3445
3809
|
}
|
|
3446
3810
|
function resolveTokenFilePath2(fileFlag) {
|
|
3447
3811
|
if (fileFlag !== void 0) {
|
|
3448
|
-
return
|
|
3812
|
+
return resolve10(process.cwd(), fileFlag);
|
|
3449
3813
|
}
|
|
3450
|
-
const configPath =
|
|
3814
|
+
const configPath = resolve10(process.cwd(), CONFIG_FILE2);
|
|
3451
3815
|
if (existsSync7(configPath)) {
|
|
3452
3816
|
try {
|
|
3453
3817
|
const raw = readFileSync6(configPath, "utf-8");
|
|
3454
3818
|
const config = JSON.parse(raw);
|
|
3455
3819
|
if (typeof config === "object" && config !== null && "tokens" in config && typeof config.tokens === "object" && config.tokens !== null && typeof config.tokens?.file === "string") {
|
|
3456
3820
|
const file = config.tokens.file;
|
|
3457
|
-
return
|
|
3821
|
+
return resolve10(process.cwd(), file);
|
|
3458
3822
|
}
|
|
3459
3823
|
} catch {
|
|
3460
3824
|
}
|
|
3461
3825
|
}
|
|
3462
|
-
return
|
|
3826
|
+
return resolve10(process.cwd(), DEFAULT_TOKEN_FILE2);
|
|
3463
3827
|
}
|
|
3464
3828
|
function loadTokens(absPath) {
|
|
3465
3829
|
if (!existsSync7(absPath)) {
|
|
@@ -3756,7 +4120,7 @@ function outputValidationResult(filePath, errors, useJson) {
|
|
|
3756
4120
|
}
|
|
3757
4121
|
}
|
|
3758
4122
|
function createTokensCommand() {
|
|
3759
|
-
const tokensCmd = new
|
|
4123
|
+
const tokensCmd = new Command7("tokens").description(
|
|
3760
4124
|
"Query and validate design tokens from a reactscope.tokens.json file"
|
|
3761
4125
|
);
|
|
3762
4126
|
registerGet2(tokensCmd);
|
|
@@ -3770,7 +4134,7 @@ function createTokensCommand() {
|
|
|
3770
4134
|
|
|
3771
4135
|
// src/program.ts
|
|
3772
4136
|
function createProgram(options = {}) {
|
|
3773
|
-
const program2 = new
|
|
4137
|
+
const program2 = new Command8("scope").version(options.version ?? "0.1.0").description("Scope \u2014 React instrumentation toolkit");
|
|
3774
4138
|
program2.command("capture <url>").description("Capture a React component tree from a live URL and output as JSON").option("-o, --output <path>", "Write JSON to file instead of stdout").option("--pretty", "Pretty-print JSON output (default: minified)", false).option("--timeout <ms>", "Max wait time for React to mount (ms)", "10000").option("--wait <ms>", "Additional wait after page load before capture (ms)", "0").action(
|
|
3775
4139
|
async (url, opts) => {
|
|
3776
4140
|
try {
|