This document provides guidance for AI agents working within this repo.
- Yarn 4 for managing the monorepo
- TypeScript 5 for writing type-safe code
- Jest for writing tests
- ESLint 9 and Prettier for linting and formatting code
- Babel for compiling ESM code so that Jest can run it
@metamask/auto-changelogfor writing and validating changelogs@metamask/create-release-branch,MetaMask/action-publish-release, andMetaMask/action-npm-publishfor creating and publishing releases- TypeDoc for generating API documentation
Yarn workspaces are used to define and manage multiple packages.
Package in this monorepo are represented by subdirectories in packages/. Each directory follows this structure:
src/— Files that get built and published when the package is released. May also contain tests (which do not get published).tests/— Optional directory that defines helpers or setup for tests.package.json— Defines the name of the package, current version, dependencies, etc.CHANGELOG.md— Each package has a changelog that lists historical changes.README.md— Introduces the package to engineers and provides instructions on how to install and use it.LICENSE— Each package has a license that describes how engineers can use it in projects.- Configuration files — See below.
Note that the package template in scripts/create-package/package-template also uses this same structure.
The monorepo uses a hierarchical configuration approach for different tools. For most tools, root-level config files define shared settings, while package-level files extend or customize them.
CODEOWNERSdefines which GitHub teams own which packages in the monorepo.teams.jsoninstructs thecreate-update-issuesGitHub workflow which labels to assign issues that are created when there are new major version releases of packages.
.yarnrc.ymlconfigures Yarn.yarn.config.cjsdefines Yarn constraints for monorepo packages, run viayarn constraints.
tsconfig.base.jsondefines shared development-specific TypeScript settings for all other config files.tsconfig.build.jsondefines shared build-specific TypeScript settings for all other config files.tsconfig.packages.jsondefines shared development-specific TypeScript settings for all directories inpackages/.tsconfig.packages.build.jsondefines shared build-specific TypeScript settings for all directories inpackages/.tsconfig.scripts.jsondefines shared TypeScript settings for directories inscripts/.packages/**/tsconfig.json(andscripts/create-package/package-template/tsconfig.json) defines TypeScript settings for each package that are meant to be used by code editors and lint tasks.packages/**/tsconfig.build.json(andscripts/create-package/package-template/tsconfig.build.json) defines TypeScript settings for each package that are used to produce a build.scripts/create-package/tsconfig.jsoncustomizes TypeScript settings for thecreate-packagetool.
jest.config.packages.jsdefines shared Jest settings for all directories inpackages/.jest.config.scripts.jsdefines shared Jest settings for all directories inscripts/.packages/**/jest.config.js(andscripts/create-package/package-template/jest.config.js) customizes Jest settings for each package.
eslint.config.mjsconfigures ESLint for the entire monorepo.eslint-suppressions.jsonisn't a config file per se, but defines ESLint errors that are being ignored (temporarily).
.prettierrc.jsconfigures Prettier for the entire repo.
packages/**/typedoc.json(andscripts/create-package/package-template/typedoc.js) defines TypeDoc settings for each package.
babel.config.jsconfigures Babel, which is used for tests withinscripts/create-package.release.config.jsconfigures the@metamask/create-release-branchtool.
Follow test-driven development when making changes:
- Update tests first: Before modifying implementation, update or write tests that describe the desired behavior. Aim for 100% test coverage.
- Watch tests fail: Run tests to verify they fail with the current implementation (or fail appropriately if adding new functionality).
- Make tests pass: Implement changes to make the tests pass.
- Run tests after changes: Always run tests after making code changes to ensure nothing is broken.
Additionally, make sure the following checks pass after completing a request:
- There should be no lint violations or type errors.
- All tests should pass.
- All changelogs should pass validation.
- Run
yarn workspace <package-name> run <script>to run a package script within a package. - Run
yarn workspace <package-name> exec <command>to run an executable within a package. - Run
yarn workspace <package-name> add <dependency>to add a dependency to a package. - Run
yarn workspace <package-name> add -D <dependency>to add a development dependency to a package. - Run
yarn workspaces foreach --all run <script>to run a package script across all packages. - Run
yarn workspaces foreach --all exec <command>to run an executable across all packages. - Run
yarn workspaces foreach --all add <dependency>to add a dependency to all packages. - Run
yarn workspaces foreach --all add -D <dependency>to add a development dependency to all packages. - Run
yarn run <script>to run a package script defined in the rootpackage.json. - Run
yarn exec <command>to run an executable from the root of the project. - Run
yarn add <dependency>to add a dependency to the rootpackage.json. - Run
yarn add -D <dependency>to add a dependency to the rootpackage.json. - Run
yarn up -R <dependency>to upgrade a dependency across the monorepo.
For more on these commands, see:
- Run
yarn workspace <package-name> run testto run all tests for a package. - Run
yarn workspace <package-name> run jest --no-coverage <file>to run a specific test file. - Prefer the above two commands, but if you must, run
yarn testto run tests for all packages in the monorepo.
- Run
yarn lintto check for code quality issues across the monorepo. - Run
yarn validate:changelogto check for formatting issues in changelogs. - Run
yarn lint:fixto automatically fix fixable violations.
- Run
yarn buildto build all packages. - Run
yarn workspace <package-name> run buildto build a single package. - Built files appear in
dist/directories and are what gets published to NPM. Test files, secrets, or other things that should not be public should not show up in this directory.
Each consumer-facing change to a package should be accompanied by one or more entries in the changelog for that package. Use the following guidelines when updating changelogs:
- When updating changelogs, follow the "Keep a Changelog" specification:
- When releasing a new version, ensure that there is a header for the version linked to the corresponding tag on GitHub.
- Always ensure there is an Unreleased section above any version section. It may be empty.
- Within each version section or within Unreleased, place changelog entries into one of the following categories (note: categories must be listed in this order):
- Added
- Changed
- Deprecated
- Removed
- Fixed
- Security
- Within a category section, follow these guidelines:
- Highlight breaking changes by prefixing them with
**BREAKING:**. List breaking changes above non-breaking changes in the same category section. A change is breaking if it removes, renames, or changes the signature of any public export (function, type, class, constant), or changes default behavior that consumers rely on. - Omit non-consumer facing changes and reverted changes from the changelog.
- Use a nested list to add more details about the change if it would help engineers. For breaking changes in particular, highlight steps engineers need to take to adapt to the changes.
- Each changelog entry should be followed by links to the pull request(s) that introduced the change.
- Do not simply reuse the PR title in the entry, but describe exact changes to the API or usable surface area of the project.
- When there are multiple upgrades to a package in the same release, combine them into a single entry.
- Each changelog entry should describe one kind of change; if an entry describes too many things, split it up.
- Highlight breaking changes by prefixing them with
- After updating a changelog, run
yarn validate:changelogand fix any errors reported.
- Use
create-release-branchto create a release branch. Readdocs/processes/releasing.mdfor more.
Use yarn create-package --name <name> --description <description> to add a new package to the monorepo.
-
Each package should have an
index.tsfile insrc/that explicitly lists all exports. -
Avoid barrel exports (
export * from './file').ts`. Instead, explicitly name each export:// Bad export * from './foo-controller'; // Good export { FooController } from './foo-controller'; export type { FooControllerMessenger } from './foo-controller';
When adding or updating controllers in packages, follow these guidelines:
- Controller classes should extend
BaseController. - Controllers should not be stateless; if a controller does not have state, it should be a service.
- The controller should define a public messenger type.
- All messenger actions and events should be publicly defined. The default set should include the
:getStateaction and:stateChangeevent. - All actions and events the messenger uses from other controllers and services should also be declared in the messenger type.
- Controllers should initialize state by combining default and provided state. Provided state should be optional.
- The constructor should take
messengerandstateoptions at a minimum. - Make sure to write comprehensive tests for the controller class.
Use the full set of guidelines is in docs/controller-guidelines.md for reference.
Use SampleGasPricesController and SamplePetnamesController in the sample-controllers package as examples for implementation and tests.
When adding or updating data services in packages, follow these guidelines:
- Data services should define a public messenger type.
- All methods defined on the service class should be exposed through the messenger.
- All messenger actions and events should be publicly defined.
- All actions and events the messenger uses from other services should also be declared in the messenger type.
- The constructor should take
messengerandfetchoptions at a minimum. - The constructor should construct a policy using the
createServicePolicyfunction. - Each method in a service class should represent a single endpoint of an API.
- Use the policy to wrap each request to the endpoint.
- If a request has a non-2xx response, throw an error.
- Validate each request's response (throwing an error if invalid) before returning its data.
- Service classes should also define
onRetry,onBreak, andonDegradedmethods. - Make sure to write comprehensive tests for the service class.
Use the sample-gas-prices-service/ directory in the sample-controllers package as examples for implementation and tests.