recon-generate 0.0.21 → 0.0.23

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.
@@ -0,0 +1,25 @@
1
+ interface ConstructorParam {
2
+ name: string;
3
+ type: string;
4
+ }
5
+ interface ContractInfo {
6
+ constructor: ConstructorParam[];
7
+ state_variables: {
8
+ [variableName: string]: string;
9
+ };
10
+ }
11
+ interface FileContracts {
12
+ [contractName: string]: ContractInfo;
13
+ }
14
+ interface DictionaryOutput {
15
+ [filePath: string]: FileContracts;
16
+ }
17
+ /**
18
+ * Run the dictionary command
19
+ */
20
+ export declare const runDictionary: (buildInfoPath: string, options?: {
21
+ outputPath?: string;
22
+ json?: boolean;
23
+ srcPrefix?: string;
24
+ }) => Promise<DictionaryOutput>;
25
+ export {};
@@ -0,0 +1,246 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.runDictionary = void 0;
37
+ const fs = __importStar(require("fs/promises"));
38
+ const solc_typed_ast_1 = require("solc-typed-ast");
39
+ const utils_1 = require("./utils");
40
+ /**
41
+ * Load build-info from a JSON file
42
+ */
43
+ const loadBuildInfo = async (buildInfoPath) => {
44
+ var _a;
45
+ const fileContent = await fs.readFile(buildInfoPath, 'utf-8');
46
+ const buildInfo = JSON.parse(fileContent);
47
+ const buildOutput = (_a = buildInfo.output) !== null && _a !== void 0 ? _a : buildInfo;
48
+ if (!buildOutput) {
49
+ throw new Error(`Build-info file ${buildInfoPath} is missing output data.`);
50
+ }
51
+ const filteredAstData = { ...buildOutput };
52
+ if (filteredAstData.sources) {
53
+ const validSources = {};
54
+ for (const [key, content] of Object.entries(filteredAstData.sources)) {
55
+ const ast = content.ast || content.legacyAST || content.AST;
56
+ if (ast && (ast.nodeType === 'SourceUnit' || ast.name === 'SourceUnit')) {
57
+ validSources[key] = content;
58
+ }
59
+ }
60
+ filteredAstData.sources = validSources;
61
+ }
62
+ const reader = new solc_typed_ast_1.ASTReader();
63
+ const sourceUnits = reader.read(filteredAstData);
64
+ return { sourceUnits };
65
+ };
66
+ /**
67
+ * Check if a type string represents an address, contract, or interface type
68
+ */
69
+ const isRelevantType = (typeString) => {
70
+ return (typeString === 'address' ||
71
+ typeString === 'address payable' ||
72
+ typeString.startsWith('contract ') ||
73
+ typeString.startsWith('interface '));
74
+ };
75
+ /**
76
+ * Extract the type name from a typeString
77
+ * e.g., "contract IHub" -> "IHub", "address" -> "address"
78
+ */
79
+ const extractTypeName = (typeString) => {
80
+ if (typeString.startsWith('contract ')) {
81
+ return typeString.slice('contract '.length);
82
+ }
83
+ if (typeString.startsWith('interface ')) {
84
+ return typeString.slice('interface '.length);
85
+ }
86
+ return typeString;
87
+ };
88
+ /**
89
+ * Get state variable map by ID for a contract (including inherited)
90
+ */
91
+ const getStateVarMap = (contract) => {
92
+ const stateVars = (0, utils_1.getDefinitions)(contract, 'vStateVariables', true);
93
+ const varMap = new Map();
94
+ for (const stateVar of stateVars) {
95
+ varMap.set(stateVar.id, stateVar);
96
+ }
97
+ return varMap;
98
+ };
99
+ /**
100
+ * Check if an expression references a state variable and return the variable name
101
+ */
102
+ const getReferencedStateVar = (node, stateVarIds) => {
103
+ const ref = node.vReferencedDeclaration;
104
+ if (ref instanceof solc_typed_ast_1.VariableDeclaration && stateVarIds.has(ref.id)) {
105
+ return ref.name;
106
+ }
107
+ return null;
108
+ };
109
+ /**
110
+ * Extract the casted type name from a type conversion call
111
+ * e.g., IERC20(token) -> "IERC20"
112
+ */
113
+ const getCastedTypeName = (call) => {
114
+ if (call.kind !== solc_typed_ast_1.FunctionCallKind.TypeConversion) {
115
+ return null;
116
+ }
117
+ const expr = call.vExpression;
118
+ if (expr instanceof solc_typed_ast_1.Identifier) {
119
+ const ref = expr.vReferencedDeclaration;
120
+ if (ref instanceof solc_typed_ast_1.ContractDefinition) {
121
+ return ref.name;
122
+ }
123
+ }
124
+ return null;
125
+ };
126
+ /**
127
+ * Collect state variables that are address, contract, or interface types
128
+ */
129
+ const collectRelevantStateVars = (stateVars) => {
130
+ const vars = new Map();
131
+ for (const stateVar of stateVars) {
132
+ const typeString = stateVar.typeString;
133
+ if (typeString && isRelevantType(typeString)) {
134
+ const typeName = extractTypeName(typeString);
135
+ vars.set(stateVar.name, typeName);
136
+ }
137
+ }
138
+ return vars;
139
+ };
140
+ /**
141
+ * Get constructor parameters for a contract
142
+ */
143
+ const getConstructorParams = (contract) => {
144
+ const functions = (0, utils_1.getDefinitions)(contract, 'vFunctions', true);
145
+ const constructor = functions.find(fn => fn.kind === solc_typed_ast_1.FunctionKind.Constructor);
146
+ if (!constructor) {
147
+ return [];
148
+ }
149
+ return constructor.vParameters.vParameters.map(param => ({
150
+ name: param.name,
151
+ type: extractTypeName(param.typeString),
152
+ }));
153
+ };
154
+ /**
155
+ * Find all state variable casts in a contract
156
+ * e.g., IFoo(stateVar) or Foo(stateVar)
157
+ */
158
+ const findStateVarCasts = (contract, stateVarMap) => {
159
+ const casts = new Map();
160
+ for (const call of contract.getChildrenByType(solc_typed_ast_1.FunctionCall)) {
161
+ const castedType = getCastedTypeName(call);
162
+ if (!castedType)
163
+ continue;
164
+ for (const arg of call.vArguments) {
165
+ let stateVarName = null;
166
+ if (arg instanceof solc_typed_ast_1.Identifier) {
167
+ stateVarName = getReferencedStateVar(arg, stateVarMap);
168
+ }
169
+ else if (arg instanceof solc_typed_ast_1.MemberAccess) {
170
+ stateVarName = getReferencedStateVar(arg, stateVarMap);
171
+ }
172
+ if (stateVarName) {
173
+ if (!casts.has(stateVarName)) {
174
+ casts.set(stateVarName, castedType);
175
+ }
176
+ }
177
+ }
178
+ }
179
+ return casts;
180
+ };
181
+ /**
182
+ * Run the dictionary command
183
+ */
184
+ const runDictionary = async (buildInfoPath, options = {}) => {
185
+ var _a;
186
+ const log = options.json ? () => { } : console.log.bind(console);
187
+ const srcPrefix = (_a = options.srcPrefix) !== null && _a !== void 0 ? _a : 'src/';
188
+ log(`[recon-generate] Loading build-info from ${buildInfoPath}`);
189
+ const { sourceUnits } = await loadBuildInfo(buildInfoPath);
190
+ if (!sourceUnits || sourceUnits.length === 0) {
191
+ throw new Error('No source units were produced from the build-info; cannot generate dictionary.');
192
+ }
193
+ log(`[recon-generate] Found ${sourceUnits.length} source units`);
194
+ const output = {};
195
+ for (const unit of sourceUnits) {
196
+ const filePath = unit.absolutePath;
197
+ if (!filePath.startsWith(srcPrefix)) {
198
+ continue;
199
+ }
200
+ for (const contract of unit.getChildrenByType(solc_typed_ast_1.ContractDefinition)) {
201
+ // Skip interfaces, libraries, and abstract contracts
202
+ if (contract.kind !== solc_typed_ast_1.ContractKind.Contract) {
203
+ continue;
204
+ }
205
+ if (contract.abstract) {
206
+ continue;
207
+ }
208
+ // Get all state variables including inherited ones
209
+ const stateVars = (0, utils_1.getDefinitions)(contract, 'vStateVariables', true);
210
+ const stateVarMap = getStateVarMap(contract);
211
+ // 1. Get constructor parameters
212
+ const constructorParams = getConstructorParams(contract);
213
+ // 2. Collect state variables that are address/contract/interface types
214
+ const declaredVars = collectRelevantStateVars(stateVars);
215
+ // 3. Find all casts of state variables to contract/interface types
216
+ const casts = findStateVarCasts(contract, stateVarMap);
217
+ // Merge: casts override declared types
218
+ const mergedVars = {};
219
+ for (const [varName, typeName] of declaredVars) {
220
+ mergedVars[varName] = typeName;
221
+ }
222
+ for (const [varName, castedType] of casts) {
223
+ mergedVars[varName] = castedType;
224
+ }
225
+ // Include all implemented contracts
226
+ if (!output[filePath]) {
227
+ output[filePath] = {};
228
+ }
229
+ output[filePath][contract.name] = {
230
+ constructor: constructorParams,
231
+ state_variables: mergedVars,
232
+ };
233
+ }
234
+ }
235
+ const jsonOutput = JSON.stringify(output, null, 2);
236
+ if (options.json) {
237
+ console.log(jsonOutput);
238
+ }
239
+ if (options.outputPath || !options.json) {
240
+ const dictPath = options.outputPath || 'recon-dictionary.json';
241
+ await fs.writeFile(dictPath, jsonOutput);
242
+ log(`[recon-generate] Wrote dictionary file to ${dictPath}`);
243
+ }
244
+ return output;
245
+ };
246
+ exports.runDictionary = runDictionary;
package/dist/index.js CHANGED
@@ -46,6 +46,7 @@ const utils_1 = require("./utils");
46
46
  const link_1 = require("./link");
