Skip to content

Commit e335251

Browse files
rekmarksclaude
andauthored
feat: patch-package for SES-compat patches; root patches/ as single source of truth (#874)
## Summary `@MetaMask/ocap-kernel` and `@metamask/kernel-utils` depend on `@chainsafe/libp2p-yamux@7.0.4`, which requires a patch for SES/lockdown compatibility. Previously the patch was applied via Yarn's `patch:` protocol from a root `.yarn/patches/` file — invisible to and unreproducible by consumers. This PR switches to `patch-package` and exposes the patch in published npm tarballs so consumers can apply it. Root `patches/` is the single source of truth; per-package copies are generated at publish time by a script that uses dependency graph analysis to determine which packages need to ship the patch. ## Changes **patch-package setup** - Replace the Yarn `patch:` URL for `@chainsafe/libp2p-yamux` with a plain `7.0.4` version specifier across all dependent packages - Add `patch-package` as a root `devDependency`; add it to the root `postinstall` so the patch is applied after every `yarn install` - Add `patches/@ChainSafe+libp2p-yamux+7.0.4.patch` at the repo root and remove the old `.yarn/patches/` file **Graph-based sink analysis** - Add `scripts/copy-patches.cjs`: for each root patch file, computes which published packages are "sinks" — packages whose consumers may not install any sibling that already handles the patch — and copies patch files to their `patches/` dirs before packaging - A sink is any package that directly depends on the patched dep and has no other package in that set as a transitive dependency. Currently `@metamask/kernel-utils` is the only sink (`@MetaMask/ocap-kernel` depends on it, so its consumers always get `kernel-utils`' `postinstall`) - Add `node scripts/copy-patches.cjs` to `scripts/prepack.sh`; add `packages/*/patches/` to `.gitignore` **Sink package (`@metamask/kernel-utils`)** - Add `"patches/"` to `files` and `"postinstall": "patch-package --patch-dir patches"` so the patch is applied for consumers - Add `"patch-package": "*"` to `peerDependencies` to signal the requirement **Non-sink package (`@MetaMask/ocap-kernel`)** - Remove `"patches/"` from `files`; no `postinstall` needed (kernel-utils handles it) **`yarn constraints` enforcement** - Replace `expectPatchedDependenciesAreDeclaredAndShipped` with graph-aware rules: `expectPatchShippingIsCorrect` (sinks must have the required fields; non-sinks must not — auto-fixable where possible) and `expectNoPatchesForRemovedDependencies` (errors if a root patch targets a package no longer depended on by any workspace) - Keep `expectNoPatchProtocolProductionDependencies` ## Testing - `yarn constraints` passes; `kernel-utils` is identified as a sink and has all required fields; `ocap-kernel` is identified as a non-sink and has none - `node scripts/copy-patches.cjs` copies the patch to `packages/kernel-utils/patches/` only; the generated dir does not appear in `git status` (gitignored) - The patch is applied on `yarn install` (verified via `grep Object.defineProperty node_modules/@chainsafe/libp2p-yamux/dist/src/decode.js`) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes install and publish-time behavior by introducing `patch-package`, root `patches/`, and new Yarn constraint enforcement, which could break dependency installation or package publishing if misconfigured. Runtime code changes are limited to applying an existing third-party patch. > > **Overview** > Moves SES/lockdown dependency patching from Yarn’s `patch:` protocol to `patch-package`, making the repo root `patches/` directory the single source of truth and applying patches automatically on root `postinstall`. > > Adds publish-time patch shipping: `scripts/copy-patches.cjs` computes “sink” packages from the workspace dependency graph and copies root patches into each sink’s `patches/` directory during `prepack` (generated dirs are gitignored). Yarn constraints are extended to forbid `patch:` in production deps, enforce correct sink/non-sink patch shipping config (e.g., `kernel-utils` ships `patches/`, declares `patch-package` peer dep, and runs a guarded `postinstall`), and error on orphaned root patches. > > Also updates a few package dependencies (e.g., `@metamask/snaps-utils`, `@metamask/design-system-react`) and regenerates `yarn.lock` accordingly. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 0de6bb7. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 68dd2dc commit e335251

17 files changed

Lines changed: 770 additions & 114 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ node_modules/
8181
# Bundle files
8282
*.bundle
8383

84+
# Generated per-package patches dirs (populated by scripts/copy-patches.cjs at publish time)
85+
packages/*/patches/
86+
8487
# Playwright reports
8588
playwright-report
8689

.yarn/patches/@chainsafe-libp2p-yamux-npm-7.0.4-284c2f6812.patch

Lines changed: 0 additions & 41 deletions
This file was deleted.

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,30 @@ See [`packages/create-package/README.md`](packages/create-package/README.md).
6666

6767
For information on creating releases, see the [MetaMask/core release documentation](https://github.com/MetaMask/core/blob/main/docs/contributing.md#releasing-changes).
6868

69+
### Patches
70+
71+
Some third-party dependencies require patches for SES/lockdown compatibility. The root
72+
`patches/` directory is the single source of truth for all patches, applied automatically
73+
on `yarn install` via `patch-package`.
74+
75+
Published packages that ship patches to consumers are called "sinks". Sinks are determined
76+
by analyzing the dependency graph: a non-private package that directly depends on a patched
77+
dependency is a sink if none of its transitive internal dependencies also depend on that
78+
patched dependency. Only `dependencies` are considered for sink analysis (not
79+
`peerDependencies` or `devDependencies`).
80+
81+
Sink packages include `patches/` in their `files` field, declare `patch-package` as a
82+
`peerDependency`, and have a `postinstall` script that runs `patch-package --patch-dir patches`.
83+
The `scripts/copy-patches.cjs` script copies root patches into each sink at publish time,
84+
and `yarn constraints` enforces the correct configuration.
85+
86+
**Adding a patch:** Place the `.patch` file in the root `patches/` directory. Run
87+
`yarn constraints --fix` to update sink packages, and verify with
88+
`node scripts/copy-patches.cjs`.
89+
90+
**Removing a patch:** Delete the `.patch` file from the root `patches/` directory and run
91+
`yarn constraints --fix` to clean up sink packages.
92+
6993
## References
7094

7195
- [Glossary](./docs/glossary.md)

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"lint:eslint": "yarn eslint . --cache",
2626
"lint:fix": "yarn constraints --fix && yarn lint:eslint --fix && yarn lint:misc --write && yarn lint:dependencies:fix",
2727
"lint:misc": "prettier --no-error-on-unmatched-pattern '**/*.json' '**/*.md' '**/*.html' '**/*.yml' '!**/CHANGELOG.old.md' '!.yarnrc.yml' '!CLAUDE.md' '!merged-packages/**' --ignore-path .gitignore --log-level error",
28-
"postinstall": "simple-git-hooks && yarn rebuild:native",
28+
"postinstall": "patch-package && simple-git-hooks && yarn rebuild:native",
2929
"prepack": "./scripts/prepack.sh",
3030
"rebuild:native": "./scripts/rebuild-native.sh",
3131
"test": "vitest run",
@@ -89,6 +89,7 @@
8989
"globals": "^16.0.0",
9090
"lint-staged": "^15.5.0",
9191
"lodash": "^4.17.21",
92+
"patch-package": "^8.0.0",
9293
"playwright": "^1.58.2",
9394
"prettier": "^3.5.3",
9495
"prettier-plugin-packagejson": "^2.5.10",
@@ -126,7 +127,8 @@
126127
"@metamask/kernel-cli>@metamask/kernel-node-runtime>@libp2p/webrtc>@ipshipyard/node-datachannel": false,
127128
"@metamask/kernel-cli>@metamask/kernel-node-runtime>@metamask/kernel-store>better-sqlite3": false,
128129
"@metamask/kernel-cli>@metamask/kernel-node-runtime>@metamask/streams": false,
129-
"@metamask/kernel-cli>@metamask/kernel-shims>@libp2p/webrtc>@ipshipyard/node-datachannel": false
130+
"@metamask/kernel-cli>@metamask/kernel-shims>@libp2p/webrtc>@ipshipyard/node-datachannel": false,
131+
"@metamask/kernel-cli>@metamask/kernel-utils": false
130132
}
131133
},
132134
"resolutions": {

packages/brow-2-brow/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
},
3131
"dependencies": {
3232
"@chainsafe/libp2p-noise": "^16.1.3",
33-
"@chainsafe/libp2p-yamux": "patch:@chainsafe/libp2p-yamux@npm%3A7.0.4#~/.yarn/patches/@chainsafe-libp2p-yamux-npm-7.0.4-284c2f6812.patch",
33+
"@chainsafe/libp2p-yamux": "7.0.4",
3434
"@libp2p/autonat": "2.0.38",
3535
"@libp2p/bootstrap": "11.0.47",
3636
"@libp2p/circuit-relay-v2": "3.2.24",

packages/kernel-browser-runtime/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
"@metamask/logger": "workspace:^",
7474
"@metamask/ocap-kernel": "workspace:^",
7575
"@metamask/rpc-errors": "^7.0.3",
76-
"@metamask/snaps-utils": "^11.7.1",
76+
"@metamask/snaps-utils": "^12.1.0",
7777
"@metamask/streams": "workspace:^",
7878
"@metamask/superstruct": "^3.2.1",
7979
"@metamask/utils": "^11.9.0",

packages/kernel-ui/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
"test:dev:quiet": "yarn test:dev --reporter @ocap/repo-tools/vitest-reporters/silent"
6161
},
6262
"dependencies": {
63-
"@metamask/design-system-react": "^0.6.0",
63+
"@metamask/design-system-react": "^0.9.0",
6464
"@metamask/design-system-tailwind-preset": "^0.6.1",
6565
"@metamask/design-tokens": "^8.1.1",
6666
"@metamask/kernel-browser-runtime": "workspace:^",

packages/kernel-utils/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,22 @@ or
1010

1111
`npm install @metamask/kernel-utils`
1212

13+
## SES/Lockdown Compatibility
14+
15+
This package is designed to run under [SES](https://github.com/endojs/endo/tree/master/packages/ses) (Secure ECMAScript lockdown). Some of its dependencies require patches to work in a locked-down environment. The required patch files are included in the `patches/` directory of this package and are applied automatically via the `postinstall` script using [`patch-package`](https://github.com/ds300/patch-package).
16+
17+
Add `patch-package` as a development dependency of your project:
18+
19+
```sh
20+
yarn add --dev patch-package
21+
```
22+
23+
or
24+
25+
```sh
26+
npm install --save-dev patch-package
27+
```
28+
1329
## Contributing
1430

1531
This package is part of a monorepo. Instructions for contributing can be found in the [monorepo README](https://github.com/MetaMask/ocap-kernel#readme).

packages/kernel-utils/package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@
7575
"module": "./dist/index.mjs",
7676
"types": "./dist/index.d.cts",
7777
"files": [
78-
"dist/"
78+
"dist/",
79+
"patches/"
7980
],
8081
"scripts": {
8182
"build": "ts-bridge --project tsconfig.build.json --no-references --clean",
@@ -88,6 +89,7 @@
8889
"lint:eslint": "eslint . --cache",
8990
"lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write && yarn constraints --fix && yarn lint:dependencies",
9091
"lint:misc": "prettier --no-error-on-unmatched-pattern '**/*.json' '**/*.md' '**/*.html' '!**/CHANGELOG.old.md' '**/*.yml' '!.yarnrc.yml' '!merged-packages/**' --ignore-path ../../.gitignore --log-level error",
92+
"postinstall": "[ ! -d patches ] || patch-package --patch-dir patches",
9193
"publish:preview": "yarn npm publish --tag preview",
9294
"test": "vitest run --config vitest.config.ts",
9395
"test:clean": "yarn test --no-cache --coverage.clean",
@@ -98,7 +100,7 @@
98100
},
99101
"dependencies": {
100102
"@chainsafe/libp2p-noise": "^16.1.3",
101-
"@chainsafe/libp2p-yamux": "patch:@chainsafe/libp2p-yamux@npm%3A7.0.4#~/.yarn/patches/@chainsafe-libp2p-yamux-npm-7.0.4-284c2f6812.patch",
103+
"@chainsafe/libp2p-yamux": "7.0.4",
102104
"@endo/captp": "^4.4.8",
103105
"@endo/errors": "^1.2.13",
104106
"@endo/exo": "^1.5.12",
@@ -153,6 +155,7 @@
153155
},
154156
"peerDependencies": {
155157
"acorn": "^8.15.0",
158+
"patch-package": "^8.0.0",
156159
"vite": "^7.3.0"
157160
},
158161
"peerDependenciesMeta": {

packages/ocap-kernel/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,34 @@ or
1010

1111
`npm install @metamask/ocap-kernel`
1212

13+
## SES/Lockdown Compatibility
14+
15+
This package is designed to run under [SES](https://github.com/endojs/endo/tree/master/packages/ses) (Secure ECMAScript lockdown). One of its dependencies, `@chainsafe/libp2p-yamux`, requires a patch to work in a locked-down environment. The required patches are listed in the `patchedDependencies` field of this package's `package.json`, and the patch files are included in the `patches/` directory of this package.
16+
17+
Apply them using [`patch-package`](https://github.com/ds300/patch-package):
18+
19+
1. Install `patch-package`:
20+
21+
```sh
22+
npm install --save-dev patch-package
23+
```
24+
25+
2. Copy the patch file(s) to your project's `patches/` directory:
26+
27+
```sh
28+
cp node_modules/@metamask/ocap-kernel/patches/* patches/
29+
```
30+
31+
3. Add a `postinstall` script to your `package.json`:
32+
33+
```json
34+
"scripts": {
35+
"postinstall": "patch-package"
36+
}
37+
```
38+
39+
4. Run `npm install` (or your package manager's equivalent) to apply the patches.
40+
1341
## Contributing
1442

1543
This package is part of a monorepo. Instructions for contributing can be found in the [monorepo README](https://github.com/MetaMask/ocap-kernel#readme).

0 commit comments

Comments
 (0)