UNPKG

@graphprotocol/graph-cli

Version:

CLI for building for and deploying to The Graph

925 lines (920 loc) 37.4 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 fs_1 = __importDefault(require("fs")); const os_1 = __importDefault(require("os")); const path_1 = __importDefault(require("path")); const toolbox = __importStar(require("gluegun")); const gluegun_1 = require("gluegun"); const ipfs_http_client_1 = require("ipfs-http-client"); const core_1 = require("@oclif/core"); const abi_1 = require("../command-helpers/abi"); const compiler_1 = require("../command-helpers/compiler"); const ipfs_1 = require("../command-helpers/ipfs"); const network_1 = require("../command-helpers/network"); const node_1 = require("../command-helpers/node"); const scaffold_1 = require("../command-helpers/scaffold"); const sort_1 = require("../command-helpers/sort"); const spinner_1 = require("../command-helpers/spinner"); const subgraph_1 = require("../command-helpers/subgraph"); const constants_1 = require("../constants"); const debug_1 = __importDefault(require("../debug")); const protocols_1 = __importDefault(require("../protocols")); const schema_1 = require("../scaffold/schema"); const schema_2 = __importDefault(require("../schema")); const utils_1 = __importDefault(require("../utils")); const validation_1 = require("../validation"); const add_1 = __importDefault(require("./add")); const protocolChoices = Array.from(protocols_1.default.availableProtocols().keys()); const initDebugger = (0, debug_1.default)('graph-cli:commands:init'); /** * a dynamic list of available networks supported by the studio */ const AVAILABLE_NETWORKS = async () => { const logger = initDebugger.extend('AVAILABLE_NETWORKS'); try { logger('fetching chain_list from studio'); const res = await fetch(node_1.SUBGRAPH_STUDIO_URL, { method: 'POST', headers: { ...constants_1.GRAPH_CLI_SHARED_HEADERS, 'content-type': 'application/json', }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'chain_list', params: [], }), }); if (!res.ok) { logger("Something went wrong while fetching 'chain_list' from studio HTTP code: %o", res.status); return null; } const result = await res.json(); if (result?.result) { logger('chain_list result: %o', result.result); return result.result; } logger("Unable to get result for 'chain_list' from studio: %O", result); return null; } catch (e) { logger('error: %O', e); return null; } }; const DEFAULT_EXAMPLE_SUBGRAPH = 'ethereum-gravatar'; class InitCommand extends core_1.Command { async run() { const { args: { subgraphName, directory }, flags, } = await this.parse(InitCommand); const { protocol, node: nodeFlag, 'from-contract': fromContract, 'contract-name': contractName, 'from-example': fromExample, 'index-events': indexEvents, 'skip-install': skipInstall, 'skip-git': skipGit, ipfs, network, abi: abiPath, 'start-block': startBlock, spkg: spkgPath, } = flags; initDebugger('Flags: %O', flags); let { node } = (0, node_1.chooseNodeUrl)({ node: nodeFlag, }); if (fromContract && fromExample) { this.error('Only one of "--from-example" and "--from-contract" can be used at a time.', { exit: 1, }); } // Detect git const git = gluegun_1.system.which('git'); if (!git) { this.error('Git was not found on your system. Please install "git" so it is in $PATH.', { exit: 1, }); } // Detect Yarn and/or NPM const yarn = gluegun_1.system.which('yarn'); const npm = gluegun_1.system.which('npm'); if (!yarn && !npm) { this.error(`Neither Yarn nor NPM were found on your system. Please install one of them.`, { exit: 1, }); } const commands = { link: yarn ? 'yarn link @graphprotocol/graph-cli' : 'npm link @graphprotocol/graph-cli', install: yarn ? 'yarn' : 'npm install', codegen: yarn ? 'yarn codegen' : 'npm run codegen', deploy: yarn ? 'yarn deploy' : 'npm run deploy', }; // If all parameters are provided from the command-line, // go straight to creating the subgraph from the example if (fromExample && subgraphName && directory) { await initSubgraphFromExample.bind(this)({ fromExample, directory, subgraphName, skipInstall, skipGit, }, { commands }); // Exit with success return this.exit(0); } // Will be assigned below if ethereum let abi; // If all parameters are provided from the command-line, // go straight to creating the subgraph from an existing contract if (fromContract && protocol && subgraphName && directory && network && node) { if (!protocolChoices.includes(protocol)) { this.error(`Protocol '${protocol}' is not supported, choose from these options: ${protocolChoices.join(', ')}`, { exit: 1 }); } const protocolInstance = new protocols_1.default(protocol); if (protocolInstance.hasABIs()) { const ABI = protocolInstance.getABI(); if (abiPath) { try { abi = loadAbiFromFile(ABI, abiPath); } catch (e) { this.error(`Failed to load ABI: ${e.message}`, { exit: 1 }); } } else { try { if (network === 'poa-core') { abi = await (0, abi_1.loadAbiFromBlockScout)(ABI, network, fromContract); } else { abi = await (0, abi_1.loadAbiFromEtherscan)(ABI, network, fromContract); } } catch (e) { process.exitCode = 1; return; } } } await initSubgraphFromContract.bind(this)({ protocolInstance, abi, directory, source: fromContract, indexEvents, network, subgraphName, contractName, node, startBlock, spkgPath, skipInstall, skipGit, ipfsUrl: ipfs, }, { commands, addContract: false }); // Exit with success return this.exit(0); } if (fromExample) { const answers = await processFromExampleInitForm.bind(this)({ subgraphName, directory, }); if (!answers) { this.exit(1); return; } await initSubgraphFromExample.bind(this)({ fromExample, subgraphName: answers.subgraphName, directory: answers.directory, skipInstall, skipGit, }, { commands }); } else { // Otherwise, take the user through the interactive form const answers = await processInitForm.bind(this)({ protocol: protocol, abi, abiPath, directory, source: fromContract, indexEvents, fromExample, network, subgraphName, contractName, startBlock, spkgPath, ipfsUrl: ipfs, }); if (!answers) { this.exit(1); return; } ({ node } = (0, node_1.chooseNodeUrl)({ node, })); await initSubgraphFromContract.bind(this)({ protocolInstance: answers.protocolInstance, subgraphName: answers.subgraphName, directory: answers.directory, abi: answers.abi, network: answers.network, source: answers.source, indexEvents: answers.indexEvents, contractName: answers.contractName, node, startBlock: answers.startBlock, spkgPath: answers.spkgPath, skipInstall, skipGit, ipfsUrl: answers.ipfs, }, { commands, addContract: true }); } // Exit with success this.exit(0); } } InitCommand.description = 'Creates a new subgraph with basic scaffolding.'; InitCommand.args = { subgraphName: core_1.Args.string(), directory: core_1.Args.string(), }; InitCommand.flags = { help: core_1.Flags.help({ char: 'h', }), protocol: core_1.Flags.string({ options: protocolChoices, }), node: core_1.Flags.string({ summary: 'Graph node for which to initialize.', char: 'g', }), 'from-contract': core_1.Flags.string({ description: 'Creates a scaffold based on an existing contract.', exclusive: ['from-example'], }), 'from-example': core_1.Flags.string({ description: 'Creates a scaffold based on an example subgraph.', // TODO: using a default sets the value and therefore requires not to have --from-contract // default: 'Contract', exclusive: ['from-contract'], }), 'contract-name': core_1.Flags.string({ helpGroup: 'Scaffold from contract', description: 'Name of the contract.', dependsOn: ['from-contract'], }), 'index-events': core_1.Flags.boolean({ helpGroup: 'Scaffold from contract', description: 'Index contract events as entities.', dependsOn: ['from-contract'], }), 'skip-install': core_1.Flags.boolean({ summary: 'Skip installing dependencies.', default: false, }), 'skip-git': core_1.Flags.boolean({ summary: 'Skip initializing a Git repository.', default: false, deprecated: { message: 'In next major version, this flag will be removed. By default we will stop initializing a Git repository.', }, }), 'start-block': core_1.Flags.string({ helpGroup: 'Scaffold from contract', description: 'Block number to start indexing from.', // TODO: using a default sets the value and therefore requires --from-contract // default: '0', dependsOn: ['from-contract'], }), abi: core_1.Flags.string({ summary: 'Path to the contract ABI', // TODO: using a default sets the value and therefore requires --from-contract // default: '*Download from Etherscan*', dependsOn: ['from-contract'], }), spkg: core_1.Flags.string({ summary: 'Path to the SPKG file', }), network: core_1.Flags.string({ summary: 'Network the contract is deployed to.', description: 'Check https://thegraph.com/docs/en/developing/supported-networks/ for supported networks', dependsOn: ['from-contract'], }), ipfs: core_1.Flags.string({ summary: 'IPFS node to use for fetching subgraph data.', char: 'i', default: ipfs_1.DEFAULT_IPFS_URL, }), }; exports.default = InitCommand; async function processFromExampleInitForm({ directory: initDirectory, subgraphName: initSubgraphName, }) { try { const { subgraphName } = await gluegun_1.prompt.ask([ { type: 'input', name: 'subgraphName', message: () => 'Subgraph slug', initial: initSubgraphName, }, ]); const { directory } = await gluegun_1.prompt.ask([ { type: 'input', name: 'directory', message: 'Directory to create the subgraph in', initial: () => initDirectory || (0, subgraph_1.getSubgraphBasename)(subgraphName), }, ]); return { subgraphName, directory, }; } catch (e) { this.error(e, { exit: 1 }); } } async function retryWithPrompt(func) { for (;;) { try { return await func(); } catch (_) { const { retry } = await toolbox.prompt.ask({ type: 'confirm', name: 'retry', message: 'Do you want to retry?', initial: true, }); if (!retry) { break; } } } return undefined; } async function processInitForm({ protocol: initProtocol, abi: initAbi, abiPath: initAbiPath, directory: initDirectory, source: initContract, indexEvents: initIndexEvents, fromExample: initFromExample, network: initNetwork, subgraphName: initSubgraphName, contractName: initContractName, startBlock: initStartBlock, spkgPath: initSpkgPath, ipfsUrl, }) { let abiFromEtherscan = undefined; let startBlockFromEtherscan = undefined; let contractNameFromEtherscan = undefined; try { const { protocol } = await gluegun_1.prompt.ask({ type: 'select', name: 'protocol', message: 'Protocol', choices: protocolChoices, skip: protocolChoices.includes(String(initProtocol)), result: value => { if (initProtocol) { initDebugger.extend('processInitForm')('initProtocol: %O', initProtocol); return initProtocol; } initDebugger.extend('processInitForm')('protocol: %O', value); return value; }, }); const protocolInstance = new protocols_1.default(protocol); const isComposedSubgraph = protocolInstance.isComposedSubgraph(); const isSubstreams = protocol === 'substreams'; initDebugger.extend('processInitForm')('isSubstreams: %O', isSubstreams); const { subgraphName } = await gluegun_1.prompt.ask([ { type: 'input', name: 'subgraphName', message: 'Subgraph slug', initial: initSubgraphName, }, ]); const { directory } = await gluegun_1.prompt.ask([ { type: 'input', name: 'directory', message: 'Directory to create the subgraph in', initial: () => initDirectory || (0, subgraph_1.getSubgraphBasename)(subgraphName), }, ]); let choices = (await AVAILABLE_NETWORKS())?.['studio']; if (!choices) { this.error('Unable to fetch available networks from API. Please report this issue. As a workaround you can pass `--network` flag from the available networks: https://thegraph.com/docs/en/developing/supported-networks', { exit: 1 }); } choices = (0, sort_1.sortWithPriority)(choices, ['mainnet']); const { network } = await gluegun_1.prompt.ask([ { type: 'select', name: 'network', message: () => `${protocolInstance.displayName()} network`, choices, skip: initNetwork !== undefined, result: value => { if (initNetwork) { initDebugger.extend('processInitForm')('initNetwork: %O', initNetwork); return initNetwork; } initDebugger.extend('processInitForm')('network: %O', value); return value; }, }, ]); const sourceMessage = isComposedSubgraph ? 'Source subgraph identifier' : `Contract ${protocolInstance.getContract()?.identifierName()}`; const { source } = await gluegun_1.prompt.ask([ { type: 'input', name: 'source', message: sourceMessage, skip: () => !isComposedSubgraph, initial: initContract, validate: async (value) => { if (isComposedSubgraph) { return true; } if (initFromExample !== undefined || !protocolInstance.hasContract()) { return true; } const protocolContract = protocolInstance.getContract(); if (!protocolContract) { return 'Contract not found.'; } // Validate whether the contract is valid const { valid, error } = (0, validation_1.validateContract)(value, protocolContract); return valid ? true : error; }, result: async (value) => { if (initFromExample !== undefined || isSubstreams || initAbiPath || isComposedSubgraph) { initDebugger("value: '%s'", value); return value; } const ABI = protocolInstance.getABI(); // Try loading the ABI from Etherscan, if none was provided if (protocolInstance.hasABIs() && !initAbi) { if (network === 'poa-core') { abiFromEtherscan = await retryWithPrompt(() => (0, abi_1.loadAbiFromBlockScout)(ABI, network, value)); } else { abiFromEtherscan = await retryWithPrompt(() => (0, abi_1.loadAbiFromEtherscan)(ABI, network, value)); } } // If startBlock is not set, try to load it. if (!initStartBlock) { // Load startBlock for this contract const startBlock = await retryWithPrompt(() => (0, abi_1.loadStartBlockForContract)(network, value)); if (startBlock) { startBlockFromEtherscan = Number(startBlock).toString(); } } // If contract name is not set, try to load it. if (!initContractName) { // Load contract name for this contract const contractName = await retryWithPrompt(() => (0, abi_1.loadContractNameForAddress)(network, value)); if (contractName) { contractNameFromEtherscan = contractName; } } return value; }, }, ]); const { ipfs } = await gluegun_1.prompt.ask([ { type: 'input', name: 'ipfs', message: `IPFS node to use for fetching subgraph manifest`, initial: ipfsUrl, skip: () => !isComposedSubgraph, }, ]); const { spkg } = await gluegun_1.prompt.ask([ { type: 'input', name: 'spkg', message: 'SPKG file (path)', initial: () => initSpkgPath, skip: () => !isSubstreams || !!initSpkgPath, validate: value => gluegun_1.filesystem.exists(initSpkgPath || value) ? true : 'SPKG file does not exist', }, ]); const { abi: abiFromFile } = await gluegun_1.prompt.ask([ { type: 'input', name: 'abi', message: 'ABI file (path)', initial: initAbiPath, skip: () => !protocolInstance.hasABIs() || initFromExample !== undefined || abiFromEtherscan !== undefined || isSubstreams || !!initAbiPath || isComposedSubgraph, validate: async (value) => { if (initFromExample || abiFromEtherscan || !protocolInstance.hasABIs() || isComposedSubgraph) { return true; } const ABI = protocolInstance.getABI(); if (initAbiPath) value = initAbiPath; try { loadAbiFromFile(ABI, value); return true; } catch (e) { return e.message; } }, result: async (value) => { if (initFromExample || abiFromEtherscan || !protocolInstance.hasABIs() || isComposedSubgraph) { return null; } const ABI = protocolInstance.getABI(); if (initAbiPath) value = initAbiPath; try { return loadAbiFromFile(ABI, value); } catch (e) { return e.message; } }, }, ]); const { startBlock } = await gluegun_1.prompt.ask([ { type: 'input', name: 'startBlock', message: 'Start Block', initial: initStartBlock || startBlockFromEtherscan || '0', skip: () => initFromExample !== undefined || isSubstreams, validate: value => parseInt(value) >= 0, }, ]); const { contractName } = await gluegun_1.prompt.ask([ { type: 'input', name: 'contractName', message: 'Contract Name', initial: initContractName || contractNameFromEtherscan || 'Contract' || isSubstreams, skip: () => initFromExample !== undefined || !protocolInstance.hasContract(), validate: value => value && value.length > 0, }, ]); const { indexEvents } = await gluegun_1.prompt.ask([ { type: 'confirm', name: 'indexEvents', message: 'Index contract events as entities', initial: true, skip: () => !!initIndexEvents || isSubstreams || isComposedSubgraph, }, ]); return { abi: abiFromEtherscan || abiFromFile, protocolInstance, subgraphName, directory, startBlock, fromExample: !!initFromExample, network, contractName, source, indexEvents, spkgPath: spkg, ipfs, }; } catch (e) { this.error(e, { exit: 1 }); } } const loadAbiFromFile = (ABI, filename) => { const exists = gluegun_1.filesystem.exists(filename); if (!exists) { throw Error('File does not exist.'); } else if (exists === 'dir') { throw Error('Path points to a directory, not a file.'); } else if (exists === 'other') { throw Error('Not sure what this path points to.'); } else { return ABI.load('Contract', filename); } }; // Inspired from: https://github.com/graphprotocol/graph-tooling/issues/1450#issuecomment-1713992618 async function isInRepo() { try { const result = await gluegun_1.system.run('git rev-parse --is-inside-work-tree'); // It seems like we are returning "true\n" instead of "true". // Don't think it is great idea to check for new line character here. // So best to just check if the result includes "true". return result.includes('true'); } catch (err) { if (err.stderr.includes('not a git repository')) { return false; } throw Error(err.stderr); } } const initRepository = async (directory) => await (0, spinner_1.withSpinner)(`Initialize subgraph repository`, `Failed to initialize subgraph repository`, `Warnings while initializing subgraph repository`, async () => { // Remove .git dir in --from-example mode; in --from-contract, we're // starting from an empty directory const gitDir = path_1.default.join(directory, '.git'); if (gluegun_1.filesystem.exists(gitDir)) { gluegun_1.filesystem.remove(gitDir); } if (await isInRepo()) { await gluegun_1.system.run('git add --all', { cwd: directory }); await gluegun_1.system.run('git commit -m "Initialize subgraph"', { cwd: directory, }); } else { await gluegun_1.system.run('git init', { cwd: directory }); await gluegun_1.system.run('git add --all', { cwd: directory }); await gluegun_1.system.run('git commit -m "Initial commit"', { cwd: directory, }); } return true; }); const installDependencies = async (directory, commands) => await (0, spinner_1.withSpinner)(`Install dependencies with ${commands.install}`, `Failed to install dependencies`, `Warnings while installing dependencies`, async () => { if (process.env.GRAPH_CLI_TESTS) { await gluegun_1.system.run(commands.link, { cwd: directory }); } await gluegun_1.system.run(commands.install, { cwd: directory }); return true; }); const runCodegen = async (directory, codegenCommand) => await (0, spinner_1.withSpinner)(`Generate ABI and schema types with ${codegenCommand}`, `Failed to generate code from ABI and GraphQL schema`, `Warnings while generating code from ABI and GraphQL schema`, async () => { await gluegun_1.system.run(codegenCommand, { cwd: directory }); return true; }); function printNextSteps({ subgraphName, directory }, { commands, }) { const relativeDir = path_1.default.relative(process.cwd(), directory); // Print instructions this.log(` Subgraph ${subgraphName} created in ${relativeDir} `); this.log(`Next steps: 1. Run \`graph auth\` to authenticate with your deploy key. 2. Type \`cd ${relativeDir}\` to enter the subgraph. 3. Run \`${commands.deploy}\` to deploy the subgraph. Make sure to visit the documentation on https://thegraph.com/docs/ for further information.`); } async function initSubgraphFromExample({ fromExample, subgraphName, directory, skipInstall, skipGit, }, { commands, }) { let overwrite = false; if (gluegun_1.filesystem.exists(directory)) { overwrite = await gluegun_1.prompt.confirm('Directory already exists, do you want to initialize the subgraph here (files will be overwritten) ?', false); if (!overwrite) { this.exit(1); return; } } // Clone the example subgraph repository const cloned = await (0, spinner_1.withSpinner)(`Cloning example subgraph`, `Failed to clone example subgraph`, `Warnings while cloning example subgraph`, async () => { // Create a temporary directory const prefix = path_1.default.join(os_1.default.tmpdir(), 'example-subgraph-'); const tmpDir = fs_1.default.mkdtempSync(prefix); try { await gluegun_1.system.run(`git clone https://github.com/graphprotocol/graph-tooling ${tmpDir}`); // If an example is not specified, use the default one if (fromExample === undefined || fromExample === true) { fromExample = DEFAULT_EXAMPLE_SUBGRAPH; } // Legacy purposes when everything existed in examples repo if (fromExample === 'ethereum/gravatar') { fromExample = DEFAULT_EXAMPLE_SUBGRAPH; } const exampleSubgraphPath = path_1.default.join(tmpDir, 'examples', String(fromExample)); if (!gluegun_1.filesystem.exists(exampleSubgraphPath)) { return { result: false, error: `Example not found: ${fromExample}` }; } gluegun_1.filesystem.copy(exampleSubgraphPath, directory, { overwrite }); return true; } finally { gluegun_1.filesystem.remove(tmpDir); } }); if (!cloned) { this.exit(1); return; } const networkConf = await (0, network_1.initNetworksConfig)(directory, 'address'); if (networkConf !== true) { this.exit(1); return; } // Update package.json to match the subgraph name const prepared = await (0, spinner_1.withSpinner)(`Update subgraph name and commands in package.json`, `Failed to update subgraph name and commands in package.json`, `Warnings while updating subgraph name and commands in package.json`, async () => { try { // Load package.json const pkgJsonFilename = gluegun_1.filesystem.path(directory, 'package.json'); const pkgJson = await gluegun_1.filesystem.read(pkgJsonFilename, 'json'); pkgJson.name = (0, subgraph_1.getSubgraphBasename)(subgraphName); for (const name of Object.keys(pkgJson.scripts)) { pkgJson.scripts[name] = pkgJson.scripts[name].replace('example', subgraphName); } delete pkgJson['license']; delete pkgJson['repository']; // Remove example's cli in favor of the local one (added via `npm link`) if (process.env.GRAPH_CLI_TESTS) { delete pkgJson['devDependencies']['@graphprotocol/graph-cli']; } // Write package.json gluegun_1.filesystem.write(pkgJsonFilename, pkgJson, { jsonIndent: 2 }); return true; } catch (e) { gluegun_1.filesystem.remove(directory); this.error(`Failed to preconfigure the subgraph: ${e}`); } }); if (!prepared) { this.exit(1); return; } // Initialize a fresh Git repository if (!skipGit) { const repo = await initRepository(directory); if (repo !== true) { this.exit(1); return; } } // Install dependencies if (!skipInstall) { const installed = await installDependencies(directory, commands); if (installed !== true) { this.exit(1); return; } } // Run code-generation const codegen = await runCodegen(directory, commands.codegen); if (codegen !== true) { this.exit(1); return; } printNextSteps.bind(this)({ subgraphName, directory }, { commands }); } async function initSubgraphFromContract({ protocolInstance, subgraphName, directory, abi, network, source, indexEvents, contractName, node, startBlock, spkgPath, skipInstall, skipGit, ipfsUrl, }, { commands, addContract, }) { const isSubstreams = protocolInstance.name === 'substreams'; const isComposedSubgraph = protocolInstance.isComposedSubgraph(); if (gluegun_1.filesystem.exists(directory) && !(await gluegun_1.prompt.confirm('Directory already exists, do you want to initialize the subgraph here (files will be overwritten) ?', false))) { this.exit(1); return; } let entities; if (isComposedSubgraph) { try { const ipfsClient = (0, ipfs_http_client_1.create)({ url: (0, compiler_1.appendApiVersionForGraph)(ipfsUrl), headers: { ...constants_1.GRAPH_CLI_SHARED_HEADERS, }, }); const schemaString = await (0, utils_1.default)(ipfsClient, source); const schema = await schema_2.default.loadFromString(schemaString); entities = schema.getEntityNames(); } catch (e) { this.error(`Failed to load and parse subgraph schema: ${e.message}`, { exit: 1 }); } } if (!protocolInstance.isComposedSubgraph() && protocolInstance.hasABIs() && ((0, schema_1.abiEvents)(abi).size === 0 || // @ts-expect-error TODO: the abiEvents result is expected to be a List, how's it an array? (0, schema_1.abiEvents)(abi).length === 0)) { // Fail if the ABI does not contain any events this.error(`ABI does not contain any events`, { exit: 1 }); } // Scaffold subgraph const scaffold = await (0, spinner_1.withSpinner)(`Create subgraph scaffold`, `Failed to create subgraph scaffold`, `Warnings while creating subgraph scaffold`, async (spinner) => { const scaffold = await (0, scaffold_1.generateScaffold)({ protocolInstance, subgraphName, abi, network, source, indexEvents, contractName, startBlock, node, spkgPath, entities, }, spinner); await (0, scaffold_1.writeScaffold)(scaffold, directory, spinner); return true; }); if (scaffold !== true) { process.exitCode = 1; return; } if (protocolInstance.hasContract()) { const identifierName = protocolInstance.getContract().identifierName(); const networkConf = await (0, network_1.initNetworksConfig)(directory, identifierName); if (networkConf !== true) { process.exitCode = 1; return; } } // Initialize a fresh Git repository if (!skipGit) { const repo = await initRepository(directory); if (repo !== true) { this.exit(1); return; } } if (!skipInstall) { // Install dependencies const installed = await installDependencies(directory, commands); if (installed !== true) { this.exit(1); return; } } // Substreams we have nothing to install or generate if (!isSubstreams) { // Run code-generation const codegen = await runCodegen(directory, commands.codegen); if (codegen !== true) { this.exit(1); return; } while (addContract) { addContract = await addAnotherContract.bind(this)({ protocolInstance, directory, }); } } printNextSteps.bind(this)({ subgraphName, directory }, { commands }); } async function addAnotherContract({ protocolInstance, directory, }) { const addContractAnswer = await core_1.ux.prompt('Add another contract? (y/n)', { required: true, type: 'single', }); const addContractConfirmation = addContractAnswer.toLowerCase() === 'y'; if (addContractConfirmation) { const ProtocolContract = protocolInstance.getContract(); let contract = ''; for (;;) { contract = await core_1.ux.prompt(`\nContract ${ProtocolContract.identifierName()}`, { required: true, }); const { valid, error } = (0, validation_1.validateContract)(contract, ProtocolContract); if (valid) { break; } this.log(`✖ ${error}`); } // Get the cwd before process.chdir in order to switch back in the end of command execution const cwd = process.cwd(); try { if (fs_1.default.existsSync(directory)) { process.chdir(directory); } const commandLine = [contract]; await add_1.default.run(commandLine); } catch (e) { this.error(e); } finally { // TODO: safer way of doing this? process.chdir(cwd); } } return addContractConfirmation; }