Commit a949073
feat(platform-api-docs): add
## Explanation
`@metamask/platform-api-docs` documents the platform API — every
messenger action and event a project exposes. Until now it had one way
of finding them: parse every TypeScript source and declaration file it
can reach (the scan directories, `packages/*/src`, and
`node_modules/@metamask/*/dist/**/*.d.cts`) and walk every type alias
named `*Messenger`.
That is the right approach for this monorepo, which has no single
messenger aggregating every capability. It is a poor fit for a client,
which already declares the complete set on its root messenger.
Re-deriving that from the whole dependency tree means parsing ~11,600
files in `metamask-mobile` and ~4,500 in `metamask-extension`, to
rediscover something the client has written down in one place.
This PR adds a second strategy that reads what the client already
declares.
### `--strategy`
- **`scan`** (default) — unchanged behaviour, and the only option for a
project with no single aggregating messenger.
- **`root-messenger`** — resolves the two types named by
`--root-actions` and `--root-events` (each written `<file>#<TypeName>`)
and lets the TypeScript type checker enumerate them. Only the named
files are opened; the checker pulls in the rest.
Flags belonging to the strategy that wasn't selected are rejected rather
than ignored, via a yargs `.check`, so a mistaken invocation fails
loudly instead of quietly producing docs built the wrong way. The
`<file>#<TypeName>` references are parsed in a yargs `.coerce`, so a
malformed one is reported like any other
bad argument before work begins.
### Why the type checker rather than the AST
This is the non-obvious part. The two clients declare their root unions
differently:
- `metamask-mobile` writes `GlobalActions` by hand as a union of type
references. A syntactic walk would work.
- `metamask-extension` derives `RootMessengerActions` from a registry of
messenger factories via
`MessengerActions<ReturnType<(typeof
MESSENGER_FACTORIES)[…]['getMessenger']>>`.
**There is no syntactic union to walk** — only the type checker can say
what it contains.
Going through the checker handles both shapes with one code path. Once
it reports *which* capability types are in the union, each declaration
is handed to the **existing** extractor in `extraction.ts`, so JSDoc,
handler/payload signatures, source links, and deprecation flags come out
identical to `scan`. The new module is a discovery front-end, not a
second extractor.
Two details worth knowing:
- A capability declared as a **type alias** carries its name and JSDoc
on the *alias* symbol; the plain symbol points at the anonymous object
type. An **interface** has no alias symbol, being its own declaration,
so both are
consulted. Missing the interface case silently dropped 61 actions and 8
events on mobile before it was fixed.
- For a lone generic instantiation (`type Actions = Foo<Bar>`) the
checker attributes the alias to the *root union itself*, which would
hand the extractor the wrong declaration. That case is guarded.
### Failure behaviour
Generation now fails loudly instead of producing an empty site.
`writeOutput` deletes `docs/` before writing, so a root union that
resolves to nothing — a renamed type, or imports that don't resolve —
would previously have replaced a
published docs directory with an empty one and exited `0`. It now
throws, naming both references.
Capability types that can't be documented are reported with their names
rather than counted, in three buckets: declared inline (no name or
JSDoc), unresolved (`any`/`unknown`, usually a failed import), and
unextractable (a shape the extractor rejects). A count alone isn't
actionable at this scale.
### Also fixed: MDX escaping
`escapeJsDocTextForMdx` escaped `{` and `}` but not `<`, which MDX reads
as the start of a JSX tag. A `@returns` comment such as
`Promise<PointsBoostDto[]>` therefore **failed the site build** rather
than rendering:
```
Unexpected character `[` (U+005B) in name, expected a name character…
```
This is pre-existing and independent of the new strategy — `scan`
produces the byte-identical line — but it blocked `--build` and
`--serve` for both clients, so it is fixed here. Affects description,
`@param`, and `@returns` text; handler and payload signatures were
already safe inside fenced code blocks.
---
## Benchmarks
Measured on a warm checkout, doc generation only.
| Project | `scan` | `root-messenger` | Speedup |
| --- | --- | --- | --- |
| `metamask-extension` | 47.9s | **4.7s** | ~10× |
| `metamask-mobile` | 96.9s | **5.7s** | ~17× |
`scan` parses ~5,600 `app/**/*.ts` plus ~6,026 `.d.cts` in mobile, and
~4,472 `.d.cts` in the extension. `root-messenger` opens the entry file
and lets the checker pull in only what the union references.
## Strategy comparison
### `metamask-mobile`
| | `scan` | `root-messenger` |
| --- | --- | --- |
| Namespaces | 112 | 103 |
| Actions | 1184 | 1157 |
| Events | 181 | 171 |
| Unique capabilities | 1365 | 1328 (97.3%) |
The 37 not documented are mostly controllers genuinely **not on the root
messenger** — `PasskeyController` alone accounts for 17, plus
`RatesController` (4), the sample controllers, and the decrypt/encrypt
message managers. Nothing is found by `root-messenger` that `scan`
misses.
### `metamask-extension`
| | `scan` | `root-messenger` |
| --- | --- | --- |
| Namespaces | 119 | 115 |
| Actions | 1209 | 1085 |
| Events | 183 | 176 |
| Unique capabilities | 1392 | 1261 (90.6%) |
99 of the 131-capability gap is `PerpsController`, and the cause is
worth flagging to the extension team rather than treating as a tool
limitation:
```ts
export type PerpsControllerMessenger = Messenger<
'PerpsController',
AllowedActions, // actions Perps may CALL
AllowedEvents
>;
```
`RootMessengerActions` is `MessengerActions<ChildMessengers>` — the
union of what each child messenger is **allowed to call**, not what each
controller **provides**. A controller whose actions are only invoked
from the UI, never from another controller's messenger, never appears.
`PerpsController` is registered in
`MESSENGER_FACTORIES` yet contributes **zero** constituents.
`root-messenger` documents exactly what the named types contain. Full
coverage in. the extension needs an aggregate of *provided* actions,
which is an extension-side change.
Conversely, `root-messenger` finds 6 capabilities `scan`
misses(`MultichainRoutingService` ×4, `PPOMController` ×2).
Reported-but-skipped, current run: mobile 39 unextractable; extension 2
inline + 33 unextractable.
---
## Usage in clients
Once published, add the dependency and two scripts. For
`metamask-mobile`:
```json
{
"scripts": {
"docs:platform-api:build": "platform-api-docs --build --project-label Mobile --strategy root-messenger --root-actions 'app/core/Engine/types.ts#GlobalActions' --root-events 'app/core/Engine/types.ts#GlobalEvents'",
"docs:platform-api:serve": "platform-api-docs --serve --project-label Mobile --site-base-url / --strategy root-messenger --root-actions 'app/core/Engine/types.ts#GlobalActions' --root-events 'app/core/Engine/types.ts#GlobalEvents'"
}
}
```
For `metamask-extension`, the label and references change:
```
--project-label Extension
--root-actions 'app/scripts/lib/messenger.ts#RootMessengerActions'
--root-events 'app/scripts/lib/messenger.ts#RootMessengerEvents'
```
Notes for consumers:
- Keep the `#` **inside quotes** — unquoted, most shells treat it as a
comment and silently truncate the argument.
- `--root-actions` / `--root-events` are relative to the project path,
not the shell's working directory.
- Output defaults to `<project-path>/.platform-api-docs`; gitignore it.
- `metamask-extension` additionally needs its `postcss-loader/jiti`
resolution narrowed to `postcss-loader@^8.2.1/jiti`. The unversioned
form also stubs the `postcss-loader@^7.3.4` that `@docusaurus/bundler`
depends on, replacing `jiti` with an empty package and breaking the site
build. Narrowing preserves the stub's original intent for the
extension's own `postcss-loader@8.2.1`.
## References
Fixes: https://consensyssoftware.atlassian.net/browse/WPC-1202
* Consumer PR (mobile):
MetaMask/metamask-mobile#26526
* Consumer PR (extension, includes the `jiti` resolution fix):
MetaMask/metamask-extension#40352
## 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
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Changes are confined to the docs CLI and generation pipeline; default
`scan` behavior is preserved with an explicit `strategy` field in tests.
>
> **Overview**
> Adds a **`root-messenger`** discovery path alongside the existing
**`scan`** default: the CLI accepts `--strategy`, `--root-actions`, and
`--root-events` (`<file>#<TypeName>`), validates that strategy-specific
flags are not mixed, and routes generation through type-checker
resolution of the project’s root action/event unions instead of scanning
the whole tree.
>
> New **`root-messenger-discovery`** walks those unions (including
checker-derived unions like `MessengerActions<…>`), reuses the shared
extractor in **`extraction.ts`** (with
**`classifyMessengerCapabilityTypeDeclaration`** exported for reuse),
warns on skipped inline or unextractable capabilities, and **throws**
when unions resolve to `any`/`unknown` or would produce zero docs so an
empty site cannot overwrite published output.
>
> **`escapeJsDocTextForMdx`** now escapes `<` as well as braces so
generic types in JSDoc do not break MDX builds. README, changelog, and
broad CLI/generate/discovery tests cover the new behavior.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
1da2bbd. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Elliot Winkler <elliot.winkler@gmail.com>root-messenger strategy (#9913)1 parent 8e61b10 commit a949073
10 files changed
Lines changed: 2414 additions & 123 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
12 | | - | |
| 12 | + | |
13 | 13 | | |
14 | 14 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
38 | | - | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
39 | 45 | | |
40 | 46 | | |
41 | 47 | | |
42 | 48 | | |
43 | 49 | | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
44 | 77 | | |
45 | 78 | | |
46 | 79 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
164 | 164 | | |
165 | 165 | | |
166 | 166 | | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
167 | 338 | | |
0 commit comments