@graphprotocol/graph-cli
Version:
CLI for building for and deploying to The Graph
336 lines (333 loc) • 13.9 kB
JavaScript
;
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 url_1 = require("url");
const chalk_1 = __importDefault(require("chalk"));
const auth_1 = require("../command-helpers/auth");
const compiler_1 = require("../command-helpers/compiler");
const DataSourcesExtractor = __importStar(require("../command-helpers/data-sources"));
const gluegun_1 = require("../command-helpers/gluegun");
const ipfs_1 = require("../command-helpers/ipfs");
const jsonrpc_1 = require("../command-helpers/jsonrpc");
const network_1 = require("../command-helpers/network");
const node_1 = require("../command-helpers/node");
const studio_1 = require("../command-helpers/studio");
const version_1 = require("../command-helpers/version");
const protocols_1 = __importDefault(require("../protocols"));
const HELP = `
${chalk_1.default.bold('graph deploy')} [options] ${chalk_1.default.bold('<subgraph-name>')} ${chalk_1.default.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: ${ipfs_1.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;
}
};
exports.default = {
description: 'Deploys the subgraph to a Graph node',
run: async (toolbox) => {
// Obtain tools
const { filesystem, print } = 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 || ipfs_1.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] = (0, gluegun_1.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) {
(0, studio_1.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 } = (0, node_1.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 } = (0, node_1.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 (0, version_1.assertManifestApiVersion)(manifest, '0.0.5');
await (0, version_1.assertGraphTsVersion)(path_1.default.dirname(manifest), '0.25.0');
const dataSourcesAndTemplates = await DataSourcesExtractor.fromFilePath(manifest);
protocol = protocols_1.default.fromDataSources(dataSourcesAndTemplates);
}
catch (e) {
print.error(e.message);
process.exitCode = 1;
return;
}
if (network) {
const identifierName = protocol.getContract().identifierName();
await (0, network_1.updateSubgraphNetwork)(toolbox, manifest, network, networkFile, identifierName);
}
const isStudio = node.match(/studio/);
const isHostedService = node.match(/thegraph.com/) && !isStudio;
const compiler = (0, compiler_1.createCompiler)(manifest, {
ipfs,
headers,
outputDir,
outputFormat: 'wasm',
skipMigrations,
blockIpfsMethods: isStudio,
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;
}
const requestUrl = new url_1.URL(node);
const client = (0, jsonrpc_1.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 (0, auth_1.identifyDeployKey)(node, deployKey);
if (deployKey !== undefined && deployKey !== null) {
// @ts-expect-error options property seems to exist
client.options.headers = { Authorization: 'Bearer ' + deployKey };
}
const deploySubgraph = async (ipfsHash) => {
const 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 (
// @ts-expect-error TODO: why are the arguments not typed?
requestError,
// @ts-expect-error TODO: why are the arguments not typed?
jsonRpcError,
// @ts-expect-error TODO: why are the arguments not typed?
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_1.default.blue(`https://thegraph.com/explorer/subgraph/${subgraphName}`)}`);
}
else {
print.success(`Deployed to ${chalk_1.default.blue(String(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 {
const result = await compiler.compile();
if (result === undefined || result === false) {
// Compilation failed, not deploying.
process.exitCode = 1;
return;
}
await deploySubgraph(result);
}
},
};