UNPKG

@moonsong-labs/moonwall-cli

Version:

Testing framework for the Moon family of projects

1,457 lines (1,431 loc) 52.8 kB
var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __publicField = (obj, key, value) => { __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); return value; }; // src/cmds/entrypoint.ts var import_api_augment3 = require("@moonbeam-network/api-augment"); var import_yargs = __toESM(require("yargs"), 1); var import_helpers = require("yargs/helpers"); // src/lib/configReader.ts var import_api_augment = require("@moonbeam-network/api-augment"); var import_promises = __toESM(require("fs/promises"), 1); var import_path = __toESM(require("path"), 1); async function importJsonConfig() { const filePath = import_path.default.join(process.cwd(), "moonwall.config.json"); try { const file = await import_promises.default.readFile(filePath, "utf8"); const json = JSON.parse(file); return json; } catch (e) { console.error(e); throw new Error(`Error import config at ${filePath}`); } } // src/cmds/runTests.ts var import_node = require("vitest/node"); // src/lib/globalContext.ts var import_api_augment2 = require("@moonbeam-network/api-augment"); // src/internal/providers.ts var import_moonbeam_types_bundle = require("moonbeam-types-bundle"); var import_api = require("@polkadot/api"); var import_web3 = require("web3"); var import_web3_providers_ws = require("web3-providers-ws"); var import_ethers = require("ethers"); var import_debug = __toESM(require("debug"), 1); var import_chalk = __toESM(require("chalk"), 1); var import_moonwall_util = require("@moonsong-labs/moonwall-util"); var debug = (0, import_debug.default)("global:providers"); function prepareProviders(providerConfigs) { return providerConfigs.map(({ name, endpoints, type }) => { const url = endpoints.includes("ENV_VAR") ? process.env.WSS_URL : endpoints[0]; switch (type) { case "polkadotJs": debug(`\u{1F7E2} PolkadotJs provider ${name} details prepared`); return { name, type, connect: async () => { const api = await import_api.ApiPromise.create({ provider: new import_api.WsProvider(url), initWasm: false, noInitWarn: true }); await api.isReady; return api; }, ws: () => new import_api.WsProvider(url) }; case "moon": debug(`\u{1F7E2} Moonbeam provider ${name} details prepared`); return { name, type, connect: async () => { const moonApi = await import_api.ApiPromise.create({ provider: new import_api.WsProvider(url), rpc: import_moonbeam_types_bundle.rpcDefinitions, typesBundle: import_moonbeam_types_bundle.types, noInitWarn: true }); await moonApi.isReady; return moonApi; }, ws: () => new import_api.WsProvider(url) }; case "web3": debug(`\u{1F7E2} Web3 provider ${name} details prepared`); return { name, type, connect: () => { const provider = new import_web3_providers_ws.WebSocketProvider( url, {}, { delay: 50, autoReconnect: false, maxAttempts: 10 } ); provider.on("error", () => { throw new Error( `Cannot connect to Web3 provider ${import_chalk.default.bgWhiteBright.blackBright(url)}` ); }); return new import_web3.Web3(provider); } }; case "ethers": debug(`\u{1F7E2} Ethers provider ${name} details prepared`); return { name, type, connect: () => { const provider = new import_ethers.ethers.WebSocketProvider(url); return new import_ethers.Wallet(import_moonwall_util.ALITH_PRIVATE_KEY, provider); } }; default: return { name, type, connect: () => console.log(`\u{1F6A7} provider ${name} not yet implemented`) }; } }); } async function populateProviderInterface(name, type, connect) { switch (type) { case "polkadotJs": const pjsApi = await connect(); return { name, api: pjsApi, type, greet: () => { debug( `\u{1F44B} Provider ${name} is connected to chain ${pjsApi.consts.system.version.specName.toString()} RT${pjsApi.consts.system.version.specVersion.toNumber()}` ); return { rtVersion: pjsApi.consts.system.version.specVersion.toNumber(), rtName: pjsApi.consts.system.version.specName.toString() }; }, disconnect: async () => pjsApi.disconnect() }; case "moon": const mbApi = await connect(); return { name, api: mbApi, type, greet: () => { debug( `\u{1F44B} Provider ${name} is connected to chain ${mbApi.consts.system.version.specName.toString()} RT${mbApi.consts.system.version.specVersion.toNumber()}` ); return { rtVersion: mbApi.consts.system.version.specVersion.toNumber(), rtName: mbApi.consts.system.version.specName.toString() }; }, disconnect: async () => mbApi.disconnect() }; case "ethers": const ethApi = await connect(); return { name, api: ethApi, type, greet: async () => debug( `\u{1F44B} Provider ${name} is connected to chain ` + (await ethApi.provider.getNetwork()).chainId ), disconnect: async () => { ethApi.provider.destroy(); } }; case "web3": const web3Api = await connect(); return { name, api: web3Api, type, greet: async () => console.log( `\u{1F44B} Provider ${name} is connected to chain ` + await web3Api.eth.getChainId() ), disconnect: async () => { web3Api.currentProvider.disconnect(); } }; default: throw new Error("UNKNOWN TYPE"); } } // src/internal/localNode.ts var import_child_process = require("child_process"); var import_chalk2 = __toESM(require("chalk"), 1); var import_debug2 = __toESM(require("debug"), 1); var import_fs = require("fs"); var debugNode = (0, import_debug2.default)("global:node"); async function launchNode(cmd, args, name) { if (cmd.includes("moonbeam") && !(0, import_fs.existsSync)(cmd)) { throw new Error( `No binary file found at location: ${cmd} Are you sure your ${import_chalk2.default.bgWhiteBright.blackBright( "moonwall.config.json" )} file has the correct "binPath" in launchSpec?` ); } let runningNode; const onProcessExit = () => { runningNode && runningNode.kill(); }; const onProcessInterrupt = () => { runningNode && runningNode.kill(); }; process.once("exit", onProcessExit); process.once("SIGINT", onProcessInterrupt); runningNode = (0, import_child_process.spawn)(cmd, args); runningNode.once("exit", () => { process.removeListener("exit", onProcessExit); process.removeListener("SIGINT", onProcessInterrupt); debugNode(`Exiting dev node: ${name}`); }); runningNode.on("error", (err) => { if (err.errno == "ENOENT") { console.error( `\x1B[31mMissing Moonbeam binary at(${cmd}). Please compile the Moonbeam project\x1B[0m` ); } else { console.error(err); } process.exit(1); }); const binaryLogs = []; await new Promise((resolve, reject) => { const timer = setTimeout(() => { console.error(import_chalk2.default.redBright("Failed to start Moonbeam Test Node.")); console.error(`Command: ${cmd} ${args.join(" ")}`); console.error(`Logs:`); console.error(binaryLogs.map((chunk) => chunk.toString()).join("\n")); reject("Failed to launch node"); }, 1e4); const onData = async (chunk) => { debugNode(chunk.toString()); binaryLogs.push(chunk); if (chunk.toString().match(/Development Service Ready/) || chunk.toString().match(/ RPC listening on port/)) { clearTimeout(timer); runningNode.stderr.off("data", onData); runningNode.stdout.off("data", onData); resolve(); } }; runningNode.stderr.on("data", onData); runningNode.stdout.on("data", onData); }); return runningNode; } // src/internal/foundations.ts function parseRunCmd(launchSpec) { const launch = !!!launchSpec.running ? true : launchSpec.running; const cmd = launchSpec.binPath; let args = launchSpec.options ? [...launchSpec.options] : [ "--no-hardware-benchmarks", "--no-telemetry", "--reserved-only", "--rpc-cors=all", "--no-grandpa", "--sealing=manual", "--force-authoring", "--no-prometheus", "--alice", "--chain=moonbase-dev", "--in-peers=0", "--out-peers=0", "--tmp" ]; `ws://127.0.0.1:${1e4 + Number(process.env.VITEST_POOL_ID) * 100}`; if (launchSpec.ports) { const ports = launchSpec.ports; if (ports.p2pPort) { args.push(`--port=${ports.p2pPort}`); } if (ports.wsPort) { args.push(`--ws-port=${ports.wsPort}`); } if (ports.rpcPort) { args.push(`--rpc-port=${ports.rpcPort}`); } } else { args.push( `--port=${1e4 + Number(process.env.VITEST_POOL_ID || 1) * 100 + 2}` ); args.push( `--ws-port=${1e4 + Number(process.env.VITEST_POOL_ID || 1) * 100}` ); args.push( `--rpc-port=${1e4 + (Number(process.env.VITEST_POOL_ID || 1) * 100 + 1)}` ); } return { cmd, args, launch }; } function parseChopsticksRunCmd(launchSpecs) { const launch = !!!launchSpecs[0].running ? true : launchSpecs[0].running; if (launchSpecs.length === 1) { const chopsticksCmd2 = "node"; const chopsticksArgs2 = [ "node_modules/@acala-network/chopsticks/chopsticks.js", "dev", `--config=${launchSpecs[0].configPath}` ]; const mode = launchSpecs[0].buildBlockMode ? launchSpecs[0].buildBlockMode : "manual"; const num = mode == "batch" ? 0 : mode == "instant" ? 1 : 2; chopsticksArgs2.push(`--build-block-mode=${num}`); if (launchSpecs[0].wsPort) { chopsticksArgs2.push(`--port=${launchSpecs[0].wsPort}`); } if (launchSpecs[0].wasmOverride) { chopsticksArgs2.push(`--wasm-override=${launchSpecs[0].wasmOverride}`); } return { cmd: chopsticksCmd2, args: chopsticksArgs2, launch }; } const chopsticksCmd = "node"; const chopsticksArgs = [ "node_modules/@acala-network/chopsticks/chopsticks.js", "xcm" ]; launchSpecs.forEach((spec) => { const type = spec.type ? spec.type : "parachain"; switch (type) { case "parachain": chopsticksArgs.push(`--parachain=${spec.configPath}`); break; case "relaychain": chopsticksArgs.push(`--relaychain=${spec.configPath}`); } }); return { cmd: chopsticksCmd, args: chopsticksArgs, launch // rtUpgradePath: launchSpecs[0].rtUpgradePath // ? launchSpecs[0].rtUpgradePath // : "", }; } // src/lib/globalContext.ts var import_debug3 = __toESM(require("debug"), 1); var debugSetup = (0, import_debug3.default)("global:context"); var _MoonwallContext = class { environment; providers; nodes; foundation; _finalizedHead; rtUpgradePath; constructor(config) { this.environment; this.providers = []; this.nodes = []; const env = config.environments.find(({ name }) => name == process.env.MOON_TEST_ENV); const blob = { name: env.name, context: {}, providers: [], nodes: [], foundationType: env.foundation.type }; switch (env.foundation.type) { case "read_only": if (!env.connections) { throw new Error( `${env.name} env config is missing connections specification, required by foundation READ_ONLY` ); } else { blob.providers = prepareProviders(env.connections); } debugSetup(`\u{1F7E2} Foundation "${env.foundation.type}" parsed for environment: ${env.name}`); break; case "chopsticks": blob.nodes.push(parseChopsticksRunCmd(env.foundation.launchSpec)); blob.providers.push(...prepareProviders(env.connections)); this.rtUpgradePath = env.foundation.rtUpgradePath; debugSetup(`\u{1F7E2} Foundation "${env.foundation.type}" parsed for environment: ${env.name}`); break; case "dev": const { cmd, args, launch } = parseRunCmd(env.foundation.launchSpec[0]); blob.nodes.push({ name: env.foundation.launchSpec[0].name, cmd, args, launch }); blob.providers = env.connections ? prepareProviders(env.connections) : prepareProviders([ { name: "w3", type: "web3", endpoints: [ `ws://127.0.0.1:${1e4 + Number(process.env.VITEST_POOL_ID || 1) * 100}` ] }, { name: "eth", type: "ethers", endpoints: [ `ws://127.0.0.1:${1e4 + Number(process.env.VITEST_POOL_ID || 1) * 100}` ] }, { name: "mb", type: "moon", endpoints: [ `ws://127.0.0.1:${1e4 + Number(process.env.VITEST_POOL_ID || 1) * 100}` ] } ]); debugSetup(`\u{1F7E2} Foundation "${env.foundation.type}" parsed for environment: ${env.name}`); break; default: debugSetup(`\u{1F6A7} Foundation "${env.foundation.type}" unsupported, skipping`); return; } this.environment = blob; } get genesis() { if (this._finalizedHead) { return this._finalizedHead; } else { return ""; } } set genesis(hash) { if (hash.length !== 66) { throw new Error("Cannot set genesis to invalid hash"); } this._finalizedHead = hash; } async startNetwork() { const activeNodes = this.nodes.filter((node) => !node.killed); if (activeNodes.length > 0) { console.log("Nodes already started! Skipping command"); return _MoonwallContext.getContext(); } const nodes = _MoonwallContext.getContext().environment.nodes; const promises = nodes.map(async ({ cmd, args, name, launch }) => { return launch && this.nodes.push(await launchNode(cmd, args, name)); }); await Promise.all(promises); } async stopNetwork() { if (this.nodes.length === 0) { console.log("Nodes already stopped! Skipping command"); return _MoonwallContext.getContext(); } this.nodes.forEach((node) => node.kill()); await this.wipeNodes(); } async connectEnvironment(environmentName) { if (this.providers.length > 0) { console.log("Providers already connected! Skipping command"); return _MoonwallContext.getContext(); } const globalConfig = await importJsonConfig(); const promises = this.environment.providers.map( async ({ name, type, connect }) => new Promise(async (resolve) => { this.providers.push(await populateProviderInterface(name, type, connect)); resolve(""); }) ); await Promise.all(promises); this.foundation = globalConfig.environments.find( ({ name }) => name == environmentName ).foundation.type; if (this.foundation == "dev") { this.genesis = (await this.providers.find(({ type }) => type == "polkadotJs" || type == "moon").api.rpc.chain.getBlockHash(0)).toString(); } if (this.foundation == "chopsticks") { this.genesis = (await this.providers.find(({ type }) => type == "polkadotJs" || type == "moon").api.rpc.chain.getFinalizedHead()).toString(); } } async wipeNodes() { this.nodes = []; } async disconnect(providerName) { if (providerName) { this.providers.find(({ name }) => name === providerName).disconnect(); this.providers.filter(({ name }) => name !== providerName); } else { await Promise.all(this.providers.map((prov) => prov.disconnect())); this.providers = []; } } static printStats() { if (_MoonwallContext) { console.dir(_MoonwallContext.getContext(), { depth: 1 }); } else { console.log("Global context not created!"); } } static getContext(config, force = false) { if (!_MoonwallContext.instance || force) { if (global.moonInstance && !force) { _MoonwallContext.instance = global.moonInstance; return _MoonwallContext.instance; } if (!config) { console.error("\u274C Config must be provided on Global Context instantiation"); return void 0; } _MoonwallContext.instance = new _MoonwallContext(config); debugSetup(`\u{1F7E2} Moonwall context "${config.label}" created`); } return _MoonwallContext.instance; } static async destroy() { const ctx = _MoonwallContext.getContext(); try { ctx.disconnect(); } catch { console.log("\u{1F6D1} All connections disconnected"); } const promises = ctx.nodes.map((process2) => { return new Promise((resolve) => { process2.kill(); if (process2.killed) { resolve(`process ${process2.pid} killed`); } }); }); await Promise.all(promises); } }; var MoonwallContext = _MoonwallContext; __publicField(MoonwallContext, "instance"); var contextCreator = async (config, env) => { const ctx = MoonwallContext.getContext(config); await runNetworkOnly(config); await ctx.connectEnvironment(env); return ctx; }; var runNetworkOnly = async (config) => { const ctx = MoonwallContext.getContext(config); await ctx.startNetwork(); }; // src/cmds/runTests.ts var import_path2 = __toESM(require("path"), 1); var import_chalk3 = __toESM(require("chalk"), 1); async function testCmd(envName, additionalArgs) { const globalConfig = await importJsonConfig(); const env = globalConfig.environments.find(({ name }) => name === envName); if (!!!env) { const envList = globalConfig.environments.map((env2) => env2.name); throw new Error( `No environment found in config for: ${import_chalk3.default.bgWhiteBright.blackBright( envName )} Environments defined in config are: ${envList} ` ); } process.env.MOON_TEST_ENV = envName; try { await executeTests(env, additionalArgs); } catch (e) { console.error(e); await MoonwallContext.destroy(); process.exit(1); } } async function executeTests(env, additionalArgs) { const globalConfig = await importJsonConfig(); if (env.foundation.type === "read_only") { try { const ctx = await contextCreator(globalConfig, process.env.MOON_TEST_ENV); const chainData = ctx.providers.filter((provider) => provider.type == "moon" || provider.type == "polkadotJs").map((provider) => { return { [provider.name]: { rtName: provider.greet().rtName, rtVersion: provider.greet().rtVersion } }; }); const { rtVersion, rtName } = Object.values(chainData[0])[0]; process.env.MOON_RTVERSION = rtVersion; process.env.MOON_RTNAME = rtName; await ctx.disconnect(); } catch { } } const options = { watch: false, globals: true, reporters: env.html ? ["verbose", "html"] : ["verbose"], testTimeout: 1e4, hookTimeout: 5e5, useAtomics: false, passWithNoTests: false, threads: true, include: env.include ? env.include : ["**/{test,spec,test_,test-}*{ts,mts,cts}"] }; if (!env.multiThreads || process.env.MOON_SINGLE_THREAD === "true") { options.threads = false; } try { const folders = env.testFileDir.map((folder) => import_path2.default.join("/", folder, "/")); await (0, import_node.startVitest)("test", folders, { ...options, ...additionalArgs }); } catch (e) { console.error(e); process.exit(1); } } // src/cmds/runNetwork.ts var import_inquirer_press_to_continue = __toESM(require("inquirer-press-to-continue"), 1); var import_inquirer = __toESM(require("inquirer"), 1); var import_clear = __toESM(require("clear"), 1); var import_chalk4 = __toESM(require("chalk"), 1); var import_yaml = require("yaml"); var import_promises2 = __toESM(require("fs/promises"), 1); import_inquirer.default.registerPrompt("press-to-continue", import_inquirer_press_to_continue.default); async function runNetwork(args) { process.env.MOON_TEST_ENV = args.envName; const globalConfig = await importJsonConfig(); const testFileDirs = globalConfig.environments.find( ({ name }) => name == args.envName ).testFileDir; const questions2 = [ { type: "confirm", name: "Quit", message: "\u2139\uFE0F Are you sure you'd like to close network and quit? \n", default: false }, { name: "Choice", type: "list", message: "What would you like todo now", choices: ["Chill", "Info", "Test", "Quit"] }, { name: "MenuChoice", type: "list", message: `Environment : ${import_chalk4.default.bgGray.cyanBright(args.envName)} Please select a choice: `, default: 0, pageSize: 10, choices: [ { name: "Tail: Print the logs of the current running node to this console", value: 1, short: "tail" }, { name: `Info: Display Information about this environment ${args.envName}`, value: 2, short: "info" }, { name: testFileDirs.length > 0 ? "Test: Execute tests registered for this environment (" + import_chalk4.default.bgGrey.cyanBright(testFileDirs) + ")" : import_chalk4.default.dim("Test: NO TESTS SPECIFIED"), value: 3, disabled: testFileDirs.length > 0 ? false : true, short: "test" }, { name: testFileDirs.length > 0 ? "GrepTest: Execute individual test(s) based on grepping the name / ID (" + import_chalk4.default.bgGrey.cyanBright(testFileDirs) + ")" : import_chalk4.default.dim("Test: NO TESTS SPECIFIED"), value: 4, disabled: testFileDirs.length > 0 ? false : true, short: "test" }, { name: "Quit: Close network and quit the application", value: 5, short: "quit" } ], filter(val) { return val; } }, { name: "NetworkStarted", type: "press-to-continue", anyKey: true, pressToContinueMessage: "\u2705 Press any key to continue...\n" } ]; await runNetworkOnly(globalConfig); (0, import_clear.default)(); const portsList = await reportServicePorts(); portsList.forEach( (ports) => console.log(` \u{1F5A5}\uFE0F https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A${ports.wsPort}`) ); await import_inquirer.default.prompt(questions2.find(({ name }) => name == "NetworkStarted")); mainloop: while (true) { const choice = await import_inquirer.default.prompt(questions2.find(({ name }) => name == "MenuChoice")); const env = globalConfig.environments.find(({ name }) => name === args.envName); switch (choice.MenuChoice) { case 1: (0, import_clear.default)(); await resolveTailChoice(); (0, import_clear.default)(); break; case 2: resolveInfoChoice(env); await reportServicePorts(); break; case 3: await resolveTestChoice(env); break; case 4: await resolveGrepChoice(env); case 5: const quit = await import_inquirer.default.prompt(questions2.find(({ name }) => name == "Quit")); if (quit.Quit === true) { break mainloop; } break; default: throw new Error("invalid value"); } } await MoonwallContext.destroy(); console.log(`Goodbye! \u{1F44B}`); process.exit(0); } var reportServicePorts = async () => { const ctx = MoonwallContext.getContext(); const portsList = []; const globalConfig = await importJsonConfig(); const config = globalConfig.environments.find(({ name }) => name == process.env.MOON_TEST_ENV); if (config.foundation.type == "dev") { const ports = { wsPort: "", httpPort: "" }; ports.wsPort = ctx.environment.nodes[0].args.find((a) => a.includes("ws-port")).split("=")[1] || "9944"; ports.httpPort = ctx.environment.nodes[0].args.find((a) => a.includes("rpc-port")).split("=")[1] || "9933"; portsList.push(ports); } else if (config.foundation.type == "chopsticks") { portsList.push( ...await Promise.all( config.foundation.launchSpec.map(async ({ configPath }) => { const yaml = (0, import_yaml.parse)((await import_promises2.default.readFile(configPath)).toString()); return { wsPort: yaml.port || "8000", httpPort: "<\u{1F3D7}\uFE0F NOT YET IMPLEMENTED>" }; }) ) ); } portsList.forEach( (ports) => console.log( ` \u{1F310} Node has started, listening on ports - Websocket: ${ports.wsPort} and HTTP: ${ports.httpPort}` ) ); return portsList; }; var resolveInfoChoice = async (env) => { console.log(import_chalk4.default.bgWhite.blackBright("Node Launch args:")); console.dir(MoonwallContext.getContext().environment, { depth: null }); console.log(import_chalk4.default.bgWhite.blackBright("Launch Spec in Config File:")); console.dir(env, { depth: null }); }; var resolveGrepChoice = async (env) => { const choice = await import_inquirer.default.prompt({ name: "grep", type: "input", message: `What pattern would you like to filter for (ID/Title): `, default: "D01T01" }); return await executeTests(env, { testNamePattern: choice.grep }); }; var resolveTestChoice = async (env) => { process.env.MOON_RECYCLE = "true"; return await executeTests(env); }; var resolveTailChoice = async () => { const ui = new import_inquirer.default.ui.BottomBar(); await new Promise(async (resolve) => { const runningNode = MoonwallContext.getContext().nodes[0]; const onData = (chunk) => ui.log.write(chunk.toString()); runningNode.stderr.on("data", onData); runningNode.stdout.on("data", onData); import_inquirer.default.prompt({ name: "exitTail", type: "press-to-continue", anyKey: true, pressToContinueMessage: " Press any key to stop tailing logs and go back \u21A9\uFE0F" }).then(() => { runningNode.stderr.off("data", onData); runningNode.stdout.off("data", onData); resolve(""); }); }); }; // src/cmds/generateConfig.ts var import_inquirer2 = __toESM(require("inquirer"), 1); var import_inquirer_press_to_continue2 = __toESM(require("inquirer-press-to-continue"), 1); var import_promises3 = __toESM(require("fs/promises"), 1); import_inquirer2.default.registerPrompt("press-to-continue", import_inquirer_press_to_continue2.default); async function generateConfig() { while (true) { if (await import_promises3.default.access("moonwall.config.json").catch(() => true)) { const answers = await import_inquirer2.default.prompt(generateQuestions); const proceed = await import_inquirer2.default.prompt( questions.find(({ name }) => name === "Confirm") ); if (proceed.Confirm === false) { continue; } const JSONBlob = JSON.stringify( createConfig({ label: answers.Label, timeout: answers.Timeout, environmentName: answers.EnvironmentName, foundation: answers.EnvironmentFoundation, testDir: answers.EnvironmentTestDir }), null, 3 ); await import_promises3.default.writeFile( "moonwall.config.json", JSONBlob, "utf-8" ); break; } else { console.log("\u2139\uFE0F Config file already exists at this location. Quitting."); return; } } console.log(`Goodbye! \u{1F44B}`); } var generateQuestions = [ { name: "Label", type: "input", message: "Provide a label for the config file", default: "moonwall_config" }, { name: "Timeout", type: "number", message: "Provide a global timeout value", default: 3e4, validate: (input) => { const pass = /^\d+$/.test(input); if (pass) { return true; } return "Please enter a valid number \u274C"; } }, { name: "EnvironmentName", type: "input", message: "Provide a name for this environment", default: "default_env" }, { name: "EnvironmentTestDir", type: "input", message: "Provide the path for where tests for this environment are kept", default: "tests/" }, { name: "EnvironmentFoundation", type: "list", message: "What type of network foundation is this?", choices: ["dev", "chopsticks", "read_only", "fork", "zombie"], default: "tests/" } ]; var questions = [ { name: "Confirm", type: "confirm", message: "Would you like to generate this config? (no to restart from beginning)" }, { name: "Success", type: "press-to-continue", anyKey: true, pressToContinueMessage: "\u{1F4C4} moonwall.config.ts has been generated. Press any key to exit \u2705\n" }, { name: "Failure", type: "press-to-continue", anyKey: true, pressToContinueMessage: "Config has not been generated due to errors, Press any key to exit \u274C\n" } ]; function createConfig(options) { return { $schema: "https://raw.githubusercontent.com/Moonsong-Labs/moonwall/main/packages/cli/config_schema.json", label: options.label, defaultTestTimeout: options.timeout, environments: [ { name: options.environmentName, testFileDir: [options.testDir], foundation: { type: options.foundation }, connections: [ { name: "SAMPLE", type: "ethers", endpoints: ["wss://moonriver.api.onfinality.io/public-ws"] } ] } ] }; } // src/cmds/main.ts var import_chalk6 = __toESM(require("chalk"), 1); var import_inquirer3 = __toESM(require("inquirer"), 1); var import_inquirer_press_to_continue3 = __toESM(require("inquirer-press-to-continue"), 1); var import_colors = __toESM(require("colors"), 1); var import_clear2 = __toESM(require("clear"), 1); // src/cmds/fetchArtifact.ts var import_promises4 = __toESM(require("fs/promises"), 1); var import_path3 = __toESM(require("path"), 1); var import_node_fetch2 = __toESM(require("node-fetch"), 1); var import_chalk5 = __toESM(require("chalk"), 1); // src/internal/runner.ts var import_child_process2 = __toESM(require("child_process"), 1); var import_node_util = require("util"); var import_debug4 = __toESM(require("debug"), 1); var debug2 = (0, import_debug4.default)("actions:runner"); var execAsync = (0, import_node_util.promisify)(import_child_process2.default.exec); async function runTask(cmd, { cwd, env } = { cwd: process.cwd() }, title) { debug2(`${title ? `Title: ${title} ` : ""}Running task on directory ${cwd}: ${cmd} `); try { const result = await execAsync(cmd, { cwd, env }); return result.stdout; } catch (error) { console.log(error); debug2(`Caught exception in command execution. Error[${error.status}] ${error.message} `); throw error; } } // src/internal/downloader.ts var import_cli_progress = require("cli-progress"); var import_node_fetch = __toESM(require("node-fetch"), 1); var import_node_fs = __toESM(require("fs"), 1); var progressBar; var onStart = (length) => { progressBar = new import_cli_progress.SingleBar( { etaAsynchronousUpdate: true, etaBuffer: 40, format: "Downloading: [{bar}] {percentage}% | ETA: {eta_formatted} | {value}/{total}" }, import_cli_progress.Presets.shades_classic ); progressBar.start(length, 0); }; var onProgress = (bytes) => { progressBar.update(bytes); }; var onComplete = () => { progressBar.stop(); process.stdout.write(` \u{1F4BE} Saving binary artefact...`); }; async function downloader(url, outputPath) { const tempPath = outputPath + ".tmp"; const writeStream = import_node_fs.default.createWriteStream(tempPath); let transferredBytes = 0; const response = await (0, import_node_fetch.default)(url); const readStream = response.body; readStream.pipe(writeStream); await new Promise((resolve, reject) => { const contentLength = parseInt(response.headers.get("Content-Length") || "0"); onStart(contentLength); readStream.on("data", (chunk) => { transferredBytes += chunk.length; onProgress(transferredBytes); }); readStream.on("end", () => { writeStream.end(); onComplete(); writeStream.close(() => resolve("Finished!")); }); readStream.on("error", () => { reject("Error!"); }); }); try { import_node_fs.default.writeFileSync(outputPath, import_node_fs.default.readFileSync(tempPath)); import_node_fs.default.rmSync(tempPath); } catch (e) { throw new Error(e); } } // src/cmds/fetchArtifact.ts var supportedBinaries = [ "moonbeam", "polkadot", "moonbase-runtime", "moonbeam-runtime", "moonriver-runtime" ]; var repos = { moonbeam: "https://api.github.com/repos/purestake/moonbeam/releases", polkadot: "https://api.github.com/repos/paritytech/polkadot/releases" }; async function fetchArtifact(args) { if (!supportedBinaries.includes(args.artifact)) { throw new Error(`Downloading ${args.artifact} unsupported`); } if (await import_promises4.default.access(args.path).catch(() => true)) { console.log("Folder not exists, creating"); import_promises4.default.mkdir(args.path); } const binary = args.artifact; const repoName = args.artifact.includes("-runtime") ? "moonbeam" : args.artifact; const enteredPath = args.path ? args.path : "tmp/"; const binaryPath = import_path3.default.join("./", enteredPath, binary); const releases = await (await (0, import_node_fetch2.default)(repos[repoName])).json(); const release = args.artifact.includes("-runtime") ? releases.find((release2) => { if (args.binVersion === "latest") { return release2.assets.find((asset2) => asset2.name.includes(binary)); } else { return release2.assets.find((asset2) => asset2.name === `${binary}-${args.binVersion}.wasm`); } }) : args.binVersion === "latest" ? releases.find((release2) => release2.assets.find((asset2) => asset2.name === binary)) : releases.filter((release2) => release2.tag_name.includes("v" + args.binVersion)).find((release2) => release2.assets.find((asset2) => asset2.name === binary)); if (release == null) { throw new Error(`Release not found for ${args.binVersion}`); } const asset = args.artifact.includes("-runtime") ? release.assets.find((asset2) => asset2.name.includes(binary) && asset2.name.includes("wasm")) : release.assets.find((asset2) => asset2.name === binary); if (binary == "moonbeam" || binary == "polkadot") { await downloader(asset.browser_download_url, binaryPath); await import_promises4.default.chmod(binaryPath, "755"); const version = (await runTask(`./${binaryPath} --version`)).trim(); process.stdout.write(` ${import_chalk5.default.green(version.trim())} \u2713 `); } if (binary.includes("-runtime")) { const binaryPath2 = import_path3.default.join("./", args.path, `${args.artifact}-${args.binVersion}.wasm`); await downloader(asset.browser_download_url, binaryPath2); await import_promises4.default.chmod(binaryPath2, "755"); process.stdout.write(` ${import_chalk5.default.green("done")} \u2713 `); } } // package.json var package_default = { name: "@moonsong-labs/moonwall-cli", type: "module", version: "0.3.1", description: "Testing framework for the Moon family of projects", author: "timbrinded", license: "ISC", homepage: "https://github.com/Moonsong-Labs/moonwall#readme", repository: { type: "git", url: "git+https://github.com/Moonsong-Labs/moonwall.git", directory: "packages/moonwall" }, bugs: { url: "https://github.com/Moonsong-Labs/moonwall/issues" }, keywords: [ "moonwall", "moonbeam", "moondance", "polkadot", "kusama", "substrate" ], exports: "./dist/index.js", module: "./dist/index.mjs", types: "./dist/index.d.ts", bin: { moonwall: "./moonwall.mjs" }, engines: { node: ">=14.16.0", pnpm: ">=7" }, files: [ "dist", "bin", "*.d.ts", "*.d.cts", "*.mjs", "*.cjs" ], scripts: { build: "pnpm exec rimraf dist && tsup src --format cjs,esm --dts ", watch: "tsup src --format cjs,esm --dts --watch", typecheck: "pnpm exec tsc --noEmit", prepublish: "pnpm run build", prepare: "pnpm build", schema: "typescript-json-schema ./src/types/config.ts MoonwallConfig > config_schema.json", compile: "pnpm build ", lint: "tsc" }, dependencies: { "@acala-network/chopsticks": "^0.5.10", "@moonbeam-network/api-augment": "^0.2201.0", "@moonsong-labs/moonwall-util": "workspace:*", "@polkadot/api": "^10.2.1", "@polkadot/api-augment": "^10.2.1", "@polkadot/api-derive": "^10.2.1", "@polkadot/keyring": "^11.1.2", "@polkadot/types": "^10.2.1", "@polkadot/types-codec": "^10.2.1", "@polkadot/util": "^11.1.2", "@types/cli-progress": "^3.11.0", "@types/node": "^18.15.10", "@types/yargs": "^17.0.23", bottleneck: "^2.19.5", chalk: "^5.2.0", clear: "^0.1.0", "cli-progress": "^3.12.0", colors: "^1.4.0", debug: "^4.3.4", dotenv: "^16.0.3", ethers: "^6.2.3", inquirer: "^8.2.5", "inquirer-press-to-continue": "^1.1.4", "moonbeam-types-bundle": "^2.0.10", "node-fetch": "^3.3.1", prettier: "^2.8.7", "pretty-bytes": "^6.1.0", "request-progress": "^3.0.0", semver: "^7.3.8", superagent: "^8.0.9", "ts-node": "^10.9.1", tsup: "^6.7.0", vitest: "^0.29.7", web3: "4.0.1-rc.0", "web3-providers-ws": "4.0.1-rc.0", ws: "^8.13.0", yaml: "^2.2.1", yargs: "^17.7.1" }, devDependencies: { "@types/debug": "^4.1.7", "@vitest/ui": "^0.28.5", "regenerator-runtime": "^0.13.11", typescript: "^4.9.5", "typescript-json-schema": "^0.55.0" }, pnpm: { overrides: { "@moonsong-labs/moonwall-util": "workspace:*", "@polkadot/rpc-provider": "$@polkadot/rpc-provider", "@polkadot/util": "$@polkadot/rpc-provider", "@polkadot/keyring": "$@polkadot/rpc-provider", "@polkadot/api": "$@polkadot/api", "@polkadot/types": "$@polkadot/api" } }, publishConfig: { access: "public" } }; // src/cmds/main.ts var import_semver = require("semver"); var import_node_fetch3 = __toESM(require("node-fetch"), 1); import_inquirer3.default.registerPrompt("press-to-continue", import_inquirer_press_to_continue3.default); async function main() { while (true) { let globalConfig; try { globalConfig = await importJsonConfig(); } catch (e) { console.log(e); } (0, import_clear2.default)(); await printIntro(); if (await mainMenu(globalConfig)) { break; } else { continue; } } process.stdout.write(`Goodbye! \u{1F44B} `); process.exit(0); } async function mainMenu(config) { const configPresent = config !== void 0; const questionList = { name: "MenuChoice", type: "list", message: `Main Menu - Please select one of the following:`, default: 0, pageSize: 12, choices: [ { name: !configPresent ? "1) Initialise: Generate a new Moonwall Config File." : import_chalk6.default.dim("1) Initialise: \u2705 CONFIG ALREADY GENERATED"), value: "init", disabled: configPresent }, { name: configPresent ? `2) Network Launcher & Toolbox: Launch network, access tools: tail logs, interactive tests etc.` : import_chalk6.default.dim("2) Network Launcher & Toolbox NO CONFIG FOUND"), value: "run", disabled: !configPresent }, { name: configPresent ? "3) Test Suite Execution: Run automated tests, start network if needed." : import_chalk6.default.dim("3) Test Suite Execution: NO CONFIG FOUND"), value: "test", disabled: !configPresent }, { name: import_chalk6.default.dim("4) Batch-Run Tests: \u{1F3D7}\uFE0F NOT YET IMPLEMENTED "), value: "batch", disabled: true }, { name: "5) Artifact Downloader: Fetch artifacts from GitHub repos.", value: "download", disabled: false }, { name: `6) Quit Application`, value: "quit" } ], filter(val) { return val; } }; const answers = await import_inquirer3.default.prompt(questionList); switch (answers.MenuChoice) { case "init": await generateConfig(); return false; case "run": const chosenRunEnv = await chooseRunEnv(config); if (chosenRunEnv.envName !== "back") { await runNetwork(chosenRunEnv); } return false; case "test": const chosenTestEnv = await chooseTestEnv(config); if (chosenTestEnv.envName !== "back") { await testCmd(chosenTestEnv.envName); await import_inquirer3.default.prompt({ name: "test complete", type: "press-to-continue", anyKey: true, pressToContinueMessage: `\u2139\uFE0F Test run for ${import_chalk6.default.bgWhiteBright.black( chosenTestEnv.envName )} has been completed. Press any key to continue... ` }); } return false; case "download": await resolveDownloadChoice(); return false; case "quit": return await resolveQuitChoice(); } } async function resolveDownloadChoice() { while (true) { const firstChoice = await import_inquirer3.default.prompt({ name: "artifact", type: "list", message: `Download - which artifact?`, choices: [ "moonbeam", "polkadot", "moonbase-runtime", "moonriver-runtime", "moonbeam-runtime", new import_inquirer3.default.Separator(), "Back", new import_inquirer3.default.Separator() ] }); if (firstChoice.artifact === "Back") { return; } const otherChoices = await import_inquirer3.default.prompt([ { name: "binVersion", type: "input", default: "latest", message: `Download - which version?` }, { name: "path", type: "input", message: `Download - where would you like it placed?`, default: "./tmp" } ]); const result = await import_inquirer3.default.prompt({ name: "continue", type: "confirm", message: `You are about to download ${import_chalk6.default.bgWhite.blackBright( firstChoice.artifact )} v-${import_chalk6.default.bgWhite.blackBright(otherChoices.binVersion)} to: ${import_chalk6.default.bgWhite.blackBright( otherChoices.path )}. Would you like to continue? `, default: true }); if (result.continue === false) { continue; } await fetchArtifact({ artifact: firstChoice.artifact, binVersion: otherChoices.binVersion, path: otherChoices.path }); await import_inquirer3.default.prompt({ name: "NetworkStarted", type: "press-to-continue", anyKey: true, pressToContinueMessage: `\u2705 Artifact has been downloaded. Press any key to continue... ` }); return; } } var chooseTestEnv = async (config) => { const envs = config.environments.map((a) => ({ name: `Env: ${a.name} (${a.foundation.type})`, value: a.name, disabled: false })).sort((a, b) => a.name > b.name ? -1 : 1); envs.push( ...[ new import_inquirer3.default.Separator(), { name: "Back", value: "back" }, new import_inquirer3.default.Separator() ] ); const result = await import_inquirer3.default.prompt({ name: "envName", message: "Select a environment to run", type: "list", pageSize: 12, choices: envs }); return result; }; var chooseRunEnv = async (config) => { const envs = config.environments.map((a) => { const result2 = { name: "", value: a.name, disabled: false }; if (a.foundation.type === "dev" || a.foundation.type === "chopsticks") { result2.name = `Env: ${a.name} (${a.foundation.type})`; } else { result2.name = import_chalk6.default.dim(`Env: ${a.name} (${a.foundation.type}) NO NETWORK TO RUN`); result2.disabled = true; } return result2; }); const choices = [ ...envs.filter(({ disabled }) => disabled === false).sort((a, b) => a.name > b.name ? 1 : -1), new import_inquirer3.default.Separator(), ...envs.filter(({ disabled }) => disabled === true).sort((a, b) => a.name > b.name ? 1 : -1), new import_inquirer3.default.Separator(), { name: "Back", value: "back" }, new import_inquirer3.default.Separator() ]; const result = await import_inquirer3.default.prompt({ name: "envName", message: "Select a environment to run", type: "list", pageSize: 12, choices }); return result; }; var resolveQuitChoice = async () => { const result = await import_inquirer3.default.prompt({ name: "Quit", type: "confirm", message: "Are you sure you want to Quit?", default: false }); return result.Quit; }; var printIntro = async () => { const currentVersion = new import_semver.SemVer(package_default.version); const resp = await (0, import_node_fetch3.default)("https://registry.npmjs.org/@moonsong-labs/moonwall-cli/latest"); const json = await resp.json(); const npmVersion = new import_semver.SemVer(json["version"]); const logo = import_chalk6.default.cyan(` #################### ############################ ################################### ######################################## ########################################### ############################################## ################################################ .################################################# ################################################## ################################################## `) + import_chalk6.default.magenta(` *** ************************************************************ **** ********************************************* *** ****************************************************** ** *********************** ********************************************* ** *********************** ******************************************** *** ****************************** **** ***************************** `); process.stdout.write(logo); process.stdout.write( import_colors.default.rainbow( "================================================================================\n" ) ); if ((0, import_semver.lt)(currentVersio