47
47
  const link2_1 = require("./link2");
48
48
  const sourcemap_1 = require("./sourcemap");
49
+ const dictionary_1 = require("./dictionary");
49
50
  function parseFilter(input) {
50
51
  if (!input)
51
52
  return undefined;
@@ -242,6 +243,27 @@ async function main() {
242
243
  verbose: !!opts.verbose,
243
244
  });
244
245
  });
246
+ program
247
+ .command('dictionary <buildInfo>')
248
+ .description('Extract storage variables with contract/interface types from build-info')
249
+ .option('-o, --output <path>', 'Custom output path for the dictionary JSON file')
250
+ .option('--json', 'Output JSON to terminal (also saves to file if -o is specified)')
251
+ .option('--src-prefix <prefix>', 'Source directory prefix to filter (default: "src/")', 'src/')
252
+ .action(async function (buildInfoPath) {
253
+ // @ts-ignore - Commander types are complex
254
+ const opts = this.opts();
255
+ const resolvedBuildInfo = path.isAbsolute(buildInfoPath)
256
+ ? buildInfoPath
257
+ : path.resolve(process.cwd(), buildInfoPath);
258
+ const outputPath = opts.output
259
+ ? (path.isAbsolute(opts.output) ? opts.output : path.resolve(process.cwd(), opts.output))
260
+ : undefined;
261
+ await (0, dictionary_1.runDictionary)(resolvedBuildInfo, {
262
+ outputPath,
263
+ json: !!opts.json,
264
+ srcPrefix: opts.srcPrefix,
265
+ });
266
+ });
245
267
  program
