UNPKG

@graphprotocol/graph-cli

Version:

CLI for building for and deploying to The Graph

294 lines (292 loc) • 12.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const child_process_1 = require("child_process"); const os_1 = __importDefault(require("os")); const path_1 = __importDefault(require("path")); const binary_install_raw_1 = require("binary-install-raw"); const chalk_1 = __importDefault(require("chalk")); const gluegun_1 = require("gluegun"); const js_yaml_1 = __importDefault(require("js-yaml")); const node_fetch_1 = __importDefault(require("node-fetch")); const semver_1 = __importDefault(require("semver")); const gluegun_2 = require("../command-helpers/gluegun"); const HELP = ` ${chalk_1.default.bold('graph test')} ${chalk_1.default.dim('[options]')} ${chalk_1.default.bold('<datasource>')} ${chalk_1.default.dim('Options:')} -c, --coverage Run the tests in coverage mode -d, --docker Run the tests in a docker container(Note: Please execute from the root folder of the subgraph) -f --force Binary - overwrites folder + file when downloading. Docker - rebuilds the docker image -h, --help Show usage information -l, --logs Logs to the console information about the OS, CPU model and download url (debugging purposes) -r, --recompile Force-recompile tests -v, --version <tag> Choose the version of the rust binary that you want to be downloaded/used `; exports.default = { description: 'Runs rust binary for subgraph testing', run: async (toolbox) => { // Read CLI parameters const { c, coverage, d, docker, f, force, h, help, l, logs, r, recompile, v, version } = toolbox.parameters.options; const testsFolder = './tests'; const opts = { testsFolder, cachePath: path_1.default.join(testsFolder, '.latest.json'), }; // Support both long and short option variants opts.coverage = coverage || c; opts.docker = docker || d; opts.force = force || f; opts.help = help || h; opts.logs = logs || l; opts.recompile = recompile || r; opts.version = version || v; // Fix if a boolean flag (e.g -c, --coverage) has an argument try { (0, gluegun_2.fixParameters)(toolbox.parameters, { h, help, c, coverage, d, docker, f, force, l, logs, r, recompile, }); } catch (e) { gluegun_1.print.error(e.message); process.exitCode = 1; return; } const datasource = toolbox.parameters.first || toolbox.parameters.array?.[0]; // Show help text if requested if (opts.help) { gluegun_1.print.info(HELP); return; } // Check if matchstick.yaml config exists if (gluegun_1.filesystem.exists('matchstick.yaml')) { try { // Load the config const config = await js_yaml_1.default.load(gluegun_1.filesystem.read('matchstick.yaml', 'utf8')); // Check if matchstick.yaml and testsFolder not null if (config?.testsFolder) { // assign test folder from matchstick.yaml if present opts.testsFolder = config.testsFolder; } } catch (error) { gluegun_1.print.info('A problem occurred while reading matchstick.yaml. Please attend to the errors below:'); gluegun_1.print.error(error.message); process.exit(1); } } setVersionFromCache(opts); // Fetch the latest version tag if version is not specified with -v/--version or if the version is not cached if (opts.force || (!opts.version && !opts.latestVersion)) { gluegun_1.print.info('Fetching latest version tag'); const result = await (0, node_fetch_1.default)('https://api.github.com/repos/LimeChain/matchstick/releases/latest'); const json = await result.json(); opts.latestVersion = json.tag_name; gluegun_1.filesystem.file(opts.cachePath, { content: { version: json.tag_name, timestamp: Date.now(), }, }); } if (opts.docker) { runDocker(datasource, opts); } else { runBinary(datasource, opts); } }, }; async function setVersionFromCache(opts) { if (gluegun_1.filesystem.exists(opts.cachePath) == 'file') { const cached = gluegun_1.filesystem.read(opts.cachePath, 'json'); // Get the cache age in days const cacheAge = (Date.now() - cached.timestamp) / (1000 * 60 * 60 * 24); // If cache age is less than 1 day, use the cached version if (cacheAge < 1) { opts.latestVersion = cached.version; } } } async function runBinary(datasource, opts) { const coverageOpt = opts.coverage; const forceOpt = opts.force; const logsOpt = opts.logs; const versionOpt = opts.version; const latestVersion = opts.latestVersion; const recompileOpt = opts.recompile; const platform = await getPlatform(logsOpt); const url = `https://github.com/LimeChain/matchstick/releases/download/${versionOpt || latestVersion}/${platform}`; if (logsOpt) { gluegun_1.print.info(`Download link: ${url}`); } const binary = new binary_install_raw_1.Binary(platform, url, versionOpt || latestVersion); forceOpt ? await binary.install(true) : await binary.install(false); const args = []; if (coverageOpt) args.push('-c'); if (recompileOpt) args.push('-r'); if (datasource) args.push(datasource); args.length > 0 ? binary.run(...args) : binary.run(); } async function getPlatform(logsOpt) { const type = os_1.default.type(); const arch = os_1.default.arch(); const cpuCore = os_1.default.cpus()[0]; const isAppleSilicon = arch === 'arm64' && /Apple (M1|M2|processor)/.test(cpuCore.model); const linuxInfo = type === 'Linux' ? await getLinuxInfo() : {}; const linuxDistro = linuxInfo.name; const release = linuxInfo.version || os_1.default.release(); const majorVersion = parseInt(linuxInfo.version || '', 10) || semver_1.default.major(release); if (logsOpt) { gluegun_1.print.info(`OS type: ${linuxDistro || type}\nOS arch: ${arch}\nOS release: ${release}\nOS major version: ${majorVersion}\nCPU model: ${cpuCore.model}`); } if (arch === 'x64' || isAppleSilicon) { if (type === 'Darwin') { if (majorVersion === 18 || majorVersion === 19) { return 'binary-macos-10.15'; // GitHub dropped support for macOS 10.14 in Actions, but it seems 10.15 binary works on 10.14 too } if (isAppleSilicon) { return 'binary-macos-11-m1'; } return 'binary-macos-11'; } if (type === 'Linux') { if (majorVersion === 18) { return 'binary-linux-18'; } if (majorVersion === 22) { return 'binary-linux-22'; } return 'binary-linux-20'; } } throw new Error(`Unsupported platform: ${type} ${arch} ${majorVersion}`); } async function getLinuxInfo() { try { const result = await gluegun_1.system.run("cat /etc/*-release | grep -E '(^VERSION|^NAME)='", { trim: true, }); const infoArray = result .replace(/['"]+/g, '') .split('\n') .map(p => p.split('=')); const linuxInfo = {}; infoArray.forEach(val => { linuxInfo[val[0].toLowerCase()] = val[1]; }); return linuxInfo; } catch (error) { gluegun_1.print.error(`Error fetching the Linux version:\n ${error}`); process.exit(1); } } async function runDocker(datasource, opts) { const coverageOpt = opts.coverage; const forceOpt = opts.force; const versionOpt = opts.version; const latestVersion = opts.latestVersion; const recompileOpt = opts.recompile; // Remove binary-install-raw binaries, because docker has permission issues // when building the docker images gluegun_1.filesystem.remove('./node_modules/binary-install-raw/bin'); // Get current working directory const current_folder = gluegun_1.filesystem.cwd(); // Declate dockerfilePath with default location const dockerfilePath = path_1.default.join(opts.testsFolder || 'tests', '.docker/Dockerfile'); // Check if the Dockerfil already exists const dockerfileExists = gluegun_1.filesystem.exists(dockerfilePath); // Generate the Dockerfile only if it doesn't exists, // version flag and/or force flag is passed. if (!dockerfileExists || versionOpt || forceOpt) { await dockerfile(dockerfilePath, versionOpt, latestVersion); } // Run a command to check if matchstick image already exists (0, child_process_1.exec)('docker images -q matchstick', (_error, stdout, _stderr) => { // Collect all(if any) flags and options that have to be passed to the matchstick binary let testArgs = ''; if (coverageOpt) testArgs = testArgs + ' -c'; if (recompileOpt) testArgs = testArgs + ' -r'; if (datasource) testArgs = testArgs + ' ' + datasource; // Build the `docker run` command options and flags const dockerRunOpts = [ 'run', '-it', '--rm', '--mount', `type=bind,source=${current_folder},target=/matchstick`, ]; if (testArgs !== '') { dockerRunOpts.push('-e'); dockerRunOpts.push(`ARGS=${testArgs.trim()}`); } dockerRunOpts.push('matchstick'); // If a matchstick image does not exists, the command returns an empty string, // else it'll return the image ID. Skip `docker build` if an image already exists // Delete current image(if any) and rebuild. // Use spawn() and {stdio: 'inherit'} so we can see the logs in real time. if (!dockerfileExists || stdout === '' || versionOpt || forceOpt) { if (stdout !== '') { (0, child_process_1.exec)('docker image rm matchstick', (_error, stdout, _stderr) => { gluegun_1.print.info(chalk_1.default.bold(`Removing matchstick image\n${stdout}`)); }); } // Build a docker image. If the process has executed successfully // run a container from that image. (0, child_process_1.spawn)('docker', ['build', '-f', dockerfilePath, '-t', 'matchstick', '.'], { stdio: 'inherit', }).on('close', code => { if (code === 0) { (0, child_process_1.spawn)('docker', dockerRunOpts, { stdio: 'inherit' }); } }); } else { gluegun_1.print.info('Docker image already exists. Skipping `docker build` command.'); // Run the container from the existing matchstick docker image (0, child_process_1.spawn)('docker', dockerRunOpts, { stdio: 'inherit' }); } }); } // Downloads Dockerfile template from the demo-subgraph repo // Replaces the placeholders with their respective values async function dockerfile(dockerfilePath, versionOpt, latestVersion) { const spinner = gluegun_1.print.spin('Generating Dockerfile...'); try { // Fetch the Dockerfile template content from the demo-subgraph repo const content = await (0, node_fetch_1.default)('https://raw.githubusercontent.com/LimeChain/demo-subgraph/main/Dockerfile').then(response => { if (response.ok) { return response.text(); } throw new Error(`Status Code: ${response.status}, with error: ${response.statusText}`); }); // Write the Dockerfile gluegun_1.filesystem.write(dockerfilePath, content); // Replaces the version placeholders await gluegun_1.patching.replace(dockerfilePath, '<MATCHSTICK_VERSION>', versionOpt || latestVersion || 'unknown'); } catch (error) { spinner.fail(`A problem occurred while generating the Dockerfile. Please attend to the errors below:\n ${error.message}`); process.exit(1); } spinner.succeed('Successfully generated Dockerfile.'); }