// // Copyright 2021 Vulcanize, Inc. // import fs from 'fs'; import fetch from 'node-fetch'; import path from 'path'; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; import { flatten } from '@poanet/solidity-flattener'; import { parse, visit } from '@solidity-parser/parser'; import { KIND_ACTIVE, KIND_LAZY } from '@vulcanize/util'; import { MODE_ETH_CALL, MODE_STORAGE, MODE_ALL } from './utils/constants'; import { Visitor } from './visitor'; import { exportServer } from './server'; import { exportConfig } from './config'; import { exportArtifacts } from './artifacts'; import { exportPackage } from './package'; import { exportTSConfig } from './tsconfig'; import { exportReadme } from './readme'; import { exportEvents } from './events'; import { exportJobRunner } from './job-runner'; import { exportWatchContract } from './watch-contract'; import { exportLint } from './lint'; import { registerHandlebarHelpers } from './utils/handlebar-helpers'; const main = async (): Promise => { const argv = await yargs(hideBin(process.argv)) .option('input-file', { alias: 'i', demandOption: true, describe: 'Input contract file path or an url.', type: 'string' }) .option('contract-name', { alias: 'c', demandOption: true, describe: 'Main contract name.', type: 'string' }) .option('output-folder', { alias: 'o', describe: 'Output folder path.', type: 'string' }) .option('mode', { alias: 'm', describe: 'Code generation mode.', type: 'string', default: MODE_ALL, choices: [MODE_ETH_CALL, MODE_STORAGE, MODE_ALL] }) .option('kind', { alias: 'k', describe: 'Watcher kind.', type: 'string', default: KIND_ACTIVE, choices: [KIND_ACTIVE, KIND_LAZY] }) .option('flatten', { alias: 'f', describe: 'Flatten the input contract file.', type: 'boolean', default: true }) .argv; let data: string; if (argv['input-file'].startsWith('http')) { // Assume flattened file in case of URL. const response = await fetch(argv['input-file']); data = await response.text(); } else { data = argv.flatten ? await flatten(path.resolve(argv['input-file'])) : fs.readFileSync(path.resolve(argv['input-file'])).toString(); } const visitor = new Visitor(); parseAndVisit(data, visitor, argv.mode); generateWatcher(data, visitor, argv); }; function parseAndVisit (data: string, visitor: Visitor, mode: string) { // Get the abstract syntax tree for the flattened contract. const ast = parse(data); // Filter out library nodes. ast.children = ast.children.filter(child => !(child.type === 'ContractDefinition' && child.kind === 'library')); if ([MODE_ALL, MODE_ETH_CALL].some(value => value === mode)) { visit(ast, { FunctionDefinition: visitor.functionDefinitionVisitor.bind(visitor), EventDefinition: visitor.eventDefinitionVisitor.bind(visitor) }); } if ([MODE_ALL, MODE_STORAGE].some(value => value === mode)) { visit(ast, { StateVariableDeclaration: visitor.stateVariableDeclarationVisitor.bind(visitor), EventDefinition: visitor.eventDefinitionVisitor.bind(visitor) }); } } function generateWatcher (data: string, visitor: Visitor, argv: any) { // Prepare directory structure for the watcher. let outputDir = ''; if (argv['output-folder']) { outputDir = path.resolve(argv['output-folder']); if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true }); const environmentsFolder = path.join(outputDir, 'environments'); if (!fs.existsSync(environmentsFolder)) fs.mkdirSync(environmentsFolder); const artifactsFolder = path.join(outputDir, 'src/artifacts'); if (!fs.existsSync(artifactsFolder)) fs.mkdirSync(artifactsFolder, { recursive: true }); const entitiesFolder = path.join(outputDir, 'src/entity'); if (!fs.existsSync(entitiesFolder)) fs.mkdirSync(entitiesFolder, { recursive: true }); const cliFolder = path.join(outputDir, 'src/cli'); if (!fs.existsSync(cliFolder)) fs.mkdirSync(cliFolder, { recursive: true }); } const inputFileName = path.basename(argv['input-file'], '.sol'); registerHandlebarHelpers(); let outStream = outputDir ? fs.createWriteStream(path.join(outputDir, 'src/schema.gql')) : process.stdout; visitor.exportSchema(outStream); outStream = outputDir ? fs.createWriteStream(path.join(outputDir, 'src/resolvers.ts')) : process.stdout; visitor.exportResolvers(outStream); outStream = outputDir ? fs.createWriteStream(path.join(outputDir, 'src/indexer.ts')) : process.stdout; visitor.exportIndexer(outStream, inputFileName); outStream = outputDir ? fs.createWriteStream(path.join(outputDir, 'src/server.ts')) : process.stdout; exportServer(outStream); outStream = outputDir ? fs.createWriteStream(path.join(outputDir, 'environments/local.toml')) : process.stdout; exportConfig(argv.kind, path.basename(outputDir), outStream); outStream = outputDir ? fs.createWriteStream(path.join(outputDir, 'src/artifacts/', `${inputFileName}.json`)) : process.stdout; exportArtifacts( outStream, data, `${inputFileName}.sol`, argv['contract-name'] ); outStream = outputDir ? fs.createWriteStream(path.join(outputDir, 'src/database.ts')) : process.stdout; visitor.exportDatabase(outStream); outStream = outputDir ? fs.createWriteStream(path.join(outputDir, 'package.json')) : process.stdout; exportPackage(path.basename(outputDir), outStream); outStream = outputDir ? fs.createWriteStream(path.join(outputDir, 'tsconfig.json')) : process.stdout; exportTSConfig(outStream); const entityDir = outputDir ? path.join(outputDir, 'src/entity') : ''; visitor.exportEntities(entityDir); outStream = outputDir ? fs.createWriteStream(path.join(outputDir, 'README.md')) : process.stdout; exportReadme(path.basename(outputDir), argv['contract-name'], outStream); outStream = outputDir ? fs.createWriteStream(path.join(outputDir, 'src/events.ts')) : process.stdout; exportEvents(outStream); outStream = outputDir ? fs.createWriteStream(path.join(outputDir, 'src/job-runner.ts')) : process.stdout; exportJobRunner(outStream); outStream = outputDir ? fs.createWriteStream(path.join(outputDir, 'src/cli/watch-contract.ts')) : process.stdout; exportWatchContract(outStream); let rcOutStream; let ignoreOutStream; if (outputDir) { rcOutStream = fs.createWriteStream(path.join(outputDir, '.eslintrc.json')); ignoreOutStream = fs.createWriteStream(path.join(outputDir, '.eslintignore')); } else { rcOutStream = process.stdout; ignoreOutStream = process.stdout; } exportLint(rcOutStream, ignoreOutStream); } main().catch(err => { console.error(err); });