Skip to content

Conversation

@mcmire
Copy link
Contributor

@mcmire mcmire commented Jan 30, 2026

Description

Some MetaMask repos (e.g. core) allow upcoming package changes to be tested in Extension and/or Mobile prior to release by publishing "preview builds".

However, using a preview build can be complicated, as care must be taken to accurately simulate what the dependency tree of a project will look like when a new production version is released.

To make this process easier, this commit adds a Yarn plugin. The plugin works like this:

  1. First, you must tell the plugin which preview builds you want to use for which dependencies. You do this by adding an entry to a previewBuilds section in package.json. For instance, to specify that version 29.0.0-preview-3ec2a74 should be used to test non-breaking changes to @metamask/network-controller, you would add:

     "previewBuilds": {
       "@metamask/network-controller": {
         "type": "non-breaking",
         "previewVersion": "29.0.0-preview-3ec2a74"
       }
     }
    

    Similarly, you could set type to "breaking" to test breaking changes.

    By default the plugin will assume the NPM scope of the preview build to be metamask-previews, but you can change this with previewScope:

     "previewBuilds": {
       "@metamask/network-controller": {
         "type": "non-breaking",
         "previewScope": "my-custom-scope"
         "previewVersion": "29.0.0-preview-3ec2a74",
       }
     }
    
  2. Next, run yarn install. The plugin will read the previewBuilds section to determine how the dependencies you've specified should be resolved. If the type of an entry is "non-breaking", all version ranges of that dependency in the dependency tree that are major-compatible with the corresponding entry in dependencies will resolve to the preview build. If the type is "breaking", then only the version range of the corresponding entry in dependencies will resolve to the preview build. Finally, if a dependency is patched, the preview build will be patched in the same way.

Open in GitHub Codespaces

Changelog

CHANGELOG entry: null

Related issues

https://consensyssoftware.atlassian.net/browse/WPC-199

Manual testing steps

  • Add the following section to package.json:
    "previewBuilds": {
      "@metamask/network-controller": {
        "type": "breaking",
        "previewVersion": "29.0.0-preview-3ec2a74"
      }
    },
    
  • Run yarn install. You should see a message at the end of the installation steps:
    [plugin-preview-builds] The following dependencies were mapped to preview builds:
    - metamask@workspace:./@metamask/network-controller@npm:^29.0.0 -> npm:@metamask-previews/[email protected]
    
  • Run yarn why @metamask-previews/network-controller. You should see only one entry:
    └─ metamask@workspace:.
       └─ @metamask-previews/network-controller@npm:29.0.0-preview-3ec2a74 (via npm:@metamask-previews/[email protected])
    
    This proves that only the root instance of @metamask/network-controller was updated to use the preview build.
  • Update previewBuilds in package.json to read:
    "previewBuilds": {
      "@metamask/network-controller": {
        "type": "non-breaking",
        "previewVersion": "29.0.0-preview-3ec2a74"
      }
    },
    
  • Run yarn install. You should see a message at the end of the installation steps:
    [plugin-preview-builds] The following dependencies were mapped to preview builds:
    - @metamask/network-controller@npm:^29.0.0 -> npm:@metamask-previews/[email protected]
    
  • Run yarn why @metamask-previews/network-controller. You should see a bunch of entries:
    ├─ @metamask/accounts-controller@npm:35.0.2
    │  └─ @metamask-previews/network-controller@npm:29.0.0-preview-3ec2a74 (via npm:@metamask-previews/[email protected])
    │
    ├─ @metamask/accounts-controller@npm:35.0.2 [2d27a]
    │  └─ @metamask-previews/network-controller@npm:29.0.0-preview-3ec2a74 (via npm:@metamask-previews/[email protected])
    │
    ├─ @metamask/accounts-controller@npm:35.0.2 [30658]
    │  └─ @metamask-previews/network-controller@npm:29.0.0-preview-3ec2a74 (via npm:@metamask-previews/[email protected])
    ...
    
    This proves that all instances of @metamask/network-controller matching ^29.0.0 were updated to use the preview build.
  • Now take away previewBuilds and re-run yarn install.
  • Now run yarn patch @metamask/network-controller@npm:29.0.0. Open the resulting patch directory and make a modification to any file. Run yarn patch-commit -s <directory> to generate the patch and update package.json, then run yarn install to apply the patch.
  • Now add the following section back to package.json:
    "previewBuilds": {
      "@metamask/network-controller": {
        "type": "breaking",
        "previewVersion": "29.0.0-preview-3ec2a74"
      }
    },
    
  • Run yarn install. You should see a message at the end of the installation steps:
    [plugin-preview-builds] The following dependencies were mapped to preview builds:
    - metamask@workspace:./@metamask/network-controller@patch:@metamask/network-controller@npm%3A29.0.0#~/.yarn/patches/@metamask-network-controller-npm-29.0.0-51af044f37.patch
      -> patch:@metamask-previews/network-controller@npm%3A29.0.0-preview-3ec2a74#~/.yarn/patches/@metamask-network-controller-npm-29.0.0-51af044f37.patch
    
  • Run yarn why @metamask/network-controller. You should see a bunch of entries:
    ├─ @metamask/accounts-controller@npm:35.0.2
    │  └─ @metamask/network-controller@patch:@metamask/network-controller@npm%3A29.0.0#~/.yarn/patches/@metamask-network-controller-npm-29.0.0-51af044f37.patch::version=29.0.0&hash=3e054c (via patch:@metamask/network-controller@npm%3A29.0.0#~/.yarn/patches/@metamask-network-controller-npm-29.0.0-51af044f37.patch)
    │
    ├─ @metamask/accounts-controller@npm:35.0.2 [2d27a]
    │  └─ @metamask/network-controller@patch:@metamask/network-controller@npm%3A29.0.0#~/.yarn/patches/@metamask-network-controller-npm-29.0.0-51af044f37.patch::version=29.0.0&hash=3e054c (via patch:@metamask/network-controller@npm%3A29.0.0#~/.yarn/patches/@metamask-network-controller-npm-29.0.0-51af044f37.patch)
    │
    ├─ @metamask/accounts-controller@npm:35.0.2 [30658]
    │  └─ @metamask/network-controller@patch:@metamask/network-controller@npm%3A29.0.0#~/.yarn/patches/@metamask-network-controller-npm-29.0.0-51af044f37.patch::version=29.0.0&hash=3e054c (via patch:@metamask/network-controller@npm%3A29.0.0#~/.yarn/patches/@metamask-network-controller-npm-29.0.0-51af044f37.patch)
    │
    
    This proves that the preview build is patched.

