watcher-ts/packages/codegen/src/generate-code.ts
prathamesh0 06ba24e38f Generate GQL API for subgraph entities and auto-diff based on store set (#38)
* Add subgraph schema types to the generated schema

* Add queries for subgraph entities

* Add entity generation for subgraph entities

* Call subgraph event handler in indexer

* Refactor subgraph schema and entity generation

* Add resolvers generation for subgraph entities

* Get event signature in the event

* Add NonNullType check for field type in entity generation

* Auto-diff based on store set

* Use contract address from data source in loader

* Change subgraph-schema arg to subgraph-path arg
2021-12-28 16:08:05 +05:30

295 lines
9.2 KiB
TypeScript

//
// 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';
import { exportHooks } from './hooks';
import { exportFill } from './fill';
import { exportCheckpoint } from './checkpoint';
import { exportState } from './export-state';
import { importState } from './import-state';
import { exportIPFS } from './ipfs';
import { exportInspectCID } from './inspect-cid';
const main = async (): Promise<void> => {
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('port', {
alias: 'p',
describe: 'Server port.',
type: 'number',
default: 3008
})
.option('flatten', {
alias: 'f',
describe: 'Flatten the input contract file.',
type: 'boolean',
default: true
})
.option('subgraph-path', {
alias: 's',
describe: 'Path to the subgraph build.',
type: 'string'
})
.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 resetCmdsFolder = path.join(outputDir, 'src/cli/reset-cmds');
if (!fs.existsSync(resetCmdsFolder)) fs.mkdirSync(resetCmdsFolder, { recursive: true });
}
const inputFileName = path.basename(argv['input-file'], '.sol');
registerHandlebarHelpers();
visitor.visitSubgraph(argv['subgraph-path']);
let outStream = outputDir
? fs.createWriteStream(path.join(outputDir, 'src/schema.gql'))
: process.stdout;
const schemaContent = 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, argv['contract-name']);
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, argv.port, path.basename(outputDir), outStream, argv['subgraph-path']);
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);
outStream = outputDir
? fs.createWriteStream(path.join(outputDir, 'src/cli/checkpoint.ts'))
: process.stdout;
exportCheckpoint(outStream);
outStream = outputDir
? fs.createWriteStream(path.join(outputDir, 'src/hooks.ts'))
: process.stdout;
exportHooks(outStream);
outStream = outputDir
? fs.createWriteStream(path.join(outputDir, 'src/fill.ts'))
: process.stdout;
exportFill(outStream);
let rcOutStream, 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);
outStream = outputDir
? fs.createWriteStream(path.join(outputDir, 'src/client.ts'))
: process.stdout;
visitor.exportClient(outStream, schemaContent, path.join(outputDir, 'src/gql'));
let resetOutStream, resetJQOutStream, resetStateOutStream;
if (outputDir) {
resetOutStream = fs.createWriteStream(path.join(outputDir, 'src/cli/reset.ts'));
resetJQOutStream = fs.createWriteStream(path.join(outputDir, 'src/cli/reset-cmds/job-queue.ts'));
resetStateOutStream = fs.createWriteStream(path.join(outputDir, 'src/cli/reset-cmds/state.ts'));
} else {
resetOutStream = process.stdout;
resetJQOutStream = process.stdout;
resetStateOutStream = process.stdout;
}
visitor.exportReset(resetOutStream, resetJQOutStream, resetStateOutStream);
outStream = outputDir
? fs.createWriteStream(path.join(outputDir, 'src/cli/export-state.ts'))
: process.stdout;
exportState(outStream);
outStream = outputDir
? fs.createWriteStream(path.join(outputDir, 'src/cli/import-state.ts'))
: process.stdout;
importState(outStream);
outStream = outputDir
? fs.createWriteStream(path.join(outputDir, 'src/ipfs.ts'))
: process.stdout;
exportIPFS(outStream);
outStream = outputDir
? fs.createWriteStream(path.join(outputDir, 'src/cli/inspect-cid.ts'))
: process.stdout;
exportInspectCID(outStream);
}
main().catch(err => {
console.error(err);
});