@graphprotocol/graph-cli
Version:
CLI for building for and deploying to The Graph
135 lines (134 loc) • 6.6 kB
JavaScript
import path from 'node:path';
import fs from 'fs-extra';
import immutable from 'immutable';
import prettier from 'prettier';
import { GENERATED_FILE_NOTE } from '../../codegen/typescript.js';
import { displayPath } from '../../command-helpers/fs.js';
import { step, withSpinner } from '../../command-helpers/spinner.js';
import ABI from './abi.js';
export default class EthereumTypeGenerator {
sourceDir;
outputDir;
constructor(options) {
this.sourceDir = options.sourceDir;
this.outputDir = options.outputDir;
}
async loadABIs(subgraph) {
return await withSpinner('Load contract ABIs', 'Failed to load contract ABIs', 'Warnings while loading contract ABIs', async (spinner) => {
try {
const dataSources = subgraph.get('dataSources');
if (!dataSources)
return immutable.List();
return dataSources.reduce((accumulatedAbis, dataSource) => {
// Get ABIs from the current data source's mapping
const sourceAbis = dataSource.getIn(['mapping', 'abis']);
if (!sourceAbis)
return accumulatedAbis;
// Process each ABI in the current data source
return sourceAbis.reduce((currentAbis, abiConfig) => {
// Skip invalid ABI configurations
if (!this.isValidAbiConfig(abiConfig)) {
return currentAbis;
}
// Load and add the ABI to our list
const loadedAbi = this._loadABI(dataSource, abiConfig.get('name'), abiConfig.get('file'), spinner);
return currentAbis.push(loadedAbi);
}, accumulatedAbis);
}, immutable.List());
}
catch (e) {
throw Error(`Failed to load contract ABIs: ${e.message}`);
}
});
}
isValidAbiConfig(abiConfig) {
return !!(abiConfig?.get('name') && abiConfig?.get('file'));
}
_loadABI(dataSource, name, maybeRelativePath, spinner) {
try {
if (this.sourceDir) {
const absolutePath = path.resolve(this.sourceDir, maybeRelativePath);
step(spinner, `Load contract ABI from`, displayPath(absolutePath));
return { dataSource, abi: ABI.load(name, absolutePath) };
}
return { dataSource, abi: ABI.load(name, maybeRelativePath) };
}
catch (e) {
throw Error(`Failed to load contract ABI: ${e.message}`);
}
}
async loadDataSourceTemplateABIs(subgraph) {
return await withSpinner(`Load data source template ABIs`, `Failed to load data source template ABIs`, `Warnings while loading data source template ABIs`, async (spinner) => {
const abis = [];
for (const template of subgraph.get('templates', immutable.List())) {
for (const abi of template.getIn(['mapping', 'abis'])) {
abis.push(this._loadDataSourceTemplateABI(template, abi.get('name'), abi.get('file'), spinner));
}
}
return abis;
});
}
_loadDataSourceTemplateABI(template, name, maybeRelativePath, spinner) {
try {
if (this.sourceDir) {
const absolutePath = path.resolve(this.sourceDir, maybeRelativePath);
step(spinner, `Load data source template ABI from`, displayPath(absolutePath));
return { template, abi: ABI.load(name, absolutePath) };
}
return { template, abi: ABI.load(name, maybeRelativePath) };
}
catch (e) {
throw Error(`Failed to load data source template ABI: ${e.message}`);
}
}
async generateTypesForABIs(abis) {
return withSpinner(`Generate types for contract ABIs`, `Failed to generate types for contract ABIs`, `Warnings while generating types for contract ABIs`, async (spinner) => {
return await Promise.all(abis.map(async (abi) => await this._generateTypesForABI(abi, spinner)));
});
}
async _generateTypesForABI(abi, spinner) {
try {
step(spinner, `Generate types for contract ABI:`, `${abi.abi.name} (${displayPath(abi.abi.file)})`);
const codeGenerator = abi.abi.codeGenerator();
const code = await prettier.format([
GENERATED_FILE_NOTE,
...codeGenerator.generateModuleImports(),
...(await codeGenerator.generateTypes()),
].join('\n'), {
parser: 'typescript',
});
const outputFile = path.join(this.outputDir, abi.dataSource.get('name'), `${abi.abi.name}.ts`);
step(spinner, `Write types to`, displayPath(outputFile));
await fs.mkdirs(path.dirname(outputFile));
await fs.writeFile(outputFile, code);
}
catch (e) {
throw Error(`Failed to generate types for contract ABI: ${e.message}`);
}
}
async generateTypesForDataSourceTemplateABIs(abis) {
return await withSpinner(`Generate types for data source template ABIs`, `Failed to generate types for data source template ABIs`, `Warnings while generating types for data source template ABIs`, async (spinner) => {
return await Promise.all(abis.map(async (abi) => await this._generateTypesForDataSourceTemplateABI(abi, spinner)));
});
}
async _generateTypesForDataSourceTemplateABI(abi, spinner) {
try {
step(spinner, `Generate types for data source template ABI:`, `${abi.template.get('name')} > ${abi.abi.name} (${displayPath(abi.abi.file)})`);
const codeGenerator = abi.abi.codeGenerator();
const code = await prettier.format([
GENERATED_FILE_NOTE,
...codeGenerator.generateModuleImports(),
...(await codeGenerator.generateTypes()),
].join('\n'), {
parser: 'typescript',
});
const outputFile = path.join(this.outputDir, 'templates', abi.template.get('name'), `${abi.abi.name}.ts`);
step(spinner, `Write types to`, displayPath(outputFile));
await fs.mkdirs(path.dirname(outputFile));
await fs.writeFile(outputFile, code);
}
catch (e) {
throw Error(`Failed to generate types for data source template ABI: ${e.message}`);
}
}
}