feat: add @metamask/platform-api-docs package#8012
Conversation
63f3188 to
7c63a0d
Compare
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub. |
|
Caution MetaMask internal reviewing guidelines:
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
b55b682 to
980f677
Compare
|
@metamaskbot publish-preview |
|
Preview builds have been published. See these instructions for more information about preview builds. Expand for full list of packages and versions. |
980f677 to
f49a7dd
Compare
|
@metamaskbot publish-preview |
1 similar comment
|
@metamaskbot publish-preview |
f49a7dd to
44fb063
Compare
|
@metamaskbot publish-preview |
@metamask/messenger-docs
@metamask/messenger-docs@metamask/messenger-docs package
Docusaurus site for browsing controller messenger actions/events, with offline search powered by docusaurus-search-local.
## Explanation The messenger docs generation currently lives in `scripts/generate-messenger-docs/` and the Docusaurus site template in `docs-site/`. This makes it unusable by external clients (metamask-extension, metamask-mobile) without access to this monorepo. This PR extracts both into a new `@metamask/messenger-docs` package at `packages/messenger-docs/` with a CLI, so any project with `@metamask` controller dependencies can generate and serve messenger API docs. ### Usage ```bash # Default: scans cwd for node_modules/@MetaMask controller/service packages npx @metamask/messenger-docs # Scan a specific project npx @metamask/messenger-docs /path/to/project # Generate + build static site npx @metamask/messenger-docs --build # Generate + serve (build + http server) npx @metamask/messenger-docs --serve # Generate + dev server (hot reload) npx @metamask/messenger-docs --dev # Scan source .ts files instead of .d.cts (for monorepo development) npx @metamask/messenger-docs --source # Custom output directory (default: .messenger-docs) npx @metamask/messenger-docs --output ./my-docs ``` ## References - Builds on top of `feat/messenger-docs-site` ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them
cb82c24 to
17adacd
Compare
`eslint-plugin-import-x`, `eslint-plugin-n`, `eslint-import-resolver-typescript`, and `prettier` were floating to older versions because `yarn.lock` happened to hold them; the platform- api-docs PR (#8012) added scoped resolutions to keep that frozen. Bumping the four packages to the latest versions matching their semver ranges removes the need for those resolutions and surfaces five pre-existing lint issues that the older plugins didn't flag: - `eslint-plugin-n@18` rejects `import.meta.dirname` in `eslint.config.mjs:132` because the property is only stable in Node 22.16+/24+, while the repo's engines field is `^18.18 || >=20`. Resolve `dirname(fileURLToPath(import.meta.url))` instead so the config works on every supported Node version. - `eslint-plugin-import-x@4.16` enables `no-named-as-default` by default. `@metamask/api-specs` exports `MetaMaskOpenRPCDocument` as both a named export and the default; switch the `chain-agnostic-permission` import to the named form to avoid the ambiguous reference. - `import-x/no-extraneous-dependencies` now catches `@metamask/messenger` in `react-data-query/src/createUIQueryClient.test.ts` and `@metamask/keyring-controller` in `transaction-pay-controller/src/strategy/relay/hyperliquid-withdraw{,.test}.ts`. Both packages already used these symbols at runtime/test time without declaring them. Add them to the relevant `dependencies` / `devDependencies` blocks. `yarn dedupe` and `yarn constraints --fix` also rolled the `messenger-cli` package's `prettier` range forward to match the new root range. No behavior changes in any of the affected packages — their tests all still pass at 100% coverage.
Drops the four scoped resolutions for `eslint-import-resolver-typescript`, `eslint-plugin-import-x`, `eslint-plugin-n`, and `prettier@npm:^3.3.3`. These were a defense against lockfile regeneration drifting forward to plugin versions that the codebase wasn't yet lint-clean against. Resetting `yarn.lock` to main's copy and re-running `yarn install` + `yarn dedupe` shows the resolved versions land at the exact pins the resolutions were enforcing — `eslint-plugin-import-x@4.6.1`, `eslint-import-resolver-typescript@3.7.0`, `eslint-plugin-n@17.15.1`, and `prettier@3.4.2` — because main's lockfile already records those specific versions. So the resolutions were strictly redundant on this branch and can come out without changing anything that yarn actually installs. Coverage, lint, and the end-to-end docs build all still pass.
|
@metamaskbot publish-preview |
|
Preview builds have been published. Learn how to use preview builds in other projects. Expand for full list of packages and versions. |
`eslint-plugin-import-x`, `eslint-plugin-n`, `eslint-import-resolver-typescript`, and `prettier` were floating to older versions because `yarn.lock` happened to hold them; the platform- api-docs PR (#8012) added scoped resolutions to keep that frozen. Bumping the four packages to the latest versions matching their semver ranges removes the need for those resolutions and surfaces five pre-existing lint issues that the older plugins didn't flag: - `eslint-plugin-n@18` rejects `import.meta.dirname` in `eslint.config.mjs:132` because the property is only stable in Node 22.16+/24+, while the repo's engines field is `^18.18 || >=20`. Resolve `dirname(fileURLToPath(import.meta.url))` instead so the config works on every supported Node version. - `eslint-plugin-import-x@4.16` enables `no-named-as-default` by default. `@metamask/api-specs` exports `MetaMaskOpenRPCDocument` as both a named export and the default; switch the `chain-agnostic-permission` import to the named form to avoid the ambiguous reference. - `import-x/no-extraneous-dependencies` now catches `@metamask/messenger` in `react-data-query/src/createUIQueryClient.test.ts` and `@metamask/keyring-controller` in `transaction-pay-controller/src/strategy/relay/hyperliquid-withdraw{,.test}.ts`. Both packages already used these symbols at runtime/test time without declaring them. Add them to the relevant `dependencies` / `devDependencies` blocks. `yarn dedupe` and `yarn constraints --fix` also rolled the `messenger-cli` package's `prettier` range forward to match the new root range. No behavior changes in any of the affected packages — their tests all still pass at 100% coverage.
`eslint-plugin-import-x`, `eslint-plugin-n`, `eslint-import-resolver-typescript`, and `prettier` were floating to older versions because `yarn.lock` happened to hold them; the platform- api-docs PR (#8012) added scoped resolutions to keep that frozen. Bumping the four packages to the latest versions matching their semver ranges removes the need for those resolutions and surfaces five pre-existing lint issues that the older plugins didn't flag: - `eslint-plugin-n@18` rejects `import.meta.dirname` in `eslint.config.mjs:132` because the property is only stable in Node 22.16+/24+, while the repo's engines field is `^18.18 || >=20`. Resolve `dirname(fileURLToPath(import.meta.url))` instead so the config works on every supported Node version. - `eslint-plugin-import-x@4.16` enables `no-named-as-default` by default. `@metamask/api-specs` exports `MetaMaskOpenRPCDocument` as both a named export and the default; switch the `chain-agnostic-permission` import to the named form to avoid the ambiguous reference. - `import-x/no-extraneous-dependencies` now catches `@metamask/messenger` in `react-data-query/src/createUIQueryClient.test.ts` and `@metamask/keyring-controller` in `transaction-pay-controller/src/strategy/relay/hyperliquid-withdraw{,.test}.ts`. Both packages already used these symbols at runtime/test time without declaring them. Add them to the relevant `dependencies` / `devDependencies` blocks. `yarn dedupe` and `yarn constraints --fix` also rolled the `messenger-cli` package's `prettier` range forward to match the new root range. No behavior changes in any of the affected packages — their tests all still pass at 100% coverage.
…aMask#8971) ## Explanation `packages/react-data-query/src/createUIQueryClient.test.ts` imports `Messenger` from `@metamask/messenger`, but the package wasn't listed in `react-data-query`'s `devDependencies`. That's an `import-x/no-extraneous-dependencies` violation — the currently-pinned plugin version misses it but newer versions flag it. This PR adds the missing devDep so the relationship is declared explicitly and the test file lints cleanly under newer plugin versions. Also regenerates `README.md` so the workspace dependency graph reflects the new edge. ## References - Surfaced while investigating the scoped lint-plugin resolutions on MetaMask#8012. - Extracted from the parked MetaMask#8940 so the dep cleanup can land independently of the lint-plugin major bumps that PR also carried. ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) — N/A, devDep only, not consumer-facing - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them — N/A
…er as a dependency (MetaMask#8972) ## Explanation `src/strategy/relay/hyperliquid-withdraw{,.test}.ts` imports `@metamask/keyring-controller` at runtime, but the package wasn't listed in `dependencies`. Consumers were relying on transitive hoisting to install it — once a stricter `import-x/no-extraneous-dependencies` runs (or yarn's hoisting layout changes), the missing dep would surface. This PR adds it explicitly so the dep relationship is correct and consumers get the package installed transitively. Also regenerates `README.md` so the workspace dependency graph reflects the new edge. ## References - Surfaced while investigating the scoped lint-plugin resolutions on MetaMask#8012. - Extracted from the parked MetaMask#8940 so the dep cleanup can land independently of the lint-plugin major bumps that PR also carried. ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them — N/A
mcmire
left a comment
There was a problem hiding this comment.
I guess I hadn't reviewed the workflow in detail. I had some comments on that.
Apply 7 cleanups from #8012 review: - Drop `branches: [main]` from `pull_request` trigger so PRs targeting any base run the workflow. - Match `main.yml`'s concurrency pattern so main pushes don't cancel each other and `cancel-in-progress` flips off on main. - Remove the workflow-level `permissions:` block — `build-docs` doesn't need a token, and the `deploy` job declares its own. - Drop `contents: read` on `build-docs` — the job doesn't use `GITHUB_TOKEN`. - Consolidate the two `upload-artifact` steps into one driven by a ternary on `name` and `retention-days`. - Pin `peaceiris/actions-gh-pages` to a specific commit (`4f9cc6602d…`) instead of the unallowlisted `v4.1.0` tag. - Add an `actions/checkout@v5` step to the `deploy` job; `peaceiris/actions-gh-pages` uses `git` to remove existing files, so it needs a (shallow) checkout to run.
| name: Build Platform API docs | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout and setup environment | ||
| uses: MetaMask/action-checkout-and-setup@v3 | ||
| with: | ||
| is-high-risk-environment: true | ||
|
|
||
| - name: Generate and build Platform API docs | ||
| # The site is published under the `/platform-api/` subdirectory of | ||
| # the repo's GitHub Pages site so that other doc sites (e.g. | ||
| # package API docs) can be hosted alongside under sibling paths. | ||
| run: | | ||
| yarn docs:platform-api:build \ | ||
| --site-url "https://${{ github.repository_owner }}.github.io" \ | ||
| --site-base-url "/${{ github.event.repository.name }}/platform-api/" | ||
|
|
||
| - name: Upload build artifact | ||
| uses: actions/upload-artifact@v7 | ||
| with: | ||
| name: ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' && 'platform-api-docs-build' || 'platform-api-docs' }} | ||
| path: .platform-api-docs/build/ | ||
| retention-days: ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' && 1 || 7 }} | ||
|
|
||
| deploy: |
There was a problem hiding this comment.
Fixed in 7b7c7f2. Added permissions: contents: read to the build-docs job.
| } | ||
|
|
||
| const branch = await resolveDefaultBranch(projectPath); | ||
| return `https://github.com/${match[1]}/blob/${branch}/`; |
There was a problem hiding this comment.
Source links ignore commit SHA
Medium Severity
The CLI resolves the short Git commit and stamps it on the index and Docusaurus tagline, but GitHub Source links are built with resolveRepoBaseUrl, which always uses the default origin/HEAD branch (typically main), not that commit. In CI and on feature branches, line anchors can point at the wrong file revision relative to the documented commit.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 90a0f37. Configure here.
There was a problem hiding this comment.
Fixed in 3de1562. resolveRepoBaseUrl now takes the commit SHA and uses it as the ref when provided, falling back to the default branch only when no SHA is available. Source links generated from a specific commit now pin to that revision.
| href: 'https://github.com/MetaMask/core', | ||
| label: 'GitHub', | ||
| position: 'right', | ||
| }, |
There was a problem hiding this comment.
Navbar hardcodes MetaMask core repo
Medium Severity
The Docusaurus navbar always links to https://github.com/MetaMask/core, including the optional “commit <sha>” item, even though the tool is meant to run from other repos (Extension, Mobile) and already discovers origin when building per-item source links. Published docs from non-core projects mislead readers to the wrong repository, and the commit label never opens that commit on GitHub.
Reviewed by Cursor Bugbot for commit 90a0f37. Configure here.
There was a problem hiding this comment.
Fixed in 3de1562. The CLI now resolves the project's origin remote and passes it through as DOCS_REPO_URL. The navbar uses it for both the GitHub link and the commit <sha> item (the latter now links to <repo>/commit/<sha>). Falls back to MetaMask/core only when running outside a git checkout.
`GITHUB_TOKEN` can't be granted branch protections that disallow direct human pushes while still letting this workflow publish, which is what we want for `gh-pages`. Switch the deploy to a PAT obtained via Token Exchange Service — mirrors the pattern in `update-changelogs.yml`. - New `get-token` job (runs only on the main-branch push path) uses `MetaMask/github-tools/.github/actions/get-token@v1` with the `contents: write` permission needed to push to `gh-pages`. - `deploy` job no longer carries job-level `permissions:` (the PAT provides the auth) and now runs under the `github-pages` environment alongside `get-token`. - `peaceiris/actions-gh-pages` now receives the token via `personal_token` instead of `github_token`. Per mcmire (#8012 r3343433253). Requires the `github-pages` environment to be configured in the repo with the Token Exchange Service URL set as the `TOKEN_EXCHANGE_URL` variable — secrets/env wiring follows the Notion runbook Maarten posted.
| name: Deploy to GitHub Pages | ||
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | ||
| needs: | ||
| - build-docs | ||
| - get-token | ||
| runs-on: ubuntu-latest | ||
| environment: github-pages | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v6 | ||
| with: | ||
| fetch-depth: 1 | ||
|
|
||
| - name: Download build artifact | ||
| uses: actions/download-artifact@v8 | ||
| with: | ||
| name: platform-api-docs-build | ||
| path: build/ | ||
|
|
||
| - name: Deploy to GitHub Pages | ||
| uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0 | ||
| with: | ||
| # Use a PAT obtained via Token Exchange Service rather than the | ||
| # default `GITHUB_TOKEN`. `GITHUB_TOKEN` can't be granted branch | ||
| # protections that disallow direct human pushes while still letting | ||
| # this workflow publish, which is what we want for `gh-pages`. | ||
| personal_token: ${{ needs.get-token.outputs.token }} | ||
| publish_dir: ./build | ||
| destination_dir: platform-api | ||
| # Preserve sibling subdirectories (e.g. /package-api/) so this | ||
| # deploy only overwrites /platform-api/. | ||
| keep_files: true |
There was a problem hiding this comment.
I don't understand why we need to do this, but I can take a closer look at this tomorrow to be sure.
There was a problem hiding this comment.
Fixed in 7b7c7f2. Added permissions: contents: read to the deploy job; the actual gh-pages push uses the TES PAT, not GITHUB_TOKEN.
Per mcmire (#8012 r3343616628): chain the docs workflow off the `lint-build-test` job so any TypeScript / lint errors surface before the docs build is attempted, instead of letting both run in parallel and producing two failure reports. Convert `deploy-platform-api-docs.yml` to a `workflow_call` callable and add a `deploy-platform-api-docs` job to `main.yml` that depends on `lint-build-test`. The build-docs portion of the callable still runs on every PR (added to `all-jobs-complete`'s `needs`) so docs build regressions block merges, while the `get-token` + `deploy` jobs remain gated on main pushes.
| console.warn(error); | ||
| } | ||
| } | ||
| return items; |
There was a problem hiding this comment.
Cross-file extraction order bug
Medium Severity
extractFromDirectory parses and extracts each file in the same loop pass, so a *Messenger declaration can run before sibling imports are added to the ts-morph project. Imported umbrella unions and capability types may not resolve, so actions and events are missing from generated docs depending on lexicographic file order.
Reviewed by Cursor Bugbot for commit 961b07a. Configure here.
CodeQL flagged both jobs for not declaring an explicit `permissions:` block on `GITHUB_TOKEN`. `actions/checkout` (used by both via either `MetaMask/action-checkout-and-setup` or `actions/checkout@v6`) needs `contents: read` to fetch the repo, even though the actual `gh-pages` push in the `deploy` job uses the TES-issued PAT rather than `GITHUB_TOKEN`. Without the explicit block the job inherits the repo's default token permissions, which is broader than necessary. Resolves the two CodeQL findings linked from #8012 (153, 154).
Two related cleanups surfaced by Cursor Bugbot: - Source links built by `resolveRepoBaseUrl` previously always used the default branch (typically `main`). When docs were generated from a feature branch or a specific commit, the line anchors pointed at whatever `main` happens to be — not the documented revision. The function now takes the commit SHA and prefers it as the ref, falling back to the default branch when no SHA is available. - The Docusaurus navbar hardcoded `https://github.com/MetaMask/core` for both the "GitHub" link and the optional "commit <sha>" item, so published docs from other repos (Extension, Mobile) misled readers to the wrong repository. The CLI now passes the origin remote URL through as `DOCS_REPO_URL`, and the navbar uses it for both items (linking the commit item to `${DOCS_REPO_URL}/commit/<sha>`). Plumbing: extracted `resolveRepoUrl` (the bare `https://github.com/<owner>/<repo>`) as a reusable helper, exported it from `generate.ts`, and call it from `cli.ts` alongside `resolveCommitSha`. `resolveRepoBaseUrl` becomes a thin wrapper that composes the repo URL with the ref. Two new tests: `uses the documented commit SHA in source links when provided` and three cases for `resolveRepoUrl` covering GitHub remote, non-GitHub remote, and missing remote.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 8 total unresolved issues (including 7 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit a3545ae. Configure here.
| params, | ||
| returns, | ||
| handlerOrPayload: handlerOrPayloadSignature, | ||
| sourceFile: path.relative(projectPath, sourceFile.getFilePath()), |
There was a problem hiding this comment.
Backslashes break GitHub source links
Medium Severity
Relative sourceFile paths are built with path.relative, which uses backslashes on Windows. Those values are concatenated into GitHub blob URLs without normalizing separators, so docs generated on Windows can produce broken Source links.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit a3545ae. Configure here.
|
@metamaskbot publish-preview |
|
Preview builds have been published. Learn how to use preview builds in other projects. Expand for full list of packages and versions. |


Explanation
Adds
@metamask/platform-api-docs, a publishable package that generates and serves a searchable site documenting the Platform API — the catalog of actions and events available to clients through the message bus.The package scans TypeScript source and
.d.ctsdeclaration files, finds every*Messengertype declaration, walks itsActionsandEventstype arguments to discover the capability types they reference, extracts JSDoc/handler/payload information for each, and renders the result as a Docusaurus site with per-namespace pages and local search.Features
*Messengertype aliases and walksMessenger<Namespace, Actions, Events>to find the capability types — eliminating false positives from unrelated types that happen to share an action/event-like shape.type FooAction = { type: '...'; handler: ... }) and capability-type constructors (type FooGetStateAction = ControllerGetStateAction<typeof name, State>), including theirinterfacevariants.FooActions = FooGetAction | FooSetAction, the walker descends through it without documenting the intermediate alias.--scan-dirs,packages/*/src/(.ts), andnode_modules/@metamask/*/dist/(.d.cts).git remote get-url originandgit symbolic-ref refs/remotes/origin/HEADto buildhttps://github.com/<owner>/<repo>/blob/<branch>/<path>#L<line>URLs; falls back tomainfor shallow clones.--project-label Core/--project-label Extensionproduces titles likePlatform API (Core), and the short Git commit is shown in the intro and navbar so engineers can tell how current the docs are.Usage
From the core monorepo:
From client projects (Extension, Mobile), install
@metamask/platform-api-docsas a devDependency and add a script:{ "scripts": { "docs:platform-api:build": "platform-api-docs --build --project-label MyProject" } }Implementation
packages/platform-api-docs/— separate workspace from@metamask/messenger-cli(different deps and release cadence).execa. Flags:--build,--serve,--dev,--scan-dir(additive, repeatable),--output,--project-label. Configuration is CLI-only — nopackage.jsonconfig block.jsDoc.getDescription()+getTags()) replaces the previous hand-rolled comment parser.TypeAliasDeclaration/InterfaceDeclarationnodes directly tagged with'action'/'event'kind, so the main loop doesn't re-walk the source file looking up names.globpackage, with results sorted for deterministic output across filesystems.// istanbul ignorecomments.deploy-platform-api-docs.yml) builds docs on PRs (uploads the build as an artifact) and deploys to GitHub Pages onmain.References
Checklist
Note
Low Risk
New tooling and CI publishing static docs; no runtime changes to controllers or messenger behavior in client apps.
Overview
Introduces
@metamask/platform-api-docs, a CLI that scans TypeScript (monorepopackages/*/src, optional scan dirs, andnode_modules/@metamask/*/dist.d.cts) for*Messengertypes, walksActions/Eventsunions to extract JSDoc and handler/payload signatures, and emits a Docusaurus site with per-namespace pages and local search.Monorepo wiring: root scripts
docs:platform-api:{build,dev,serve}, README/CODEOWNERS, ESLint ignores and Node rules for the package,.gitignorefor.platform-api-docs/, and adeploy-platform-api-docsreusable workflow (artifact on PRs; onmain, deploy togh-pagesunderplatform-api/withkeep_filesand a PAT via token exchange).main.ymlruns that job after lint/build/test and gatesall-jobs-completeon it. ESLint heap for the repo is bumped to 8GB.The package is publishable (
platform-api-docsbin), heavily tested (CLI, discovery, extraction, generate), and themed for MetaMask docs; consumers can pass--project-label, commit SHA, and site URL/base for hosted docs.Reviewed by Cursor Bugbot for commit a3545ae. Bugbot is set up for automated code reviews on this repo. Configure here.