diff options
Diffstat (limited to 'packages/contracts-gen')
-rw-r--r-- | packages/contracts-gen/.npmignore | 6 | ||||
-rw-r--r-- | packages/contracts-gen/CHANGELOG.json | 1 | ||||
-rw-r--r-- | packages/contracts-gen/README.md | 76 | ||||
-rwxr-xr-x | packages/contracts-gen/bin/contracts-gen.js | 2 | ||||
-rw-r--r-- | packages/contracts-gen/package.json | 49 | ||||
-rw-r--r-- | packages/contracts-gen/src/contracts-gen.ts | 175 | ||||
-rw-r--r-- | packages/contracts-gen/src/index.ts | 6 | ||||
-rw-r--r-- | packages/contracts-gen/tsconfig.json | 8 | ||||
-rw-r--r-- | packages/contracts-gen/tslint.json | 3 |
9 files changed, 326 insertions, 0 deletions
diff --git a/packages/contracts-gen/.npmignore b/packages/contracts-gen/.npmignore new file mode 100644 index 000000000..d645458f6 --- /dev/null +++ b/packages/contracts-gen/.npmignore @@ -0,0 +1,6 @@ +.* +yarn-error.log +/src/ +/scripts/ +tsconfig.json +/lib/monorepo_scripts/ diff --git a/packages/contracts-gen/CHANGELOG.json b/packages/contracts-gen/CHANGELOG.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/packages/contracts-gen/CHANGELOG.json @@ -0,0 +1 @@ +[] diff --git a/packages/contracts-gen/README.md b/packages/contracts-gen/README.md new file mode 100644 index 000000000..feaf9e65f --- /dev/null +++ b/packages/contracts-gen/README.md @@ -0,0 +1,76 @@ +# Contracts Gen + +This package allows you to generate boilerplate TypeScript code and configs for smart contracts packages. + +## Installation + +`yarn add -g @0x/contracts-gen` + +## Usage + +Run it from within your smart contracts packages. + +```bash +contracts-gen +``` + +You should run this tool after each time you move your contracts around to regenerate boilerplate code and configs. + +## What can it generate + +This tool does the following: + +- Reads your `compiler.json`. Specifically the list of smart contracts. +- Creates `wrapper.ts` file which exports all contract wrappers. +- Creates `artifacts.ts` file which exports all contract artifacts. +- Generates list of JSON artifact files in `tsconfig.json` +- Generates a glob for abi-gen in `package.json` + +On top of that - if your `compiler.json` has contracts referenced just by name - it will resolve the name to relative path and put it there. +It also sorts all the lists in it's output leading to smaller and cleaner diffs. + +## Contributing + +We welcome improvements and fixes from the wider community! To report bugs within this package, please create an issue in this repository. + +Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started. + +### Install dependencies + +If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them: + +```bash +yarn config set workspaces-experimental true +``` + +Then install dependencies + +```bash +yarn install +``` + +### Build + +To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory: + +```bash +PKG=@0x/contracts-gen yarn build +``` + +Or continuously rebuild on change: + +```bash +PKG=@0x/contracts-gen yarn watch +``` + +### Clean + +```bash +yarn clean +``` + +### Lint + +```bash +yarn lint +``` diff --git a/packages/contracts-gen/bin/contracts-gen.js b/packages/contracts-gen/bin/contracts-gen.js new file mode 100755 index 000000000..ec6ab4db6 --- /dev/null +++ b/packages/contracts-gen/bin/contracts-gen.js @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require('../lib/src/contracts-gen.js'); diff --git a/packages/contracts-gen/package.json b/packages/contracts-gen/package.json new file mode 100644 index 000000000..1265cac69 --- /dev/null +++ b/packages/contracts-gen/package.json @@ -0,0 +1,49 @@ +{ + "name": "@0x/contracts-gen", + "version": "1.0.0", + "engines": { + "node": ">=6.12" + }, + "description": "Generates boilerplate code for smart contracts packages", + "main": "lib/src/index.js", + "types": "lib/src/index.d.ts", + "scripts": { + "lint": "tslint --format stylish --project .", + "clean": "shx rm -rf lib", + "build": "tsc -b", + "build:ci": "yarn build" + }, + "bin": { + "contracts-gen": "bin/contracts-gen.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/0xProject/0x-monorepo.git" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/0xProject/0x-monorepo/issues" + }, + "homepage": "https://github.com/0xProject/0x-monorepo/packages/contracts-gen/README.md", + "dependencies": { + "@0x/types": "^2.0.1", + "@0x/utils": "^4.0.2", + "@0x/typescript-typings": "^4.0.0", + "ethereum-types": "^2.0.0", + "@0x/sol-resolver": "^2.0.1", + "lodash": "^4.17.11", + "prettier": "^1.16.3", + "to-snake-case": "^1.0.0" + }, + "devDependencies": { + "@0x/tslint-config": "^3.0.0", + "@types/node": "*", + "@types/prettier": "^1.15.2", + "shx": "^0.2.2", + "tslint": "5.11.0", + "typescript": "3.0.1" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/contracts-gen/src/contracts-gen.ts b/packages/contracts-gen/src/contracts-gen.ts new file mode 100644 index 000000000..0160a8204 --- /dev/null +++ b/packages/contracts-gen/src/contracts-gen.ts @@ -0,0 +1,175 @@ +#!/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<CompilerOptions>('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<CompilerOptions>(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<any>(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<PackageJSON>('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<T>(filePath: string): T { + const JSONString = fs.readFileSync(filePath, 'utf8'); + const parsed: T = JSON.parse(JSONString); + return parsed; +} diff --git a/packages/contracts-gen/src/index.ts b/packages/contracts-gen/src/index.ts new file mode 100644 index 000000000..c5f4b01f1 --- /dev/null +++ b/packages/contracts-gen/src/index.ts @@ -0,0 +1,6 @@ +/** + * This module is a CLI tool. As soon as you run it - it starts doing stuff. + * At the same time - our installation tests assume that you can import package without causing side effects. + * That's why our main entry point it empty. No side effects. But our secondary entry point - contracts-gen.ts is a CLI tool and starts running as soon as you import/run it. + */ +export {}; diff --git a/packages/contracts-gen/tsconfig.json b/packages/contracts-gen/tsconfig.json new file mode 100644 index 000000000..233008d61 --- /dev/null +++ b/packages/contracts-gen/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib", + "rootDir": "." + }, + "include": ["./src/**/*"] +} diff --git a/packages/contracts-gen/tslint.json b/packages/contracts-gen/tslint.json new file mode 100644 index 000000000..dd9053357 --- /dev/null +++ b/packages/contracts-gen/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": ["@0x/tslint-config"] +} |