starknet-devnet
Version:
Starknet Devnet provider
157 lines (156 loc) • 6.57 kB
JavaScript
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;
;