Compare commits
5 Commits
pr35135
...
mcp-sync-r
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
555f844195 | ||
|
|
c94e8b4461 | ||
|
|
6b04874535 | ||
|
|
1ba1485a65 | ||
|
|
2c1e4e4513 |
@@ -8,20 +8,14 @@
|
||||
import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||
import {z} from 'zod';
|
||||
import {compile, type PrintedCompilerPipelineValue} from './compiler';
|
||||
import {
|
||||
CompilerPipelineValue,
|
||||
printReactiveFunctionWithOutlined,
|
||||
printFunctionWithOutlined,
|
||||
PluginOptions,
|
||||
SourceLocation,
|
||||
} from 'babel-plugin-react-compiler/src';
|
||||
import * as cheerio from 'cheerio';
|
||||
import {queryAlgolia} from './utils/algolia';
|
||||
import assertExhaustive from './utils/assertExhaustive';
|
||||
import {convert} from 'html-to-text';
|
||||
import {measurePerformance} from './tools/runtimePerf';
|
||||
import {parseReactComponentTree} from './tools/componentTree';
|
||||
import {
|
||||
runtimePerfTool,
|
||||
componentTreeTool,
|
||||
compileTool,
|
||||
devDocsTool,
|
||||
} from './tools';
|
||||
export type {PassNameType} from './tools';
|
||||
|
||||
function calculateMean(values: number[]): string {
|
||||
return values.length > 0
|
||||
@@ -41,38 +35,27 @@ server.tool(
|
||||
query: z.string(),
|
||||
},
|
||||
async ({query}) => {
|
||||
try {
|
||||
const pages = await queryAlgolia(query);
|
||||
if (pages.length === 0) {
|
||||
const result = await devDocsTool(query);
|
||||
|
||||
switch (result.kind) {
|
||||
case 'success': {
|
||||
return {
|
||||
content: [{type: 'text' as const, text: `No results`}],
|
||||
isError: false,
|
||||
content: result.content.map(text => {
|
||||
return {
|
||||
type: 'text' as const,
|
||||
text: text,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
const content = pages.map(html => {
|
||||
const $ = cheerio.load(html);
|
||||
// react.dev should always have at least one <article> with the main content
|
||||
const article = $('article').html();
|
||||
if (article != null) {
|
||||
return {
|
||||
type: 'text' as const,
|
||||
text: convert(article),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
type: 'text' as const,
|
||||
// Fallback to converting the whole page to text.
|
||||
text: convert($.html()),
|
||||
};
|
||||
}
|
||||
});
|
||||
return {
|
||||
content,
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
isError: true,
|
||||
content: [{type: 'text' as const, text: `Error: ${err.stack}`}],
|
||||
};
|
||||
case 'error':
|
||||
return {
|
||||
isError: true,
|
||||
content: [{type: 'text' as const, text: result.text}],
|
||||
};
|
||||
default:
|
||||
assertExhaustive(result, `Unhandled result ${JSON.stringify(result)}`);
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -93,199 +76,47 @@ server.tool(
|
||||
passName: z.enum(['HIR', 'ReactiveFunction', 'All', '@DEBUG']).optional(),
|
||||
},
|
||||
async ({text, passName}) => {
|
||||
const pipelinePasses = new Map<
|
||||
string,
|
||||
Array<PrintedCompilerPipelineValue>
|
||||
>();
|
||||
const recordPass: (
|
||||
result: PrintedCompilerPipelineValue,
|
||||
) => void = result => {
|
||||
const entry = pipelinePasses.get(result.name);
|
||||
if (Array.isArray(entry)) {
|
||||
entry.push(result);
|
||||
} else {
|
||||
pipelinePasses.set(result.name, [result]);
|
||||
}
|
||||
};
|
||||
const logIR = (result: CompilerPipelineValue): void => {
|
||||
switch (result.kind) {
|
||||
case 'ast': {
|
||||
break;
|
||||
}
|
||||
case 'hir': {
|
||||
recordPass({
|
||||
kind: 'hir',
|
||||
fnName: result.value.id,
|
||||
name: result.name,
|
||||
value: printFunctionWithOutlined(result.value),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'reactive': {
|
||||
recordPass({
|
||||
kind: 'reactive',
|
||||
fnName: result.value.id,
|
||||
name: result.name,
|
||||
value: printReactiveFunctionWithOutlined(result.value),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'debug': {
|
||||
recordPass({
|
||||
kind: 'debug',
|
||||
fnName: null,
|
||||
name: result.name,
|
||||
value: result.value,
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(result, `Unhandled result ${result}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
const errors: Array<{message: string; loc: SourceLocation | null}> = [];
|
||||
const compilerOptions: Partial<PluginOptions> = {
|
||||
panicThreshold: 'none',
|
||||
logger: {
|
||||
debugLogIRs: logIR,
|
||||
logEvent: (_filename, event): void => {
|
||||
if (event.kind === 'CompileError') {
|
||||
const detail = event.detail;
|
||||
const loc =
|
||||
detail.loc == null || typeof detail.loc == 'symbol'
|
||||
? event.fnLoc
|
||||
: detail.loc;
|
||||
errors.push({
|
||||
message: detail.reason,
|
||||
loc,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
try {
|
||||
const result = await compile({
|
||||
text,
|
||||
file: 'anonymous.tsx',
|
||||
options: compilerOptions,
|
||||
});
|
||||
if (result.code == null) {
|
||||
const results = await compileTool(text, passName);
|
||||
|
||||
switch (results.kind) {
|
||||
case 'success': {
|
||||
return {
|
||||
isError: true,
|
||||
content: [{type: 'text' as const, text: 'Error: Could not compile'}],
|
||||
};
|
||||
}
|
||||
const requestedPasses: Array<{type: 'text'; text: string}> = [];
|
||||
if (passName != null) {
|
||||
switch (passName) {
|
||||
case 'All': {
|
||||
const hir = pipelinePasses.get('PropagateScopeDependenciesHIR');
|
||||
if (hir !== undefined) {
|
||||
for (const pipelineValue of hir) {
|
||||
requestedPasses.push({
|
||||
type: 'text' as const,
|
||||
text: pipelineValue.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
const reactiveFunc = pipelinePasses.get('PruneHoistedContexts');
|
||||
if (reactiveFunc !== undefined) {
|
||||
for (const pipelineValue of reactiveFunc) {
|
||||
requestedPasses.push({
|
||||
type: 'text' as const,
|
||||
text: pipelineValue.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'HIR': {
|
||||
// Last pass before HIR -> ReactiveFunction
|
||||
const requestedPass = pipelinePasses.get(
|
||||
'PropagateScopeDependenciesHIR',
|
||||
);
|
||||
if (requestedPass !== undefined) {
|
||||
for (const pipelineValue of requestedPass) {
|
||||
requestedPasses.push({
|
||||
type: 'text' as const,
|
||||
text: pipelineValue.value,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.error(`Could not find requested pass ${passName}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'ReactiveFunction': {
|
||||
// Last pass
|
||||
const requestedPass = pipelinePasses.get('PruneHoistedContexts');
|
||||
if (requestedPass !== undefined) {
|
||||
for (const pipelineValue of requestedPass) {
|
||||
requestedPasses.push({
|
||||
type: 'text' as const,
|
||||
text: pipelineValue.value,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.error(`Could not find requested pass ${passName}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '@DEBUG': {
|
||||
for (const [, pipelinePass] of pipelinePasses) {
|
||||
for (const pass of pipelinePass) {
|
||||
requestedPasses.push({
|
||||
type: 'text' as const,
|
||||
text: `${pass.name}\n\n${pass.value}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
passName,
|
||||
`Unhandled passName option: ${passName}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
const requestedPass = pipelinePasses.get(passName);
|
||||
if (requestedPass !== undefined) {
|
||||
for (const pipelineValue of requestedPass) {
|
||||
if (pipelineValue.name === passName) {
|
||||
requestedPasses.push({
|
||||
type: 'text' as const,
|
||||
text: pipelineValue.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (errors.length > 0) {
|
||||
return {
|
||||
content: errors.map(err => {
|
||||
isError: false,
|
||||
content: results.content.map(text => {
|
||||
return {
|
||||
type: 'text' as const,
|
||||
text:
|
||||
err.loc === null || typeof err.loc === 'symbol'
|
||||
? `React Compiler bailed out:\n\n${err.message}`
|
||||
: `React Compiler bailed out:\n\n${err.message}@${err.loc.start.line}:${err.loc.end.line}`,
|
||||
text,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{type: 'text' as const, text: result.code},
|
||||
...requestedPasses,
|
||||
],
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
isError: true,
|
||||
content: [{type: 'text' as const, text: `Error: ${err.stack}`}],
|
||||
};
|
||||
case 'bailout': {
|
||||
return {
|
||||
isError: true,
|
||||
content: results.content.map(text => {
|
||||
return {
|
||||
type: 'text' as const,
|
||||
text,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
case 'error':
|
||||
case 'compile-error':
|
||||
return {
|
||||
isError: true,
|
||||
content: [
|
||||
{
|
||||
type: 'text' as const,
|
||||
text: results.text,
|
||||
},
|
||||
],
|
||||
};
|
||||
default:
|
||||
assertExhaustive(
|
||||
results,
|
||||
`Unhandled result ${JSON.stringify(results)}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -328,7 +159,7 @@ server.tool(
|
||||
},
|
||||
async ({text, iterations}) => {
|
||||
try {
|
||||
const results = await measurePerformance(text, iterations);
|
||||
const results = await runtimePerfTool(text, iterations);
|
||||
const formattedResults = `
|
||||
# React Component Performance Results
|
||||
|
||||
@@ -387,7 +218,7 @@ server.tool(
|
||||
},
|
||||
async ({url}) => {
|
||||
try {
|
||||
const componentTree = await parseReactComponentTree(url);
|
||||
const componentTree = await componentTreeTool(url);
|
||||
|
||||
return {
|
||||
content: [
|
||||
@@ -491,7 +322,17 @@ async function main() {
|
||||
console.error('React Compiler MCP Server running on stdio');
|
||||
}
|
||||
|
||||
main().catch(error => {
|
||||
console.error('Fatal error in main():', error);
|
||||
process.exit(1);
|
||||
});
|
||||
if (require.main !== module) {
|
||||
main().catch(error => {
|
||||
console.error('Fatal error in main():', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
compileTool,
|
||||
componentTreeTool,
|
||||
devDocsTool,
|
||||
runtimePerfTool,
|
||||
assertExhaustive,
|
||||
};
|
||||
|
||||
203
compiler/packages/react-mcp-server/src/tools/compileTool.ts
Normal file
203
compiler/packages/react-mcp-server/src/tools/compileTool.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
import {compile, type PrintedCompilerPipelineValue} from '../compiler';
|
||||
import {
|
||||
CompilerPipelineValue,
|
||||
printReactiveFunctionWithOutlined,
|
||||
printFunctionWithOutlined,
|
||||
PluginOptions,
|
||||
SourceLocation,
|
||||
} from 'babel-plugin-react-compiler/src';
|
||||
import assertExhaustive from '../utils/assertExhaustive';
|
||||
|
||||
export type PassNameType =
|
||||
| 'HIR'
|
||||
| 'ReactiveFunction'
|
||||
| 'All'
|
||||
| '@DEBUG'
|
||||
| undefined;
|
||||
|
||||
type CompilerToolOutput =
|
||||
| {
|
||||
kind: 'success';
|
||||
content: Array<string>;
|
||||
}
|
||||
| {
|
||||
kind: 'bailout';
|
||||
content: Array<string>;
|
||||
}
|
||||
| {
|
||||
kind: 'compile-error';
|
||||
text: string;
|
||||
}
|
||||
| {
|
||||
kind: 'error';
|
||||
text: string;
|
||||
};
|
||||
|
||||
export async function compileTool(
|
||||
text: string,
|
||||
passName: PassNameType,
|
||||
): Promise<CompilerToolOutput> {
|
||||
const pipelinePasses = new Map<string, Array<PrintedCompilerPipelineValue>>();
|
||||
const recordPass: (result: PrintedCompilerPipelineValue) => void = result => {
|
||||
const entry = pipelinePasses.get(result.name);
|
||||
if (Array.isArray(entry)) {
|
||||
entry.push(result);
|
||||
} else {
|
||||
pipelinePasses.set(result.name, [result]);
|
||||
}
|
||||
};
|
||||
const logIR = (result: CompilerPipelineValue): void => {
|
||||
switch (result.kind) {
|
||||
case 'ast': {
|
||||
break;
|
||||
}
|
||||
case 'hir': {
|
||||
recordPass({
|
||||
kind: 'hir',
|
||||
fnName: result.value.id,
|
||||
name: result.name,
|
||||
value: printFunctionWithOutlined(result.value),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'reactive': {
|
||||
recordPass({
|
||||
kind: 'reactive',
|
||||
fnName: result.value.id,
|
||||
name: result.name,
|
||||
value: printReactiveFunctionWithOutlined(result.value),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'debug': {
|
||||
recordPass({
|
||||
kind: 'debug',
|
||||
fnName: null,
|
||||
name: result.name,
|
||||
value: result.value,
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(result, `Unhandled result ${result}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
const errors: Array<{message: string; loc: SourceLocation | null}> = [];
|
||||
const compilerOptions: Partial<PluginOptions> = {
|
||||
panicThreshold: 'none',
|
||||
logger: {
|
||||
debugLogIRs: logIR,
|
||||
logEvent: (_filename, event): void => {
|
||||
if (event.kind === 'CompileError') {
|
||||
const detail = event.detail;
|
||||
const loc =
|
||||
detail.loc == null || typeof detail.loc == 'symbol'
|
||||
? event.fnLoc
|
||||
: detail.loc;
|
||||
errors.push({
|
||||
message: detail.reason,
|
||||
loc,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
try {
|
||||
const result = await compile({
|
||||
text,
|
||||
file: 'anonymous.tsx',
|
||||
options: compilerOptions,
|
||||
});
|
||||
if (result.code == null) {
|
||||
return {
|
||||
kind: 'compile-error',
|
||||
text: 'Error: Could not compile',
|
||||
};
|
||||
}
|
||||
const requestedPasses: Array<string> = [];
|
||||
if (passName != null) {
|
||||
switch (passName) {
|
||||
case 'All': {
|
||||
const hir = pipelinePasses.get('PropagateScopeDependenciesHIR');
|
||||
if (hir !== undefined) {
|
||||
for (const pipelineValue of hir) {
|
||||
requestedPasses.push(pipelineValue.value);
|
||||
}
|
||||
}
|
||||
const reactiveFunc = pipelinePasses.get('PruneHoistedContexts');
|
||||
if (reactiveFunc !== undefined) {
|
||||
for (const pipelineValue of reactiveFunc) {
|
||||
requestedPasses.push(pipelineValue.value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'HIR': {
|
||||
// Last pass before HIR -> ReactiveFunction
|
||||
const requestedPass = pipelinePasses.get(
|
||||
'PropagateScopeDependenciesHIR',
|
||||
);
|
||||
if (requestedPass !== undefined) {
|
||||
for (const pipelineValue of requestedPass) {
|
||||
requestedPasses.push(pipelineValue.value);
|
||||
}
|
||||
} else {
|
||||
console.error(`Could not find requested pass ${passName}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'ReactiveFunction': {
|
||||
// Last pass
|
||||
const requestedPass = pipelinePasses.get('PruneHoistedContexts');
|
||||
if (requestedPass !== undefined) {
|
||||
for (const pipelineValue of requestedPass) {
|
||||
requestedPasses.push(pipelineValue.value);
|
||||
}
|
||||
} else {
|
||||
console.error(`Could not find requested pass ${passName}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '@DEBUG': {
|
||||
for (const [, pipelinePass] of pipelinePasses) {
|
||||
for (const pass of pipelinePass) {
|
||||
requestedPasses.push(`${pass.name}\n\n${pass.value}`);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(passName, `Unhandled passName option: ${passName}`);
|
||||
}
|
||||
}
|
||||
const requestedPass = pipelinePasses.get(passName);
|
||||
if (requestedPass !== undefined) {
|
||||
for (const pipelineValue of requestedPass) {
|
||||
if (pipelineValue.name === passName) {
|
||||
requestedPasses.push(pipelineValue.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (errors.length > 0) {
|
||||
return {
|
||||
kind: 'bailout',
|
||||
content: errors.map(err => {
|
||||
return err.loc === null || typeof err.loc === 'symbol'
|
||||
? `React Compiler bailed out:\n\n${err.message}`
|
||||
: `React Compiler bailed out:\n\n${err.message}@${err.loc.start.line}:${err.loc.end.line}`;
|
||||
}),
|
||||
};
|
||||
}
|
||||
return {
|
||||
kind: 'success',
|
||||
content: [result.code, ...requestedPasses],
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
kind: 'error',
|
||||
text: `Error: ${err.stack}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import puppeteer from 'puppeteer';
|
||||
|
||||
export async function parseReactComponentTree(url: string): Promise<string> {
|
||||
export async function componentTreeTool(url: string): Promise<string> {
|
||||
try {
|
||||
const browser = await puppeteer.connect({
|
||||
browserURL: 'http://127.0.0.1:9222',
|
||||
49
compiler/packages/react-mcp-server/src/tools/devDocsTool.ts
Normal file
49
compiler/packages/react-mcp-server/src/tools/devDocsTool.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import * as cheerio from 'cheerio';
|
||||
import {convert} from 'html-to-text';
|
||||
import {queryAlgolia} from '../utils/algolia';
|
||||
|
||||
type DevDocsToolOutput =
|
||||
| {
|
||||
kind: 'success';
|
||||
content: Array<string>;
|
||||
}
|
||||
| {
|
||||
kind: 'error';
|
||||
text: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Tool for querying React dev docs from react.dev
|
||||
* @param query The search query to look up in the React documentation
|
||||
* @returns A promise that resolves to the search results
|
||||
*/
|
||||
export async function devDocsTool(query: string): Promise<DevDocsToolOutput> {
|
||||
try {
|
||||
const pages = await queryAlgolia(query);
|
||||
if (pages.length === 0) {
|
||||
return {
|
||||
kind: 'error',
|
||||
text: `No results`,
|
||||
};
|
||||
}
|
||||
const content = pages.map(html => {
|
||||
const $ = cheerio.load(html);
|
||||
// react.dev should always have at least one <article> with the main content
|
||||
const article = $('article').html();
|
||||
if (article != null) {
|
||||
return convert(article);
|
||||
} else {
|
||||
return convert($.html());
|
||||
}
|
||||
});
|
||||
return {
|
||||
kind: 'success',
|
||||
content,
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
kind: 'error',
|
||||
text: `Error: ${err.stack}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
11
compiler/packages/react-mcp-server/src/tools/index.ts
Normal file
11
compiler/packages/react-mcp-server/src/tools/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
export * from './runtimePerfTool';
|
||||
export * from './componentTreeTool';
|
||||
export * from './compileTool';
|
||||
export * from './devDocsTool';
|
||||
@@ -53,7 +53,7 @@ function delay(time: number) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function measurePerformance(
|
||||
export async function runtimePerfTool(
|
||||
code: string,
|
||||
iterations: number,
|
||||
): Promise<PerformanceResults> {
|
||||
@@ -69,23 +69,27 @@ export async function measurePerformance(
|
||||
throw new Error('Failed to parse code');
|
||||
}
|
||||
|
||||
const transformResult = await babel.transformFromAstAsync(parsed, undefined, {
|
||||
...babelOptions,
|
||||
plugins: [
|
||||
() => ({
|
||||
visitor: {
|
||||
ImportDeclaration(
|
||||
path: babel.NodePath<babel.types.ImportDeclaration>,
|
||||
) {
|
||||
const value = path.node.source.value;
|
||||
if (value === 'react' || value === 'react-dom') {
|
||||
path.remove();
|
||||
}
|
||||
const transformResult = await babel.transformFromAstAsync(
|
||||
parsed as babel.types.Node,
|
||||
undefined,
|
||||
{
|
||||
...babelOptions,
|
||||
plugins: [
|
||||
() => ({
|
||||
visitor: {
|
||||
ImportDeclaration(
|
||||
path: babel.NodePath<babel.types.ImportDeclaration>,
|
||||
) {
|
||||
const value = path.node.source.value;
|
||||
if (value === 'react' || value === 'react-dom') {
|
||||
path.remove();
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
}),
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
const transpiled = transformResult?.code || undefined;
|
||||
if (!transpiled) {
|
||||
@@ -125,14 +129,16 @@ export async function measurePerformance(
|
||||
|
||||
// ui chaos monkey
|
||||
const selectors = await page.evaluate(() => {
|
||||
window.__INTERACTABLE_SELECTORS__ = [];
|
||||
(window as any).__INTERACTABLE_SELECTORS__ = [];
|
||||
const elements = Array.from(document.querySelectorAll('a')).concat(
|
||||
Array.from(document.querySelectorAll('button')),
|
||||
Array.from(document.querySelectorAll('button')) as any,
|
||||
);
|
||||
for (const el of elements) {
|
||||
window.__INTERACTABLE_SELECTORS__.push(el.tagName.toLowerCase());
|
||||
(window as any).__INTERACTABLE_SELECTORS__.push(
|
||||
el.tagName.toLowerCase(),
|
||||
);
|
||||
}
|
||||
return window.__INTERACTABLE_SELECTORS__;
|
||||
return (window as any).__INTERACTABLE_SELECTORS__;
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
38
compiler/packages/react-tools-cli/package.json
Normal file
38
compiler/packages/react-tools-cli/package.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "react-tools-cli",
|
||||
"version": "0.0.0",
|
||||
"description": "CLI to execute react-mcp-server tools in isolation from the full mcp server",
|
||||
"bin": {
|
||||
"react-tools-cli": "./dist/index.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "rimraf dist && tsup"
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/facebook/react.git",
|
||||
"directory": "compiler/packages/react-tools-cli"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.26.0",
|
||||
"@babel/parser": "^7.26",
|
||||
"@babel/preset-env": "^7.26.9",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@babel/preset-typescript": "^7.27.1",
|
||||
"@modelcontextprotocol/sdk": "^1.9.0",
|
||||
"algoliasearch": "^5.23.3",
|
||||
"cheerio": "^1.0.0",
|
||||
"html-to-text": "^9.0.5",
|
||||
"prettier": "^3.3.3",
|
||||
"puppeteer": "^24.7.2",
|
||||
"yargs": "^18.0.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/html-to-text": "^9.0.4",
|
||||
"@types/jest": "^29.5.14",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.3.2"
|
||||
}
|
||||
}
|
||||
240
compiler/packages/react-tools-cli/src/index.ts
Normal file
240
compiler/packages/react-tools-cli/src/index.ts
Normal file
@@ -0,0 +1,240 @@
|
||||
import {
|
||||
PassNameType,
|
||||
assertExhaustive,
|
||||
compileTool,
|
||||
componentTreeTool,
|
||||
devDocsTool,
|
||||
runtimePerfTool,
|
||||
} from 'react-mcp-server/src';
|
||||
|
||||
import yargs from 'yargs';
|
||||
import {hideBin} from 'yargs/helpers';
|
||||
|
||||
yargs(hideBin(process.argv))
|
||||
.scriptName('react-tools-cli')
|
||||
.usage('$0 <cmd> [args]')
|
||||
.command(
|
||||
'compile [code] [pass-name]',
|
||||
'Compile React code with React Compiler',
|
||||
yargs => {
|
||||
yargs
|
||||
.positional('code', {
|
||||
type: 'string',
|
||||
describe: 'The code to compile',
|
||||
})
|
||||
.option('pass-name', {
|
||||
type: 'string',
|
||||
choices: ['HIR', 'ReactiveFunction', 'All', '@DEBUG'],
|
||||
describe: 'Compiler pass to run',
|
||||
});
|
||||
},
|
||||
async function (argv) {
|
||||
const code: string = String(argv['code'] ?? '');
|
||||
const passName: PassNameType = argv['pass-name'] as PassNameType;
|
||||
|
||||
const results = await compileTool(code, passName);
|
||||
|
||||
switch (results.kind) {
|
||||
case 'success': {
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
isError: false,
|
||||
content: results.content.map(text => {
|
||||
return {
|
||||
type: 'text' as const,
|
||||
text,
|
||||
};
|
||||
}),
|
||||
}),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'bailout': {
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
isError: true,
|
||||
content: results.content.map(text => {
|
||||
return {
|
||||
type: 'text' as const,
|
||||
text,
|
||||
};
|
||||
}),
|
||||
}),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
case 'error':
|
||||
case 'compile-error':
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
isError: true,
|
||||
content: [
|
||||
{
|
||||
type: 'text' as const,
|
||||
text: results.text,
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
process.exit(1);
|
||||
default:
|
||||
assertExhaustive(
|
||||
results,
|
||||
`Unhandled result ${JSON.stringify(results)}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
.command(
|
||||
'query-docs [query]',
|
||||
'Compile React code with React Compiler',
|
||||
yargs => {
|
||||
yargs.positional('query', {
|
||||
type: 'string',
|
||||
describe: 'Browse oficcial React documentation for a given query',
|
||||
});
|
||||
},
|
||||
async function (argv) {
|
||||
const query: string = String(argv['query'] ?? '');
|
||||
|
||||
const result = await devDocsTool(query);
|
||||
|
||||
switch (result.kind) {
|
||||
case 'success':
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
isError: false,
|
||||
content: result.content.map(text => {
|
||||
return {
|
||||
type: 'text' as const,
|
||||
text: text,
|
||||
};
|
||||
}),
|
||||
}),
|
||||
);
|
||||
break;
|
||||
case 'error':
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
isError: true,
|
||||
content: [{type: 'text' as const, text: result.text}],
|
||||
}),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
assertExhaustive(
|
||||
result,
|
||||
`Unhandled result ${JSON.stringify(result)}`,
|
||||
);
|
||||
}
|
||||
process.exit(1);
|
||||
},
|
||||
)
|
||||
.command(
|
||||
'get-component-tree [url]',
|
||||
'Get the React component tree for a given URL',
|
||||
yargs => {
|
||||
yargs.positional('url', {
|
||||
type: 'string',
|
||||
default: 'https://localhost:3000',
|
||||
describe: 'URL for a React App to get the component tree for',
|
||||
});
|
||||
},
|
||||
async function (argv) {
|
||||
const url: string = String(argv['url']);
|
||||
try {
|
||||
const result = await componentTreeTool(url);
|
||||
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
content: [
|
||||
{
|
||||
type: 'text' as const,
|
||||
text: result,
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
} catch (err) {
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
isError: true,
|
||||
content: [{type: 'text' as const, text: `Error: ${err.stack}`}],
|
||||
}),
|
||||
);
|
||||
}
|
||||
process.exit(1);
|
||||
},
|
||||
)
|
||||
.command(
|
||||
'review-code-runtime [code] [iterations',
|
||||
'Get the React component tree for a given URL',
|
||||
yargs => {
|
||||
yargs.positional('code', {
|
||||
type: 'string',
|
||||
default: '',
|
||||
describe: 'React code to run',
|
||||
});
|
||||
yargs.positional('iterations', {
|
||||
type: 'number',
|
||||
default: 10,
|
||||
describe: 'Number of iterations to run the code for',
|
||||
});
|
||||
},
|
||||
async function (argv) {
|
||||
const code: string = String(argv['code']);
|
||||
const iterations: number = Number(argv['iterations']);
|
||||
|
||||
try {
|
||||
const results = await runtimePerfTool(code, iterations);
|
||||
|
||||
const formattedResults = `
|
||||
# React Component Performance Results
|
||||
|
||||
## Mean Render Time
|
||||
${calculateMean(results.renderTime)}
|
||||
|
||||
## Mean Web Vitals
|
||||
- Cumulative Layout Shift (CLS): ${calculateMean(results.webVitals.cls)}
|
||||
- Largest Contentful Paint (LCP): ${calculateMean(results.webVitals.lcp)}
|
||||
- Interaction to Next Paint (INP): ${calculateMean(results.webVitals.inp)}
|
||||
|
||||
## Mean React Profiler
|
||||
- Actual Duration: ${calculateMean(results.reactProfiler.actualDuration)}
|
||||
- Base Duration: ${calculateMean(results.reactProfiler.baseDuration)}
|
||||
`;
|
||||
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
content: [
|
||||
{
|
||||
type: 'text' as const,
|
||||
text: formattedResults,
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
isError: true,
|
||||
content: [
|
||||
{
|
||||
type: 'text' as const,
|
||||
text: `Error measuring performance: ${error.message}\n\n${error.stack}`,
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
}
|
||||
process.exit(1);
|
||||
},
|
||||
)
|
||||
.help()
|
||||
.parse();
|
||||
|
||||
function calculateMean(values: number[]): string {
|
||||
return values.length > 0
|
||||
? values.reduce((acc, curr) => acc + curr, 0) / values.length + 'ms'
|
||||
: 'could not collect';
|
||||
}
|
||||
22
compiler/packages/react-tools-cli/tsconfig.json
Normal file
22
compiler/packages/react-tools-cli/tsconfig.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"extends": "@tsconfig/strictest/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "Node16",
|
||||
"moduleResolution": "Node16",
|
||||
"rootDir": "../",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsxdev",
|
||||
"lib": ["ES2022"],
|
||||
|
||||
// weaken strictness from preset
|
||||
"importsNotUsedAsValues": "remove",
|
||||
"noUncheckedIndexedAccess": false,
|
||||
"noUnusedParameters": false,
|
||||
"useUnknownInCatchVariables": false,
|
||||
"target": "ES2022",
|
||||
// ideally turn off only during dev, or on a per-file basis
|
||||
"noUnusedLocals": false,
|
||||
},
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["src/**/*.ts"],
|
||||
}
|
||||
37
compiler/packages/react-tools-cli/tsup.config.ts
Normal file
37
compiler/packages/react-tools-cli/tsup.config.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {defineConfig} from 'tsup';
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['./src/index.ts'],
|
||||
outDir: './dist',
|
||||
external: [],
|
||||
splitting: false,
|
||||
sourcemap: false,
|
||||
dts: false,
|
||||
bundle: true,
|
||||
format: 'cjs',
|
||||
platform: 'node',
|
||||
target: 'es2022',
|
||||
banner: {
|
||||
js: `#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @lightSyntaxTransform
|
||||
* @noflow
|
||||
* @nolint
|
||||
* @preventMunge
|
||||
* @preserve-invariant-messages
|
||||
*/`,
|
||||
},
|
||||
});
|
||||
@@ -542,11 +542,6 @@
|
||||
resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz"
|
||||
integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==
|
||||
|
||||
"@babel/helper-string-parser@^7.27.1":
|
||||
version "7.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687"
|
||||
integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==
|
||||
|
||||
"@babel/helper-validator-identifier@^7.19.1", "@babel/helper-validator-identifier@^7.25.9":
|
||||
version "7.25.9"
|
||||
resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz"
|
||||
@@ -1605,7 +1600,7 @@
|
||||
debug "^4.3.1"
|
||||
globals "^11.1.0"
|
||||
|
||||
"@babel/types@^7.0.0", "@babel/types@^7.19.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.21.2", "@babel/types@^7.24.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.3", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.4":
|
||||
"@babel/types@7.26.3", "@babel/types@^7.0.0", "@babel/types@^7.19.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.21.2", "@babel/types@^7.24.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.10", "@babel/types@^7.26.3", "@babel/types@^7.27.0", "@babel/types@^7.27.1", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.4":
|
||||
version "7.26.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.3.tgz#37e79830f04c2b5687acc77db97fbc75fb81f3c0"
|
||||
integrity sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==
|
||||
@@ -1613,14 +1608,6 @@
|
||||
"@babel/helper-string-parser" "^7.25.9"
|
||||
"@babel/helper-validator-identifier" "^7.25.9"
|
||||
|
||||
"@babel/types@^7.26.10", "@babel/types@^7.27.0", "@babel/types@^7.27.1":
|
||||
version "7.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.1.tgz#9defc53c16fc899e46941fc6901a9eea1c9d8560"
|
||||
integrity sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==
|
||||
dependencies:
|
||||
"@babel/helper-string-parser" "^7.27.1"
|
||||
"@babel/helper-validator-identifier" "^7.27.1"
|
||||
|
||||
"@bcoe/v8-coverage@^0.2.3":
|
||||
version "0.2.3"
|
||||
resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz"
|
||||
@@ -3745,7 +3732,7 @@ ansi-styles@^5.0.0:
|
||||
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz"
|
||||
integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
|
||||
|
||||
ansi-styles@^6.1.0:
|
||||
ansi-styles@^6.1.0, ansi-styles@^6.2.1:
|
||||
version "6.2.1"
|
||||
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz"
|
||||
integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
|
||||
@@ -4435,6 +4422,15 @@ cliui@^8.0.1:
|
||||
strip-ansi "^6.0.1"
|
||||
wrap-ansi "^7.0.0"
|
||||
|
||||
cliui@^9.0.1:
|
||||
version "9.0.1"
|
||||
resolved "https://registry.yarnpkg.com/cliui/-/cliui-9.0.1.tgz#6f7890f386f6f1f79953adc1f78dec46fcc2d291"
|
||||
integrity sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==
|
||||
dependencies:
|
||||
string-width "^7.2.0"
|
||||
strip-ansi "^7.1.0"
|
||||
wrap-ansi "^9.0.0"
|
||||
|
||||
clone-deep@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz"
|
||||
@@ -4963,7 +4959,7 @@ emittery@^0.13.1:
|
||||
resolved "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz"
|
||||
integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==
|
||||
|
||||
emoji-regex@^10.2.1:
|
||||
emoji-regex@^10.2.1, emoji-regex@^10.3.0:
|
||||
version "10.4.0"
|
||||
resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz"
|
||||
integrity sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==
|
||||
@@ -5798,6 +5794,11 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5:
|
||||
resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz"
|
||||
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
||||
|
||||
get-east-asian-width@^1.0.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz#21b4071ee58ed04ee0db653371b55b4299875389"
|
||||
integrity sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==
|
||||
|
||||
get-intrinsic@^1.2.5, get-intrinsic@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz"
|
||||
@@ -9825,6 +9826,15 @@ string-width@^6.1.0:
|
||||
emoji-regex "^10.2.1"
|
||||
strip-ansi "^7.0.1"
|
||||
|
||||
string-width@^7.0.0, string-width@^7.2.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.2.0.tgz#b5bb8e2165ce275d4d43476dd2700ad9091db6dc"
|
||||
integrity sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==
|
||||
dependencies:
|
||||
emoji-regex "^10.3.0"
|
||||
get-east-asian-width "^1.0.0"
|
||||
strip-ansi "^7.1.0"
|
||||
|
||||
string_decoder@^1.1.1:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz"
|
||||
@@ -10551,6 +10561,15 @@ wrap-ansi@^8.1.0:
|
||||
string-width "^5.0.1"
|
||||
strip-ansi "^7.0.1"
|
||||
|
||||
wrap-ansi@^9.0.0:
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-9.0.0.tgz#1a3dc8b70d85eeb8398ddfb1e4a02cd186e58b3e"
|
||||
integrity sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==
|
||||
dependencies:
|
||||
ansi-styles "^6.2.1"
|
||||
string-width "^7.0.0"
|
||||
strip-ansi "^7.1.0"
|
||||
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
|
||||
@@ -10617,6 +10636,11 @@ yargs-parser@^21.0.1, yargs-parser@^21.1.1:
|
||||
resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz"
|
||||
integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
|
||||
|
||||
yargs-parser@^22.0.0:
|
||||
version "22.0.0"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-22.0.0.tgz#87b82094051b0567717346ecd00fd14804b357c8"
|
||||
integrity sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==
|
||||
|
||||
yargs-unparser@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz"
|
||||
@@ -10670,6 +10694,18 @@ yargs@^17.3.1, yargs@^17.7.1, yargs@^17.7.2:
|
||||
y18n "^5.0.5"
|
||||
yargs-parser "^21.1.1"
|
||||
|
||||
yargs@^18.0.0:
|
||||
version "18.0.0"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-18.0.0.tgz#6c84259806273a746b09f579087b68a3c2d25bd1"
|
||||
integrity sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==
|
||||
dependencies:
|
||||
cliui "^9.0.1"
|
||||
escalade "^3.1.1"
|
||||
get-caller-file "^2.0.5"
|
||||
string-width "^7.2.0"
|
||||
y18n "^5.0.5"
|
||||
yargs-parser "^22.0.0"
|
||||
|
||||
yauzl@^2.10.0:
|
||||
version "2.10.0"
|
||||
resolved "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz"
|
||||
|
||||
Reference in New Issue
Block a user