UNPKG

@graphprotocol/graph-cli

Version:

CLI for building for and deploying to The Graph

1,008 lines (997 loc) 41 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 gluegun_1 = require("gluegun"); const toolbox = __importStar(require("gluegun")); const core_1 = require("@oclif/core"); const abi_1 = require("../command-helpers/abi"); const network_1 = require("../command-helpers/network"); const node_1 = require("../command-helpers/node"); const scaffold_1 = require("../command-helpers/scaffold"); 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 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, product, studio, node: nodeFlag, 'allow-simple-name': allowSimpleNameFlag, 'from-contract': fromContract, 'contract-name': contractName, 'from-example': fromExample, 'index-events': indexEvents, 'skip-install': skipInstall, 'skip-git': skipGit, network, abi: abiPath, 'start-block': startBlock, spkg: spkgPath, } = flags; initDebugger('Flags: %O', flags); let { node, allowSimpleName } = (0, node_1.chooseNodeUrl)({ product, // if we are loading example, we want to ensure we are using studio studio: studio || fromExample !== undefined, node: nodeFlag, allowSimpleName: allowSimpleNameFlag, }); 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, allowSimpleName, 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, allowSimpleName, directory, contract: fromContract, indexEvents, network, subgraphName, contractName, node, startBlock, spkgPath, skipInstall, skipGit, }, { commands, addContract: false }); // Exit with success return this.exit(0); } if (fromExample) { const answers = await processFromExampleInitForm.bind(this)({ allowSimpleName, subgraphName, directory, }); if (!answers) { this.exit(1); return; } await initSubgraphFromExample.bind(this)({ allowSimpleName, 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, product, studio, node, abi, abiPath, allowSimpleName, directory, contract: fromContract, indexEvents, fromExample, network, subgraphName, contractName, startBlock, spkgPath, }); if (!answers) { this.exit(1); return; } ({ node, allowSimpleName } = (0, node_1.chooseNodeUrl)({ product: answers.product, studio: answers.studio, node, allowSimpleName, })); await initSubgraphFromContract.bind(this)({ protocolInstance: answers.protocolInstance, allowSimpleName, subgraphName: answers.subgraphName, directory: answers.directory, abi: answers.abi, network: answers.network, contract: answers.contract, indexEvents: answers.indexEvents, contractName: answers.contractName, node, startBlock: answers.startBlock, spkgPath: answers.spkgPath, skipInstall, skipGit, }, { 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, }), product: core_1.Flags.string({ summary: 'Selects the product for which to initialize.', options: ['subgraph-studio', 'hosted-service'], deprecated: { message: 'In next major version, this flag will be removed. By default we will deploy to the Graph Studio. Learn more about Sunrise of Decentralized Data https://thegraph.com/blog/unveiling-updated-sunrise-decentralized-data/', }, }), studio: core_1.Flags.boolean({ summary: 'Shortcut for "--product subgraph-studio".', exclusive: ['product'], deprecated: { message: 'In next major version, this flag will be removed. By default we will deploy to the Graph Studio. Learn more about Sunrise of Decentralized Data https://thegraph.com/blog/unveiling-updated-sunrise-decentralized-data/', }, }), node: core_1.Flags.string({ summary: 'Graph node for which to initialize.', char: 'g', }), 'allow-simple-name': core_1.Flags.boolean({ description: 'Use a subgraph name without a prefix.', default: false, deprecated: { message: 'In next major version, this flag will be removed. By default we will deploy to the Graph Studio. Learn more about Sunrise of Decentralized Data https://thegraph.com/blog/unveiling-updated-sunrise-decentralized-data/', }, }), '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'], }), }; exports.default = InitCommand; async function processFromExampleInitForm({ directory: initDirectory, subgraphName: initSubgraphName, allowSimpleName: initAllowSimpleName, }) { try { const { subgraphName } = await gluegun_1.prompt.ask([ { type: 'input', name: 'subgraphName', // TODO: is defaulting to studio ok? message: () => 'Subgraph slug', initial: initSubgraphName, validate: name => { try { (0, subgraph_1.validateSubgraphName)(name, { allowSimpleName: initAllowSimpleName, }); return true; } catch (e) { return `${e.message} Examples: $ graph init ${os_1.default.userInfo().username}/${name} $ graph init ${name} --allow-simple-name`; } }, }, ]); 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), validate: value => gluegun_1.filesystem.exists(value || initDirectory || (0, subgraph_1.getSubgraphBasename)(subgraphName)) ? 'Directory already exists' : true, }, ]); 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, product: initProduct, studio: initStudio, node: initNode, abi: initAbi, abiPath: initAbiPath, directory: initDirectory, contract: initContract, indexEvents: initIndexEvents, fromExample: initFromExample, network: initNetwork, subgraphName: initSubgraphName, contractName: initContractName, startBlock: initStartBlock, allowSimpleName: initAllowSimpleName, spkgPath: initSpkgPath, }) { let abiFromEtherscan = 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 isSubstreams = protocol === 'substreams'; initDebugger.extend('processInitForm')('isSubstreams: %O', isSubstreams); const { product } = await gluegun_1.prompt.ask([ { type: 'select', name: 'product', message: 'Product for which to initialize', choices: ['subgraph-studio', 'hosted-service'], skip: protocol === 'arweave' || protocol === 'cosmos' || protocol === 'near' || initProduct === 'subgraph-studio' || initProduct === 'hosted-service' || initStudio !== undefined || initNode !== undefined, result: value => { if (initProduct) return initProduct; if (initStudio) return 'subgraph-studio'; // For now we only support NEAR subgraphs in the Hosted Service if (protocol === 'near') { return 'hosted-service'; } if (value == 'subgraph-studio') { initAllowSimpleName = true; } return value; }, }, ]); const { subgraphName } = await gluegun_1.prompt.ask([ { type: 'input', name: 'subgraphName', message: () => (product == 'subgraph-studio' ? 'Subgraph slug' : 'Subgraph name'), initial: initSubgraphName, validate: name => { try { (0, subgraph_1.validateSubgraphName)(name, { allowSimpleName: initAllowSimpleName, }); return true; } catch (e) { return `${e.message} Examples: $ graph init ${os_1.default.userInfo().username}/${name} $ graph init ${name} --allow-simple-name`; } }, }, ]); 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), validate: value => gluegun_1.filesystem.exists(value || initDirectory || (0, subgraph_1.getSubgraphBasename)(subgraphName)) ? 'Directory already exists' : true, }, ]); const choices = (await AVAILABLE_NETWORKS())?.[product === 'subgraph-studio' ? 'studio' : 'hostedService']; 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 }); } 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 { contract } = await gluegun_1.prompt.ask([ // TODO: // protocols that don't support contract // - arweave // - cosmos { type: 'input', name: 'contract', message: `Contract ${protocolInstance.getContract()?.identifierName()}`, skip: () => initFromExample !== undefined || !protocolInstance.hasContract() || isSubstreams, initial: initContract, validate: async (value) => { 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) { 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) { initStartBlock = Number(startBlock).toString(); } } return value; }, }, ]); 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: initAbi, skip: () => !protocolInstance.hasABIs() || initFromExample !== undefined || abiFromEtherscan !== undefined || isSubstreams || !!initAbiPath, validate: async (value) => { if (initFromExample || abiFromEtherscan || !protocolInstance.hasABIs()) { return true; } const ABI = protocolInstance.getABI(); if (initAbiPath) { try { loadAbiFromFile(ABI, initAbiPath); return true; } catch (e) { this.error(e.message); } } try { loadAbiFromFile(ABI, value); return true; } catch (e) { this.error(e.message); } }, result: async (value) => { if (initFromExample || abiFromEtherscan || !protocolInstance.hasABIs()) { return null; } const ABI = protocolInstance.getABI(); if (initAbiPath) { try { return loadAbiFromFile(ABI, initAbiPath); } catch (e) { return e.message; } } 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 || '0', skip: () => initFromExample !== undefined || isSubstreams, validate: value => parseInt(value) >= 0, result(value) { if (initStartBlock) return initStartBlock; return value; }, }, ]); const { contractName } = await gluegun_1.prompt.ask([ { type: 'input', name: 'contractName', message: 'Contract Name', initial: initContractName || '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, }, ]); return { abi: abiFromEtherscan || abiFromFile, protocolInstance, subgraphName, directory, studio: product === 'subgraph-studio', startBlock, fromExample: !!initFromExample, product, network, contractName, contract, indexEvents, spkgPath: spkg, }; } 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); } }; function revalidateSubgraphName(subgraphName, { allowSimpleName }) { // Fail if the subgraph name is invalid try { (0, subgraph_1.validateSubgraphName)(subgraphName, { allowSimpleName }); return true; } catch (e) { this.error(`${e.message} Examples: $ graph init ${os_1.default.userInfo().username}/${subgraphName} $ graph init ${subgraphName} --allow-simple-name`); } } // 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, allowSimpleName, subgraphName, directory, skipInstall, skipGit, }, { commands, }) { // Fail if the subgraph name is invalid if (!revalidateSubgraphName.bind(this)(subgraphName, { allowSimpleName })) { process.exitCode = 1; return; } // Fail if the output directory already exists if (gluegun_1.filesystem.exists(directory)) { this.error(`Directory or file "${directory}" already exists`, { exit: 1 }); } // 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); 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, allowSimpleName, subgraphName, directory, abi, network, contract, indexEvents, contractName, node, startBlock, spkgPath, skipInstall, skipGit, }, { commands, addContract, }) { const isSubstreams = protocolInstance.name === 'substreams'; // Fail if the subgraph name is invalid if (!revalidateSubgraphName.bind(this)(subgraphName, { allowSimpleName })) { this.exit(1); return; } // Fail if the output directory already exists if (gluegun_1.filesystem.exists(directory)) { this.error(`Directory or file "${directory}" already exists`, { exit: 1 }); } if (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, contract, indexEvents, contractName, startBlock, node, spkgPath, }, 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}`); } const contractName = await core_1.ux.prompt('\nContract Name', { required: true, default: 'Contract', }); // 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, '--contract-name', contractName]; await add_1.default.run(commandLine); } catch (e) { this.error(e); } finally { // TODO: safer way of doing this? process.chdir(cwd); } } return addContractConfirmation; }