Screenshots/Recordings

(N/A)

Before

After

Pre-merge author checklist

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

Note

Medium Risk
Introduces a custom Yarn resolution plugin that can rewrite dependency ranges (including patch: ranges), which can affect installs across the repo if misconfigured, but is limited to @metamask/* packages and opt-in via previewBuilds.

Overview
Adds a new Yarn plugin (.yarn/plugins/@yarnpkg/plugin-preview-builds.cjs) that reads a previewBuilds config from package.json and rewrites matching @metamask/* dependency resolutions to @metamask-previews/* (or a custom scope), supporting breaking (top-level only) vs non-breaking (major-compatible across the tree) behavior and preserving patch: dependencies.

Updates .yarnrc.yml to load the new plugin so the mapping and end-of-install summary/errors run during yarn install.

Written by Cursor Bugbot for commit 4fd9d4c. This will update automatically on new commits. Configure here.

Some MetaMask repos (e.g. `core`) allow upcoming package changes to be
tested in Extension and/or Mobile prior to release by publishing
"preview builds".

However, using a preview build can be complicated, as care must be taken
to accurately simulate what the dependency tree of a project will look
like when a new production version is released.

To make this process easier, this commit adds a Yarn plugin. The plugin
works like this:

1. First, you must tell the plugin which preview builds you want to use
   for which dependencies. You do this by adding an entry to a
   `previewBuilds` section in `package.json`. For instance, to specify
   that version `29.0.0-preview-3ec2a74` should be used to test
   non-breaking changes to `@metamask/network-controller`, you would
   add:

        "previewBuilds": {
          "@metamask/network-controller": {
            "type": "non-breaking",
            "previewVersion": "29.0.0-preview-3ec2a74"
          }
        }

    Similarly, you could set `type` to "breaking" to test breaking
    changes.

    By default the plugin will assume the NPM scope of the preview build
    to be `metamask-previews`, but you can change this with
    `previewScope`:

        "previewBuilds": {
          "@metamask/network-controller": {
            "type": "non-breaking",
            "previewScope": "my-custom-scope"
            "previewVersion": "29.0.0-preview-3ec2a74",
          }
        }

2. Next, run `yarn install`. The plugin will read the `previewBuilds`
   section to determine how the dependencies you've specified should be
   resolved. If the `type` of an entry is "non-breaking", all version
   ranges of that dependency in the dependency tree that are
   major-compatible with the corresponding entry in `dependencies` will
   resolve to the preview build. If the `type` is "breaking", then only
   the version range of the corresponding entry in `dependencies` will
   resolve to the preview build. Finally, if a dependency is patched,
   the preview build will be patched in the same way.
@github-actions
Copy link
Contributor

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

@metamaskbot metamaskbot added the team-core-platform Core Platform team label Jan 30, 2026
@metamaskbotv2
Copy link
Contributor

metamaskbotv2 bot commented Jan 30, 2026

✨ Files requiring CODEOWNER review ✨

👨‍🔧 @MetaMask/extension-platform (1 files, +1 -0)
  • 📄 .yarnrc.yml +1 -0

@metamaskbotv2
Copy link
Contributor

metamaskbotv2 bot commented Jan 30, 2026

Builds ready [4fd9d4c]
UI Startup Metrics (1324 ± 100 ms)
PlatformBuildTypePageMetricTest Title (ms)Persona (ms)Mean (ms)Min (ms)Max (ms)Std Dev (ms)P 75 (ms)P 95 (ms)
ChromeBrowserifyStandard Home0--------
1--------
2--------
3--------
4--------
5--------
6--------
7--------
8--------
9--------
10--------
11--------
12--------
13--------
14--------
15--------
16--------
17--------
18--------
uiStartup--13241090161410013871508
load--1129922137010112001308
domContentLoaded--1121919135210011941304
domInteractive--271686172478
firstPaint--166691020109201310
backgroundConnect--23922034719243271
firstReactRender--17113651928
initialActions--105113
loadScripts--89770011241009671076
setupStore--1263051523
numNetworkReqs--211585171572
19--------
BrowserifyPower User Home0--------
1--------
2--------
3--------
4--------
5--------
6--------
7--------
8--------
9--------
10--------
11--------
12--------
13--------
14--------
15--------
16--------
17--------
18--------
uiStartup--19691566226813920702222
load--1095975158614610861519
domContentLoaded--1079967157914310691495
domInteractive--3719144243892
firstPaint--1896948381242328
backgroundConnect--32929152732340385
firstReactRender--23164052433
initialActions--107113
loadScripts--83572813171338211201
setupStore--1675471829
numNetworkReqs--1254825553144235
19--------
WebpackStandard Home0--------
1--------
2--------
3--------
4--------
5--------
6--------
7--------
8--------
9--------
10--------
11--------
12--------
13--------
14--------
15--------
16--------
17--------
18--------
uiStartup--86268511411039301072
load--704607101986764881
domContentLoaded--698604101485756872
domInteractive--261596172379
firstPaint--1136235856144225
backgroundConnect--46191953550136
firstReactRender--16103451928
initialActions--102112
loadScripts--695602101284753867
setupStore--13689101427
numNetworkReqs--221590181578
19--------
WebpackPower User Home0--------
1--------
2--------
3--------
4--------
5--------
6--------
7--------
8--------
9--------
10--------
11--------
12--------
13--------
14--------
15--------
16--------
17--------
18--------
uiStartup--1257845216224114911669
load--7116151261111710999
domContentLoaded--7016071257112697991
domInteractive--35181532933130
firstPaint--1416548591180333
backgroundConnect--15913136653156349
firstReactRender--22153832327
initialActions--105112
loadScripts--6986051249110695983
setupStore--1151831316
numNetworkReqs--1254926557153248
19--------
FirefoxBrowserifyStandard Home0--------
1--------
2--------
3--------
4--------
5--------
6--------
7--------
8--------
9--------
10--------
11--------
12--------
13--------
14--------
15--------
16--------
17--------
18--------
uiStartup--13771093192315914511652
load--1121951167412212021349
domContentLoaded--1120951167412212021348
domInteractive--71332444592145
firstPaint--------
backgroundConnect--59233045161171
firstReactRender--1494971338
initialActions--103122
loadScripts--1093938164811411641300
setupStore--1259512945
numNetworkReqs--231287181876
19--------
BrowserifyPower User Home0--------
1--------
2--------
3--------
4--------
5--------
6--------
7--------
8--------
9--------
10--------
11--------
12--------
13--------
14--------
15--------
16--------
17--------
18--------
uiStartup--26271941435447227753902
load--12871031260528512311900
domContentLoaded--12871030260528512311900
domInteractive--139371451175116508
firstPaint--------
backgroundConnect--3021171454262289932
firstReactRender--21153542328
initialActions--204123
loadScripts--12371010258024811951750
setupStore--14210838205119747
numNetworkReqs--72362154691192
19--------
WebpackStandard Home0--------
1--------
2--------
3--------
4--------
5--------
6--------
7--------
8--------
9--------
10--------
11--------
12--------
13--------
14--------
15--------
16--------
17--------
18--------
uiStartup--16111306215917516861939
load--13841134178712214711601
domContentLoaded--13831129178612214711601
domInteractive--813023145114147
firstPaint--------
backgroundConnect--66242654680159
firstReactRender--14113231417
initialActions--102122
loadScripts--13441116159210014151520
setupStore--165209291157
numNetworkReqs--221284161964
19--------
WebpackPower User Home0--------
1--------
2--------
3--------
4--------
5--------
6--------
7--------
8--------
9--------
10--------
11--------
12--------
13--------
14--------
15--------
16--------
17--------
18--------
uiStartup--29532034419159834194120
load--16261178266341318822578
domContentLoaded--16261178266341318822578
domInteractive--12132972140104427
firstPaint--------
backgroundConnect--3081221465242312931
firstReactRender--22153952733
initialActions--203122
loadScripts--15651155263938117652380
setupStore--1518890210199707
numNetworkReqs--69362154292175
19--------
📊 Page Load Benchmark Results

Current Commit: 4fd9d4c | Date: 1/30/2026

📄 Localhost MetaMask Test Dapp

Samples: 100

Summary

  • pageLoadTime-> current mean value: 1.06s (±61ms) 🟡 | historical mean value: 1.03s ⬆️ (historical data)
  • domContentLoaded-> current mean value: 740ms (±82ms) 🟢 | historical mean value: 717ms ⬆️ (historical data)
  • firstContentfulPaint-> current mean value: 93ms (±150ms) 🟢 | historical mean value: 76ms ⬆️ (historical data)

📈 Detailed Results

Metric Mean Std Dev Min Max P95 P99
pageLoadTime 1.06s 61ms 1.01s 1.60s 1.07s 1.60s
domContentLoaded 740ms 82ms 705ms 1.52s 751ms 1.52s
firstPaint 93ms 150ms 60ms 1.58s 88ms 1.58s
firstContentfulPaint 93ms 150ms 60ms 1.58s 88ms 1.58s
largestContentfulPaint 0ms 0ms 0ms 0ms 0ms 0ms
Bundle size diffs
  • background: 58 Bytes (0%)
  • ui: 0 Bytes (0%)
  • common: 20 Bytes (0%)

@mcmire mcmire marked this pull request as ready for review January 30, 2026 20:32
@mcmire mcmire requested a review from a team as a code owner January 30, 2026 20:32
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

originalDependencyDescriptor,
});
}
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale state persists causing unintended preview resolutions

Medium Severity

The module-level state (validatedPreviewBuilds, errors, resolutions) is only cleared in afterAllInstalled. If installation fails before that hook runs, and the user modifies or removes entries from previewBuilds config, the next installation will use stale state. Notably, if all preview builds are removed (packageNames.length === 0), validateProject returns early without clearing validatedPreviewBuilds, causing reduceDependency to silently apply old preview build resolutions that the user explicitly removed.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validatePreviewBuilds isn't persisted across runs. I don't think the problem presented here is likely to happen.

(previewBuilds, [packageName, config]) => {
previewBuilds[packageName] = {
...config,
previewScope: config.previewScope ?? DEFAULT_PREVIEW_SCOPE,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Null config value crashes before validation can run

Low Severity

When a previewBuilds entry has a null value (e.g., "@metamask/foo": null), getPreviewBuildConfigurations crashes at config.previewScope with a confusing TypeError before validatePreviewBuildConfiguration can provide the intended "Expected an object" error message. The validation logic exists but is bypassed because the config processing happens first.

Fix in Cursor Fix in Web

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this is true. I can fix this in a followup PR (I think this is true of the plugin on the mobile side as well).

Copy link
Member

@Gudahtt Gudahtt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@mcmire mcmire enabled auto-merge January 30, 2026 21:19
@mcmire mcmire added this pull request to the merge queue Feb 2, 2026
Merged via the queue into main with commit 407e069 Feb 2, 2026
355 of 358 checks passed
@mcmire mcmire deleted the add-use-preview-build-yarn-plugin branch February 2, 2026 18:32
@github-actions github-actions bot locked and limited conversation to collaborators Feb 2, 2026
@metamaskbot metamaskbot added the release-13.18.0 Issue or pull request that will be included in release 13.18.0 label Feb 2, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release-13.18.0 Issue or pull request that will be included in release 13.18.0 size-XS team-core-platform Core Platform team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants