@moonwall/cli
Version:
Testing framework for the Moon family of projects
1,360 lines (1,331 loc) • 155 kB
JavaScript
var __getOwnPropNames = Object.getOwnPropertyNames;
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
// src/lib/configReader.ts
import { readFile, access } from "fs/promises";
import { readFileSync as readFileSync2, existsSync as existsSync2, constants } from "fs";
import JSONC from "jsonc-parser";
import path2, { extname } from "path";
async function parseConfig(filePath) {
let result;
const file = await readFile(filePath, "utf8");
switch (extname(filePath)) {
case ".json":
result = JSON.parse(file);
break;
case ".config":
result = JSONC.parse(file);
break;
default:
result = void 0;
break;
}
return result;
}
function parseConfigSync(filePath) {
let result;
const file = readFileSync2(filePath, "utf8");
switch (extname(filePath)) {
case ".json":
result = JSON.parse(file);
break;
case ".config":
result = JSONC.parse(file);
break;
default:
result = void 0;
break;
}
return result;
}
function isOptionSet(option) {
const env = getEnvironmentFromConfig();
const optionValue = traverseConfig(env, option);
return optionValue !== void 0;
}
function isEthereumZombieConfig() {
const env = getEnvironmentFromConfig();
return env.foundation.type === "zombie" && !env.foundation.zombieSpec.disableDefaultEthProviders;
}
function isEthereumDevConfig() {
const env = getEnvironmentFromConfig();
return env.foundation.type === "dev" && !env.foundation.launchSpec[0].disableDefaultEthProviders;
}
function getEnvironmentFromConfig() {
const globalConfig = importJsonConfig();
const config = globalConfig.environments.find(({ name }) => name === process.env.MOON_TEST_ENV);
if (!config) {
throw new Error(`Environment ${process.env.MOON_TEST_ENV} not found in config`);
}
return config;
}
function importJsonConfig() {
if (cachedConfig) {
return cachedConfig;
}
const configPath = process.env.MOON_CONFIG_PATH;
if (!configPath) {
throw new Error("No moonwall config path set. This is a defect, please raise it.");
}
const filePath = path2.isAbsolute(configPath) ? configPath : path2.join(process.cwd(), configPath);
try {
const config = parseConfigSync(filePath);
const replacedConfig = replaceEnvVars(config);
cachedConfig = replacedConfig;
return cachedConfig;
} catch (e) {
console.error(e);
throw new Error(`Error import config at ${filePath}`);
}
}
async function importAsyncConfig() {
if (cachedConfig) {
return cachedConfig;
}
const configPath = process.env.MOON_CONFIG_PATH;
if (!configPath) {
throw new Error("No moonwall config path set. This is a defect, please raise it.");
}
const filePath = path2.isAbsolute(configPath) ? configPath : path2.join(process.cwd(), configPath);
try {
const config = await parseConfig(filePath);
const replacedConfig = replaceEnvVars(config);
cachedConfig = replacedConfig;
return cachedConfig;
} catch (e) {
console.error(e);
throw new Error(`Error import config at ${filePath}`);
}
}
function replaceEnvVars(value) {
if (typeof value === "string") {
return value.replace(/\$\{([^}]+)\}/g, (match, group) => {
const envVarValue = process.env[group];
return envVarValue || match;
});
}
if (Array.isArray(value)) {
return value.map(replaceEnvVars);
}
if (typeof value === "object" && value !== null) {
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, replaceEnvVars(v)]));
}
return value;
}
function traverseConfig(configObj, option) {
if (typeof configObj !== "object" || !configObj) return void 0;
if (Object.hasOwn(configObj, option)) {
return configObj[option];
}
for (const key in configObj) {
const result = traverseConfig(configObj[key], option);
if (result !== void 0) {
return result;
}
}
return void 0;
}
var cachedConfig;
var init_configReader = __esm({
"src/lib/configReader.ts"() {
"use strict";
}
});
// src/lib/upgradeProcedures.ts
import { blake2AsHex as blake2AsHex2 } from "@polkadot/util-crypto";
import chalk from "chalk";
import { sha256 } from "ethers";
import fs2, { existsSync, readFileSync } from "fs";
// src/lib/binariesHelpers.ts
import path from "path";
import fs from "fs";
import child_process from "child_process";
import { OVERRIDE_RUNTIME_PATH } from "@moonwall/util";
var BINARY_DIRECTORY = process.env.BINARY_DIRECTORY || "binaries";
var RUNTIME_DIRECTORY = process.env.RUNTIME_DIRECTORY || "runtimes";
var SPECS_DIRECTORY = process.env.SPECS_DIRECTORY || "specs";
async function getRuntimeWasm(runtimeName, runtimeTag, localPath) {
const runtimePath = path.join(RUNTIME_DIRECTORY, `${runtimeName}-${runtimeTag}.wasm`);
if (!fs.existsSync(RUNTIME_DIRECTORY)) {
fs.mkdirSync(RUNTIME_DIRECTORY, { recursive: true });
}
if (runtimeTag === "local") {
const builtRuntimePath = localPath ? localPath : path.join(
OVERRIDE_RUNTIME_PATH || `../target/release/wbuild/${runtimeName}-runtime/`,
`${runtimeName}_runtime.compact.compressed.wasm`
);
const code = fs.readFileSync(builtRuntimePath);
fs.writeFileSync(runtimePath, `0x${code.toString("hex")}`);
} else if (!fs.existsSync(runtimePath)) {
console.log(` Missing ${runtimePath} locally, downloading it...`);
child_process.execSync(
`mkdir -p ${path.dirname(
runtimePath
)} && wget -q https://github.com/PureStake/moonbeam/releases/download/${runtimeTag}/${runtimeName}-${runtimeTag}.wasm -O ${runtimePath}.bin`
);
const code = fs.readFileSync(`${runtimePath}.bin`);
fs.writeFileSync(runtimePath, `0x${code.toString("hex")}`);
console.log(`${runtimePath} downloaded !`);
}
return runtimePath;
}
// src/lib/governanceProcedures.ts
import {
GLMR,
alith,
baltathar,
charleth,
dorothy,
ethan,
faith,
filterAndApply,
signAndSend
} from "@moonwall/util";
import { blake2AsHex } from "@polkadot/util-crypto";
var COUNCIL_MEMBERS = [baltathar, charleth, dorothy];
var COUNCIL_THRESHOLD = Math.ceil(COUNCIL_MEMBERS.length * 2 / 3);
var TECHNICAL_COMMITTEE_MEMBERS = [alith, baltathar];
var TECHNICAL_COMMITTEE_THRESHOLD = Math.ceil(
TECHNICAL_COMMITTEE_MEMBERS.length * 2 / 3
);
var OPEN_TECHNICAL_COMMITTEE_MEMBERS = [alith, baltathar];
var OPEN_TECHNICAL_COMMITTEE_THRESHOLD = Math.ceil(
OPEN_TECHNICAL_COMMITTEE_MEMBERS.length * 2 / 3
);
var executeOpenTechCommitteeProposal = async (api, encodedHash) => {
console.log("Executing OpenTechCommittee proposal");
const queryPreimage = await api.query.preimage.requestStatusFor(encodedHash);
if (queryPreimage.isNone) {
throw new Error("Preimage not found");
}
process.stdout.write(`Sending proposal + vote for ${encodedHash}...`);
const proposalLen = queryPreimage.unwrap().asUnrequested.len;
const dispatchCallHex = api.tx.whitelist.dispatchWhitelistedCall(encodedHash, proposalLen, {
refTime: 2e9,
proofSize: 1e5
}).method.toHex();
const dispatchCallPreimageHash = blake2AsHex(dispatchCallHex);
await signAndSend(api.tx.preimage.notePreimage(dispatchCallHex), charleth);
const queryDispatchPreimage = await api.query.preimage.requestStatusFor(
dispatchCallPreimageHash
);
if (queryDispatchPreimage.isNone) {
throw new Error("Dispatch preimage not found");
}
const dispatchCallPreimageLen = queryDispatchPreimage.unwrap().asUnrequested.len;
await signAndSend(
api.tx.referenda.submit(
{
Origins: { whitelistedcaller: "WhitelistedCaller" }
},
{
Lookup: {
hash: dispatchCallPreimageHash,
len: dispatchCallPreimageLen
}
},
{ After: { After: 0 } }
),
charleth
);
const proposalId = (await api.query.referenda.referendumCount()).toNumber() - 1;
if (proposalId < 0) {
throw new Error("Proposal id not found");
}
await api.tx.referenda.placeDecisionDeposit(proposalId).signAndSend(alith);
process.stdout.write(`Sending proposal to openTechCommittee to whitelist ${encodedHash}...`);
await signAndSend(
api.tx.openTechCommitteeCollective.propose(2, api.tx.whitelist.whitelistCall(encodedHash), 100)
);
const openTechProposal = (await api.query.openTechCommitteeCollective.proposals()).at(
-1
);
if (!openTechProposal || openTechProposal?.isEmpty) {
throw new Error("OpenTechProposal not found");
}
const index = (await api.query.openTechCommitteeCollective.proposalCount()).toNumber() - 1;
if (index < 0) {
throw new Error("OpenTechProposal index not found");
}
process.stdout.write("\u2705\n");
const baltaNonce = (await api.rpc.system.accountNextIndex(baltathar.address)).toNumber();
process.stdout.write("Voting on openTechCommittee proposal...");
await Promise.all([
signAndSend(api.tx.openTechCommitteeCollective.vote(openTechProposal, index, true)),
signAndSend(
api.tx.openTechCommitteeCollective.vote(openTechProposal, index, true),
baltathar,
baltaNonce
),
signAndSend(
api.tx.openTechCommitteeCollective.close(
openTechProposal,
index,
{
refTime: 2e9,
proofSize: 1e5
},
100
),
baltathar,
baltaNonce + 1
)
]);
process.stdout.write("\u2705\n");
process.stdout.write("Voting on main referendum proposal...");
const bal = (await api.query.system.account(dorothy.address)).data.free.toBigInt();
if (bal <= GLMR) {
throw new Error("Dorothy has no funds to vote with");
}
await signAndSend(
api.tx.convictionVoting.vote(proposalId, {
Standard: {
vote: { aye: true, conviction: "Locked6x" },
balance: bal - GLMR
}
}),
dorothy
);
process.stdout.write("\u2705\n");
process.stdout.write(`Waiting for referendum [${proposalId}] to be no longer ongoing...`);
let referendaInfo;
for (; ; ) {
try {
referendaInfo = (await api.query.referenda.referendumInfoFor(proposalId)).unwrap();
if (!referendaInfo?.isOngoing) {
process.stdout.write("\u2705\n");
break;
}
await new Promise((resolve) => setTimeout(resolve, 1e3));
} catch (e) {
console.error(e);
throw new Error(`Error querying referendum info for proposalId: ${proposalId}`);
}
}
process.stdout.write(`${referendaInfo?.isApproved ? "\u2705" : "\u274C"}
`);
if (!referendaInfo?.isApproved) {
throw new Error("Finished Referendum was not approved");
}
};
var executeProposalWithCouncil = async (api, encodedHash) => {
let nonce = (await api.rpc.system.accountNextIndex(alith.address)).toNumber();
const referendumNextIndex = (await api.query.democracy.referendumCount()).toNumber();
const callData = api.consts.system.version.specVersion.toNumber() >= 2e3 ? { Legacy: encodedHash } : encodedHash;
const external = api.tx.democracy.externalProposeMajority(callData);
const fastTrack = api.tx.democracy.fastTrack(encodedHash, 1, 0);
const voteAmount = 1n * 10n ** BigInt(api.registry.chainDecimals[0]);
process.stdout.write(`Sending motion + fast-track + vote for ${encodedHash}...`);
await Promise.all([
api.tx.councilCollective.propose(1, external, external.length).signAndSend(alith, { nonce: nonce++ }),
api.tx.techCommitteeCollective.propose(1, fastTrack, fastTrack.length).signAndSend(alith, { nonce: nonce++ }),
api.tx.democracy.vote(referendumNextIndex, {
Standard: {
balance: voteAmount,
vote: { aye: true, conviction: 1 }
}
}).signAndSend(alith, { nonce: nonce++ })
]);
process.stdout.write("\u2705\n");
process.stdout.write(`Waiting for referendum [${referendumNextIndex}] to be executed...`);
let referenda;
while (!referenda) {
try {
referenda = ((await api.query.democracy.referendumInfoOf.entries()).find(
(ref) => ref[1].unwrap().isFinished && api.registry.createType("u32", ref[0].toU8a().slice(-4)).toNumber() === referendumNextIndex
)?.[1]).unwrap();
} catch {
await new Promise((resolve) => setTimeout(resolve, 1e3));
}
}
process.stdout.write(`${referenda.asFinished.approved ? "\u2705" : "\u274C"}
`);
if (!referenda.asFinished.approved) {
throw new Error("Finished Referendum was not approved");
}
};
var cancelReferendaWithCouncil = async (api, refIndex) => {
const proposal = api.tx.democracy.cancelReferendum(refIndex);
const encodedProposal = proposal.method.toHex();
const encodedHash = blake2AsHex(encodedProposal);
let nonce = (await api.rpc.system.accountNextIndex(alith.address)).toNumber();
await api.tx.democracy.notePreimage(encodedProposal).signAndSend(alith, { nonce: nonce++ });
await executeProposalWithCouncil(api, encodedHash);
};
// src/lib/upgradeProcedures.ts
async function upgradeRuntime(api, preferences) {
const options = {
waitMigration: true,
upgradeMethod: "Sudo",
...preferences
};
return new Promise(async (resolve, reject) => {
const log = (text) => {
if (options.logger) {
if (typeof options.logger === "function") {
return options.logger(text);
}
if (typeof options.logger.info === "function") {
return options.logger.info(text);
}
}
return;
};
if (!options.runtimeName) {
throw new Error("'runtimeName' is required to upgrade runtime");
}
if (!options.runtimeTag) {
throw new Error("'runtimeTag' is required to upgrade runtime");
}
if (!options.from) {
throw new Error("'from' is required to upgrade runtime");
}
try {
const code = fs2.readFileSync(
await getRuntimeWasm(options.runtimeName, options.runtimeTag, options.localPath)
).toString();
log("Checking if upgrade is needed...");
const existingCode = await api.rpc.state.getStorage(":code");
if (!existingCode) {
throw "No existing runtime code found";
}
if (existingCode.toString() === code) {
reject(
`Runtime upgrade with same code: ${existingCode.toString().slice(0, 20)} vs ${code.toString().slice(0, 20)}`
);
}
let nonce = (await api.rpc.system.accountNextIndex(options.from.address)).toNumber();
switch (options.upgradeMethod) {
case "Sudo": {
log(
`Sending sudo.setCode (${sha256(Buffer.from(code))} [~${Math.floor(
code.length / 1024
)} kb])...`
);
const isWeightV1 = !api.registry.createType("Weight").proofSize;
await api.tx.sudo.sudoUncheckedWeight(
await api.tx.system.setCodeWithoutChecks(code),
isWeightV1 ? "1" : {
proofSize: 1,
refTime: 1
}
).signAndSend(options.from, { nonce: nonce++ });
log("\u2705");
break;
}
case "Governance": {
log("Using governance...");
const proposal = api.consts.system.version.specVersion.toNumber() >= 2400 ? api.tx.parachainSystem.authorizeUpgrade(blake2AsHex2(code), true) : api.tx.parachainSystem.authorizeUpgrade(blake2AsHex2(code));
const encodedProposal = proposal.method.toHex();
const encodedHash = blake2AsHex2(encodedProposal);
log("Checking if preimage already exists...");
const preImageExists = api.query.preimage && await api.query.preimage.statusFor(encodedHash);
const democracyPreImageExists = !api.query.preimage && await api.query.democracy.preimages(encodedHash);
if (api.query.preimage && preImageExists.isSome && preImageExists.unwrap().isRequested) {
log(`Preimage ${encodedHash} already exists !
`);
} else if (!api.query.preimage && democracyPreImageExists) {
log(`Preimage ${encodedHash} already exists !
`);
} else {
log(
`Registering preimage (${sha256(Buffer.from(code))} [~${Math.floor(
code.length / 1024
)} kb])...`
);
if (api.query.preimage) {
await api.tx.preimage.notePreimage(encodedProposal).signAndSend(options.from, { nonce: nonce++ });
} else {
await api.tx.democracy.notePreimage(encodedProposal).signAndSend(options.from, { nonce: nonce++ });
}
log("Complete \u2705");
}
const referendum = await api.query.democracy.referendumInfoOf.entries();
const referendaIndex = api.query.preimage ? referendum.filter(
(ref) => ref[1].unwrap().isOngoing && ref[1].unwrap().asOngoing.proposal.isLookup && ref[1].unwrap().asOngoing.proposal.asLookup.hash.toHex() === encodedHash
).map(
(ref) => api.registry.createType("u32", ref[0].toU8a().slice(-4)).toNumber()
)?.[0] : referendum.filter(
(ref) => ref[1].unwrap().isOngoing && ref[1].unwrap().asOngoing.proposalHash.toHex() === encodedHash
).map(
(ref) => api.registry.createType("u32", ref[0].toU8a().slice(-4)).toNumber()
)?.[0];
if (referendaIndex !== null && referendaIndex !== void 0) {
log("Vote for upgrade already in referendum, cancelling it.");
await cancelReferendaWithCouncil(api, referendaIndex);
}
await executeProposalWithCouncil(api, encodedHash);
nonce = (await api.rpc.system.accountNextIndex(options.from.address)).toNumber();
log("Enacting authorized upgrade...");
await api.tx.parachainSystem.enactAuthorizedUpgrade(code).signAndSend(options.from, { nonce: nonce++ });
log("Complete \u2705");
break;
}
case "WhiteListedCaller": {
log("Using WhiteListed Caller...");
const proposal = api.tx.parachainSystem.authorizeUpgrade(blake2AsHex2(code), true);
const encodedProposal = proposal.method.toHex();
const encodedHash = blake2AsHex2(encodedProposal);
log("Checking if preimage already exists...");
const preImageExists = api.query.preimage && await api.query.preimage.statusFor(encodedHash);
if (preImageExists.isSome && preImageExists.unwrap().isRequested) {
log(`Preimage ${encodedHash} already exists !
`);
} else {
log(
`Registering preimage (${sha256(Buffer.from(code))} [~${Math.floor(
code.length / 1024
)} kb])...`
);
await api.tx.preimage.notePreimage(encodedProposal).signAndSend(options.from, { nonce: nonce++ });
log("Complete \u2705");
}
const referendum = await api.query.referenda.referendumInfoFor.entries();
const _referendaIndex = referendum.filter(
(ref) => ref[1].unwrap().isOngoing && ref[1].unwrap().asOngoing.proposal.isLookup && ref[1].unwrap().asOngoing.proposal.asLookup.hash.toHex() === encodedHash
).map(
(ref) => api.registry.createType("u32", ref[0].toU8a().slice(-4)).toNumber()
)?.[0];
await executeOpenTechCommitteeProposal(api, encodedHash);
break;
}
}
log(`Waiting to apply new runtime (${chalk.red("~4min")})...`);
let isInitialVersion = true;
const unsub = await api.rpc.state.subscribeStorage([":code"], async (newCode) => {
if (!isInitialVersion) {
const blockNumber = (await api.rpc.chain.getHeader()).number.toNumber();
log(
`Complete \u2705 [New Code: ${newCode.toString().slice(0, 5)}...${newCode.toString().slice(-4)} , Old Code:${existingCode.toString().slice(0, 5)}...${existingCode.toString().slice(-4)}] [#${blockNumber}]`
);
unsub();
if (newCode.toString() !== code) {
reject(
`Unexpected new code: ${newCode.toString().slice(0, 20)} vs ${code.toString().slice(0, 20)}`
);
}
if (options.waitMigration) {
const blockToWait = (await api.rpc.chain.getHeader()).number.toNumber() + 1;
await new Promise(async (resolve2) => {
const subBlocks = await api.rpc.chain.subscribeNewHeads(async (header) => {
if (header.number.toNumber() === blockToWait) {
subBlocks();
resolve2(blockToWait);
}
});
});
}
resolve(blockNumber);
}
isInitialVersion = false;
});
} catch (e) {
console.error(e);
console.error("Failed to setCode");
reject(e);
}
});
}
// src/lib/globalContext.ts
import { createLogger as createLogger15 } from "@moonwall/util";
import zombie from "@zombienet/orchestrator";
import Docker4 from "dockerode";
import { ChildProcess, exec as exec3, execSync as execSync4 } from "child_process";
import fs17 from "fs";
import net3 from "net";
import path13 from "path";
import readline from "readline";
import { setTimeout as timer4 } from "timers/promises";
import { promisify as promisify3 } from "util";
import invariant5 from "tiny-invariant";
// src/internal/logging.ts
var originalWrite = process.stderr.write.bind(process.stderr);
var blockList = [
"has multiple versions, ensure that there is only one installed",
"Unable to map [u8; 32] to a lookup index",
"Either remove and explicitly install matching versions or dedupe using your package manager."
];
process.stderr.write = (chunk, encodingOrCallback, callback) => {
let shouldWrite = true;
if (typeof chunk === "string") {
shouldWrite = !blockList.some((phrase) => chunk.includes(phrase));
}
if (shouldWrite) {
if (typeof encodingOrCallback === "function") {
return originalWrite.call(process.stderr, chunk, void 0, encodingOrCallback);
}
return originalWrite.call(process.stderr, chunk, encodingOrCallback, callback);
}
const cb = typeof encodingOrCallback === "function" ? encodingOrCallback : callback;
if (cb) cb(null);
return true;
};
// src/internal/cmdFunctions/downloader.ts
import { SingleBar, Presets } from "cli-progress";
import fs3 from "fs";
import { Readable } from "stream";
// src/internal/cmdFunctions/fetchArtifact.ts
import fs4 from "fs/promises";
import path3 from "path";
import semver from "semver";
import chalk2 from "chalk";
// src/internal/processHelpers.ts
import child_process2 from "child_process";
import { promisify } from "util";
import { createLogger } from "@moonwall/util";
var logger = createLogger({ name: "actions:runner" });
var debug = logger.debug.bind(logger);
var execAsync = promisify(child_process2.exec);
var withTimeout = (promise, ms) => {
return Promise.race([
promise,
new Promise(
(_, reject) => setTimeout(() => reject(new Error("Operation timed out")), ms)
)
]);
};
// src/internal/cmdFunctions/fetchArtifact.ts
import { minimatch } from "minimatch";
// src/lib/repoDefinitions/moonbeam.ts
var repo = {
name: "moonbeam",
binaries: [
{
name: "moonbeam",
defaultArgs: [
"--no-hardware-benchmarks",
"--no-telemetry",
"--reserved-only",
"--rpc-cors=all",
"--unsafe-rpc-external",
"--unsafe-force-node-key-generation",
"--no-grandpa",
"--sealing=manual",
"--force-authoring",
"--database=paritydb",
"--no-prometheus",
"--alice",
"--chain=moonbase-dev",
"--tmp"
]
},
{ name: "moonbase-runtime" },
{ name: "moonbeam-runtime" },
{ name: "moonriver-runtime" }
],
ghAuthor: "moonbeam-foundation",
ghRepo: "moonbeam"
};
var moonbeam_default = repo;
// src/lib/repoDefinitions/polkadot.ts
var repo2 = {
name: "polkadot",
binaries: [
{ name: "polkadot" },
{ name: "polkadot-prepare-worker" },
{ name: "polkadot-execute-worker" }
],
ghAuthor: "paritytech",
ghRepo: "polkadot-sdk"
};
var polkadot_default = repo2;
// src/lib/repoDefinitions/tanssi.ts
var repo3 = {
name: "tanssi",
binaries: [
{
name: "tanssi-node",
defaultArgs: ["--dev", "--sealing=manual", "--no-hardware-benchmarks"]
},
{ name: "container-chain-template-simple-node" },
{ name: "container-chain-template-frontier-node" }
],
ghAuthor: "moondance-labs",
ghRepo: "tanssi"
};
var tanssi_default = repo3;
// src/lib/repoDefinitions/index.ts
init_configReader();
function standardRepos() {
const defaultRepos = [moonbeam_default, polkadot_default, tanssi_default];
return [...defaultRepos];
}
// src/internal/cmdFunctions/fetchArtifact.ts
init_configReader();
import { execSync } from "child_process";
import { Octokit } from "@octokit/rest";
import { confirm } from "@inquirer/prompts";
var octokit = new Octokit({
baseUrl: "https://api.github.com",
log: {
debug: () => {
},
info: () => {
},
warn: console.warn,
error: console.error
}
});
// src/internal/cmdFunctions/initialisation.ts
import fs5 from "fs/promises";
import { confirm as confirm2, input, number } from "@inquirer/prompts";
// src/internal/cmdFunctions/tempLogs.ts
import path4 from "path";
import fs6 from "fs";
// src/internal/commandParsers.ts
import { createLogger as createLogger3 } from "@moonwall/util";
import path5 from "path";
import net from "net";
import { Effect as Effect3 } from "effect";
// src/lib/shardManager.ts
var ShardManager = class _ShardManager {
static instance = null;
_shardInfo = null;
constructor() {
}
static getInstance() {
if (!_ShardManager.instance) {
_ShardManager.instance = new _ShardManager();
}
return _ShardManager.instance;
}
/**
* Initialize shard configuration from command line argument or environment
* @param shardArg Optional shard argument from CLI (format: "current/total")
*/
initializeSharding(shardArg) {
if (shardArg) {
this._shardInfo = this.parseShardString(shardArg);
process.env.MOONWALL_TEST_SHARD = shardArg;
} else if (process.env.MOONWALL_TEST_SHARD) {
this._shardInfo = this.parseShardString(process.env.MOONWALL_TEST_SHARD);
} else {
this._shardInfo = { current: 1, total: 1, isSharded: false };
}
}
/**
* Get current shard information
*/
getShardInfo() {
if (!this._shardInfo) {
this.initializeSharding();
}
return this._shardInfo;
}
/**
* Get shard index (0-based) for port calculations
*/
getShardIndex() {
return this.getShardInfo().current - 1;
}
/**
* Get total number of shards
*/
getTotalShards() {
return this.getShardInfo().total;
}
/**
* Check if sharding is enabled
*/
isSharded() {
return this.getShardInfo().isSharded;
}
/**
* Reset shard configuration (mainly for testing)
*/
reset() {
this._shardInfo = null;
delete process.env.MOONWALL_TEST_SHARD;
}
parseShardString(shardString) {
if (!shardString.includes("/")) {
throw new Error(
`Invalid shard format: "${shardString}". Expected format: "current/total" (e.g., "1/3")`
);
}
const [currentStr, totalStr] = shardString.split("/");
const current = parseInt(currentStr, 10);
const total = parseInt(totalStr, 10);
if (Number.isNaN(current) || Number.isNaN(total) || current < 1 || total < 1) {
throw new Error(
`Invalid shard numbers in "${shardString}". Both current and total must be positive integers.`
);
}
if (current > total) {
throw new Error(
`Invalid shard configuration: current shard ${current} cannot be greater than total ${total}`
);
}
const isSharded = total > 1;
return { current, total, isSharded };
}
};
var shardManager = ShardManager.getInstance();
// src/internal/commandParsers.ts
import invariant from "tiny-invariant";
// src/internal/effect/StartupCacheService.ts
import { Command, FileSystem as FileSystem2, Path } from "@effect/platform";
import { NodeContext } from "@effect/platform-node";
import { createLogger as createLogger2 } from "@moonwall/util";
import { Context, Duration as Duration2, Effect as Effect2, Layer, Option, Stream } from "effect";
import * as crypto from "crypto";
// src/internal/effect/errors.ts
import { Data } from "effect";
var PortDiscoveryError = class extends Data.TaggedError("PortDiscoveryError") {
};
var NodeLaunchError = class extends Data.TaggedError("NodeLaunchError") {
};
var NodeReadinessError = class extends Data.TaggedError("NodeReadinessError") {
};
var ProcessError = class extends Data.TaggedError("ProcessError") {
};
var StartupCacheError = class extends Data.TaggedError("StartupCacheError") {
};
var FileLockError = class extends Data.TaggedError("FileLockError") {
};
// src/internal/effect/FileLock.ts
import { FileSystem } from "@effect/platform";
import { Duration, Effect, Schedule } from "effect";
import * as os from "os";
var LOCK_MAX_AGE = Duration.minutes(2);
var LOCK_POLL_INTERVAL = Duration.millis(500);
var isProcessAlive = (pid) => Effect.try(() => {
process.kill(pid, 0);
return true;
}).pipe(Effect.orElseSucceed(() => false));
var isLockStale = (info) => Effect.gen(function* () {
const isTimedOut = Date.now() - info.timestamp > Duration.toMillis(LOCK_MAX_AGE);
if (isTimedOut) return true;
const isSameHost = info.hostname === os.hostname();
if (!isSameHost) return false;
const alive = yield* isProcessAlive(info.pid);
return !alive;
});
var cleanupStaleLock = (lockPath) => Effect.gen(function* () {
const fs18 = yield* FileSystem.FileSystem;
const infoPath = `${lockPath}/lock.json`;
const exists = yield* fs18.exists(infoPath).pipe(Effect.orElseSucceed(() => false));
if (!exists) return;
const content = yield* fs18.readFileString(infoPath).pipe(Effect.orElseSucceed(() => ""));
const info = yield* Effect.try(() => JSON.parse(content)).pipe(
Effect.orElseSucceed(() => null)
);
if (!info) return;
const stale = yield* isLockStale(info);
if (stale) {
yield* fs18.remove(lockPath, { recursive: true }).pipe(Effect.ignore);
}
});
var writeLockInfo = (lockPath) => Effect.gen(function* () {
const fs18 = yield* FileSystem.FileSystem;
const info = {
pid: process.pid,
timestamp: Date.now(),
hostname: os.hostname()
};
yield* fs18.writeFileString(`${lockPath}/lock.json`, JSON.stringify(info)).pipe(Effect.ignore);
});
var tryAcquireLock = (lockPath) => Effect.gen(function* () {
const fs18 = yield* FileSystem.FileSystem;
yield* cleanupStaleLock(lockPath);
yield* fs18.makeDirectory(lockPath).pipe(Effect.mapError(() => new FileLockError({ reason: "acquisition_failed", lockPath })));
yield* writeLockInfo(lockPath);
});
var acquireFileLock = (lockPath, timeout = Duration.minutes(2)) => tryAcquireLock(lockPath).pipe(
Effect.retry(Schedule.fixed(LOCK_POLL_INTERVAL).pipe(Schedule.upTo(timeout))),
Effect.catchAll(() => Effect.fail(new FileLockError({ reason: "timeout", lockPath })))
);
var releaseFileLock = (lockPath) => Effect.gen(function* () {
const fs18 = yield* FileSystem.FileSystem;
yield* fs18.remove(lockPath, { recursive: true }).pipe(Effect.ignore);
});
var withFileLock = (lockPath, effect, timeout = Duration.minutes(2)) => Effect.acquireUseRelease(
acquireFileLock(lockPath, timeout),
() => effect,
() => releaseFileLock(lockPath)
);
// src/internal/effect/StartupCacheService.ts
var logger2 = createLogger2({ name: "StartupCacheService" });
var StartupCacheService = class extends Context.Tag("StartupCacheService")() {
};
var hashFile = (filePath) => Effect2.gen(function* () {
const fs18 = yield* FileSystem2.FileSystem;
const hash = crypto.createHash("sha256");
yield* fs18.stream(filePath).pipe(Stream.runForEach((chunk) => Effect2.sync(() => hash.update(chunk))));
return hash.digest("hex");
}).pipe(Effect2.mapError((cause) => new StartupCacheError({ cause, operation: "hash" })));
var findPrecompiledWasm = (dir) => Effect2.gen(function* () {
const fs18 = yield* FileSystem2.FileSystem;
const pathService = yield* Path.Path;
const exists = yield* fs18.exists(dir);
if (!exists) return Option.none();
const files = yield* fs18.readDirectory(dir).pipe(Effect2.orElseSucceed(() => []));
const wasmFile = files.find(
(f) => f.startsWith("precompiled_wasm_") || f.endsWith(".cwasm") || f.endsWith(".wasm")
);
return wasmFile ? Option.some(pathService.join(dir, wasmFile)) : Option.none();
}).pipe(Effect2.catchAll(() => Effect2.succeed(Option.none())));
var checkCache = (cacheDir, hashPath, expectedHash) => Effect2.gen(function* () {
const fs18 = yield* FileSystem2.FileSystem;
const savedHash = yield* fs18.readFileString(hashPath).pipe(Effect2.orElseSucceed(() => ""));
if (savedHash.trim() !== expectedHash) return Option.none();
const wasmPath = yield* findPrecompiledWasm(cacheDir);
if (Option.isNone(wasmPath)) return Option.none();
const accessible = yield* fs18.access(wasmPath.value).pipe(
Effect2.as(true),
Effect2.orElseSucceed(() => false)
);
return accessible ? wasmPath : Option.none();
});
var runPrecompile = (binPath, chainArg, outputDir) => Effect2.gen(function* () {
const fs18 = yield* FileSystem2.FileSystem;
const pathService = yield* Path.Path;
const args = chainArg ? ["precompile-wasm", chainArg, outputDir] : ["precompile-wasm", outputDir];
logger2.debug(`Precompiling: ${binPath} ${args.join(" ")}`);
const startTime = Date.now();
const exitCode = yield* Command.exitCode(Command.make(binPath, ...args)).pipe(
Effect2.mapError(
(e) => new StartupCacheError({ cause: e, operation: "precompile" })
)
);
const files = yield* fs18.readDirectory(outputDir).pipe(Effect2.mapError((e) => new StartupCacheError({ cause: e, operation: "precompile" })));
const wasmFile = files.find(
(f) => f.startsWith("precompiled_wasm_") || f.endsWith(".cwasm") || f.endsWith(".wasm")
);
if (!wasmFile) {
return yield* Effect2.fail(
new StartupCacheError({
cause: `precompile-wasm failed (code ${exitCode}): no WASM file generated`,
operation: "precompile"
})
);
}
const wasmPath = pathService.join(outputDir, wasmFile);
logger2.debug(`Precompiled in ${Date.now() - startTime}ms: ${wasmPath}`);
return wasmPath;
});
var generateRawChainSpec = (binPath, chainName, outputPath) => Effect2.gen(function* () {
const fs18 = yield* FileSystem2.FileSystem;
const args = chainName === "dev" || chainName === "default" ? ["build-spec", "--dev", "--raw"] : ["build-spec", `--chain=${chainName}`, "--raw"];
logger2.debug(`Generating raw chain spec: ${binPath} ${args.join(" ")}`);
const stdout = yield* Command.string(Command.make(binPath, ...args)).pipe(
Effect2.mapError(
(e) => new StartupCacheError({ cause: e, operation: "chainspec" })
)
);
if (!stdout.length) {
return yield* Effect2.fail(
new StartupCacheError({ cause: "build-spec produced no output", operation: "chainspec" })
);
}
yield* fs18.writeFileString(outputPath, stdout).pipe(Effect2.mapError((e) => new StartupCacheError({ cause: e, operation: "chainspec" })));
return outputPath;
});
var maybeGetRawChainSpec = (binPath, chainName, cacheSubDir, shouldGenerate) => Effect2.gen(function* () {
if (!shouldGenerate) return Option.none();
const fs18 = yield* FileSystem2.FileSystem;
const pathService = yield* Path.Path;
const rawSpecPath = pathService.join(cacheSubDir, `${chainName}-raw.json`);
const exists = yield* fs18.exists(rawSpecPath).pipe(Effect2.orElseSucceed(() => false));
if (exists) return Option.some(rawSpecPath);
return yield* generateRawChainSpec(binPath, chainName, rawSpecPath).pipe(
Effect2.map(Option.some),
Effect2.catchAll(() => Effect2.succeed(Option.none()))
);
});
var getCachedArtifactsImpl = (config) => Effect2.gen(function* () {
const fs18 = yield* FileSystem2.FileSystem;
const pathService = yield* Path.Path;
const binaryHash = yield* hashFile(config.binPath);
const shortHash = binaryHash.substring(0, 12);
const chainName = config.isDevMode ? "dev" : config.chainArg?.match(/--chain[=\s]?(\S+)/)?.[1] || "default";
const binName = pathService.basename(config.binPath);
const cacheSubDir = pathService.join(config.cacheDir, `${binName}-${chainName}-${shortHash}`);
const hashPath = pathService.join(cacheSubDir, "binary.hash");
const lockPath = pathService.join(config.cacheDir, `${binName}-${chainName}.lock`);
yield* fs18.makeDirectory(cacheSubDir, { recursive: true }).pipe(Effect2.mapError((e) => new StartupCacheError({ cause: e, operation: "cache" })));
const cached = yield* checkCache(cacheSubDir, hashPath, binaryHash);
if (Option.isSome(cached)) {
logger2.debug(`Using cached precompiled WASM: ${cached.value}`);
const rawChainSpecPath = yield* maybeGetRawChainSpec(
config.binPath,
chainName,
cacheSubDir,
config.generateRawChainSpec ?? false
);
return {
precompiledPath: cached.value,
fromCache: true,
rawChainSpecPath: Option.getOrUndefined(rawChainSpecPath)
};
}
return yield* withFileLock(
lockPath,
Effect2.gen(function* () {
const nowCached = yield* checkCache(cacheSubDir, hashPath, binaryHash);
if (Option.isSome(nowCached)) {
logger2.debug(
`Using cached precompiled WASM (created by another process): ${nowCached.value}`
);
const rawChainSpecPath2 = yield* maybeGetRawChainSpec(
config.binPath,
chainName,
cacheSubDir,
config.generateRawChainSpec ?? false
);
return {
precompiledPath: nowCached.value,
fromCache: true,
rawChainSpecPath: Option.getOrUndefined(rawChainSpecPath2)
};
}
logger2.debug("Precompiling WASM (this may take a moment)...");
const wasmPath = yield* runPrecompile(config.binPath, config.chainArg, cacheSubDir);
yield* fs18.writeFileString(hashPath, binaryHash).pipe(Effect2.mapError((e) => new StartupCacheError({ cause: e, operation: "cache" })));
const rawChainSpecPath = yield* maybeGetRawChainSpec(
config.binPath,
chainName,
cacheSubDir,
config.generateRawChainSpec ?? false
);
return {
precompiledPath: wasmPath,
fromCache: false,
rawChainSpecPath: Option.getOrUndefined(rawChainSpecPath)
};
}),
Duration2.minutes(2)
);
});
var StartupCacheServiceLive = Layer.succeed(StartupCacheService, {
getCachedArtifacts: (config) => getCachedArtifactsImpl(config).pipe(
Effect2.mapError(
(e) => e._tag === "FileLockError" ? new StartupCacheError({ cause: e, operation: "lock" }) : e
),
Effect2.provide(NodeContext.layer)
)
});
var StartupCacheServiceTestable = Layer.succeed(StartupCacheService, {
getCachedArtifacts: (config) => getCachedArtifactsImpl(config).pipe(
Effect2.mapError(
(e) => e._tag === "FileLockError" ? new StartupCacheError({ cause: e, operation: "lock" }) : e
)
)
});
// src/internal/commandParsers.ts
var logger3 = createLogger3({ name: "commandParsers" });
function parseZombieCmd(launchSpec) {
if (launchSpec) {
return { cmd: launchSpec.configPath };
}
throw new Error(
"No ZombieSpec found in config. Are you sure your moonwall.config.json file has the correct 'configPath' in zombieSpec?"
);
}
function fetchDefaultArgs(binName, additionalRepos = []) {
let defaultArgs;
const repos = [...standardRepos(), ...additionalRepos];
for (const repo4 of repos) {
const foundBin = repo4.binaries.find((bin) => bin.name === binName);
if (foundBin) {
defaultArgs = foundBin.defaultArgs;
break;
}
}
if (!defaultArgs) {
defaultArgs = ["--dev"];
}
return defaultArgs;
}
var LaunchCommandParser = class _LaunchCommandParser {
args;
cmd;
launch;
launchSpec;
launchOverrides;
constructor(options) {
const { launchSpec, additionalRepos, launchOverrides } = options;
this.launchSpec = launchSpec;
this.launchOverrides = launchOverrides;
this.launch = !launchSpec.running ? true : launchSpec.running;
this.cmd = launchSpec.binPath;
this.args = launchSpec.options ? [...launchSpec.options] : fetchDefaultArgs(path5.basename(launchSpec.binPath), additionalRepos);
}
overrideArg(newArg) {
const newArgKey = newArg.split("=")[0];
const existingIndex = this.args.findIndex((arg) => arg.startsWith(`${newArgKey}=`));
if (existingIndex !== -1) {
this.args[existingIndex] = newArg;
} else {
this.args.push(newArg);
}
}
async withPorts() {
if (process.env.MOON_RECYCLE === "true") {
const existingPort = process.env.MOONWALL_RPC_PORT;
if (existingPort) {
this.overrideArg(`--rpc-port=${existingPort}`);
}
return this;
}
if (this.launchSpec.ports) {
const ports = this.launchSpec.ports;
if (ports.p2pPort) {
this.overrideArg(`--port=${ports.p2pPort}`);
}
if (ports.wsPort) {
this.overrideArg(`--ws-port=${ports.wsPort}`);
}
if (ports.rpcPort) {
this.overrideArg(`--rpc-port=${ports.rpcPort}`);
}
} else {
const freePort = (await getFreePort()).toString();
process.env.MOONWALL_RPC_PORT = freePort;
this.overrideArg(`--rpc-port=${freePort}`);
}
return this;
}
withDefaultForkConfig() {
const forkOptions = this.launchSpec.defaultForkConfig;
if (forkOptions) {
this.applyForkOptions(forkOptions);
}
return this;
}
withLaunchOverrides() {
if (this.launchOverrides?.forkConfig) {
this.applyForkOptions(this.launchOverrides.forkConfig);
}
return this;
}
print() {
logger3.debug(`Command to run: ${this.cmd}`);
logger3.debug(`Arguments: ${this.args.join(" ")}`);
return this;
}
applyForkOptions(forkOptions) {
if (forkOptions.url) {
invariant(forkOptions.url.startsWith("http"), "Fork URL must start with http:// or https://");
this.overrideArg(`--fork-chain-from-rpc=${forkOptions.url}`);
}
if (forkOptions.blockHash) {
this.overrideArg(`--block=${forkOptions.blockHash}`);
}
if (forkOptions.stateOverridePath) {
this.overrideArg(`--fork-state-overrides=${forkOptions.stateOverridePath}`);
}
if (forkOptions.verbose) {
this.overrideArg("-llazy-loading=trace");
}
}
/**
* Cache startup artifacts if enabled in launchSpec.
* This uses an Effect-based service that caches artifacts by binary hash.
*
* When cacheStartupArtifacts is enabled, this generates:
* 1. Precompiled WASM for the runtime
* 2. Raw chain spec to skip genesis WASM compilation
*
* This reduces startup from ~3s to ~200ms (~10x improvement).
*/
async withStartupCache() {
if (!this.launchSpec.cacheStartupArtifacts) {
return this;
}
if (this.launchSpec.useDocker) {
logger3.warn("Startup caching is not supported for Docker images, skipping");
return this;
}
const chainArg = this.args.find((arg) => arg.startsWith("--chain"));
const hasDevFlag = this.args.includes("--dev");
const existingChainName = chainArg?.match(/--chain[=\s]?(\S+)/)?.[1];
const canGenerateRawSpec = hasDevFlag || !!existingChainName;
const cacheDir = this.launchSpec.startupCacheDir || path5.join(process.cwd(), "tmp", "startup-cache");
const program = StartupCacheService.pipe(
Effect3.flatMap(
(service) => service.getCachedArtifacts({
binPath: this.launchSpec.binPath,
chainArg,
cacheDir,
// Generate raw chain spec for faster startup (works for both --dev and --chain=XXX)
generateRawChainSpec: canGenerateRawSpec,
// Pass dev mode flag for proper chain name detection
isDevMode: hasDevFlag
})
),
Effect3.provide(StartupCacheServiceLive)
);
try {
const result = await Effect3.runPromise(program);
const precompiledDir = path5.dirname(result.precompiledPath);
this.overrideArg(`--wasmtime-precompiled=${precompiledDir}`);
if (result.rawChainSpecPath) {
if (hasDevFlag) {
this.args = this.args.filter((arg) => arg !== "--dev");
this.overrideArg(`--chain=${result.rawChainSpecPath}`);
this.overrideArg("--alice");
this.overrideArg("--force-authoring");
this.overrideArg("--rpc-cors=all");
this.overrideArg(
"--node-key=0000000000000000000000000000000000000000000000000000000000000001"
);
} else if (existingChainName) {
this.overrideArg(`--chain=${result.rawChainSpecPath}`);
}
logger3.debug(`Using raw chain spec for ~10x faster startup: ${result.rawChainSpecPath}`);
}
process.env.MOONWALL_CACHE_DIR = precompiledDir;
logger3.debug(
result.fromCache ? `Using cached precompiled WASM: ${result.precompiledPath}` : `Precompiled WASM created: ${result.precompiledPath}`
);
} catch (error) {
logger3.warn(`WASM precompilation failed, continuing without: ${error}`);
}
return this;
}
build() {
return {
cmd: this.cmd,
args: this.args,
launch: this.launch
};
}
static async create(options) {
const parser = new _LaunchCommandParser(options);
const parsed = await parser.withPorts().then((p) => p.withDefaultForkConfig().withLaunchOverrides()).then((p) => p.withStartupCache());
if (options.verbose) {
parsed.print();
}
return parsed.build();
}
};
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.cjs",
`--config=${launchSpecs[0].configPath}`,
`--host=${launchSpecs[0].address ?? "127.0.0.1"}`
];
const mode = launchSpecs[0].buildBlockMode ? launchSpecs[0].buildBlockMode : "manual";
const num = mode === "batch" ? "Batch" : mode === "instant" ? "Instant" : "Manual";
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}`);
}
if (launchSpecs[0].allowUnresolvedImports) {
chopsticksArgs2.push("--allow-unresolved-imports");
}
return {
cmd: chopsticksCmd2,
args: chopsticksArgs2,
launch
};
}
const chopsticksCmd = "node";
const chopsticksArgs = ["node_modules/@acala-network/chopsticks/chopsticks.cjs", "xcm"];
for (const spec of launchSpecs) {
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
};
}
var isPortAvailable = async (port) => {
return new Promise((resolve) => {
const server = net.createServer();
server.listen(port, () => {
server.once("close", () => resolve(true));
server.close();
});
server.on("error", () => resolve(false));
});
};
var getNextAvailablePort = async (startPort) => {
let port = startPort;
while (port <= 65535) {
if (await isPortAvailable(port)) {
return port;
}
port++;
}
throw new Error(`No available ports found starting from ${startPort}`);
};
var getFreePort = async () => {
const shardIndex = shardManager.getShardIndex();
const totalShards = shardManager.getTotalShards();
const poolId = parseInt(process.env.VITEST_POOL_ID || "0", 10);
const basePort = 1e4;
const shardOffset = shardIndex * 1e3;
const poolOffset = poolId * 100;
const processOffset = process.pid % 50;
const calculatedPort = basePort + shardOffset + poolOffset + processOffset;
const startPort = Math.min(calculatedPort, 6e4 + shardIndex * 100 + poolId);
logger3.debug(
`Port calculation: shard=${shardIndex + 1}/${totalShards}, pool=${poolId}, final=${startPort}`
);
return getNextAvailablePort(startPort);
};
// src/internal/deriveTestIds.ts
import chalk3 from "chalk";
import fs7 from "fs";
import { confirm as confirm3 } from "@inquirer/prompts";
import path6 from "path";
// src/internal/testIdParser.ts
import { Lang, parse, findInFiles } from "@ast-grep/napi";
// src/internal/fileCheckers.ts
import fs8 from "fs";
import { execSync as execSync2 } from "child_process";
import chalk4 from "chalk";
import os2 from "os";
import path7 from "path";
import { select } from "@inquirer/prompts";
async function checkExists(path14) {
const binPath = path14.split(" ")[0];
const fsResult = fs8.existsSync(binPath);
if (!fsResult) {
throw new Error(
`No binary file found at location: ${binPath}
Are you sure your ${chalk4.bgWhiteBright.blackBright(
"moonwall.config.json"
)} file has the correct "binPath" in launchSpec?`
);
}
const binArch = await getBinaryArchitecture(binPath);
const currentArch = os2.arch();
if (binArch !== currentArch && binArch !== "unknown") {
throw new Error(
`The binary architecture ${chalk4.bgWhiteBright.blackBright(
binArch
)} does not match this system's architecture ${chalk4.bgWhiteBright.blackBright(
currentArch
)}
Download or compile a new binary executable for ${chalk4.bgWhiteBright.blackBright(
currentArch
)} `
);
}
return true;
}
function checkAccess(path14) {
const binPath = path14.split(" ")[0];
try {
fs8.accessSync(binPath, fs8.constants.X_OK);
} catch (_err) {
console.error(`The file ${binPath} is not executable`);
throw new Error(`The file at ${binPath} , lacks execute