UNPKG

@graphprotocol/graph-cli

Version:

CLI for building for and deploying to The Graph

250 lines (249 loc) • 12.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const path_1 = __importDefault(require("path")); const fs_extra_1 = __importDefault(require("fs-extra")); const toolbox = __importStar(require("gluegun")); const graphql = __importStar(require("graphql/language")); const immutable_1 = __importDefault(require("immutable")); const prettier_1 = __importDefault(require("prettier")); // @ts-expect-error TODO: type out if necessary const Index_bs_js_1 = __importDefault(require("@float-capital/float-subgraph-uncrashable/src/Index.bs.js")); const template_1 = __importDefault(require("./codegen/template")); const typescript_1 = require("./codegen/typescript"); const fs_1 = require("./command-helpers/fs"); const spinner_1 = require("./command-helpers/spinner"); const debug_1 = __importDefault(require("./debug")); const migrations_1 = require("./migrations"); const schema_1 = __importDefault(require("./schema")); const subgraph_1 = __importDefault(require("./subgraph")); const watcher_1 = __importDefault(require("./watcher")); const typeGenDebug = (0, debug_1.default)('graph-cli:type-generator'); class TypeGenerator { constructor(options) { this.options = options; this.sourceDir = this.options.sourceDir || (this.options.subgraphManifest && path_1.default.dirname(this.options.subgraphManifest)); this.protocol = this.options.protocol; this.protocolTypeGenerator = this.protocol?.getTypeGenerator?.({ sourceDir: this.sourceDir, outputDir: this.options.outputDir, }); process.on('uncaughtException', e => { toolbox.print.error(`UNCAUGHT EXCEPTION: ${e}`); }); } async generateTypes() { if (this.protocol.name === 'substreams') { typeGenDebug.extend('generateTypes')('Subgraph uses a substream datasource. Skipping code generation.'); toolbox.print.success('Subgraph uses a substream datasource. Codegeneration is not required.'); process.exit(0); return; } try { if (!this.options.skipMigrations && this.options.subgraphManifest) { await (0, migrations_1.applyMigrations)({ sourceDir: this.sourceDir, manifestFile: this.options.subgraphManifest, }); } const subgraph = await this.loadSubgraph(); // Not all protocols support/have ABIs. if (this.protocol.hasABIs()) { typeGenDebug.extend('generateTypes')('Generating types for ABIs'); const abis = await this.protocolTypeGenerator.loadABIs(subgraph); await this.protocolTypeGenerator.generateTypesForABIs(abis); } typeGenDebug.extend('generateTypes')('Generating types for templates'); await this.generateTypesForDataSourceTemplates(subgraph); // Not all protocols support/have ABIs. if (this.protocol.hasABIs()) { const templateAbis = await this.protocolTypeGenerator.loadDataSourceTemplateABIs(subgraph); await this.protocolTypeGenerator.generateTypesForDataSourceTemplateABIs(templateAbis); } const schema = await this.loadSchema(subgraph); typeGenDebug.extend('generateTypes')('Generating types for schema'); await this.generateTypesForSchema(schema); toolbox.print.success('\nTypes generated successfully\n'); if (this.options.uncrashable && this.options.uncrashableConfig) { await this.generateUncrashableEntities(schema); toolbox.print.success('\nUncrashable Helpers generated successfully\n'); } return true; } catch (e) { return false; } } async generateUncrashableEntities(graphSchema) { const ast = graphql.parse(graphSchema.document); const entityDefinitions = ast['definitions']; return await (0, spinner_1.withSpinner)(`Generate Uncrashable Entity Helpers`, `Failed to generate Uncrashable Entity Helpers`, `Warnings while generating Uncrashable Entity Helpers`, async (spinner) => { Index_bs_js_1.default.run(entityDefinitions, this.options.uncrashableConfig, this.options.outputDir); const outputFile = path_1.default.join(this.options.outputDir, 'UncrashableEntityHelpers.ts'); (0, spinner_1.step)(spinner, 'Save uncrashable entities to', (0, fs_1.displayPath)(outputFile)); }); } async loadSubgraph({ quiet } = { quiet: false }) { const subgraphLoadOptions = { protocol: this.protocol, skipValidation: false, }; if (quiet) { return (this.options.subgraph || (await subgraph_1.default.load(this.options.subgraphManifest, subgraphLoadOptions)).result); } const manifestPath = (0, fs_1.displayPath)(this.options.subgraphManifest); return await (0, spinner_1.withSpinner)(`Load subgraph from ${manifestPath}`, `Failed to load subgraph from ${manifestPath}`, `Warnings while loading subgraph from ${manifestPath}`, async (_spinner) => { return (this.options.subgraph || subgraph_1.default.load(this.options.subgraphManifest, subgraphLoadOptions)); }); } async loadSchema(subgraph) { const maybeRelativePath = subgraph.getIn(['schema', 'file']); const absolutePath = path_1.default.resolve(this.sourceDir, maybeRelativePath); return await (0, spinner_1.withSpinner)(`Load GraphQL schema from ${(0, fs_1.displayPath)(absolutePath)}`, `Failed to load GraphQL schema from ${(0, fs_1.displayPath)(absolutePath)}`, `Warnings while loading GraphQL schema from ${(0, fs_1.displayPath)(absolutePath)}`, async (_spinner) => { const absolutePath = path_1.default.resolve(this.sourceDir, maybeRelativePath); return schema_1.default.load(absolutePath); }); } async generateTypesForSchema(schema) { return await (0, spinner_1.withSpinner)(`Generate types for GraphQL schema`, `Failed to generate types for GraphQL schema`, `Warnings while generating types for GraphQL schema`, async (spinner) => { // Generate TypeScript module from schema const codeGenerator = schema.codeGenerator(); const code = await prettier_1.default.format([ typescript_1.GENERATED_FILE_NOTE, ...codeGenerator.generateModuleImports(), ...codeGenerator.generateTypes(), ...codeGenerator.generateDerivedLoaders(), ].join('\n'), { parser: 'typescript', }); const outputFile = path_1.default.join(this.options.outputDir, 'schema.ts'); (0, spinner_1.step)(spinner, 'Write types to', (0, fs_1.displayPath)(outputFile)); await fs_extra_1.default.mkdirs(path_1.default.dirname(outputFile)); await fs_extra_1.default.writeFile(outputFile, code); }); } async generateTypesForDataSourceTemplates(subgraph) { const moduleImports = []; return await (0, spinner_1.withSpinner)(`Generate types for data source templates`, `Failed to generate types for data source templates`, `Warnings while generating types for data source templates`, async (spinner) => { // Combine the generated code for all templates const codeSegments = subgraph .get('templates', immutable_1.default.List()) .reduce((codeSegments, template) => { (0, spinner_1.step)(spinner, 'Generate types for data source template', String(template.get('name'))); const codeGenerator = new template_1.default(template, this.protocol); // we want to get all the imports from the templates moduleImports.push(...codeGenerator.generateModuleImports()); return codeSegments.concat(codeGenerator.generateTypes()); }, immutable_1.default.List()); // we want to dedupe the imports from the templates const dedupeModulesImports = moduleImports.reduce((acc, curr) => { const found = acc.find(item => item.module === curr.module); if (found) { const foundNames = Array.isArray(found.nameOrNames) ? found.nameOrNames : [found.nameOrNames]; const currNames = Array.isArray(curr.nameOrNames) ? curr.nameOrNames : [curr.nameOrNames]; const names = new Set([...foundNames, ...currNames]); found.nameOrNames = Array.from(names); } else { acc.push(curr); } return acc; }, []); if (!codeSegments.isEmpty()) { const code = await prettier_1.default.format([typescript_1.GENERATED_FILE_NOTE, ...dedupeModulesImports, ...codeSegments].join('\n'), { parser: 'typescript', }); const outputFile = path_1.default.join(this.options.outputDir, 'templates.ts'); (0, spinner_1.step)(spinner, `Write types for templates to`, (0, fs_1.displayPath)(outputFile)); await fs_extra_1.default.mkdirs(path_1.default.dirname(outputFile)); await fs_extra_1.default.writeFile(outputFile, code); } }); } async getFilesToWatch() { try { const files = []; const subgraph = await this.loadSubgraph({ quiet: true }); // Add the subgraph manifest file files.push(this.options.subgraphManifest); // Add the GraphQL schema to the watched files files.push(subgraph.getIn(['schema', 'file'])); // Add all file paths specified in manifest subgraph.get('dataSources').map((dataSource) => { dataSource.getIn(['mapping', 'abis']).map((abi) => { files.push(abi.get('file')); }); }); // Make paths absolute return files.map(file => path_1.default.resolve(file)); } catch (e) { throw Error(`Failed to load subgraph: ${e.message}`); } } async watchAndGenerateTypes() { const generator = this; let spinner; // Create watcher and generate types once and then on every change to a watched file const watcher = new watcher_1.default({ onReady: () => (spinner = toolbox.print.spin('Watching subgraph files')), onTrigger: async (changedFile) => { if (changedFile !== undefined) { spinner.info(`File change detected: ${(0, fs_1.displayPath)(changedFile)}\n`); } await generator.generateTypes(); spinner.start(); }, onCollectFiles: async () => await generator.getFilesToWatch(), onError: error => { spinner.stop(); toolbox.print.error(`${error}\n`); spinner.start(); }, }); // Catch keyboard interrupt: close watcher and exit process process.on('SIGINT', () => { watcher.close(); process.exit(); }); try { await watcher.watch(); } catch (e) { toolbox.print.error(String(e.message)); } } } exports.default = TypeGenerator;