@foundry-rs/forge 1.3.5-nightly.20250914.72c1aa6 → 1.3.6-nightly.20250917.5dbd958
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/bin/forge.mjs +17 -27
- package/dist/install.mjs +73 -22
- package/package.json +6 -10
- package/dist/index.mjs +0 -36
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Forge
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Forge is a command-line tool that ships with Foundry. Forge tests, builds, and deploys your smart contracts.
|
|
4
|
+
Forge is part of the Foundry suite and is installed alongside `cast`, `chisel`, and `anvil`.
|
package/bin/forge.mjs
CHANGED
|
@@ -19,43 +19,33 @@ const BINARY_DISTRIBUTION_PACKAGES = {
|
|
|
19
19
|
};
|
|
20
20
|
const BINARY_NAME = process.platform === "win32" ? "forge.exe" : "forge";
|
|
21
21
|
const PLATFORM_SPECIFIC_PACKAGE_NAME = BINARY_DISTRIBUTION_PACKAGES[process.platform][process.arch];
|
|
22
|
+
const colors = {
|
|
23
|
+
red: "\x1B[31m",
|
|
24
|
+
green: "\x1B[32m",
|
|
25
|
+
yellow: "\x1B[33m",
|
|
26
|
+
blue: "\x1B[34m",
|
|
27
|
+
magenta: "\x1B[35m",
|
|
28
|
+
cyan: "\x1B[36m",
|
|
29
|
+
white: "\x1B[37m",
|
|
30
|
+
reset: "\x1B[0m"
|
|
31
|
+
};
|
|
22
32
|
|
|
23
33
|
//#endregion
|
|
24
34
|
//#region src/forge.ts
|
|
25
35
|
const require = NodeModule.createRequire(import.meta.url);
|
|
26
36
|
const __dirname = NodePath.dirname(fileURLToPath(import.meta.url));
|
|
27
37
|
function getBinaryPath() {
|
|
28
|
-
const { platform, arch } = process;
|
|
29
|
-
let packageName;
|
|
30
|
-
let binaryName = "forge";
|
|
31
|
-
switch (platform) {
|
|
32
|
-
case "win32":
|
|
33
|
-
binaryName += ".exe";
|
|
34
|
-
if (arch === "x64") packageName = "@foundry-rs/forge-win32-amd64";
|
|
35
|
-
break;
|
|
36
|
-
case "darwin":
|
|
37
|
-
if (arch === "x64") packageName = "@foundry-rs/forge-darwin-amd64";
|
|
38
|
-
else if (arch === "arm64") packageName = "@foundry-rs/forge-darwin-arm64";
|
|
39
|
-
break;
|
|
40
|
-
case "linux":
|
|
41
|
-
if (arch === "x64") packageName = "@foundry-rs/forge-linux-amd64";
|
|
42
|
-
else if (arch === "arm64") packageName = "@foundry-rs/forge-linux-arm64";
|
|
43
|
-
break;
|
|
44
|
-
default: throw new Error(`Unsupported platform: ${platform}-${arch}`);
|
|
45
|
-
}
|
|
46
|
-
if (!packageName) {
|
|
47
|
-
console.error(`Unsupported platform: ${platform}-${arch}`);
|
|
48
|
-
process.exit(1);
|
|
49
|
-
}
|
|
50
38
|
try {
|
|
51
|
-
const binaryPath = require.resolve(`${
|
|
39
|
+
const binaryPath = require.resolve(`${PLATFORM_SPECIFIC_PACKAGE_NAME}/bin/${BINARY_NAME}`);
|
|
52
40
|
if (NodeFS.existsSync(binaryPath)) return binaryPath;
|
|
53
|
-
} catch
|
|
41
|
+
} catch {
|
|
54
42
|
return NodePath.join(__dirname, "..", "dist", BINARY_NAME);
|
|
55
43
|
}
|
|
56
|
-
console.error(`Platform-specific package ${
|
|
57
|
-
console.error("This usually means the installation failed or your platform is not supported.");
|
|
58
|
-
console.error(
|
|
44
|
+
console.error(colors.red, `Platform-specific package ${PLATFORM_SPECIFIC_PACKAGE_NAME} not found.`);
|
|
45
|
+
console.error(colors.yellow, "This usually means the installation failed or your platform is not supported.");
|
|
46
|
+
console.error(colors.reset);
|
|
47
|
+
console.error(colors.yellow, `Platform: ${process.platform}, Architecture: ${process.arch}`);
|
|
48
|
+
console.error(colors.reset);
|
|
59
49
|
process.exit(1);
|
|
60
50
|
}
|
|
61
51
|
NodeChildProcess.spawn(getBinaryPath(), process.argv.slice(2), { stdio: "inherit" });
|
package/dist/install.mjs
CHANGED
|
@@ -41,26 +41,49 @@ const colors = {
|
|
|
41
41
|
const __dirname = NodePath.dirname(fileURLToPath(import.meta.url));
|
|
42
42
|
const fallbackBinaryPath = NodePath.join(__dirname, BINARY_NAME);
|
|
43
43
|
const require = NodeModule.createRequire(import.meta.url);
|
|
44
|
+
const isLocalhostHost = (hostname) => hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
|
|
45
|
+
function ensureSecureUrl(urlString, purpose) {
|
|
46
|
+
try {
|
|
47
|
+
const url = new URL(urlString);
|
|
48
|
+
if (url.protocol === "http:") {
|
|
49
|
+
const allowInsecure = process.env.ALLOW_INSECURE_REGISTRY === "true";
|
|
50
|
+
if (!isLocalhostHost(url.hostname) && !allowInsecure) throw new Error(`Refusing to use insecure HTTP for ${purpose}: ${urlString}. Set ALLOW_INSECURE_REGISTRY=true to override (not recommended).`);
|
|
51
|
+
}
|
|
52
|
+
} catch {}
|
|
53
|
+
}
|
|
44
54
|
function makeRequest(url) {
|
|
45
55
|
return new Promise((resolve, reject) => {
|
|
46
|
-
|
|
47
|
-
|
|
56
|
+
ensureSecureUrl(url, "HTTP request");
|
|
57
|
+
(url.startsWith("https:") ? NodeHttps : NodeHttp).get(url, (response) => {
|
|
48
58
|
if (response?.statusCode && response.statusCode >= 200 && response.statusCode < 300) {
|
|
49
59
|
const chunks = [];
|
|
50
60
|
response.on("data", (chunk) => chunks.push(chunk));
|
|
51
|
-
response.on("end", () =>
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
61
|
+
response.on("end", () => resolve(Buffer.concat(chunks)));
|
|
62
|
+
} else if (response?.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
63
|
+
const redirected = (() => {
|
|
64
|
+
try {
|
|
65
|
+
return new URL(response.headers.location, url).href;
|
|
66
|
+
} catch {
|
|
67
|
+
return response.headers.location;
|
|
68
|
+
}
|
|
69
|
+
})();
|
|
70
|
+
makeRequest(redirected).then(resolve, reject);
|
|
71
|
+
} else reject(/* @__PURE__ */ new Error(`Package registry responded with status code ${response.statusCode} when downloading the package.`));
|
|
72
|
+
}).on("error", (error) => reject(error));
|
|
59
73
|
});
|
|
60
74
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Scoped package names should be percent-encoded
|
|
77
|
+
* e.g. @scope/pkg -> %40scope%2Fpkg
|
|
78
|
+
*/
|
|
79
|
+
const encodePackageNameForRegistry = (name) => name.startsWith("@") ? encodeURIComponent(name) : name;
|
|
80
|
+
/**
|
|
81
|
+
* Tar archives are organized in 512 byte blocks.
|
|
82
|
+
* Blocks can either be header blocks or data blocks.
|
|
83
|
+
* Header blocks contain file names of the archive in the first 100 bytes, terminated by a null byte.
|
|
84
|
+
* The size of a file is contained in bytes 124-135 of a header block and in octal format.
|
|
85
|
+
* The following blocks will be data blocks containing the file.
|
|
86
|
+
*/
|
|
64
87
|
function extractFileFromTarball(tarballBuffer, filepath) {
|
|
65
88
|
let offset = 0;
|
|
66
89
|
while (offset < tarballBuffer.length) {
|
|
@@ -73,8 +96,9 @@ function extractFileFromTarball(tarballBuffer, filepath) {
|
|
|
73
96
|
}
|
|
74
97
|
throw new Error(`File ${filepath} not found in tarball`);
|
|
75
98
|
}
|
|
76
|
-
async function
|
|
99
|
+
async function downloadBinaryFromRegistry() {
|
|
77
100
|
const registryUrl = getRegistryUrl().replace(/\/$/, "");
|
|
101
|
+
ensureSecureUrl(registryUrl, "registry URL");
|
|
78
102
|
const encodedName = encodePackageNameForRegistry(PLATFORM_SPECIFIC_PACKAGE_NAME);
|
|
79
103
|
let desiredVersion;
|
|
80
104
|
try {
|
|
@@ -86,16 +110,43 @@ async function downloadBinaryFromNpm() {
|
|
|
86
110
|
const metaBuffer = await makeRequest(metaUrl);
|
|
87
111
|
const metadata = JSON.parse(metaBuffer.toString("utf8"));
|
|
88
112
|
const version = desiredVersion || metadata?.["dist-tags"]?.latest;
|
|
89
|
-
const
|
|
90
|
-
const dist = versionMeta?.dist;
|
|
113
|
+
const dist = (metadata?.versions?.[version])?.dist;
|
|
91
114
|
if (!dist?.tarball) throw new Error(`Could not find tarball for ${PLATFORM_SPECIFIC_PACKAGE_NAME}@${version} from ${metaUrl}`);
|
|
115
|
+
ensureSecureUrl(dist.tarball, "tarball URL");
|
|
92
116
|
console.info(colors.green, "Downloading binary from:\n", dist.tarball, "\n", colors.reset);
|
|
117
|
+
/**
|
|
118
|
+
* Download the tarball of the right binary distribution package
|
|
119
|
+
* Verify integrity: prefer SRI integrity (sha512/sha256/sha1),
|
|
120
|
+
* fallback to legacy dist.shasum (sha1 hex). Fail if neither unless explicitly allowed.
|
|
121
|
+
*/
|
|
93
122
|
const tarballDownloadBuffer = await makeRequest(dist.tarball);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
if (
|
|
98
|
-
|
|
123
|
+
(() => {
|
|
124
|
+
let verified = false;
|
|
125
|
+
const sriMatch = (typeof dist.integrity === "string" ? dist.integrity : "").match(/^([a-z0-9]+)-([A-Za-z0-9+/=]+)$/i);
|
|
126
|
+
if (sriMatch) {
|
|
127
|
+
const algo = sriMatch[1].toLowerCase();
|
|
128
|
+
const expected = sriMatch[2];
|
|
129
|
+
if (new Set([
|
|
130
|
+
"sha512",
|
|
131
|
+
"sha256",
|
|
132
|
+
"sha1"
|
|
133
|
+
]).has(algo)) {
|
|
134
|
+
const actual = NodeCrypto.createHash(algo).update(tarballDownloadBuffer).digest("base64");
|
|
135
|
+
if (expected !== actual) throw new Error(`Downloaded tarball failed integrity check (${algo} mismatch)`);
|
|
136
|
+
verified = true;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (!verified && typeof dist.shasum === "string" && dist.shasum.length === 40) {
|
|
140
|
+
const expectedSha1Hex = dist.shasum.toLowerCase();
|
|
141
|
+
const actualSha1Hex = NodeCrypto.createHash("sha1").update(tarballDownloadBuffer).digest("hex");
|
|
142
|
+
if (expectedSha1Hex !== actualSha1Hex) throw new Error("Downloaded tarball failed integrity check (sha1 shasum mismatch)");
|
|
143
|
+
verified = true;
|
|
144
|
+
}
|
|
145
|
+
if (!verified) {
|
|
146
|
+
if (!(process.env.ALLOW_NO_INTEGRITY === "true" || process.env.ALLOW_UNVERIFIED_TARBALL === "true")) throw new Error("No integrity metadata found for downloaded tarball. Set ALLOW_NO_INTEGRITY=true to bypass (not recommended).");
|
|
147
|
+
console.warn(colors.yellow, "Warning: proceeding without integrity verification (explicitly allowed).", colors.reset);
|
|
148
|
+
}
|
|
149
|
+
})();
|
|
99
150
|
const tarballBuffer = NodeZlib.gunzipSync(tarballDownloadBuffer);
|
|
100
151
|
NodeFS.writeFileSync(fallbackBinaryPath, extractFileFromTarball(tarballBuffer, `package/bin/${BINARY_NAME}`), { mode: 493 });
|
|
101
152
|
}
|
|
@@ -110,7 +161,7 @@ function isPlatformSpecificPackageInstalled() {
|
|
|
110
161
|
if (!PLATFORM_SPECIFIC_PACKAGE_NAME) throw new Error("Platform not supported!");
|
|
111
162
|
if (!isPlatformSpecificPackageInstalled()) {
|
|
112
163
|
console.log("Platform specific package not found. Will manually download binary.");
|
|
113
|
-
|
|
164
|
+
downloadBinaryFromRegistry();
|
|
114
165
|
} else console.log("Platform specific package already installed. Skipping manual download.");
|
|
115
166
|
|
|
116
167
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foundry-rs/forge",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.6-nightly.20250917.5dbd958",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"homepage": "https://getfoundry.sh/forge",
|
|
6
6
|
"description": "Fast and flexible Ethereum testing framework",
|
|
7
|
-
"main": "./dist/index.mjs",
|
|
8
|
-
"engines": {
|
|
9
|
-
"node": ">=20"
|
|
10
|
-
},
|
|
11
7
|
"bin": {
|
|
12
8
|
"forge": "./bin/forge.mjs"
|
|
13
9
|
},
|
|
@@ -19,11 +15,11 @@
|
|
|
19
15
|
"postinstall": "node ./dist/install.mjs"
|
|
20
16
|
},
|
|
21
17
|
"optionalDependencies": {
|
|
22
|
-
"@foundry-rs/forge-darwin-arm64": "1.3.
|
|
23
|
-
"@foundry-rs/forge-darwin-amd64": "1.3.
|
|
24
|
-
"@foundry-rs/forge-linux-arm64": "1.3.
|
|
25
|
-
"@foundry-rs/forge-linux-amd64": "1.3.
|
|
26
|
-
"@foundry-rs/forge-win32-amd64": "1.3.
|
|
18
|
+
"@foundry-rs/forge-darwin-arm64": "1.3.6-nightly.20250917.5dbd958",
|
|
19
|
+
"@foundry-rs/forge-darwin-amd64": "1.3.6-nightly.20250917.5dbd958",
|
|
20
|
+
"@foundry-rs/forge-linux-arm64": "1.3.6-nightly.20250917.5dbd958",
|
|
21
|
+
"@foundry-rs/forge-linux-amd64": "1.3.6-nightly.20250917.5dbd958",
|
|
22
|
+
"@foundry-rs/forge-win32-amd64": "1.3.6-nightly.20250917.5dbd958"
|
|
27
23
|
},
|
|
28
24
|
"publishConfig": {
|
|
29
25
|
"access": "public",
|
package/dist/index.mjs
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import NodeModule from "node:module";
|
|
3
|
-
import NodeChildProcess from "node:child_process";
|
|
4
|
-
import NodePath from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
|
|
7
|
-
//#region src/const.ts
|
|
8
|
-
const BINARY_DISTRIBUTION_PACKAGES = {
|
|
9
|
-
darwin: {
|
|
10
|
-
x64: "@foundry-rs/forge-darwin-amd64",
|
|
11
|
-
arm64: "@foundry-rs/forge-darwin-arm64"
|
|
12
|
-
},
|
|
13
|
-
linux: {
|
|
14
|
-
x64: "@foundry-rs/forge-linux-amd64",
|
|
15
|
-
arm64: "@foundry-rs/forge-linux-arm64"
|
|
16
|
-
},
|
|
17
|
-
win32: { x64: "@foundry-rs/forge-win32-amd64" }
|
|
18
|
-
};
|
|
19
|
-
const BINARY_NAME = process.platform === "win32" ? "forge.exe" : "forge";
|
|
20
|
-
const PLATFORM_SPECIFIC_PACKAGE_NAME = BINARY_DISTRIBUTION_PACKAGES[process.platform][process.arch];
|
|
21
|
-
|
|
22
|
-
//#endregion
|
|
23
|
-
//#region src/index.ts
|
|
24
|
-
const require = NodeModule.createRequire(import.meta.url);
|
|
25
|
-
const __dirname = NodePath.dirname(fileURLToPath(import.meta.url));
|
|
26
|
-
function getBinaryPath() {
|
|
27
|
-
try {
|
|
28
|
-
return require.resolve(`${PLATFORM_SPECIFIC_PACKAGE_NAME}/bin/${BINARY_NAME}`);
|
|
29
|
-
} catch (_error) {
|
|
30
|
-
return NodePath.join(__dirname, BINARY_NAME);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
if (import.meta.url === `file://${process.argv[1]}`) NodeChildProcess.execFileSync(getBinaryPath(), process.argv.slice(2), { stdio: "inherit" });
|
|
34
|
-
|
|
35
|
-
//#endregion
|
|
36
|
-
export { };
|