UNPKG

@graphprotocol/graph-cli

Version:

CLI for building for and deploying to The Graph

367 lines (330 loc) • 11 kB
const URL = require('url').URL const chalk = require('chalk') const path = require('path') const { identifyDeployKey } = require('../command-helpers/auth') const { createCompiler } = require('../command-helpers/compiler') const { fixParameters } = require('../command-helpers/gluegun') const { createJsonRpcClient } = require('../command-helpers/jsonrpc') const { chooseNodeUrl } = require('../command-helpers/node') const { withSpinner } = require('../command-helpers/spinner') const { validateSubgraphName } = require('../command-helpers/subgraph') const { DEFAULT_IPFS_URL } = require('../command-helpers/ipfs') const { assertManifestApiVersion, assertGraphTsVersion } = require('../command-helpers/version') const DataSourcesExtractor = require('../command-helpers/data-sources') const { validateStudioNetwork } = require('../command-helpers/studio') const Protocol = require('../protocols') const { updateSubgraphNetwork } = require('../command-helpers/network') const HELP = ` ${chalk.bold('graph deploy')} [options] ${chalk.bold('<subgraph-name>')} ${chalk.bold( '[<subgraph-manifest>]', )} Options: --product <subgraph-studio|hosted-service> Selects the product to which to deploy --studio Shortcut for --product subgraph-studio -g, --node <node> Graph node to which to deploy --deploy-key <key> User deploy key -l --version-label <label> Version label used for the deployment -h, --help Show usage information -i, --ipfs <node> Upload build results to an IPFS node (default: ${DEFAULT_IPFS_URL}) -hdr, --headers <map> Add custom headers that will be used by the IPFS HTTP client (default: {}) --debug-fork ID of a remote subgraph whose store will be GraphQL queried -o, --output-dir <path> Output directory for build results (default: build/) --skip-migrations Skip subgraph migrations (default: false) -w, --watch Regenerate types when subgraph files change (default: false) --network <name> Network configuration to use from the networks config file --network-file <path> Networks config file path (default: "./networks.json") ` const processForm = async ( toolbox, { product, studio, node, versionLabel, }, ) => { const questions = [ { type: 'select', name: 'product', message: 'Product for which to deploy', choices: ['subgraph-studio', 'hosted-service'], skip: product === 'subgraph-studio' || product === 'hosted-service' || studio !== undefined || node !== undefined, }, { type: 'input', name: 'versionLabel', message: 'Version Label (e.g. v0.0.1)', skip: versionLabel !== undefined, }, ] try { const answers = await toolbox.prompt.ask(questions) return answers } catch (e) { return undefined } } module.exports = { description: 'Deploys the subgraph to a Graph node', run: async toolbox => { // Obtain tools let { filesystem, print, system } = toolbox // Parse CLI parameters let { product, studio, deployKey, accessToken, versionLabel, l, g, h, i, help, ipfs, headers, hdr, node, o, outputDir, skipMigrations, w, watch, debugFork, network, networkFile, } = toolbox.parameters.options // Support both long and short option variants help = help || h ipfs = ipfs || i || DEFAULT_IPFS_URL headers = headers || hdr || "{}" node = node || g outputDir = outputDir || o watch = watch || w versionLabel = versionLabel || l try { headers = JSON.parse(headers) } catch (e) { print.error("Please make sure headers is a valid JSON value") process.exitCode = 1 return } let subgraphName, manifest try { ;[subgraphName, manifest] = fixParameters(toolbox.parameters, { h, help, w, watch, studio }) } catch (e) { print.error(e.message) process.exitCode = 1 return } // Fall back to default values for options / parameters outputDir = outputDir && outputDir !== '' ? outputDir : filesystem.path('build') manifest = manifest !== undefined && manifest !== '' ? manifest : filesystem.resolve('subgraph.yaml') networkFile = networkFile !== undefined && networkFile !== '' ? networkFile : filesystem.resolve("networks.json") try { const dataSourcesAndTemplates = await DataSourcesExtractor.fromFilePath(manifest) for (const { network } of dataSourcesAndTemplates) { validateStudioNetwork({ studio, product, network }) } } catch (e) { print.error(e.message) process.exitCode = 1 return } // Show help text if requested if (help) { print.info(HELP) return } ;({ node } = chooseNodeUrl({ product, studio, node })) if (!node) { const inputs = await processForm(toolbox, { product, studio, node, versionLabel: 'skip', // determine label requirement later }) if (inputs === undefined) { process.exit(1) } product = inputs.product ;({ node } = chooseNodeUrl({ product, studio, node, })) } // Validate the subgraph name if (!subgraphName) { print.error(`No subgraph ${product == 'subgraph-studio' || studio ? 'slug' : 'name'} provided`) print.info(HELP) process.exitCode = 1 return } // Validate node if (!node) { print.error(`No Graph node provided`) print.info(HELP) process.exitCode = 1 return } // Validate IPFS if (!ipfs) { print.error(`No IPFS node provided`) print.info(HELP) process.exitCode = 1 return } let protocol try { // Checks to make sure deploy doesn't run against // older subgraphs (both apiVersion and graph-ts version). // // We don't want the deploy to run without these conditions // because that would mean the CLI would try to compile code // using the wrong AssemblyScript compiler. await assertManifestApiVersion(manifest, '0.0.5') await assertGraphTsVersion(path.dirname(manifest), '0.25.0') const dataSourcesAndTemplates = await DataSourcesExtractor.fromFilePath(manifest) protocol = Protocol.fromDataSources(dataSourcesAndTemplates) } catch (e) { print.error(e.message) process.exitCode = 1 return } if (network) { let identifierName = protocol.getContract().identifierName() await updateSubgraphNetwork(toolbox, manifest, network, networkFile, identifierName) } const isStudio = node.match(/studio/) const isHostedService = node.match(/thegraph.com/) && !isStudio let compiler = createCompiler(manifest, { ipfs, headers, outputDir, outputFormat: 'wasm', skipMigrations, blockIpfsMethods: isStudio, // Network does not support publishing subgraphs with IPFS methods protocol, }) // Exit with an error code if the compiler couldn't be created if (!compiler) { process.exitCode = 1 return } // Ask for label if not on hosted service if (!versionLabel && !isHostedService) { const inputs = await processForm(toolbox, { product, studio, node, versionLabel, }) if (inputs === undefined) { process.exit(1) } versionLabel = inputs.versionLabel } let requestUrl = new URL(node) let client = createJsonRpcClient(requestUrl) // Exit with an error code if the client couldn't be created if (!client) { process.exitCode = 1 return } // Use the deploy key, if one is set if (!deployKey && accessToken) { deployKey = accessToken // backwards compatibility } deployKey = await identifyDeployKey(node, deployKey) if (deployKey !== undefined && deployKey !== null) { client.options.headers = { Authorization: 'Bearer ' + deployKey } } let deploySubgraph = async ipfsHash => { let spinner = print.spin(`Deploying to Graph node ${requestUrl}`) // `Failed to deploy to Graph node ${requestUrl}`, client.request( 'subgraph_deploy', { name: subgraphName, ipfs_hash: ipfsHash, version_label: versionLabel, debug_fork: debugFork }, async (requestError, jsonRpcError, res) => { if (jsonRpcError) { spinner.fail( `Failed to deploy to Graph node ${requestUrl}: ${jsonRpcError.message}`, ) // Provide helpful advice when the subgraph has not been created yet if (jsonRpcError.message.match(/subgraph name not found/)) { if (isHostedService) { print.info(` You may need to create it at https://thegraph.com/explorer/dashboard.`) } else { print.info(` Make sure to create the subgraph first by running the following command: $ graph create --node ${node} ${subgraphName}`) } } process.exitCode = 1 } else if (requestError) { spinner.fail(`HTTP error deploying the subgraph ${requestError.code}`) process.exitCode = 1 } else { spinner.stop() const base = requestUrl.protocol + '//' + requestUrl.hostname let playground = res.playground let queries = res.queries // Add a base URL if graph-node did not return the full URL if (playground.charAt(0) === ':') { playground = base + playground } if (queries.charAt(0) === ':') { queries = base + queries } if (isHostedService) { print.success( `Deployed to ${chalk.blue( `https://thegraph.com/explorer/subgraph/${subgraphName}`, )}`, ) } else { print.success(`Deployed to ${chalk.blue(`${playground}`)}`) } print.info('\nSubgraph endpoints:') print.info(`Queries (HTTP): ${queries}`) print.info(``) } }, ) } if (watch) { await compiler.watchAndCompile(async ipfsHash => { if (ipfsHash !== undefined) { await deploySubgraph(ipfsHash) } }) } else { let result = await compiler.compile() if (result === undefined || result === false) { // Compilation failed, not deploying. process.exitCode = 1 return } await deploySubgraph(result) } }, }