246
268
  .command('info <contractName>')
247
269
  .description('Generate contract info JSON (payable, constants, function relations, etc.)')
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.TemplateManager = void 0;
37
37
  const path = __importStar(require("path"));
38
38
  const fs = __importStar(require("fs/promises"));
39
+ const case_1 = require("case");
39
40
  const templates = __importStar(require("./templates"));
40
41
  const types_1 = require("./types");
41
42
  class TemplateManager {
@@ -209,7 +210,7 @@ class TemplateManager {
209
210
  [path.join(suiteTargetsDir, 'ManagersTargets.sol')]: templates.managersTargetsTemplate({}),
210
211
  };
211
212
  for (const [contractName, functions] of Object.entries(separatedByContract)) {
212
- const targetPath = path.join(suiteTargetsDir, `${contractName}Targets.sol`);
213
+ const targetPath = path.join(suiteTargetsDir, `${(0, case_1.pascal)(contractName)}Targets.sol`);
213
214
  files[targetPath] = templates.targetsTemplate({
214
215
  contractName,
215
216
  path: functions.length > 0 ? functions[0].contractPath : '',
@@ -0,0 +1,3 @@
1
+ export interface UnpackOptions {
2
+ }
3
+ export declare function unpackBuildInfo(inputPath: string, outputDir: string, _options?: UnpackOptions): Promise<void>;
package/dist/unpack.js ADDED
@@ -0,0 +1,276 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.unpackBuildInfo = unpackBuildInfo;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ /**
40
+ * Load a single build-info JSON file
41
+ */
42
+ async function loadBuildInfo(filePath) {
43
+ const content = await fs.promises.readFile(filePath, 'utf8');
44
+ return JSON.parse(content);
45
+ }
46
+ /**
47
+ * Load all build-info files from a path (file or directory)
48
+ */
49
+ async function loadAllBuildInfos(inputPath) {
50
+ const stat = await fs.promises.stat(inputPath);
51
+ if (stat.isFile()) {
52
+ const buildInfo = await loadBuildInfo(inputPath);
53
+ return [buildInfo];
54
+ }
55
+ else if (stat.isDirectory()) {
56
+ const entries = await fs.promises.readdir(inputPath);
57
+ const jsonFiles = entries.filter(f => f.endsWith('.json'));
58
+ if (jsonFiles.length === 0) {
59
+ throw new Error(`No JSON files found in directory: ${inputPath}`);
60
+ }
61
+ console.log(`Found ${jsonFiles.length} build-info files in directory`);
62
+ const buildInfos = [];
63
+ for (const file of jsonFiles) {
64
+ const filePath = path.join(inputPath, file);
65
+ console.log(` Loading: ${file}`);
66
+ const buildInfo = await loadBuildInfo(filePath);
67
+ buildInfos.push(buildInfo);
68
+ }
69
+ return buildInfos;
70
+ }
71
+ else {
72
+ throw new Error(`Invalid input path: ${inputPath}`);
73
+ }
74
+ }
75
+ /**
76
+ * Merge multiple build-info files into a single structure
77
+ */
78
+ function mergeBuildInfos(buildInfos) {
79
+ var _a, _b, _c;
80
+ const merged = {
81
+ sources: {},
82
+ remappings: new Set(),
83
+ includePaths: new Set(),
84
+ basePath: undefined,
85
+ solcVersions: new Set(),
86
+ optimizer: undefined,
87
+ viaIR: false,
88
+ };
89
+ for (const buildInfo of buildInfos) {
90
+ merged.solcVersions.add(buildInfo.solcVersion);
91
+ for (const [sourcePath, sourceData] of Object.entries(buildInfo.input.sources)) {
92
+ if (!merged.sources[sourcePath]) {
93
+ merged.sources[sourcePath] = sourceData;
94
+ }
95
+ }
96
+ if ((_a = buildInfo.input.settings) === null || _a === void 0 ? void 0 : _a.remappings) {
97
+ for (const remap of buildInfo.input.settings.remappings) {
98
+ merged.remappings.add(remap);
99
+ }
100
+ }
101
+ if (buildInfo.input.includePaths) {
102
+ for (const includePath of buildInfo.input.includePaths) {
103
+ merged.includePaths.add(includePath);
104
+ }
105
+ }
106
+ if (!merged.basePath && buildInfo.input.basePath) {
107
+ merged.basePath = buildInfo.input.basePath;
108
+ }
109
+ if (!merged.optimizer && ((_b = buildInfo.input.settings) === null || _b === void 0 ? void 0 : _b.optimizer)) {
110
+ merged.optimizer = buildInfo.input.settings.optimizer;
111
+ }
112
+ if ((_c = buildInfo.input.settings) === null || _c === void 0 ? void 0 : _c.viaIR) {
113
+ merged.viaIR = true;
114
+ }
115
+ }
116
+ return merged;
117
+ }
118
+ /**
119
+ * Normalize a source path: contracts/ -> src/
120
+ */
121
+ function normalizePath(sourcePath) {
122
+ if (sourcePath.startsWith('contracts/')) {
123
+ return 'src/' + sourcePath.slice('contracts/'.length);
124
+ }
125
+ return sourcePath;
126
+ }
127
+ /**
128
+ * Rewrite imports in source content:
129
+ * - contracts/ -> src/
130
+ * - Handle includePath-based src/ imports
131
+ */
132
+ function rewriteImports(sourcePath, content, includePaths, basePath) {
133
+ let result = content;
134
+ // Rewrite contracts/ imports to src/
135
+ result = result.replace(/(import\s+(?:{[^}]+}\s+from\s+)?["'])contracts\//g, '$1src/');
136
+ // Handle includePath-based src/ imports (for libraries with self-referential imports)
137
+ if (includePaths.size > 0) {
138
+ let relativeIncludePath;
139
+ for (const includePath of includePaths) {
140
+ let relative;
141
+ if (basePath && includePath.startsWith(basePath)) {
142
+ relative = includePath.slice(basePath.length).replace(/^\//, '');
143
+ }
144
+ else {
145
+ continue;
146
+ }
147
+ // Normalize the path for comparison
148
+ const normalizedSourcePath = normalizePath(sourcePath);
149
+ const normalizedRelative = normalizePath(relative);
150
+ if (normalizedRelative && normalizedSourcePath.startsWith(`${normalizedRelative}/`)) {
151
+ relativeIncludePath = normalizedRelative;
152
+ break;
153
+ }
154
+ }
155
+ if (relativeIncludePath) {
156
+ const importRegex = /import\s+(?:{[^}]+}\s+from\s+)?["']src\/([^"']+)["']/g;
157
+ result = result.replace(importRegex, (match, importPath) => {
158
+ const currentDir = path.dirname(normalizePath(sourcePath));
159
+ const targetPath = `${relativeIncludePath}/src/${importPath}`;
160
+ let relativeTo = path.relative(currentDir, path.dirname(targetPath));
161
+ if (!relativeTo) {
162
+ relativeTo = '.';
163
+ }
164
+ else if (!relativeTo.startsWith('.')) {
165
+ relativeTo = './' + relativeTo;
166
+ }
167
+ let newImport = path.posix.join(relativeTo, path.basename(targetPath));
168
+ if (!newImport.startsWith('.')) {
169
+ newImport = './' + newImport;
170
+ }
171
+ return match.replace(`"src/${importPath}"`, `"${newImport}"`).replace(`'src/${importPath}'`, `'${newImport}'`);
172
+ });
173
+ }
174
+ }
175
+ return result;
176
+ }
177
+ /**
178
+ * Detect remappings needed based on source paths (for Hardhat-style @scope/ imports)
179
+ */
180
+ function detectRemappings(sources) {
181
+ const remappings = new Set();
182
+ const scopedDirs = new Set();
183
+ // Find all @scope/ style directories
184
+ for (const sourcePath of Object.keys(sources)) {
185
+ if (sourcePath.startsWith('@')) {
186
+ const parts = sourcePath.split('/');
187
+ if (parts.length >= 2) {
188
+ const scopeDir = parts[0] + '/';
189
+ scopedDirs.add(scopeDir);
190
+ }
191
+ }
192
+ }
193
+ // Create remappings for each scoped directory
194
+ for (const scopeDir of scopedDirs) {
195
+ remappings.add(`${scopeDir}=${scopeDir}`);
196
+ }
197
+ return remappings;
198
+ }
199
+ async function unpackBuildInfo(inputPath, outputDir, _options = {}) {
200
+ var _a;
201
+ console.log(`Reading build-info from: ${inputPath}`);
202
+ const buildInfos = await loadAllBuildInfos(inputPath);
203
+ const merged = mergeBuildInfos(buildInfos);
204
+ const solcVersionsArray = Array.from(merged.solcVersions);
205
+ console.log(`Solc Version(s): ${solcVersionsArray.join(', ')}`);
206
+ const sourceCount = Object.keys(merged.sources).length;
207
+ console.log(`Found ${sourceCount} unique source files`);
208
+ if (merged.includePaths.size > 0) {
209
+ console.log(`Found ${merged.includePaths.size} include paths`);
210
+ }
211
+ await fs.promises.mkdir(outputDir, { recursive: true });
212
+ // Write each source file
213
+ let written = 0;
214
+ for (const [sourcePath, sourceData] of Object.entries(merged.sources)) {
215
+ // Normalize path: contracts/ -> src/
216
+ const normalizedPath = normalizePath(sourcePath);
217
+ const fullPath = path.join(outputDir, normalizedPath);
218
+ const dir = path.dirname(fullPath);
219
+ await fs.promises.mkdir(dir, { recursive: true });
220
+ // Rewrite imports
221
+ const content = rewriteImports(sourcePath, sourceData.content, merged.includePaths, merged.basePath);
222
+ await fs.promises.writeFile(fullPath, content);
223
+ written++;
224
+ if (written % 50 === 0) {
225
+ console.log(` Written ${written}/${sourceCount} files...`);
226
+ }
227
+ }
228
+ console.log(`Written ${written} source files to ${outputDir}`);
229
+ // Detect if this is a Hardhat-style project (has @scope/ directories)
230
+ const hasNodeModulesStyle = Array.from(Object.keys(merged.sources)).some(p => p.startsWith('@'));
231
+ const libsDir = hasNodeModulesStyle ? '.' : 'lib';
232
+ // Generate foundry.toml - no solc version specified, let auto_detect handle it
233
+ let foundryToml = `[profile.default]\nsrc = "src"\nout = "out"\nlibs = ["${libsDir}"]\nauto_detect_solc = true\n`;
234
+ if ((_a = merged.optimizer) === null || _a === void 0 ? void 0 : _a.enabled) {
235
+ foundryToml += `optimizer = true\noptimizer_runs = ${merged.optimizer.runs}\n`;
236
+ }
237
+ if (merged.viaIR) {
238
+ foundryToml += `via_ir = true\n`;
239
+ }
240
+ // Collect remappings: from build-info + auto-detected @scope/ dirs
241
+ const allRemappings = new Set();
242
+ for (const remap of merged.remappings) {
243
+ // Also normalize contracts/ -> src/ in remappings
244
+ allRemappings.add(remap.replace(/contracts\//g, 'src/'));
245
+ }
246
+ const detectedRemappings = detectRemappings(merged.sources);
247
+ for (const remap of detectedRemappings) {
248
+ allRemappings.add(remap);
249
+ }
250
+ if (allRemappings.size > 0) {
251
+ foundryToml += `\nremappings = [\n`;
252
+ for (const remap of allRemappings) {
253
+ foundryToml += ` "${remap}",\n`;
254
+ }
255
+ foundryToml += `]\n`;
256
+ }
257
+ const foundryTomlPath = path.join(outputDir, 'foundry.toml');
258
+ await fs.promises.writeFile(foundryTomlPath, foundryToml);
259
+ console.log(`Generated ${foundryTomlPath}`);
260
+ console.log('\nDone! To build the project:');
261
+ console.log(` cd ${outputDir}`);
262
+ console.log(' forge build');
263
+ }
264
+ // CLI entry point
265
+ if (require.main === module) {
266
+ const args = process.argv.slice(2);
267
+ if (args.length < 2) {
268
+ console.error('Usage: npx ts-node src/unpack.ts <build-info.json|build-info-dir> <output-dir>');
269
+ process.exit(1);
270
+ }
271
+ const [inputPath, outputDir] = args;
272
+ unpackBuildInfo(inputPath, outputDir).catch(err => {
273
+ console.error('Failed:', err.message);
274
+ process.exit(1);
275
+ });
276
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "recon-generate",
3
- "version": "0.0.21",
3
+ "version": "0.0.23",
4
4
  "description": "CLI to scaffold Recon fuzzing suite inside Foundry projects",
5
5
  "main": "dist/index.js",
6
6
  "bin": {