forge-pack 0.1.0 → 0.3.0

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 ADDED
@@ -0,0 +1,88 @@
1
+ # forge-pack
2
+
3
+ Generate self-contained Solidity deployer libraries from [Forge](https://book.getfoundry.sh/) build artifacts.
4
+
5
+ `forge-pack` reads your compiled contract JSON, resolves library dependencies, and outputs a single `.sol` file containing a deployer library with `deploy()`, `deploy2()` (CREATE2), and `initcode()` functions — ready to use in scripts or tests.
6
+
7
+ ## Usage
8
+
9
+ The package does not require installation, you can directly use `npx`/`pnpx` to run it:
10
+
11
+ ```bash
12
+ npx forge-pack@latest <contract-name>
13
+ ```
14
+
15
+ ## CLI Usage
16
+
17
+ ```
18
+ forge-pack <ContractName> [options]
19
+ ```
20
+
21
+ ### Options
22
+
23
+ | Flag | Description | Default |
24
+ | ------------------ | ------------------------------------------ | ------------- |
25
+ | `--out <dir>` | Forge output directory | `./out` |
26
+ | `--output <dir>` | Where to write the deployer `.sol` file | `./deployers` |
27
+ | `--build` | Run `forge build` before reading artifacts | `false` |
28
+ | `--pragma <range>` | Solidity pragma for generated file | `>=0.8.0` |
29
+ | `-h, --help` | Show help message | — |
30
+
31
+ ### Example
32
+
33
+ ```bash
34
+ # Generate a deployer for MyToken
35
+ forge-pack MyToken
36
+
37
+ # Build first, then generate with a specific pragma
38
+ forge-pack MyToken --build --pragma "^0.8.20"
39
+
40
+ # Use a custom output directory
41
+ forge-pack MyToken --output src/deployers
42
+ ```
43
+
44
+ This produces a file like `deployers/MyTokenDeployer.sol`:
45
+
46
+ ```solidity
47
+ // SPDX-License-Identifier: UNLICENSED
48
+ pragma solidity >=0.8.0;
49
+
50
+ library MyTokenDeployer {
51
+ function deploy(string memory name, string memory symbol) internal returns (address deployed) { ... }
52
+ function deploy2(bytes32 salt, string memory name, string memory symbol) internal returns (address deployed) { ... }
53
+ function initcode(string memory name, string memory symbol) internal pure returns (bytes memory) { ... }
54
+ }
55
+ ```
56
+
57
+ Constructor parameters are automatically extracted from the ABI. Struct types used in constructor arguments get their definitions included in the generated file.
58
+
59
+ ## Library Dependencies
60
+
61
+ If your contract links against external libraries, `forge-pack` resolves them recursively in topological order and generates inline deployment helpers. The deployer handles deploying libraries before the main contract, so the output remains self-contained.
62
+
63
+ ## Programmatic API
64
+
65
+ ```typescript
66
+ import { findArtifact, parseArtifact, generateDeployer, resolveLibraries } from "forge-pack";
67
+
68
+ const artifactPath = findArtifact("MyToken", "./out");
69
+ const parsed = parseArtifact(artifactPath, "MyToken");
70
+
71
+ const libraries = resolveLibraries(parsed.linkReferences, "./out");
72
+ const solidity = generateDeployer(parsed, { pragma: ">=0.8.0", libraries });
73
+ ```
74
+
75
+ ### Exports
76
+
77
+ **Functions:**
78
+
79
+ - `findArtifact(contractName, outDir, options?)` — Locate a contract artifact in the Forge output directory
80
+ - `parseArtifact(artifactPath, contractName)` — Parse a Forge artifact JSON into a structured object
81
+ - `resolveLibraries(linkReferences, outDir)` — Recursively resolve library dependencies in deploy order
82
+ - `generateDeployer(parsed, options?)` — Generate Solidity deployer library source code
83
+
84
+ **Types:** `ParsedArtifact`, `LinkReference`, `LinkReferences`, `AbiParam`, `AbiEntry`, `FindArtifactOptions`, `ResolvedLibrary`, `GenerateDeployerOptions`
85
+
86
+ ## License
87
+
88
+ MIT
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ export { };
package/dist/cli.mjs ADDED
@@ -0,0 +1,134 @@
1
+ import { i as parseArtifact, n as resolveLibraries, r as findArtifact, t as generateDeployer } from "./codegen--WByOfZu.mjs";
2
+ import { parseArgs } from "node:util";
3
+ import { execSync } from "node:child_process";
4
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
+ import { join, resolve } from "node:path";
6
+
7
+ //#region src/deploy-helper.ts
8
+ const DEPLOY_HELPER_SOL = `// SPDX-License-Identifier: MIT
9
+ pragma solidity ^0.8.10;
10
+
11
+ library DeployHelper {
12
+ function deploy(bytes memory initcode, bytes32 salt) internal returns (address contractAddress) {
13
+ assembly ("memory-safe") {
14
+ contractAddress := create2(callvalue(), add(initcode, 32), mload(initcode), salt)
15
+ if iszero(contractAddress) {
16
+ let ptr := mload(0x40)
17
+ let errorSize := returndatasize()
18
+ returndatacopy(ptr, 0, errorSize)
19
+ revert(ptr, errorSize)
20
+ }
21
+ }
22
+ }
23
+
24
+ function deploy(bytes memory initcode) internal returns (address contractAddress) {
25
+ contractAddress = deploy(initcode, bytes32(0));
26
+ }
27
+
28
+ /// @dev Deploys a library using CREATE2 with zero salt.
29
+ /// If the library is already deployed (duplicate across contracts), returns the existing address.
30
+ function deployLibrary(bytes memory initcode) internal returns (address contractAddress) {
31
+ bytes32 salt = bytes32(0);
32
+ contractAddress = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, keccak256(initcode))))));
33
+ if (contractAddress.code.length > 0) return contractAddress;
34
+ contractAddress = deploy(initcode, salt);
35
+ }
36
+ }
37
+ `;
38
+
39
+ //#endregion
40
+ //#region src/cli.ts
41
+ const pkgPath = new URL("../package.json", import.meta.url);
42
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
43
+ const { values, positionals } = parseArgs({
44
+ allowPositionals: true,
45
+ options: {
46
+ out: {
47
+ type: "string",
48
+ default: "./out"
49
+ },
50
+ output: {
51
+ type: "string",
52
+ default: "./deployers"
53
+ },
54
+ build: {
55
+ type: "boolean",
56
+ default: false
57
+ },
58
+ pragma: {
59
+ type: "string",
60
+ default: ">=0.8.0"
61
+ },
62
+ version: {
63
+ type: "boolean",
64
+ short: "v",
65
+ default: false
66
+ },
67
+ help: {
68
+ type: "boolean",
69
+ short: "h",
70
+ default: false
71
+ }
72
+ }
73
+ });
74
+ if (values.version) {
75
+ console.log(pkg.version);
76
+ process.exit(0);
77
+ }
78
+ if (values.help || positionals.length === 0) {
79
+ console.log(`Usage: forge-pack <ContractName...> [options]
80
+
81
+ Options:
82
+ --out <dir> Forge output directory (default: ./out)
83
+ --output <dir> Where to write the deployer .sol files (default: ./deployers)
84
+ --build Run \`forge build\` before reading artifacts
85
+ --pragma <range> Solidity pragma for generated files (default: >=0.8.0)
86
+ -v, --version Show version number
87
+ -h, --help Show this help message`);
88
+ process.exit(values.help ? 0 : 1);
89
+ }
90
+ const outDir = resolve(values.out);
91
+ const outputDir = resolve(values.output);
92
+ const pragma = values.pragma;
93
+ if (values.build) {
94
+ console.log("Running forge build...");
95
+ execSync("forge build", { stdio: "inherit" });
96
+ }
97
+ const utilsDir = join(outputDir, "utils");
98
+ const deployHelperPath = join(utilsDir, "DeployHelper.sol");
99
+ mkdirSync(utilsDir, { recursive: true });
100
+ if (!existsSync(deployHelperPath)) {
101
+ writeFileSync(deployHelperPath, DEPLOY_HELPER_SOL);
102
+ console.log(`Generated ${deployHelperPath}`);
103
+ }
104
+ let hasError = false;
105
+ for (const contractName of positionals) try {
106
+ const parsed = parseArtifact(findArtifact(contractName, outDir), contractName);
107
+ if (!parsed.bytecode) {
108
+ console.error(`Error: No bytecode found for "${contractName}". Is it an abstract contract or interface?`);
109
+ hasError = true;
110
+ continue;
111
+ }
112
+ const hasLinks = Object.keys(parsed.linkReferences).length > 0;
113
+ let libraries;
114
+ if (hasLinks) {
115
+ libraries = resolveLibraries(parsed.linkReferences, outDir);
116
+ const libNames = libraries.map((l) => l.lib);
117
+ console.log(`[${contractName}] Resolved ${libNames.length} library dep(s): ${libNames.join(", ")}`);
118
+ }
119
+ const solidity = generateDeployer(parsed, {
120
+ pragma,
121
+ libraries
122
+ });
123
+ mkdirSync(outputDir, { recursive: true });
124
+ const outPath = join(outputDir, `${contractName}Deployer.sol`);
125
+ writeFileSync(outPath, solidity);
126
+ console.log(`Generated ${outPath}`);
127
+ } catch (err) {
128
+ console.error(`Error [${contractName}]: ${err.message}`);
129
+ hasError = true;
130
+ }
131
+ if (hasError) process.exit(1);
132
+
133
+ //#endregion
134
+ export { };
@@ -1,8 +1,8 @@
1
1
  import { readFileSync, readdirSync, statSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
 
4
- //#region src/pack.ts
5
- function findArtifact(contractName, outDir, options) {
4
+ //#region src/artifact.ts
5
+ function findArtifact(contractName, outDir) {
6
6
  const candidates = [];
7
7
  const entries = readdirSync(outDir);
8
8
  for (const entry of entries) {
@@ -15,11 +15,6 @@ function findArtifact(contractName, outDir, options) {
15
15
  } catch {}
16
16
  }
17
17
  if (candidates.length === 0) throw new Error(`No artifact found for "${contractName}" in ${outDir}. Run \`forge build\` first.`);
18
- if (options?.solcVersion && candidates.length > 1) {
19
- for (const c of candidates) if ((JSON.parse(readFileSync(c, "utf-8"))?.metadata?.compiler?.version)?.startsWith(options.solcVersion)) return c;
20
- throw new Error(`No artifact for "${contractName}" compiled with solc ${options.solcVersion}.`);
21
- }
22
- if (candidates.length > 1) throw new Error(`Multiple artifacts found for "${contractName}". Use --solc to disambiguate:\n` + candidates.map((c) => ` ${c}`).join("\n"));
23
18
  return candidates[0];
24
19
  }
25
20
  function parseArtifact(artifactPath, contractName) {
@@ -34,6 +29,7 @@ function parseArtifact(artifactPath, contractName) {
34
29
  const optimizerRuns = settings?.optimizer?.runs;
35
30
  const viaIR = settings?.viaIR;
36
31
  const evmVersion = settings?.evmVersion;
32
+ const bytecodeHash = metadata?.settings?.metadata?.bytecodeHash ?? settings?.metadata?.bytecodeHash;
37
33
  let sourcePath;
38
34
  if (metadata?.settings?.compilationTarget) {
39
35
  const targets = metadata.settings.compilationTarget;
@@ -48,13 +44,13 @@ function parseArtifact(artifactPath, contractName) {
48
44
  solcVersion,
49
45
  optimizerRuns,
50
46
  viaIR,
51
- evmVersion
47
+ evmVersion,
48
+ bytecodeHash
52
49
  };
53
50
  }
54
- /**
55
- * Collect unique library identities from linkReferences.
56
- * Returns deduped entries as { file, lib } pairs.
57
- */
51
+
52
+ //#endregion
53
+ //#region src/resolve.ts
58
54
  function collectLibIds(linkRefs) {
59
55
  const seen = /* @__PURE__ */ new Set();
60
56
  const result = [];
@@ -76,6 +72,7 @@ function makeParamName(libName) {
76
72
  /**
77
73
  * Recursively resolve all library dependencies for a contract's linkReferences.
78
74
  * Returns libraries in topological order (deploy-order: leaves first).
75
+ * Disambiguates duplicate paramName values with numeric suffixes.
79
76
  */
80
77
  function resolveLibraries(linkRefs, outDir) {
81
78
  const resolved = /* @__PURE__ */ new Map();
@@ -105,8 +102,31 @@ function resolveLibraries(linkRefs, outDir) {
105
102
  }
106
103
  const topLevel = collectLibIds(linkRefs);
107
104
  for (const { file, lib } of topLevel) resolve(file, lib);
108
- return Array.from(resolved.values());
105
+ const libs = Array.from(resolved.values());
106
+ const nameCount = /* @__PURE__ */ new Map();
107
+ for (const entry of libs) {
108
+ const count = nameCount.get(entry.paramName) ?? 0;
109
+ nameCount.set(entry.paramName, count + 1);
110
+ }
111
+ const collisions = /* @__PURE__ */ new Set();
112
+ for (const [name, count] of nameCount) if (count > 1) collisions.add(name);
113
+ if (collisions.size > 0) {
114
+ const nameIndex = /* @__PURE__ */ new Map();
115
+ for (const entry of libs) {
116
+ if (!collisions.has(entry.paramName)) continue;
117
+ const idx = (nameIndex.get(entry.paramName) ?? 0) + 1;
118
+ nameIndex.set(entry.paramName, idx);
119
+ if (idx > 1) entry.paramName = `${entry.paramName}${idx}`;
120
+ }
121
+ const keyToName = /* @__PURE__ */ new Map();
122
+ for (const entry of libs) keyToName.set(`${entry.file}:${entry.lib}`, entry.paramName);
123
+ for (const entry of libs) entry.deps = collectLibIds(entry.artifact.linkReferences).map((d) => keyToName.get(`${d.file}:${d.lib}`));
124
+ }
125
+ return libs;
109
126
  }
127
+
128
+ //#endregion
129
+ //#region src/codegen.ts
110
130
  function extractStructName(internalType) {
111
131
  let name = internalType.replace(/^struct\s+/, "");
112
132
  name = name.replace(/(\[\d*\])+$/, "");
@@ -216,13 +236,31 @@ function renderInitcodeBody(bytecode, segments, hasLinks) {
216
236
  if (!hasLinks) return ` return hex"${bytecode}";`;
217
237
  return ` return abi.encodePacked(${segments.map((seg) => seg.type === "hex" ? `hex"${seg.value}"` : seg.value).join(", ")});`;
218
238
  }
239
+ function renderDeployBody(opts) {
240
+ const { inlineLibs, resolvedLibs, ctorParams, initcodeCallArgs } = opts;
241
+ const lines = [];
242
+ if (inlineLibs) for (const rlib of resolvedLibs) {
243
+ const { libParams: libLibParams } = buildBytecodeSegments(rlib.artifact.bytecode, rlib.artifact.linkReferences);
244
+ const callArgs = libLibParams.length > 0 ? libLibParams.map((lp) => lp.name).join(", ") : "";
245
+ lines.push(` address ${rlib.paramName} = DeployHelper.deployLibrary(_${rlib.paramName}Initcode(${callArgs}));`);
246
+ }
247
+ if (ctorParams.length > 0) {
248
+ const encodeArgs = ctorParams.map((p, i) => p.name || `arg${i}`).join(", ");
249
+ lines.push(` bytes memory args = abi.encode(${encodeArgs});`);
250
+ lines.push(` bytes memory initcode_ = abi.encodePacked(initcode(${initcodeCallArgs}), args);`);
251
+ } else lines.push(` bytes memory initcode_ = initcode(${initcodeCallArgs});`);
252
+ lines.push(` deployed = DeployHelper.deploy(initcode_, salt);`);
253
+ return lines.join("\n");
254
+ }
219
255
  function generateDeployer(parsed, pragmaOrOpts) {
220
256
  const opts = typeof pragmaOrOpts === "string" ? { pragma: pragmaOrOpts } : pragmaOrOpts ?? {};
221
257
  const pragma = opts.pragma ?? ">=0.8.0";
222
258
  const resolvedLibs = opts.libraries ?? [];
223
259
  const { contractName, abi, bytecode, linkReferences } = parsed;
224
260
  const libName = `${contractName}Deployer`;
225
- const ctorParams = abi.find((e) => e.type === "constructor")?.inputs ?? [];
261
+ const ctorEntry = abi.find((e) => e.type === "constructor");
262
+ const ctorParams = ctorEntry?.inputs ?? [];
263
+ const isPayable = ctorEntry?.stateMutability === "payable";
226
264
  const structDefs = collectStructDefs(ctorParams);
227
265
  const structNames = new Set(structDefs.map((s) => s.name));
228
266
  const { segments: mainSegments, libParams: mainLibParams } = buildBytecodeSegments(bytecode, linkReferences);
@@ -232,8 +270,9 @@ function generateDeployer(parsed, pragmaOrOpts) {
232
270
  if (parsed.sourcePath) metaLines.push(`@notice Source Contract: ${parsed.sourcePath}`);
233
271
  if (parsed.solcVersion) metaLines.push(`- solc: ${parsed.solcVersion}`);
234
272
  if (parsed.optimizerRuns !== void 0) metaLines.push(`- optimizer_runs: ${parsed.optimizerRuns}`);
235
- if (parsed.viaIR) metaLines.push(`- viaIR: true`);
273
+ metaLines.push(`- viaIR: ${parsed.viaIR ?? false}`);
236
274
  if (parsed.evmVersion) metaLines.push(`- evm_version: ${parsed.evmVersion}`);
275
+ metaLines.push(`- bytecodeHash: ${parsed.bytecodeHash ?? "ipfs"}`);
237
276
  const metaBlock = metaLines.length > 0 ? [
238
277
  ` /**`,
239
278
  ` * @dev autogenerated by forge-pack`,
@@ -258,74 +297,33 @@ function generateDeployer(parsed, pragmaOrOpts) {
258
297
  const deployParams = [];
259
298
  for (let i = 0; i < ctorParams.length; i++) deployParams.push(formatParamWithStructs(ctorParams[i], i, structNames));
260
299
  if (hasLinks && !inlineLibs) for (const lp of mainLibParams) deployParams.push(`address ${lp.name}`);
261
- const deployParamStr = deployParams.join(", ");
262
- const deployBodyLines = [];
263
- if (inlineLibs) for (const rlib of resolvedLibs) {
264
- const { libParams: libLibParams } = buildBytecodeSegments(rlib.artifact.bytecode, rlib.artifact.linkReferences);
265
- const callArgs = libLibParams.length > 0 ? libLibParams.map((lp) => lp.name).join(", ") : "";
266
- deployBodyLines.push(` address ${rlib.paramName} = _create(_${rlib.paramName}Initcode(${callArgs}));`);
267
- }
300
+ const deployParamStr = deployParams.length > 0 ? `${deployParams.join(", ")}, bytes32 salt` : `bytes32 salt`;
268
301
  const initcodeCallArgs = hasLinks ? mainLibParams.map((lp) => lp.name).join(", ") : "";
269
- if (ctorParams.length > 0) {
270
- const encodeArgs = ctorParams.map((p, i) => p.name || `arg${i}`).join(", ");
271
- deployBodyLines.push(` bytes memory args = abi.encode(${encodeArgs});`);
272
- deployBodyLines.push(` bytes memory initcode_ = abi.encodePacked(initcode(${initcodeCallArgs}), args);`);
273
- deployBodyLines.push(` deployed = _create(initcode_);`);
274
- } else {
275
- deployBodyLines.push(` bytes memory initcode_ = initcode(${initcodeCallArgs});`);
276
- deployBodyLines.push(` deployed = _create(initcode_);`);
277
- }
278
- const deployBody = deployBodyLines.join("\n");
279
- const deploy2BodyLines = [];
280
- if (inlineLibs) for (const rlib of resolvedLibs) {
281
- const { libParams: libLibParams } = buildBytecodeSegments(rlib.artifact.bytecode, rlib.artifact.linkReferences);
282
- const callArgs = libLibParams.length > 0 ? libLibParams.map((lp) => lp.name).join(", ") : "";
283
- deploy2BodyLines.push(` address ${rlib.paramName} = _create(_${rlib.paramName}Initcode(${callArgs}));`);
284
- }
285
- if (ctorParams.length > 0) {
286
- const encodeArgs = ctorParams.map((p, i) => p.name || `arg${i}`).join(", ");
287
- deploy2BodyLines.push(` bytes memory args = abi.encode(${encodeArgs});`);
288
- deploy2BodyLines.push(` bytes memory initcode_ = abi.encodePacked(initcode(${initcodeCallArgs}), args);`);
289
- deploy2BodyLines.push(` deployed = _create2(initcode_, salt);`);
290
- } else {
291
- deploy2BodyLines.push(` bytes memory initcode_ = initcode(${initcodeCallArgs});`);
292
- deploy2BodyLines.push(` deployed = _create2(initcode_, salt);`);
293
- }
294
- const deploy2Body = deploy2BodyLines.join("\n");
295
- const deploy2Params = deployParams.length > 0 ? `${deployParamStr}, bytes32 salt` : `bytes32 salt`;
302
+ const payableModifier = isPayable ? " payable" : "";
303
+ const deployBody = renderDeployBody({
304
+ inlineLibs,
305
+ resolvedLibs,
306
+ ctorParams,
307
+ initcodeCallArgs,
308
+ isPayable
309
+ });
296
310
  return `// SPDX-License-Identifier: MIT
297
311
  pragma solidity ${pragma};
298
312
 
313
+ import {DeployHelper} from "./utils/DeployHelper.sol";
314
+
299
315
  library ${libName} {
300
316
  ${metaBlock}${structBlock ? `\n${structBlock}\n` : ""}
301
- function deploy(${deployParamStr}) internal returns (address deployed) {
317
+ function deploy(${deployParamStr}) internal${payableModifier} returns (address deployed) {
302
318
  ${deployBody}
303
319
  }
304
320
 
305
- function deploy2(${deploy2Params}) internal returns (address deployed) {
306
- ${deploy2Body}
307
- }
308
-
309
321
  function initcode(${initcodeParams}) internal pure returns (bytes memory) {
310
322
  ${initcodeBody}
311
323
  }
312
- ${libInitcodeFns.length > 0 ? "\n" + libInitcodeFns.join("\n\n") + "\n" : ""}
313
- function _create(bytes memory initcode_) private returns (address deployed) {
314
- assembly {
315
- deployed := create(0, add(initcode_, 0x20), mload(initcode_))
316
- if iszero(deployed) { revert(0, returndatasize()) }
317
- }
318
- }
319
-
320
- function _create2(bytes memory initcode_, bytes32 salt) private returns (address deployed) {
321
- assembly {
322
- deployed := create2(0, add(initcode_, 0x20), mload(initcode_), salt)
323
- if iszero(deployed) { revert(0, returndatasize()) }
324
- }
325
- }
326
- }
324
+ ${libInitcodeFns.length > 0 ? "\n" + libInitcodeFns.join("\n\n") + "\n" : ""}}
327
325
  `;
328
326
  }
329
327
 
330
328
  //#endregion
331
- export { resolveLibraries as i, generateDeployer as n, parseArtifact as r, findArtifact as t };
329
+ export { parseArtifact as i, resolveLibraries as n, findArtifact as r, generateDeployer as t };
@@ -1,4 +1,4 @@
1
- //#region src/pack.d.ts
1
+ //#region src/types.d.ts
2
2
  interface LinkReference {
3
3
  start: number;
4
4
  length: number;
@@ -32,14 +32,8 @@ interface ParsedArtifact {
32
32
  optimizerRuns?: number;
33
33
  viaIR?: boolean;
34
34
  evmVersion?: string;
35
+ bytecodeHash?: string;
35
36
  }
36
- interface FindArtifactOptions {
37
- solcVersion?: string;
38
- }
39
- /**
40
- * A library dependency resolved from linkReferences, with its own parsed
41
- * artifact and the param name used in generated code.
42
- */
43
37
  interface ResolvedLibrary {
44
38
  /** Parameter-style name, e.g. "mathLib" */
45
39
  paramName: string;
@@ -52,17 +46,24 @@ interface ResolvedLibrary {
52
46
  /** Param names of libraries this library depends on (its own linkReferences) */
53
47
  deps: string[];
54
48
  }
55
- declare function findArtifact(contractName: string, outDir: string, options?: FindArtifactOptions): string;
49
+ interface GenerateDeployerOptions {
50
+ pragma?: string;
51
+ libraries?: ResolvedLibrary[];
52
+ }
53
+ //#endregion
54
+ //#region src/artifact.d.ts
55
+ declare function findArtifact(contractName: string, outDir: string): string;
56
56
  declare function parseArtifact(artifactPath: string, contractName: string): ParsedArtifact;
57
+ //#endregion
58
+ //#region src/resolve.d.ts
57
59
  /**
58
60
  * Recursively resolve all library dependencies for a contract's linkReferences.
59
61
  * Returns libraries in topological order (deploy-order: leaves first).
62
+ * Disambiguates duplicate paramName values with numeric suffixes.
60
63
  */
61
64
  declare function resolveLibraries(linkRefs: LinkReferences, outDir: string): ResolvedLibrary[];
62
- interface GenerateDeployerOptions {
63
- pragma?: string;
64
- libraries?: ResolvedLibrary[];
65
- }
65
+ //#endregion
66
+ //#region src/codegen.d.ts
66
67
  declare function generateDeployer(parsed: ParsedArtifact, pragmaOrOpts?: string | GenerateDeployerOptions): string;
67
68
  //#endregion
68
- export { type AbiEntry, type AbiParam, type FindArtifactOptions, type GenerateDeployerOptions, type LinkReference, type LinkReferences, type ParsedArtifact, type ResolvedLibrary, findArtifact, generateDeployer, parseArtifact, resolveLibraries };
69
+ export { type AbiEntry, type AbiParam, type GenerateDeployerOptions, type LinkReference, type LinkReferences, type ParsedArtifact, type ResolvedLibrary, findArtifact, generateDeployer, parseArtifact, resolveLibraries };
package/dist/index.mjs ADDED
@@ -0,0 +1,3 @@
1
+ import { i as parseArtifact, n as resolveLibraries, r as findArtifact, t as generateDeployer } from "./codegen--WByOfZu.mjs";
2
+
3
+ export { findArtifact, generateDeployer, parseArtifact, resolveLibraries };
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "forge-pack",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Generate self-contained Solidity deployer files from Forge build artifacts",
5
5
  "type": "module",
6
6
  "bin": {
7
- "forge-pack": "./dist/cli.js"
7
+ "forge-pack": "./dist/cli.mjs"
8
8
  },
9
- "main": "./dist/index.js",
10
- "types": "./dist/index.d.ts",
9
+ "main": "./dist/index.mjs",
10
+ "types": "./dist/index.d.mts",
11
11
  "exports": {
12
12
  ".": {
13
- "import": "./dist/index.js",
14
- "types": "./dist/index.d.ts"
13
+ "import": "./dist/index.mjs",
14
+ "types": "./dist/index.d.mts"
15
15
  }
16
16
  },
17
17
  "files": [
@@ -24,18 +24,30 @@
24
24
  "deployer",
25
25
  "bytecode"
26
26
  ],
27
- "author": "",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/akshatmittal/forge-pack.git"
30
+ },
31
+ "author": "Akshat Mittal <hi@akshatmittal.com>",
28
32
  "license": "MIT",
29
33
  "devDependencies": {
30
- "tsdown": "^0.12.5",
31
- "typescript": "^5.8.3",
32
- "@types/node": "^24.10.13"
34
+ "@changesets/changelog-github": "^0.5.2",
35
+ "@changesets/cli": "^2.29.8",
36
+ "@changesets/config": "^3.1.2",
37
+ "@types/node": "^24.10.13",
38
+ "prettier": "^3.8.1",
39
+ "tsdown": "^0.20.3",
40
+ "typescript": "^5.9.3"
33
41
  },
34
42
  "publishConfig": {
35
43
  "access": "public"
36
44
  },
37
45
  "scripts": {
38
- "build": "tsdown && node -e \"const fs=require('fs'),p=require('path'),d='dist';fs.readdirSync(d).filter(f=>f.startsWith('index-')&&f.endsWith('.d.ts')).forEach(f=>{fs.copyFileSync(p.join(d,f),p.join(d,'index.d.ts'))});const c=fs.readFileSync(p.join(d,'cli.js'),'utf8');fs.writeFileSync(p.join(d,'cli.js'),'#!/usr/bin/env node\\n'+c)\"",
39
- "dev": "tsdown --watch"
46
+ "build": "tsdown",
47
+ "dev": "tsdown --watch",
48
+ "changeset": "changeset",
49
+ "version": "changeset version",
50
+ "release": "changeset publish",
51
+ "format": "prettier --write \"**/*.{ts,tsx,md,json}\""
40
52
  }
41
53
  }
@@ -1 +0,0 @@
1
- export {};
package/dist/cli.js DELETED
@@ -1,83 +0,0 @@
1
- #!/usr/bin/env node
2
- import { i as resolveLibraries, n as generateDeployer, r as parseArtifact, t as findArtifact } from "./pack-A6vKuf56.js";
3
- import { parseArgs } from "node:util";
4
- import { execSync } from "node:child_process";
5
- import { mkdirSync, writeFileSync } from "node:fs";
6
- import { join, resolve } from "node:path";
7
-
8
- //#region src/cli.ts
9
- const { values, positionals } = parseArgs({
10
- allowPositionals: true,
11
- options: {
12
- out: {
13
- type: "string",
14
- default: "./out"
15
- },
16
- output: {
17
- type: "string",
18
- default: "./deployers"
19
- },
20
- build: {
21
- type: "boolean",
22
- default: false
23
- },
24
- solc: { type: "string" },
25
- pragma: {
26
- type: "string",
27
- default: ">=0.8.0"
28
- },
29
- help: {
30
- type: "boolean",
31
- short: "h",
32
- default: false
33
- }
34
- }
35
- });
36
- if (values.help || positionals.length === 0) {
37
- console.log(`Usage: forge-pack <ContractName> [options]
38
-
39
- Options:
40
- --out <dir> Forge output directory (default: ./out)
41
- --output <dir> Where to write the deployer .sol file (default: ./deployers)
42
- --build Run \`forge build\` before reading artifacts
43
- --solc <version> Filter artifact by solc version (when multiple exist)
44
- --pragma <range> Solidity pragma for generated file (default: >=0.8.0)
45
- -h, --help Show this help message`);
46
- process.exit(values.help ? 0 : 1);
47
- }
48
- const contractName = positionals[0];
49
- const outDir = resolve(values.out);
50
- const outputDir = resolve(values.output);
51
- const pragma = values.pragma;
52
- if (values.build) {
53
- console.log("Running forge build...");
54
- execSync("forge build", { stdio: "inherit" });
55
- }
56
- try {
57
- const parsed = parseArtifact(findArtifact(contractName, outDir, { solcVersion: values.solc }), contractName);
58
- if (!parsed.bytecode) {
59
- console.error(`Error: No bytecode found for "${contractName}". Is it an abstract contract or interface?`);
60
- process.exit(1);
61
- }
62
- const hasLinks = Object.keys(parsed.linkReferences).length > 0;
63
- let libraries;
64
- if (hasLinks) {
65
- libraries = resolveLibraries(parsed.linkReferences, outDir);
66
- const libNames = libraries.map((l) => l.lib);
67
- console.log(`Resolved ${libNames.length} library dep(s): ${libNames.join(", ")}`);
68
- }
69
- const solidity = generateDeployer(parsed, {
70
- pragma,
71
- libraries
72
- });
73
- mkdirSync(outputDir, { recursive: true });
74
- const outPath = join(outputDir, `${contractName}Deployer.sol`);
75
- writeFileSync(outPath, solidity);
76
- console.log(`Generated ${outPath}`);
77
- } catch (err) {
78
- console.error(`Error: ${err.message}`);
79
- process.exit(1);
80
- }
81
-
82
- //#endregion
83
- export { };
package/dist/index.d.ts DELETED
@@ -1,68 +0,0 @@
1
- //#region src/pack.d.ts
2
- interface LinkReference {
3
- start: number;
4
- length: number;
5
- }
6
- interface LinkReferences {
7
- [file: string]: {
8
- [lib: string]: LinkReference[];
9
- };
10
- }
11
- interface AbiParam {
12
- name: string;
13
- type: string;
14
- internalType?: string;
15
- components?: AbiParam[];
16
- indexed?: boolean;
17
- }
18
- interface AbiEntry {
19
- type: string;
20
- name?: string;
21
- inputs?: AbiParam[];
22
- outputs?: AbiParam[];
23
- stateMutability?: string;
24
- }
25
- interface ParsedArtifact {
26
- contractName: string;
27
- abi: AbiEntry[];
28
- bytecode: string;
29
- linkReferences: LinkReferences;
30
- sourcePath?: string;
31
- solcVersion?: string;
32
- optimizerRuns?: number;
33
- viaIR?: boolean;
34
- evmVersion?: string;
35
- }
36
- interface FindArtifactOptions {
37
- solcVersion?: string;
38
- }
39
- /**
40
- * A library dependency resolved from linkReferences, with its own parsed
41
- * artifact and the param name used in generated code.
42
- */
43
- interface ResolvedLibrary {
44
- /** Parameter-style name, e.g. "mathLib" */
45
- paramName: string;
46
- /** Source file declaring the library, e.g. "src/libs/MathLib.sol" */
47
- file: string;
48
- /** Library contract name, e.g. "MathLib" */
49
- lib: string;
50
- /** Parsed artifact for this library */
51
- artifact: ParsedArtifact;
52
- /** Param names of libraries this library depends on (its own linkReferences) */
53
- deps: string[];
54
- }
55
- declare function findArtifact(contractName: string, outDir: string, options?: FindArtifactOptions): string;
56
- declare function parseArtifact(artifactPath: string, contractName: string): ParsedArtifact;
57
- /**
58
- * Recursively resolve all library dependencies for a contract's linkReferences.
59
- * Returns libraries in topological order (deploy-order: leaves first).
60
- */
61
- declare function resolveLibraries(linkRefs: LinkReferences, outDir: string): ResolvedLibrary[];
62
- interface GenerateDeployerOptions {
63
- pragma?: string;
64
- libraries?: ResolvedLibrary[];
65
- }
66
- declare function generateDeployer(parsed: ParsedArtifact, pragmaOrOpts?: string | GenerateDeployerOptions): string;
67
- //#endregion
68
- export { type AbiEntry, type AbiParam, type FindArtifactOptions, type GenerateDeployerOptions, type LinkReference, type LinkReferences, type ParsedArtifact, type ResolvedLibrary, findArtifact, generateDeployer, parseArtifact, resolveLibraries };
package/dist/index.js DELETED
@@ -1,3 +0,0 @@
1
- import { i as resolveLibraries, n as generateDeployer, r as parseArtifact, t as findArtifact } from "./pack-A6vKuf56.js";
2
-
3
- export { findArtifact, generateDeployer, parseArtifact, resolveLibraries };