mirror of
https://github.com/cerc-io/watcher-ts
synced 2026-09-08 00:44:06 +00:00
Support artifacts generation for multiple contracts (#82)
This commit is contained in:
@@ -7,6 +7,8 @@ import fetch from 'node-fetch';
|
||||
import path from 'path';
|
||||
import yargs from 'yargs';
|
||||
import { hideBin } from 'yargs/helpers';
|
||||
import assert from 'assert';
|
||||
import { Writable } from 'stream';
|
||||
|
||||
import { flatten } from '@poanet/solidity-flattener';
|
||||
import { parse, visit } from '@solidity-parser/parser';
|
||||
@@ -35,17 +37,17 @@ import { exportInspectCID } from './inspect-cid';
|
||||
|
||||
const main = async (): Promise<void> => {
|
||||
const argv = await yargs(hideBin(process.argv))
|
||||
.option('input-file', {
|
||||
.option('input-files', {
|
||||
alias: 'i',
|
||||
demandOption: true,
|
||||
describe: 'Input contract file path or an url.',
|
||||
type: 'string'
|
||||
describe: 'Input contract file path(s) or url(s).',
|
||||
type: 'array'
|
||||
})
|
||||
.option('contract-name', {
|
||||
.option('contract-names', {
|
||||
alias: 'c',
|
||||
demandOption: true,
|
||||
describe: 'Main contract name.',
|
||||
type: 'string'
|
||||
describe: 'Contract name(s).',
|
||||
type: 'array'
|
||||
})
|
||||
.option('output-folder', {
|
||||
alias: 'o',
|
||||
@@ -85,47 +87,57 @@ const main = async (): Promise<void> => {
|
||||
})
|
||||
.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();
|
||||
// Create an array of flattened contract strings.
|
||||
const contractStrings: string[] = [];
|
||||
|
||||
for (const inputFile of argv['input-files']) {
|
||||
assert(typeof inputFile === 'string', 'Input file path should be a string');
|
||||
|
||||
if (inputFile.startsWith('http')) {
|
||||
// Assume flattened file in case of URL.
|
||||
const response = await fetch(inputFile);
|
||||
const contractString = await response.text();
|
||||
contractStrings.push(contractString);
|
||||
} else {
|
||||
contractStrings.push(argv.flatten
|
||||
? await flatten(path.resolve(inputFile))
|
||||
: fs.readFileSync(path.resolve(inputFile)).toString()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const visitor = new Visitor();
|
||||
|
||||
parseAndVisit(data, visitor, argv.mode);
|
||||
parseAndVisit(contractStrings, visitor, argv.mode);
|
||||
|
||||
generateWatcher(data, visitor, argv);
|
||||
generateWatcher(contractStrings, visitor, argv);
|
||||
};
|
||||
|
||||
function parseAndVisit (data: string, visitor: Visitor, mode: string) {
|
||||
// Get the abstract syntax tree for the flattened contract.
|
||||
const ast = parse(data);
|
||||
function parseAndVisit (contractStrings: string[], visitor: Visitor, mode: string) {
|
||||
for (const contractString of contractStrings) {
|
||||
// Get the abstract syntax tree for the flattened contract.
|
||||
const ast = parse(contractString);
|
||||
|
||||
// Filter out library nodes.
|
||||
ast.children = ast.children.filter(child => !(child.type === 'ContractDefinition' && child.kind === 'library'));
|
||||
// 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_ETH_CALL].includes(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)
|
||||
});
|
||||
if ([MODE_ALL, MODE_STORAGE].includes(mode)) {
|
||||
visit(ast, {
|
||||
StateVariableDeclaration: visitor.stateVariableDeclarationVisitor.bind(visitor),
|
||||
EventDefinition: visitor.eventDefinitionVisitor.bind(visitor)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function generateWatcher (data: string, visitor: Visitor, argv: any) {
|
||||
function generateWatcher (contractStrings: string[], visitor: Visitor, argv: any) {
|
||||
// Prepare directory structure for the watcher.
|
||||
let outputDir = '';
|
||||
if (argv['output-folder']) {
|
||||
@@ -145,13 +157,34 @@ function generateWatcher (data: string, visitor: Visitor, argv: any) {
|
||||
if (!fs.existsSync(resetCmdsFolder)) fs.mkdirSync(resetCmdsFolder, { recursive: true });
|
||||
}
|
||||
|
||||
const inputFileName = path.basename(argv['input-file'], '.sol');
|
||||
let outStream: Writable;
|
||||
|
||||
const contractNames = argv['contract-names'];
|
||||
const inputFileNames: string[] = [];
|
||||
|
||||
// Export artifacts for the contracts.
|
||||
argv['input-files'].forEach((inputFile: string, index: number) => {
|
||||
const inputFileName = path.basename(inputFile, '.sol');
|
||||
inputFileNames.push(inputFileName);
|
||||
|
||||
outStream = outputDir
|
||||
? fs.createWriteStream(path.join(outputDir, 'src/artifacts/', `${inputFileName}.json`))
|
||||
: process.stdout;
|
||||
|
||||
exportArtifacts(
|
||||
outStream,
|
||||
contractStrings[index],
|
||||
`${inputFileName}.sol`,
|
||||
contractNames[index]
|
||||
);
|
||||
});
|
||||
|
||||
// Register the handlebar helpers to be used in the templates.
|
||||
registerHandlebarHelpers();
|
||||
|
||||
visitor.visitSubgraph(argv['subgraph-path']);
|
||||
|
||||
let outStream = outputDir
|
||||
outStream = outputDir
|
||||
? fs.createWriteStream(path.join(outputDir, 'src/schema.gql'))
|
||||
: process.stdout;
|
||||
const schemaContent = visitor.exportSchema(outStream);
|
||||
@@ -164,7 +197,7 @@ function generateWatcher (data: string, visitor: Visitor, argv: any) {
|
||||
outStream = outputDir
|
||||
? fs.createWriteStream(path.join(outputDir, 'src/indexer.ts'))
|
||||
: process.stdout;
|
||||
visitor.exportIndexer(outStream, inputFileName, argv['contract-name']);
|
||||
visitor.exportIndexer(outStream, inputFileNames);
|
||||
|
||||
outStream = outputDir
|
||||
? fs.createWriteStream(path.join(outputDir, 'src/server.ts'))
|
||||
@@ -176,16 +209,6 @@ function generateWatcher (data: string, visitor: Visitor, argv: any) {
|
||||
: 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;
|
||||
@@ -242,6 +265,7 @@ function generateWatcher (data: string, visitor: Visitor, argv: any) {
|
||||
exportFill(outStream);
|
||||
|
||||
let rcOutStream, ignoreOutStream;
|
||||
|
||||
if (outputDir) {
|
||||
rcOutStream = fs.createWriteStream(path.join(outputDir, '.eslintrc.json'));
|
||||
ignoreOutStream = fs.createWriteStream(path.join(outputDir, '.eslintignore'));
|
||||
@@ -249,6 +273,7 @@ function generateWatcher (data: string, visitor: Visitor, argv: any) {
|
||||
rcOutStream = process.stdout;
|
||||
ignoreOutStream = process.stdout;
|
||||
}
|
||||
|
||||
exportLint(rcOutStream, ignoreOutStream);
|
||||
|
||||
outStream = outputDir
|
||||
@@ -257,6 +282,7 @@ function generateWatcher (data: string, visitor: Visitor, argv: any) {
|
||||
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'));
|
||||
@@ -266,6 +292,7 @@ function generateWatcher (data: string, visitor: Visitor, argv: any) {
|
||||
resetJQOutStream = process.stdout;
|
||||
resetStateOutStream = process.stdout;
|
||||
}
|
||||
|
||||
visitor.exportReset(resetOutStream, resetJQOutStream, resetStateOutStream);
|
||||
|
||||
outStream = outputDir
|
||||
|
||||
@@ -104,12 +104,11 @@ export class Indexer {
|
||||
* @param outStream A writable output stream to write the indexer file to.
|
||||
* @param inputFileName Input contract file name to be passed to the template.
|
||||
*/
|
||||
exportIndexer (outStream: Writable, inputFileName: string, contractName: string): void {
|
||||
exportIndexer (outStream: Writable, inputFileNames: string[]): void {
|
||||
const template = Handlebars.compile(this._templateString);
|
||||
|
||||
const obj = {
|
||||
inputFileName,
|
||||
contractName,
|
||||
inputFileNames,
|
||||
queries: this._queries,
|
||||
constants: {
|
||||
MODE_ETH_CALL,
|
||||
|
||||
@@ -36,7 +36,7 @@ export class Schema {
|
||||
|
||||
// TODO: Handle cases where returnType/params type is an array.
|
||||
const tsReturnType = getTsForSol(returnType);
|
||||
assert(tsReturnType);
|
||||
assert(tsReturnType, `ts type for sol type ${returnType} for ${name} not found`);
|
||||
|
||||
const queryObject: { [key: string]: any; } = {};
|
||||
queryObject[name] = {
|
||||
@@ -51,7 +51,7 @@ export class Schema {
|
||||
if (params.length > 0) {
|
||||
queryObject[name].args = params.reduce((acc, curr) => {
|
||||
const tsCurrType = getTsForSol(curr.type);
|
||||
assert(tsCurrType);
|
||||
assert(tsCurrType, `ts type for sol type ${curr.type} for ${curr.name} not found`);
|
||||
acc[curr.name] = `${getGqlForTs(tsCurrType)}!`;
|
||||
return acc;
|
||||
}, queryObject[name].args);
|
||||
@@ -81,7 +81,7 @@ export class Schema {
|
||||
if (params.length > 0) {
|
||||
typeObject.fields = params.reduce((acc, curr) => {
|
||||
const tsCurrType = getTsForSol(curr.type);
|
||||
assert(tsCurrType);
|
||||
assert(tsCurrType, `ts type for sol type ${curr.type} for ${curr.name} not found`);
|
||||
acc[curr.name] = `${getGqlForTs(tsCurrType)}!`;
|
||||
return acc;
|
||||
}, typeObject.fields);
|
||||
|
||||
@@ -26,7 +26,11 @@ import { SyncStatus } from './entity/SyncStatus';
|
||||
import { HookStatus } from './entity/HookStatus';
|
||||
import { BlockProgress } from './entity/BlockProgress';
|
||||
import { IPLDBlock } from './entity/IPLDBlock';
|
||||
|
||||
{{#each inputFileNames as | inputFileName |}}
|
||||
import artifacts from './artifacts/{{inputFileName}}.json';
|
||||
{{/each}}
|
||||
|
||||
import { createInitialCheckpoint, handleEvent, createStateDiff, createStateCheckpoint } from './hooks';
|
||||
import { IPFSClient } from './ipfs';
|
||||
|
||||
|
||||
@@ -11,10 +11,15 @@ const _gqlToTs: Map<string, string> = new Map();
|
||||
// Solidity to Typescript type-mapping.
|
||||
_solToTs.set('string', 'string');
|
||||
_solToTs.set('uint8', 'number');
|
||||
_solToTs.set('uint16', 'number');
|
||||
_solToTs.set('uint64', 'bigint');
|
||||
_solToTs.set('uint128', 'bigint');
|
||||
_solToTs.set('uint256', 'bigint');
|
||||
_solToTs.set('address', 'string');
|
||||
_solToTs.set('bool', 'boolean');
|
||||
_solToTs.set('bytes', 'string');
|
||||
_solToTs.set('bytes4', 'string');
|
||||
_solToTs.set('bytes32', 'string');
|
||||
|
||||
// Typescript to Graphql type-mapping.
|
||||
_tsToGql.set('string', 'String');
|
||||
|
||||
@@ -140,10 +140,10 @@ export class Visitor {
|
||||
/**
|
||||
* Writes the indexer file generated from a template to a stream.
|
||||
* @param outStream A writable output stream to write the indexer file to.
|
||||
* @param inputFileName Input contract file name to be passed to the template.
|
||||
* @param inputFileName Input contract file names to be passed to the template.
|
||||
*/
|
||||
exportIndexer (outStream: Writable, inputFileName: string, contractName: string): void {
|
||||
this._indexer.exportIndexer(outStream, inputFileName, contractName);
|
||||
exportIndexer (outStream: Writable, inputFileNames: string[]): void {
|
||||
this._indexer.exportIndexer(outStream, inputFileNames);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user