#!/usr/bin/env node import { NameResolver } from '@0x/sol-resolver'; import { PackageJSON } from '@0x/types'; import { logUtils } from '@0x/utils'; import { CompilerOptions } from 'ethereum-types'; import * as fs from 'fs'; import * as _ from 'lodash'; import * as path from 'path'; import * as prettier from 'prettier'; import toSnakeCase = require('to-snake-case'); const SOLIDITY_EXTENSION = '.sol'; const DEFAULT_ARTIFACTS_DIR = 'artifacts'; const DEFAULT_CONTRACTS_DIR = 'contracts'; const DEFAULT_WRAPPERS_DIR = 'generated-wrappers'; const AUTO_GENERATED_BANNER = `/* * ----------------------------------------------------------------------------- * Warning: This file is auto-generated by contracts-gen. Don't edit manually. * ----------------------------------------------------------------------------- */`; const AUTO_GENERATED_BANNER_FOR_LISTS = `This list is auto-generated by contracts-gen. Don't edit manually.`; (async () => { const packageDir = process.cwd(); const compilerJSON = readJSONFile('compiler.json'); const contracts = compilerJSON.contracts; const contractsDir = compilerJSON.contractsDir || DEFAULT_CONTRACTS_DIR; const artifactsDir = compilerJSON.artifactsDir || DEFAULT_ARTIFACTS_DIR; const wrappersDir = DEFAULT_WRAPPERS_DIR; if (!_.isArray(contracts)) { throw new Error('Unable to run the generator bacause contracts key in compiler.json is not of type array'); } const prettierConfig = await prettier.resolveConfig(packageDir); generateCompilerJSONContractsList(contracts, contractsDir, prettierConfig); generateArtifactsTs(contracts, artifactsDir, prettierConfig); generateWrappersTs(contracts, wrappersDir, prettierConfig); generateTsConfigJSONFilesList(contracts, artifactsDir, prettierConfig); generatePackageJSONABIConfig(contracts, artifactsDir, prettierConfig); process.exit(0); })().catch(err => { logUtils.log(err); process.exit(1); }); function generateCompilerJSONContractsList( contracts: string[], contractsDir: string, prettierConfig: prettier.Options | null, ): void { const COMPILER_JSON_FILE_PATH = 'compiler.json'; const compilerJSON = readJSONFile(COMPILER_JSON_FILE_PATH); compilerJSON.contracts = _.map(contracts, contract => { if (contract.endsWith(SOLIDITY_EXTENSION)) { // If it's already a relative path - NO-OP. return contract; } else { // If it's just a contract name - resolve it and rewrite. return new NameResolver(contractsDir).resolve(contract).path; } }); compilerJSON.contracts = _.sortBy(compilerJSON.contracts); const compilerJSONString = JSON.stringify(compilerJSON); const formattedCompilerJSON = prettier.format(compilerJSONString, { ...prettierConfig, filepath: COMPILER_JSON_FILE_PATH, }); fs.writeFileSync(COMPILER_JSON_FILE_PATH, formattedCompilerJSON); } function generateArtifactsTs(contracts: string[], artifactsDir: string, prettierConfig: prettier.Options | null): void { const imports = _.map(contracts, contract => { const contractName = path.basename(contract, SOLIDITY_EXTENSION); const importPath = path.join('..', artifactsDir, `${contractName}.json`); return `import * as ${contractName} from '${importPath}';`; }); const sortedImports = _.sortBy(imports); const artifacts = _.map(contracts, contract => { const contractName = path.basename(contract, SOLIDITY_EXTENSION); if (contractName === 'ZRXToken') { // HACK(albrow): "as any" hack still required here because ZRXToken does not // conform to the v2 artifact type. return `${contractName}: (${contractName} as any) as ContractArtifact,`; } else { return `${contractName}: ${contractName} as ContractArtifact,`; } }); const artifactsTs = ` ${AUTO_GENERATED_BANNER} import { ContractArtifact } from 'ethereum-types'; ${sortedImports.join('\n')} export const artifacts = {${artifacts.join('\n')}}; `; const ARTIFACTS_TS_FILE_PATH = 'src/artifacts.ts'; const formattedArtifactsTs = prettier.format(artifactsTs, { ...prettierConfig, filepath: ARTIFACTS_TS_FILE_PATH }); fs.writeFileSync(ARTIFACTS_TS_FILE_PATH, formattedArtifactsTs); } function generateWrappersTs(contracts: string[], wrappersDir: string, prettierConfig: prettier.Options | null): void { const imports = _.map(contracts, contract => { const contractName = path.basename(contract, SOLIDITY_EXTENSION); const outputFileName = makeOutputFileName(contractName); const exportPath = path.join('..', wrappersDir, outputFileName); return `export * from '${exportPath}';`; }); const sortedImports = _.sortBy(imports); const wrappersTs = ` ${AUTO_GENERATED_BANNER} ${sortedImports.join('\n')} `; const WRAPPERS_TS_FILE_PATH = 'src/wrappers.ts'; const formattedArtifactsTs = prettier.format(wrappersTs, { ...prettierConfig, filepath: WRAPPERS_TS_FILE_PATH }); fs.writeFileSync(WRAPPERS_TS_FILE_PATH, formattedArtifactsTs); } function generateTsConfigJSONFilesList( contracts: string[], artifactsDir: string, prettierConfig: prettier.Options | null, ): void { const TS_CONFIG_FILE_PATH = 'tsconfig.json'; const tsConfig = readJSONFile(TS_CONFIG_FILE_PATH); tsConfig.files = _.map(contracts, contract => { const contractName = path.basename(contract, SOLIDITY_EXTENSION); const artifactPath = path.join(artifactsDir, `${contractName}.json`); return artifactPath; }); tsConfig.files = _.sortBy(tsConfig.files); const tsConfigString = JSON.stringify(tsConfig); const formattedTsConfig = prettier.format(tsConfigString, { ...prettierConfig, filepath: TS_CONFIG_FILE_PATH }); fs.writeFileSync(TS_CONFIG_FILE_PATH, formattedTsConfig); } function generatePackageJSONABIConfig( contracts: string[], artifactsDir: string, prettierConfig: prettier.Options | null, ): void { let packageJSON = readJSONFile('package.json'); const contractNames = _.map(contracts, contract => { const contractName = path.basename(contract, SOLIDITY_EXTENSION); return contractName; }); const sortedContractNames = _.sortBy(contractNames); packageJSON = { ...packageJSON, config: { ...packageJSON.config, 'abis:comment': AUTO_GENERATED_BANNER_FOR_LISTS, abis: `${artifactsDir}/@(${sortedContractNames.join('|')}).json`, }, }; const PACKAGE_JSON_FILE_PATH = 'package.json'; const packageJSONString = JSON.stringify(packageJSON); const formattedPackageJSON = prettier.format(packageJSONString, { ...prettierConfig, filepath: PACKAGE_JSON_FILE_PATH, }); fs.writeFileSync(PACKAGE_JSON_FILE_PATH, formattedPackageJSON); } function makeOutputFileName(name: string): string { let fileName = toSnakeCase(name); // HACK: Snake case doesn't make a lot of sense for abbreviated names but we can't reliably detect abbreviations // so we special-case the abbreviations we use. fileName = fileName.replace('z_r_x', 'zrx').replace('e_r_c', 'erc'); return fileName; } function readJSONFile(filePath: string): T { const JSONString = fs.readFileSync(filePath, 'utf8'); const parsed: T = JSON.parse(JSONString); return parsed; }