-
-
Notifications
You must be signed in to change notification settings - Fork 230
feat: add @metamask/foundryup
package
#5810
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
9c6da58
0826ab0
1593955
30fb702
6ac55b2
2c366b2
a8ed967
a187b22
c1fef6e
54f6380
9a7c16e
e2f4380
362d8ba
23b6f64
e303de9
e8cc2e9
f9f190b
7b1ee0d
e249f64
59227bf
a91cd2d
cb02fdf
55badf4
aeccadb
1bb5bbb
4e0a45b
04d71d9
6e2b446
53f2c14
410089a
9cd1c7f
29c8199
dee3f17
ce07926
bbcbf1f
2242cfe
58beb89
560f21f
4ff51b7
8eba3af
9fb5bed
8ef7b12
de21a3f
7d2ed49
839d43f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.metamask | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we adding this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
enableGlobalCache: false | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we adding this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the default yarn setting on Extension, so it best replicates how it is used in practice right now. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# Changelog | ||
|
||
All notable changes to this project will be documented in this file. | ||
|
||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), | ||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||
|
||
## [Unreleased] | ||
|
||
[Unreleased]: https://github.com/MetaMask/core/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
MIT License | ||
|
||
Copyright (c) 2025 MetaMask | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# `@metamask/foundryup` | ||
|
||
foundryup | ||
|
||
## Installation | ||
|
||
`yarn add @metamask/foundryup` | ||
|
||
or | ||
|
||
`npm install @metamask/foundryup` | ||
|
||
## Usage | ||
|
||
Once installed into a package you can do `yarn bin mm-foundryup`. | ||
|
||
This will install the latest version of Foundry things by default. | ||
|
||
Try `yarn bin mm-foundryup --help` for more options. | ||
|
||
Once you have the binaries installed, you have to figure out how to get to them. | ||
|
||
Probably best to just add each as a `package.json` script: | ||
|
||
```json | ||
"scripts": { | ||
"anvil": "node_modules/.bin/anvil", | ||
} | ||
``` | ||
|
||
Kind of weird, but it seems to work okay. You can probably use `npx anvil` in place of `node_modules/.bin/anvil`, but | ||
getting it to work in all scenarios (cross platform and in CI) wasn't straightforward. `yarn bin anvil` doesn't work | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IIRC, |
||
in yarn v4 because it isn't a bin of `@metamask/foundryup`, so yarn pretends it doesn't exist. | ||
|
||
This all needs to work. | ||
|
||
--- | ||
|
||
You can try it here in the monorepo by running `yarn workspace @metamask/foundryup anvil`. | ||
|
||
## Contributing | ||
|
||
This package is part of a monorepo. Instructions for contributing can be found in the [monorepo README](https://github.com/MetaMask/core#readme). |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/* | ||
* For a detailed explanation regarding each configuration property and type check, visit: | ||
* https://jestjs.io/docs/configuration | ||
*/ | ||
|
||
const merge = require('deepmerge'); | ||
const path = require('path'); | ||
|
||
const baseConfig = require('../../jest.config.packages'); | ||
|
||
const displayName = path.basename(__dirname); | ||
|
||
module.exports = merge(baseConfig, { | ||
// The display name when running multiple projects | ||
displayName, | ||
|
||
// An object that configures minimum threshold enforcement for coverage results | ||
coverageThreshold: { | ||
global: { | ||
branches: 50, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a way we can start with 100% test coverage instead of 50%? If the threshold is below 100%, it ends up being extremely annoying in the future, as any new gaps in coverage that are introduced in the future are very difficult to find. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hey @mcmire, the reason for adjusting the threshold is that we need a bit more flexibility to reach 100% coverage over time. For now, the team's goal was to hit at least 50%, and we're currently at around 55%, so we're above that baseline. Since I have another PR that depends on this package, would you be open to us following up in a separate PR focused solely on bringing coverage to 100%? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd love to, and normally wouldn't put a PR with such low coverage. But I didn't want to keep blocking Mobile. I won't be able to do any more work for at least a month. Not sure if @cortisiko has the time to increase coverage. |
||
functions: 50, | ||
lines: 50, | ||
statements: 50, | ||
}, | ||
}, | ||
}); |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,82 @@ | ||||||||||||||||||||||
{ | ||||||||||||||||||||||
"name": "@metamask/foundryup", | ||||||||||||||||||||||
"version": "0.0.0", | ||||||||||||||||||||||
"description": "foundryup", | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a more descriptive, hmm, description that we can give here? |
||||||||||||||||||||||
"keywords": [ | ||||||||||||||||||||||
"MetaMask", | ||||||||||||||||||||||
"Ethereum" | ||||||||||||||||||||||
], | ||||||||||||||||||||||
"homepage": "https://github.com/MetaMask/core/tree/main/packages/foundryup#readme", | ||||||||||||||||||||||
"bugs": { | ||||||||||||||||||||||
"url": "https://github.com/MetaMask/core/issues" | ||||||||||||||||||||||
}, | ||||||||||||||||||||||
"repository": { | ||||||||||||||||||||||
"type": "git", | ||||||||||||||||||||||
"url": "https://github.com/MetaMask/core.git" | ||||||||||||||||||||||
}, | ||||||||||||||||||||||
"license": "MIT", | ||||||||||||||||||||||
"sideEffects": false, | ||||||||||||||||||||||
"exports": { | ||||||||||||||||||||||
"./package.json": "./package.json", | ||||||||||||||||||||||
".": { | ||||||||||||||||||||||
"import": { | ||||||||||||||||||||||
"types": "./dist/index.d.mts", | ||||||||||||||||||||||
"default": "./dist/index.mjs" | ||||||||||||||||||||||
}, | ||||||||||||||||||||||
"require": { | ||||||||||||||||||||||
"types": "./dist/index.d.cts", | ||||||||||||||||||||||
"default": "./dist/index.cjs" | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
Comment on lines
+21
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this package isn't a library, should we remove these lines? Note that the Yarn constraints currently enforce that all libraries have the default root export, but we can make an exception for this package.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmmm, the main reason i added this was because the CI lint job would fail because
https://github.com/MetaMask/core/actions/runs/15289137090/job/43005311736 I am happy to remove it. Can I ignore the lint rule for the above? Is that possible? |
||||||||||||||||||||||
}, | ||||||||||||||||||||||
"bin": { | ||||||||||||||||||||||
"mm-foundryup": "./dist/cli.mjs" | ||||||||||||||||||||||
}, | ||||||||||||||||||||||
"files": [ | ||||||||||||||||||||||
"dist/" | ||||||||||||||||||||||
], | ||||||||||||||||||||||
"scripts": { | ||||||||||||||||||||||
"anvil": "node_modules/.bin/anvil", | ||||||||||||||||||||||
"build": "ts-bridge --project tsconfig.build.json --verbose --clean --no-references", | ||||||||||||||||||||||
"build:docs": "typedoc", | ||||||||||||||||||||||
"changelog:update": "../../scripts/update-changelog.sh @metamask/foundryup", | ||||||||||||||||||||||
"changelog:validate": "../../scripts/validate-changelog.sh @metamask/foundryup", | ||||||||||||||||||||||
"publish:preview": "yarn npm publish --tag preview", | ||||||||||||||||||||||
"test": "NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter", | ||||||||||||||||||||||
"test:clean": "NODE_OPTIONS=--experimental-vm-modules jest --clearCache", | ||||||||||||||||||||||
"test:verbose": "NODE_OPTIONS=--experimental-vm-modules jest --verbose", | ||||||||||||||||||||||
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch", | ||||||||||||||||||||||
"since-latest-release": "../../scripts/since-latest-release.sh" | ||||||||||||||||||||||
}, | ||||||||||||||||||||||
"devDependencies": { | ||||||||||||||||||||||
"@metamask/auto-changelog": "^3.4.4", | ||||||||||||||||||||||
"@types/jest": "^27.4.1", | ||||||||||||||||||||||
"@types/unzipper": "^0.10.10", | ||||||||||||||||||||||
"@types/yargs": "^17.0.32", | ||||||||||||||||||||||
"@types/yargs-parser": "^21.0.3", | ||||||||||||||||||||||
"deepmerge": "^4.2.2", | ||||||||||||||||||||||
"jest": "^27.5.1", | ||||||||||||||||||||||
"nock": "^13.3.1", | ||||||||||||||||||||||
"ts-jest": "^27.1.4", | ||||||||||||||||||||||
"typedoc": "^0.24.8", | ||||||||||||||||||||||
"typedoc-plugin-missing-exports": "^2.0.0", | ||||||||||||||||||||||
"typescript": "~5.2.2", | ||||||||||||||||||||||
"yaml": "^2.3.4" | ||||||||||||||||||||||
}, | ||||||||||||||||||||||
"engines": { | ||||||||||||||||||||||
"node": "^18.18 || >=20" | ||||||||||||||||||||||
}, | ||||||||||||||||||||||
"publishConfig": { | ||||||||||||||||||||||
"access": "public", | ||||||||||||||||||||||
"registry": "https://registry.npmjs.org/" | ||||||||||||||||||||||
}, | ||||||||||||||||||||||
"dependencies": { | ||||||||||||||||||||||
"minipass": "^7.1.2", | ||||||||||||||||||||||
"tar": "^7.4.3", | ||||||||||||||||||||||
"unzipper": "^0.12.3", | ||||||||||||||||||||||
"yargs": "^17.7.2", | ||||||||||||||||||||||
"yargs-parser": "^21.1.1" | ||||||||||||||||||||||
}, | ||||||||||||||||||||||
"main": "./dist/index.cjs", | ||||||||||||||||||||||
"types": "./dist/index.d.cts" | ||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
#!/usr/bin/env node | ||
|
||
/** | ||
* CLI entry point for Foundryup. | ||
* | ||
* This script downloads and installs Foundry binaries. | ||
* If an error occurs, it logs the error and exits with code 1. | ||
*/ | ||
import { downloadAndInstallFoundryBinaries } from '.'; | ||
|
||
/** | ||
* Run the main installation process and handle errors. | ||
*/ | ||
downloadAndInstallFoundryBinaries().catch((error) => { | ||
/** | ||
* Log any error that occurs during installation and exit with code 1. | ||
* | ||
* @param error - The error thrown during installation. | ||
*/ | ||
console.error('Error:', error); | ||
process.exit(1); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import { request as httpRequest, type IncomingMessage } from 'node:http'; | ||
import { request as httpsRequest } from 'node:https'; | ||
import { Stream } from 'node:stream'; | ||
import { pipeline } from 'node:stream/promises'; | ||
|
||
import type { DownloadOptions } from './types'; | ||
|
||
/** | ||
* A PassThrough stream that emits a 'response' event when the HTTP(S) response is available. | ||
*/ | ||
class DownloadStream extends Stream.PassThrough { | ||
/** | ||
* Returns a promise that resolves with the HTTP(S) IncomingMessage response. | ||
* | ||
* @returns The HTTP(S) response stream. | ||
*/ | ||
async response(): Promise<IncomingMessage> { | ||
return new Promise((resolve, reject) => { | ||
this.once('response', resolve); | ||
this.once('error', reject); | ||
}); | ||
} | ||
} | ||
|
||
/** | ||
* Starts a download from the given URL. | ||
* | ||
* @param url - The URL to download from | ||
* @param options - The download options | ||
* @param redirects - The number of redirects that have occurred | ||
* @returns A stream of the download | ||
*/ | ||
export function startDownload( | ||
url: URL, | ||
options: DownloadOptions = {}, | ||
redirects: number = 0, | ||
) { | ||
const MAX_REDIRECTS = options.maxRedirects ?? 5; | ||
const request = url.protocol === 'http:' ? httpRequest : httpsRequest; | ||
const stream = new DownloadStream(); | ||
request(url, options, (response) => { | ||
stream.once('close', () => { | ||
response.destroy(); | ||
}); | ||
|
||
const { statusCode, statusMessage, headers } = response; | ||
// handle redirects | ||
if ( | ||
statusCode && | ||
statusCode >= 300 && | ||
statusCode < 400 && | ||
headers.location | ||
) { | ||
if (redirects >= MAX_REDIRECTS) { | ||
stream.emit('error', new Error('Too many redirects')); | ||
response.destroy(); | ||
} else { | ||
// note: we don't emit a response until we're done redirecting, because | ||
// handlers only expect it to be emitted once. | ||
pipeline( | ||
startDownload(new URL(headers.location, url), options, redirects + 1) | ||
// emit the response event to the stream | ||
.once('response', stream.emit.bind(stream, 'response')), | ||
stream, | ||
).catch(stream.emit.bind(stream, 'error')); | ||
response.destroy(); | ||
} | ||
} | ||
|
||
// check for HTTP errors | ||
else if (!statusCode || statusCode < 200 || statusCode >= 300) { | ||
stream.emit( | ||
'error', | ||
new Error( | ||
`Request to ${url} failed. Status Code: ${statusCode} - ${statusMessage}`, | ||
), | ||
); | ||
response.destroy(); | ||
} else { | ||
// resolve with response stream | ||
stream.emit('response', response); | ||
|
||
response.once('error', stream.emit.bind(stream, 'error')); | ||
pipeline(response, stream).catch(stream.emit.bind(stream, 'error')); | ||
} | ||
}) | ||
.once('error', stream.emit.bind(stream, 'error')) | ||
.end(); | ||
return stream; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice if we didn't have to add these rules globally. It's one of our (soft) goals to simplify this file eventually.
Can you explain why these are needed? I can probably guess why, but just want to make sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah so the rule is disabled because this is a Node.js package that needs to use Node.js built-in modules.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure why these needed to be added. cc @cortisiko ?