UNPKG

starknet-devnet

Version:
157 lines (156 loc) 6.57 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Devnet = void 0; const child_process_1 = require("child_process"); const devnet_provider_1 = require("./devnet-provider"); const types_1 = require("./types"); const util_1 = require("./util"); const constants_1 = require("./constants"); const version_handler_1 = require("./version-handler"); /** * Attempt to extract the URL from the provided Devnet CLI args. If host or present not present, * populates the received array with default values. The host defaults to 127.0.0.1 and the port * is randomly assigned. * @param args CLI args to Devnet * @returns the URL enabling communication with the Devnet instance */ async function ensureUrl(args) { let host; const hostParamIndex = args.indexOf("--host"); if (hostParamIndex === -1) { host = constants_1.DEFAULT_DEVNET_HOST; args.push("--host", host); } else { host = args[hostParamIndex + 1]; } let port; const portParamIndex = args.indexOf("--port"); if (portParamIndex === -1) { port = await getFreePort(); args.push("--port", port); } else { port = args[portParamIndex + 1]; } return `http://${host}:${port}`; } async function getFreePort() { const step = 1000; const maxPort = 65535; for (let port = constants_1.DEFAULT_DEVNET_PORT + step; port <= maxPort; port += step) { if (await (0, util_1.isFreePort)(port)) { return port.toString(); } } throw new types_1.DevnetError("Could not find a free port! Try rerunning your command."); } class Devnet { constructor(process, provider) { this.process = process; this.provider = provider; } /** * Assumes `starknet-devnet` is installed and present in the environment PATH and executes it, using the args provided in `config`. * @param config an object for configuring Devnet * @returns a newly spawned Devnet instance */ static async spawnInstalled(config = {}) { return this.spawnCommand("starknet-devnet", config); } /** * Spawns a new Devnet using the provided command and optional args in `config`. * The `command` can be an absolute or a relative path, or a command in your environment's PATH. * @param command the command used for starting Devnet; can be a path * @param config configuration object * @returns a newly spawned Devnet instance */ static async spawnCommand(command, config = {}) { const args = config.args || []; const devnetUrl = await ensureUrl(args); const devnetProcess = (0, child_process_1.spawn)(command, args, { detached: true, stdio: [undefined, config.stdout || "inherit", config.stderr || "inherit"], }); devnetProcess.unref(); const devnetInstance = new Devnet(devnetProcess, new devnet_provider_1.DevnetProvider({ url: devnetUrl })); if (!config.keepAlive) { // store it now to ensure it's cleaned up automatically if the remaining steps fail Devnet.instances.push(devnetInstance); if (!Devnet.CLEANUP_REGISTERED) { Devnet.registerCleanup(); } } return new Promise((resolve, reject) => { const maxStartupMillis = config?.maxStartupMillis ?? 5000; devnetInstance.ensureAlive(maxStartupMillis).then(() => resolve(devnetInstance)); devnetProcess.on("error", function (e) { reject(e); }); devnetProcess.on("exit", function () { if (devnetProcess.exitCode) { reject(`Devnet exited with code ${devnetProcess.exitCode}. \ Check Devnet's logged output for more info. \ The output location is configurable via the config object passed to the Devnet spawning method.`); } }); }); } /** * Spawns a Devnet of the provided `version` using the parameters provided in `config`. * If not present locally, a precompiled version is fetched, extracted and executed. * If you already have a local Devnet you would like to run, use {@link spawnCommand}. * @param version if set to `"latest"`, uses the latest Devnet version compatible with this library; * otherwise needs to be a semver string with a prepended "v" (e.g. "v1.2.3") and * should be available in https://github.com/0xSpaceShard/starknet-devnet/releases * @param config configuration object * @returns a newly spawned Devnet instance */ static async spawnVersion(version, config = {}) { version = version === "latest" ? constants_1.LATEST_COMPATIBLE_DEVNET_VERSION : version; const command = await version_handler_1.VersionHandler.getExecutable(version); return this.spawnCommand(command, config); } async ensureAlive(maxStartupMillis) { const checkPeriod = 100; // ms const maxIterations = maxStartupMillis / checkPeriod; for (let i = 0; !this.process.exitCode && i < maxIterations; ++i) { if (await this.provider.isAlive()) { return; } await (0, util_1.sleep)(checkPeriod); } throw new types_1.DevnetError("Could not spawn Devnet! Ensure that you can spawn using the chosen method. \ Alternatively, increase the startup time defined in the config object provided on spawning."); } /** * Sends the provided signal to the underlying Devnet process. Keep in mind * that the process is killed automatically on program exit. * @param signal the signal to be sent; deaults to `SIGTERM` * @returns `true` if successful; `false` otherwise */ kill(signal = "SIGTERM") { return this.process.kill(signal); } static cleanup() { for (const instance of Devnet.instances) { if (!instance.process.killed) { instance.kill(); } } } static registerCleanup() { // This handler just propagates the exit code. process.on("exit", Devnet.cleanup); for (const event of ["SIGINT", "SIGTERM", "SIGQUIT", "uncaughtException"]) { process.on(event, () => { Devnet.cleanup(); process.exit(1); // Omitting this results in ignoring e.g. ctrl+c }); } Devnet.CLEANUP_REGISTERED = true; } } exports.Devnet = Devnet; Devnet.instances = []; Devnet.CLEANUP_REGISTERED = false;