From 16084238f5e24173d7f071d309cb6b3256885214 Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Wed, 1 Apr 2026 22:49:49 +0200 Subject: [PATCH 1/5] chore: use @metamask/messenger CLI for action type generation Replace local scripts/generate-method-action-types.mts with the messenger-generate-action-types CLI from @metamask/messenger@^1.1.0. - Delete 773-line duplicated script - Bump @metamask/messenger to ^1.1.0 in all packages - Update snaps-controllers generate script to use CLI - Regenerate all 7 action type files --- packages/snaps-controllers/package.json | 4 +- .../CronjobController-method-action-types.ts | 2 +- ...InterfaceController-method-action-types.ts | 2 +- ...chainRoutingService-method-action-types.ts | 2 +- .../ExecutionService-method-action-types.ts | 2 +- .../SnapController-method-action-types.ts | 2 +- ...pRegistryController-method-action-types.ts | 2 +- .../WebSocketService-method-action-types.ts | 2 +- packages/snaps-rpc-methods/package.json | 2 +- packages/snaps-simulation/package.json | 2 +- packages/snaps-utils/package.json | 2 +- scripts/generate-method-action-types.mts | 773 ------------------ yarn.lock | 23 +- 13 files changed, 31 insertions(+), 789 deletions(-) delete mode 100644 scripts/generate-method-action-types.mts diff --git a/packages/snaps-controllers/package.json b/packages/snaps-controllers/package.json index d3e44e1e63..379d442b76 100644 --- a/packages/snaps-controllers/package.json +++ b/packages/snaps-controllers/package.json @@ -62,7 +62,7 @@ "build": "ts-bridge --project tsconfig.build.json --verbose --clean --no-references", "changelog:update": "../../scripts/update-changelog.sh @metamask/snaps-controllers", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/snaps-controllers", - "generate-method-action-types": "tsx ../../scripts/generate-method-action-types.mts", + "generate-method-action-types": "messenger-generate-action-types", "lint": "yarn lint:eslint && yarn lint:misc --check && yarn changelog:validate && yarn lint:dependencies", "lint:ci": "yarn lint", "lint:dependencies": "depcheck", @@ -85,7 +85,7 @@ "@metamask/json-rpc-engine": "^10.2.4", "@metamask/json-rpc-middleware-stream": "^8.0.8", "@metamask/key-tree": "^10.1.1", - "@metamask/messenger": "^1.0.0", + "@metamask/messenger": "^1.1.0", "@metamask/object-multiplex": "^2.1.0", "@metamask/permission-controller": "^12.3.0", "@metamask/post-message-stream": "^10.0.0", diff --git a/packages/snaps-controllers/src/cronjob/CronjobController-method-action-types.ts b/packages/snaps-controllers/src/cronjob/CronjobController-method-action-types.ts index 162de24e67..f815bf61f3 100644 --- a/packages/snaps-controllers/src/cronjob/CronjobController-method-action-types.ts +++ b/packages/snaps-controllers/src/cronjob/CronjobController-method-action-types.ts @@ -1,5 +1,5 @@ /** - * This file is auto generated by `scripts/generate-method-action-types.ts`. + * This file is auto generated. * Do not edit manually. */ diff --git a/packages/snaps-controllers/src/interface/SnapInterfaceController-method-action-types.ts b/packages/snaps-controllers/src/interface/SnapInterfaceController-method-action-types.ts index 1641d798e4..1112ac5294 100644 --- a/packages/snaps-controllers/src/interface/SnapInterfaceController-method-action-types.ts +++ b/packages/snaps-controllers/src/interface/SnapInterfaceController-method-action-types.ts @@ -1,5 +1,5 @@ /** - * This file is auto generated by `scripts/generate-method-action-types.ts`. + * This file is auto generated. * Do not edit manually. */ diff --git a/packages/snaps-controllers/src/multichain/MultichainRoutingService-method-action-types.ts b/packages/snaps-controllers/src/multichain/MultichainRoutingService-method-action-types.ts index e851e9c737..2d89b2222e 100644 --- a/packages/snaps-controllers/src/multichain/MultichainRoutingService-method-action-types.ts +++ b/packages/snaps-controllers/src/multichain/MultichainRoutingService-method-action-types.ts @@ -1,5 +1,5 @@ /** - * This file is auto generated by `scripts/generate-method-action-types.ts`. + * This file is auto generated. * Do not edit manually. */ diff --git a/packages/snaps-controllers/src/services/ExecutionService-method-action-types.ts b/packages/snaps-controllers/src/services/ExecutionService-method-action-types.ts index 0993572ac4..7329e1c351 100644 --- a/packages/snaps-controllers/src/services/ExecutionService-method-action-types.ts +++ b/packages/snaps-controllers/src/services/ExecutionService-method-action-types.ts @@ -1,5 +1,5 @@ /** - * This file is auto generated by `scripts/generate-method-action-types.ts`. + * This file is auto generated. * Do not edit manually. */ diff --git a/packages/snaps-controllers/src/snaps/SnapController-method-action-types.ts b/packages/snaps-controllers/src/snaps/SnapController-method-action-types.ts index 8c2bcdfb26..aade71d1ff 100644 --- a/packages/snaps-controllers/src/snaps/SnapController-method-action-types.ts +++ b/packages/snaps-controllers/src/snaps/SnapController-method-action-types.ts @@ -1,5 +1,5 @@ /** - * This file is auto generated by `scripts/generate-method-action-types.ts`. + * This file is auto generated. * Do not edit manually. */ diff --git a/packages/snaps-controllers/src/snaps/registry/SnapRegistryController-method-action-types.ts b/packages/snaps-controllers/src/snaps/registry/SnapRegistryController-method-action-types.ts index 5fcac09d6e..526ff3d0e6 100644 --- a/packages/snaps-controllers/src/snaps/registry/SnapRegistryController-method-action-types.ts +++ b/packages/snaps-controllers/src/snaps/registry/SnapRegistryController-method-action-types.ts @@ -1,5 +1,5 @@ /** - * This file is auto generated by `scripts/generate-method-action-types.ts`. + * This file is auto generated. * Do not edit manually. */ diff --git a/packages/snaps-controllers/src/websocket/WebSocketService-method-action-types.ts b/packages/snaps-controllers/src/websocket/WebSocketService-method-action-types.ts index 1dff4bae87..80f4ef6e81 100644 --- a/packages/snaps-controllers/src/websocket/WebSocketService-method-action-types.ts +++ b/packages/snaps-controllers/src/websocket/WebSocketService-method-action-types.ts @@ -1,5 +1,5 @@ /** - * This file is auto generated by `scripts/generate-method-action-types.ts`. + * This file is auto generated. * Do not edit manually. */ diff --git a/packages/snaps-rpc-methods/package.json b/packages/snaps-rpc-methods/package.json index cfabe02cb6..a5f912b662 100644 --- a/packages/snaps-rpc-methods/package.json +++ b/packages/snaps-rpc-methods/package.json @@ -71,7 +71,7 @@ "@lavamoat/allow-scripts": "^4.0.0", "@metamask/auto-changelog": "^5.3.2", "@metamask/json-rpc-engine": "^10.2.4", - "@metamask/messenger": "^1.0.0", + "@metamask/messenger": "^1.1.0", "@swc/core": "1.11.31", "@swc/jest": "^0.2.38", "@ts-bridge/cli": "^0.6.1", diff --git a/packages/snaps-simulation/package.json b/packages/snaps-simulation/package.json index 657a3643e5..96c58d5db8 100644 --- a/packages/snaps-simulation/package.json +++ b/packages/snaps-simulation/package.json @@ -59,7 +59,7 @@ "@metamask/json-rpc-engine": "^10.2.4", "@metamask/json-rpc-middleware-stream": "^8.0.8", "@metamask/key-tree": "^10.1.1", - "@metamask/messenger": "^1.0.0", + "@metamask/messenger": "^1.1.0", "@metamask/permission-controller": "^12.3.0", "@metamask/rpc-errors": "^7.0.3", "@metamask/snaps-controllers": "workspace:^", diff --git a/packages/snaps-utils/package.json b/packages/snaps-utils/package.json index e79bdd497d..56948210e8 100644 --- a/packages/snaps-utils/package.json +++ b/packages/snaps-utils/package.json @@ -81,7 +81,7 @@ "@babel/core": "^7.23.2", "@babel/types": "^7.23.0", "@metamask/key-tree": "^10.1.1", - "@metamask/messenger": "^1.0.0", + "@metamask/messenger": "^1.1.0", "@metamask/permission-controller": "^12.3.0", "@metamask/rpc-errors": "^7.0.3", "@metamask/slip44": "^4.4.0", diff --git a/scripts/generate-method-action-types.mts b/scripts/generate-method-action-types.mts deleted file mode 100644 index 27f3a5a436..0000000000 --- a/scripts/generate-method-action-types.mts +++ /dev/null @@ -1,773 +0,0 @@ -#!yarn tsx - -// ESLint is saying `ts` can be replaced with named imports, but this doesn't -// seem to actually work with the current TypeScript version. -/* eslint-disable no-console, import-x/no-named-as-default-member */ - -import { assert, hasProperty, isObject } from '@metamask/utils'; -import { ESLint } from 'eslint'; -import * as fs from 'fs'; -import * as path from 'path'; -import ts from 'typescript'; -import yargs from 'yargs'; - -type MethodInfo = { - name: string; - jsDoc: string; - signature: string; -}; - -type ControllerInfo = { - name: string; - filePath: string; - exposedMethods: string[]; - methods: MethodInfo[]; -}; - -/** - * The parsed command-line arguments. - */ -type CommandLineArguments = { - /** - * Whether to check if the action types files are up to date. - */ - check: boolean; - /** - * Whether to fix the action types files. - */ - fix: boolean; - /** - * Optional path to a specific controller to process. - */ - controllerPath: string; -}; - -/** - * Uses `yargs` to parse the arguments given to the script. - * - * @returns The command line arguments. - */ -async function parseCommandLineArguments(): Promise { - const { - check, - fix, - path: controllerPath, - } = await yargs(process.argv.slice(2)) - .command( - '$0 [path]', - 'Generate method action types for a controller messenger', - (yargsInstance) => { - yargsInstance.positional('path', { - type: 'string', - description: 'Path to the folder where controllers are located', - default: 'src', - }); - }, - ) - .option('check', { - type: 'boolean', - description: 'Check if generated action type files are up to date', - default: false, - }) - .option('fix', { - type: 'boolean', - description: 'Generate/update action type files', - default: false, - }) - .help() - .check((argv) => { - if (!argv.check && !argv.fix) { - throw new Error('Either --check or --fix must be provided.\n'); - } - return true; - }).argv; - - return { - check, - fix, - // TypeScript doesn't narrow the type of `controllerPath` even though we defined it as a string in yargs, so we need to cast it here. - controllerPath: controllerPath as string, - }; -} - -/** - * Checks if generated action types files are up to date. - * - * @param controllers - Array of controller information objects. - * @param eslint - The ESLint instance to use for formatting. - */ -async function checkActionTypesFiles( - controllers: ControllerInfo[], - eslint: ESLint, -): Promise { - let hasErrors = false; - - // Track files that exist and their corresponding temp files - const fileComparisonJobs: { - expectedTempFile: string; - actualFile: string; - baseFileName: string; - }[] = []; - - try { - // Check each controller and prepare comparison jobs - for (const controller of controllers) { - console.log(`\nšŸ”§ Checking ${controller.name}...`); - const outputDir = path.dirname(controller.filePath); - const baseFileName = path.basename(controller.filePath, '.ts'); - const actualFile = path.join( - outputDir, - `${baseFileName}-method-action-types.ts`, - ); - - const expectedContent = generateActionTypesContent(controller); - const expectedTempFile = actualFile.replace('.ts', '.tmp.ts'); - - try { - // Check if actual file exists first - await fs.promises.access(actualFile); - - // Write expected content to temp file - await fs.promises.writeFile(expectedTempFile, expectedContent, 'utf8'); - - // Add to comparison jobs - fileComparisonJobs.push({ - expectedTempFile, - actualFile, - baseFileName, - }); - } catch (error) { - if ((error as NodeJS.ErrnoException).code === 'ENOENT') { - console.error( - `āŒ ${baseFileName}-method-action-types.ts does not exist`, - ); - } else { - console.error( - `āŒ Error reading ${baseFileName}-method-action-types.ts:`, - error, - ); - } - hasErrors = true; - } - } - - // Run ESLint on all files at once if we have comparisons to make - if (fileComparisonJobs.length > 0) { - console.log('\nšŸ“ Running ESLint to compare files...'); - - const results = await eslint.lintFiles( - fileComparisonJobs.map((job) => job.expectedTempFile), - ); - await ESLint.outputFixes(results); - - // Compare expected vs actual content - for (const job of fileComparisonJobs) { - const expectedContent = await fs.promises.readFile( - job.expectedTempFile, - 'utf8', - ); - const actualContent = await fs.promises.readFile( - job.actualFile, - 'utf8', - ); - - if (expectedContent === actualContent) { - console.log( - `āœ… ${job.baseFileName}-method-action-types.ts is up to date`, - ); - } else { - console.error( - `āŒ ${job.baseFileName}-method-action-types.ts is out of date`, - ); - hasErrors = true; - } - } - } - } finally { - // Clean up temp files - for (const job of fileComparisonJobs) { - try { - await fs.promises.unlink(job.expectedTempFile); - } catch { - // Ignore cleanup errors - } - } - } - - if (hasErrors) { - console.error('\nšŸ’„ Some action type files are out of date or missing.'); - console.error( - 'Run `yarn generate-method-action-types --fix` to update them.', - ); - process.exitCode = 1; - } else { - console.log('\nšŸŽ‰ All action type files are up to date!'); - } -} - -/** - * Main entry point for the script. - */ -async function main(): Promise { - const { fix, controllerPath } = await parseCommandLineArguments(); - - console.log('šŸ” Searching for controllers with MESSENGER_EXPOSED_METHODS...'); - - const controllers = await findControllersWithExposedMethods(controllerPath); - - if (controllers.length === 0) { - console.log('āš ļø No controllers found with MESSENGER_EXPOSED_METHODS'); - return; - } - - console.log( - `šŸ“¦ Found ${controllers.length} controller(s) with exposed methods`, - ); - - const eslint = new ESLint({ - fix: true, - errorOnUnmatchedPattern: false, - }); - - if (fix) { - await generateAllActionTypesFiles(controllers, eslint); - console.log('\nšŸŽ‰ All action types generated successfully!'); - } else { - // -check mode: check files - await checkActionTypesFiles(controllers, eslint); - } -} - -/** - * Check if a path is a directory. - * - * @param pathValue - The path to check. - * @returns True if the path is a directory, false otherwise. - * @throws If an error occurs other than the path not existing. - */ -async function isDirectory(pathValue: string): Promise { - try { - const stats = await fs.promises.stat(pathValue); - return stats.isDirectory(); - } catch (error) { - if ( - isObject(error) && - hasProperty(error, 'code') && - error.code === 'ENOENT' - ) { - return false; - } - - throw error; - } -} - -/** - * Recursively get all files in a directory and its subdirectories. - * - * @param directory - The directory to search. - * @returns An array of file paths. - */ -async function getFiles(directory: string): Promise { - const entries = await fs.promises.readdir(directory, { withFileTypes: true }); - const files = await Promise.all( - entries.map(async (entry) => { - const fullPath = path.join(directory, entry.name); - return entry.isDirectory() ? await getFiles(fullPath) : fullPath; - }), - ); - - return files.flat(); -} - -/** - * Finds all controller files that have MESSENGER_EXPOSED_METHODS constants. - * - * @param controllerPath - Path to the folder where controllers are located. - * @returns A list of controller information objects. - */ -async function findControllersWithExposedMethods( - controllerPath: string, -): Promise { - const srcPath = path.resolve(process.cwd(), controllerPath); - const controllers: ControllerInfo[] = []; - - if (!(await isDirectory(srcPath))) { - throw new Error(`The specified path is not a directory: ${srcPath}`); - } - - const srcFiles = await getFiles(srcPath); - - for (const file of srcFiles) { - if (!file.endsWith('.ts') || file.endsWith('.test.ts')) { - continue; - } - - const content = await fs.promises.readFile(file, 'utf8'); - - if (content.includes('MESSENGER_EXPOSED_METHODS')) { - const controllerInfo = await parseControllerFile(file); - if (controllerInfo) { - controllers.push(controllerInfo); - } - } - } - - return controllers; -} - -/** - * Context for AST visiting. - */ -type VisitorContext = { - exposedMethods: string[]; - className: string; - methods: MethodInfo[]; - sourceFile: ts.SourceFile; -}; - -/** - * Visits AST nodes to find exposed methods and controller class. - * - * @param context - The visitor context. - * @returns A function to visit nodes. - */ -function createASTVisitor(context: VisitorContext): (node: ts.Node) => void { - /** - * Visits AST nodes to find exposed methods and controller class. - * - * @param node - The AST node to visit. - */ - function visitNode(node: ts.Node): void { - if (ts.isVariableStatement(node)) { - const declaration = node.declarationList.declarations[0]; - if ( - ts.isIdentifier(declaration.name) && - declaration.name.text === 'MESSENGER_EXPOSED_METHODS' - ) { - if (declaration.initializer) { - let arrayExpression: ts.ArrayLiteralExpression | undefined; - - // Handle direct array literal - if (ts.isArrayLiteralExpression(declaration.initializer)) { - arrayExpression = declaration.initializer; - } - // Handle "as const" assertion: expression is wrapped in type assertion - else if ( - ts.isAsExpression(declaration.initializer) && - ts.isArrayLiteralExpression(declaration.initializer.expression) - ) { - arrayExpression = declaration.initializer.expression; - } - - if (arrayExpression) { - context.exposedMethods = arrayExpression.elements - .filter(ts.isStringLiteral) - .map((element) => element.text); - } - } - } - } - - // Find the controller or service class - if (ts.isClassDeclaration(node) && node.name) { - const classText = node.name.text; - if (classText.includes('Controller') || classText.includes('Service')) { - context.className = classText; - - // Extract method info for exposed methods - const seenMethods = new Set(); - for (const member of node.members) { - if ( - ts.isMethodDeclaration(member) && - member.name && - ts.isIdentifier(member.name) - ) { - const methodName = member.name.text; - if ( - context.exposedMethods.includes(methodName) && - !seenMethods.has(methodName) - ) { - seenMethods.add(methodName); - const jsDoc = extractJSDoc(member, context.sourceFile); - const signature = extractMethodSignature(member); - context.methods.push({ - name: methodName, - jsDoc, - signature, - }); - } - } - } - } - } - - ts.forEachChild(node, visitNode); - } - - return visitNode; -} - -/** - * Create a TypeScript program for the given file by locating the nearest - * tsconfig.json. - * - * @param filePath - Absolute path to the source file. - * @returns A TypeScript program, or null if no tsconfig was found. - */ -function createProgramForFile(filePath: string): ts.Program | null { - const configPath = ts.findConfigFile( - path.dirname(filePath), - ts.sys.fileExists.bind(ts.sys), - 'tsconfig.json', - ); - if (!configPath) { - return null; - } - - const { config, error } = ts.readConfigFile( - configPath, - ts.sys.readFile.bind(ts.sys), - ); - - if (error) { - return null; - } - - const parsedConfig = ts.parseJsonConfigFileContent( - config, - ts.sys, - path.dirname(configPath), - ); - - return ts.createProgram({ - rootNames: parsedConfig.fileNames, - options: parsedConfig.options, - }); -} - -/** - * Find a class declaration with the given name in a source file. - * - * @param sourceFile - The source file to search. - * @param className - The class name to look for. - * @returns The class declaration node, or null if not found. - */ -function findClassInSourceFile( - sourceFile: ts.SourceFile, - className: string, -): ts.ClassDeclaration | null { - return ( - sourceFile.statements.find( - (node): node is ts.ClassDeclaration => - ts.isClassDeclaration(node) && node.name?.text === className, - ) ?? null - ); -} - -/** - * Search through the class hierarchy of a TypeScript type to find the - * declaration of a method with the given name. - * - * @param classType - The class type to search. - * @param methodName - The method name to look for. - * @returns The method declaration node, or null if not found. - */ -function findMethodInHierarchy( - classType: ts.Type, - methodName: string, -): ts.MethodDeclaration | null { - const symbol = classType.getProperty(methodName); - if (!symbol) { - return null; - } - - const declarations = symbol.getDeclarations(); - if (!declarations) { - return null; - } - - for (const declaration of declarations) { - if (ts.isMethodDeclaration(declaration)) { - return declaration; - } - } - - return null; -} - -/** - * Parses a controller file to extract exposed methods and their metadata. - * - * @param filePath - Path to the controller file to parse. - * @returns Controller information or null if parsing fails. - */ -async function parseControllerFile( - filePath: string, -): Promise { - try { - const content = await fs.promises.readFile(filePath, 'utf8'); - const sourceFile = ts.createSourceFile( - filePath, - content, - ts.ScriptTarget.Latest, - true, - ); - - const context: VisitorContext = { - exposedMethods: [], - className: '', - methods: [], - sourceFile, - }; - - createASTVisitor(context)(sourceFile); - - if (context.exposedMethods.length === 0 || !context.className) { - return null; - } - - // For exposed methods not found directly in the class body, attempt to - // locate them in the inheritance hierarchy using the type checker. - const foundMethodNames = new Set( - context.methods.map((method) => method.name), - ); - - const inheritedMethodNames = context.exposedMethods.filter( - (name) => !foundMethodNames.has(name), - ); - - if (inheritedMethodNames.length > 0) { - const program = createProgramForFile(filePath); - const checker = program?.getTypeChecker(); - const programSourceFile = program?.getSourceFile(filePath); - - assert( - checker, - `Type checker could not be created for "${filePath}". Ensure a valid tsconfig.json is present.`, - ); - - assert( - programSourceFile, - `Source file "${filePath}" not found in program.`, - ); - - const classNode = findClassInSourceFile( - programSourceFile, - context.className, - ); - - assert( - classNode, - `Class "${context.className}" not found in "${filePath}".`, - ); - - const classType = checker.getTypeAtLocation(classNode); - for (const methodName of inheritedMethodNames) { - const methodDeclaration = findMethodInHierarchy(classType, methodName); - - const jsDoc = methodDeclaration - ? extractJSDoc(methodDeclaration, methodDeclaration.getSourceFile()) - : ''; - context.methods.push({ name: methodName, jsDoc, signature: '' }); - } - } - - return { - name: context.className, - filePath, - exposedMethods: context.exposedMethods, - methods: context.methods, - }; - } catch (error) { - console.error(`Error parsing ${filePath}:`, error); - return null; - } -} - -/** - * Extracts JSDoc comment from a method declaration. - * - * @param node - The method declaration node. - * @param sourceFile - The source file. - * @returns The JSDoc comment. - */ -function extractJSDoc( - node: ts.MethodDeclaration, - sourceFile: ts.SourceFile, -): string { - const jsDocTags = ts.getJSDocCommentsAndTags(node); - if (jsDocTags.length === 0) { - return ''; - } - - const jsDoc = jsDocTags[0]; - if (ts.isJSDoc(jsDoc)) { - const fullText = sourceFile.getFullText(); - const start = jsDoc.getFullStart(); - const end = jsDoc.getEnd(); - const rawJsDoc = fullText.substring(start, end).trim(); - return formatJSDoc(rawJsDoc); - } - - return ''; -} - -/** - * Formats JSDoc comments to have consistent indentation for the generated file. - * - * @param rawJsDoc - The raw JSDoc comment from the source. - * @returns The formatted JSDoc comment. - */ -function formatJSDoc(rawJsDoc: string): string { - const lines = rawJsDoc.split('\n'); - const formattedLines: string[] = []; - - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - if (i === 0) { - // First line should be /** - formattedLines.push('/**'); - } else if (i === lines.length - 1) { - // Last line should be */ - formattedLines.push(' */'); - } else { - // Middle lines should start with ' * ' - const trimmed = line.trim(); - if (trimmed.startsWith('*')) { - // Remove existing * and normalize - const content = trimmed.substring(1).trim(); - formattedLines.push(content ? ` * ${content}` : ' *'); - } else { - // Handle lines that don't start with * - formattedLines.push(trimmed ? ` * ${trimmed}` : ' *'); - } - } - } - - return formattedLines.join('\n'); -} - -/** - * Extracts method signature as a string for the handler type. - * - * @param node - The method declaration node. - * @returns The method signature. - */ -function extractMethodSignature(node: ts.MethodDeclaration): string { - // Since we're just using the method reference in the handler type, - // we don't need the full signature - just return the method name - // The actual signature will be inferred from the controller class - return node.name ? (node.name as ts.Identifier).text : ''; -} - -/** - * Generates action types files for all controllers. - * - * @param controllers - Array of controller information objects. - * @param eslint - The ESLint instance to use for formatting. - */ -async function generateAllActionTypesFiles( - controllers: ControllerInfo[], - eslint: ESLint, -): Promise { - const outputFiles: string[] = []; - - // Write all files first - for (const controller of controllers) { - console.log(`\nšŸ”§ Processing ${controller.name}...`); - const outputDir = path.dirname(controller.filePath); - const baseFileName = path.basename(controller.filePath, '.ts'); - const outputFile = path.join( - outputDir, - `${baseFileName}-method-action-types.ts`, - ); - - const generatedContent = generateActionTypesContent(controller); - await fs.promises.writeFile(outputFile, generatedContent, 'utf8'); - outputFiles.push(outputFile); - console.log(`āœ… Generated action types for ${controller.name}`); - } - - // Run ESLint on all the actual files - if (outputFiles.length > 0) { - console.log('\nšŸ“ Running ESLint on generated files...'); - - const results = await eslint.lintFiles(outputFiles); - await ESLint.outputFixes(results); - const errors = ESLint.getErrorResults(results); - if (errors.length > 0) { - console.error('āŒ ESLint errors:', errors); - process.exitCode = 1; - } else { - console.log('āœ… ESLint formatting applied'); - } - } -} - -/** - * Generates the content for the action types file. - * - * @param controller - The controller information object. - * @returns The content for the action types file. - */ -function generateActionTypesContent(controller: ControllerInfo): string { - const baseFileName = path.basename(controller.filePath, '.ts'); - const controllerImportPath = `./${baseFileName}`; - - let content = `/** - * This file is auto generated by \`scripts/generate-method-action-types.ts\`. - * Do not edit manually. - */ - -import type { ${controller.name} } from '${controllerImportPath}'; - -`; - - const actionTypeNames: string[] = []; - - // Generate action types for each exposed method - for (const method of controller.methods) { - const actionTypeName = `${controller.name}${capitalize(method.name)}Action`; - const actionString = `${controller.name}:${method.name}`; - - actionTypeNames.push(actionTypeName); - - // Add the JSDoc if available - if (method.jsDoc) { - content += `${method.jsDoc}\n`; - } - - content += `export type ${actionTypeName} = { - type: \`${actionString}\`; - handler: ${controller.name}['${method.name}']; -};\n\n`; - } - - // Generate union type of all action types - if (actionTypeNames.length > 0) { - const unionTypeName = `${controller.name}MethodActions`; - content += `/** - * Union of all ${controller.name} action types. - */ -export type ${unionTypeName} = ${actionTypeNames.join(' | ')};\n`; - } - - return `${content.trimEnd()}\n`; -} - -/** - * Capitalizes the first letter of a string. - * - * @param str - The string to capitalize. - * @returns The capitalized string. - */ -function capitalize(str: string): string { - return str.charAt(0).toUpperCase() + str.slice(1); -} - -// Error handling wrapper -main().catch((error) => { - console.error('āŒ Script failed:', error); - process.exitCode = 1; -}); diff --git a/yarn.lock b/yarn.lock index 40dd5fe5e0..4c6c636792 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3777,6 +3777,21 @@ __metadata: languageName: node linkType: hard +"@metamask/messenger@npm:^1.1.0": + version: 1.1.0 + resolution: "@metamask/messenger@npm:1.1.0" + dependencies: + "@metamask/utils": "npm:^11.9.0" + yargs: "npm:^17.7.2" + peerDependencies: + eslint: ">=8" + typescript: ">=5.0.0" + bin: + messenger-generate-action-types: ./dist/generate-action-types/cli.mjs + checksum: 10/ea2b25c4d8cae8d60dd4365738763752283b27eca17cae749b3fedf6733207c180de917715a6ccb8b41f7c06ee769b82b01de5c43ed20c182b3fe6a0cf5c1ae1 + languageName: node + linkType: hard + "@metamask/multichain-provider-example-snap@workspace:^, @metamask/multichain-provider-example-snap@workspace:packages/examples/packages/multichain-provider": version: 0.0.0-use.local resolution: "@metamask/multichain-provider-example-snap@workspace:packages/examples/packages/multichain-provider" @@ -4221,7 +4236,7 @@ __metadata: "@metamask/json-rpc-engine": "npm:^10.2.4" "@metamask/json-rpc-middleware-stream": "npm:^8.0.8" "@metamask/key-tree": "npm:^10.1.1" - "@metamask/messenger": "npm:^1.0.0" + "@metamask/messenger": "npm:^1.1.0" "@metamask/object-multiplex": "npm:^2.1.0" "@metamask/permission-controller": "npm:^12.3.0" "@metamask/post-message-stream": "npm:^10.0.0" @@ -4479,7 +4494,7 @@ __metadata: "@metamask/auto-changelog": "npm:^5.3.2" "@metamask/json-rpc-engine": "npm:^10.2.4" "@metamask/key-tree": "npm:^10.1.1" - "@metamask/messenger": "npm:^1.0.0" + "@metamask/messenger": "npm:^1.1.0" "@metamask/permission-controller": "npm:^12.3.0" "@metamask/rpc-errors": "npm:^7.0.3" "@metamask/snaps-sdk": "workspace:^" @@ -4579,7 +4594,7 @@ __metadata: "@metamask/json-rpc-engine": "npm:^10.2.4" "@metamask/json-rpc-middleware-stream": "npm:^8.0.8" "@metamask/key-tree": "npm:^10.1.1" - "@metamask/messenger": "npm:^1.0.0" + "@metamask/messenger": "npm:^1.1.0" "@metamask/permission-controller": "npm:^12.3.0" "@metamask/rpc-errors": "npm:^7.0.3" "@metamask/snaps-controllers": "workspace:^" @@ -4623,7 +4638,7 @@ __metadata: "@lavamoat/allow-scripts": "npm:^4.0.0" "@metamask/auto-changelog": "npm:^5.3.2" "@metamask/key-tree": "npm:^10.1.1" - "@metamask/messenger": "npm:^1.0.0" + "@metamask/messenger": "npm:^1.1.0" "@metamask/permission-controller": "npm:^12.3.0" "@metamask/post-message-stream": "npm:^10.0.0" "@metamask/rpc-errors": "npm:^7.0.3" From 308d82c0aab2bc5be8ff888a17c4ed6429b54ffb Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Wed, 1 Apr 2026 22:52:19 +0200 Subject: [PATCH 2/5] chore: add changelog entries for messenger bump --- packages/snaps-controllers/CHANGELOG.md | 4 ++++ packages/snaps-rpc-methods/CHANGELOG.md | 4 ++++ packages/snaps-simulation/CHANGELOG.md | 4 ++++ packages/snaps-utils/CHANGELOG.md | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/packages/snaps-controllers/CHANGELOG.md b/packages/snaps-controllers/CHANGELOG.md index 854edcf5de..0a93995cc8 100644 --- a/packages/snaps-controllers/CHANGELOG.md +++ b/packages/snaps-controllers/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Bump `@metamask/messenger` from `^1.0.0` to `^1.1.0` ([#3934](https://github.com/MetaMask/snaps/pull/3934)) + ## [19.0.0] ### Changed diff --git a/packages/snaps-rpc-methods/CHANGELOG.md b/packages/snaps-rpc-methods/CHANGELOG.md index dc07e5edda..c3e8805faa 100644 --- a/packages/snaps-rpc-methods/CHANGELOG.md +++ b/packages/snaps-rpc-methods/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Bump `@metamask/messenger` from `^1.0.0` to `^1.1.0` ([#3934](https://github.com/MetaMask/snaps/pull/3934)) + ## [15.0.1] ### Changed diff --git a/packages/snaps-simulation/CHANGELOG.md b/packages/snaps-simulation/CHANGELOG.md index f17ea9f5d4..4043f06d5c 100644 --- a/packages/snaps-simulation/CHANGELOG.md +++ b/packages/snaps-simulation/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Bump `@metamask/messenger` from `^1.0.0` to `^1.1.0` ([#3934](https://github.com/MetaMask/snaps/pull/3934)) + ## [4.1.2] ### Changed diff --git a/packages/snaps-utils/CHANGELOG.md b/packages/snaps-utils/CHANGELOG.md index 9b5227420f..1670689d95 100644 --- a/packages/snaps-utils/CHANGELOG.md +++ b/packages/snaps-utils/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Bump `@metamask/messenger` from `^1.0.0` to `^1.1.0` ([#3934](https://github.com/MetaMask/snaps/pull/3934)) + ## [12.1.2] ### Changed From d2afb4419126169699e1ee4824d62e5c5235aa1d Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Wed, 1 Apr 2026 23:23:59 +0200 Subject: [PATCH 3/5] fix: add resolution to force @metamask/messenger@^1.1.0 --- package.json | 1 + yarn.lock | 7 ------- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/package.json b/package.json index c929cdbc5c..8e5c8699cd 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ }, "resolutions": { "@esbuild-plugins/node-modules-polyfill@^0.2.2": "patch:@esbuild-plugins/node-modules-polyfill@npm%3A0.2.2#./.yarn/patches/@esbuild-plugins-node-modules-polyfill-npm-0.2.2-f612681798.patch", + "@metamask/messenger": "^1.1.0", "@puppeteer/browsers@1.4.6": "patch:@puppeteer/browsers@npm%3A1.7.0#./.yarn/patches/@puppeteer-browsers-npm-1.7.0-203cb4f44b.patch", "@puppeteer/browsers@^1.6.0": "patch:@puppeteer/browsers@npm%3A1.7.0#./.yarn/patches/@puppeteer-browsers-npm-1.7.0-203cb4f44b.patch", "@types/glob@*": "patch:@types/glob@npm%3A7.1.4#./.yarn/patches/@types-glob-npm-7.1.4-d45247eaa2.patch", diff --git a/yarn.lock b/yarn.lock index 4c6c636792..9282c76d6a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3770,13 +3770,6 @@ __metadata: languageName: unknown linkType: soft -"@metamask/messenger@npm:^1.0.0": - version: 1.0.0 - resolution: "@metamask/messenger@npm:1.0.0" - checksum: 10/ab1219a922d5acc86f2b1b557d79c75ca0c5f42572f50da8a2337bc5c8feb1ae95c0aaa2d2ee55b677acd4401fb2cc9c2dbacca7513edcddf20d88fb73fa7bea - languageName: node - linkType: hard - "@metamask/messenger@npm:^1.1.0": version: 1.1.0 resolution: "@metamask/messenger@npm:1.1.0" From 0db3096d4c07d0c02df2b624c0792cdaa7cb7d9f Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Thu, 2 Apr 2026 14:04:18 +0200 Subject: [PATCH 4/5] Revert "fix: add resolution to force @metamask/messenger@^1.1.0" This reverts commit d2afb4419126169699e1ee4824d62e5c5235aa1d. --- package.json | 1 - yarn.lock | 7 +++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 8e5c8699cd..c929cdbc5c 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ }, "resolutions": { "@esbuild-plugins/node-modules-polyfill@^0.2.2": "patch:@esbuild-plugins/node-modules-polyfill@npm%3A0.2.2#./.yarn/patches/@esbuild-plugins-node-modules-polyfill-npm-0.2.2-f612681798.patch", - "@metamask/messenger": "^1.1.0", "@puppeteer/browsers@1.4.6": "patch:@puppeteer/browsers@npm%3A1.7.0#./.yarn/patches/@puppeteer-browsers-npm-1.7.0-203cb4f44b.patch", "@puppeteer/browsers@^1.6.0": "patch:@puppeteer/browsers@npm%3A1.7.0#./.yarn/patches/@puppeteer-browsers-npm-1.7.0-203cb4f44b.patch", "@types/glob@*": "patch:@types/glob@npm%3A7.1.4#./.yarn/patches/@types-glob-npm-7.1.4-d45247eaa2.patch", diff --git a/yarn.lock b/yarn.lock index 9282c76d6a..4c6c636792 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3770,6 +3770,13 @@ __metadata: languageName: unknown linkType: soft +"@metamask/messenger@npm:^1.0.0": + version: 1.0.0 + resolution: "@metamask/messenger@npm:1.0.0" + checksum: 10/ab1219a922d5acc86f2b1b557d79c75ca0c5f42572f50da8a2337bc5c8feb1ae95c0aaa2d2ee55b677acd4401fb2cc9c2dbacca7513edcddf20d88fb73fa7bea + languageName: node + linkType: hard + "@metamask/messenger@npm:^1.1.0": version: 1.1.0 resolution: "@metamask/messenger@npm:1.1.0" From 2bd8d0b55f1783c8326d750c571ef38bc7b437e3 Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Thu, 2 Apr 2026 14:04:58 +0200 Subject: [PATCH 5/5] fix: dedupe deps --- yarn.lock | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index 4c6c636792..25e294f0df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3770,14 +3770,7 @@ __metadata: languageName: unknown linkType: soft -"@metamask/messenger@npm:^1.0.0": - version: 1.0.0 - resolution: "@metamask/messenger@npm:1.0.0" - checksum: 10/ab1219a922d5acc86f2b1b557d79c75ca0c5f42572f50da8a2337bc5c8feb1ae95c0aaa2d2ee55b677acd4401fb2cc9c2dbacca7513edcddf20d88fb73fa7bea - languageName: node - linkType: hard - -"@metamask/messenger@npm:^1.1.0": +"@metamask/messenger@npm:^1.0.0, @metamask/messenger@npm:^1.1.0": version: 1.1.0 resolution: "@metamask/messenger@npm:1.1.0" dependencies: