@moonwall/cli
Version:
Testing framework for the Moon family of projects
399 lines • 15.2 kB
JavaScript
import chalk from "chalk";
import clear from "clear";
import colors from "colors";
import fs from "node:fs";
import cfonts from "cfonts";
import path from "node:path";
import { SemVer, lt } from "semver";
import pkg from "../../package.json" with { type: "json" };
import { createFolders, deriveTestIds, executeScript, fetchArtifact, generateConfig, getVersions, } from "../internal";
import { configExists, importAsyncConfig } from "../lib/configReader";
import { allReposAsync, standardRepos } from "../lib/repoDefinitions";
import { runNetworkCmd } from "./runNetwork";
import { testCmd } from "./runTests";
import { Octokit } from "@octokit/rest";
import { checkbox, confirm, input, select, Separator } from "@inquirer/prompts";
const octokit = new Octokit({
baseUrl: "https://api.github.com",
log: {
debug: () => { },
info: () => { },
warn: console.warn,
error: console.error,
},
});
export async function main() {
for (;;) {
const globalConfig = (await configExists()) ? await importAsyncConfig() : undefined;
clear();
await printIntro();
if (await mainMenu(globalConfig)) {
break;
}
}
process.stdout.write("Goodbye! 👋\n");
}
async function mainMenu(config) {
const configPresent = config !== undefined;
// const questionList = {
// name: "MenuChoice",
// type: "list",
// message: "Main Menu - Please select one of the following:",
// default: 0,
// pageSize: 12,
// choices: !configPresent
// ? [
// {
// name: !configPresent
// ? "1) Initialise: Generate a new Moonwall Config File"
// : chalk.dim("1) Initialise: ✅ CONFIG ALREADY GENERATED"),
// value: "init",
// },
// {
// name: "2) Artifact Downloader: Fetch artifacts (x86) from GitHub repos",
// value: "download",
// },
// {
// name: "3) Quit Application",
// value: "quit",
// },
// ]
// : [
// {
// name: "1) Execute Script: Run scripts placed in your config defined script directory",
// value: "exec",
// },
// {
// name: "2) Network Launcher & Toolbox: Launch network, access tools: tail logs, interactive tests etc",
// value: "run",
// },
// {
// name: "3) Test Suite Execution: Run automated tests, start network if needed",
// value: "test",
// },
// {
// name: "4) Artifact Downloader: Fetch artifacts (x86) from GitHub repos",
// value: "download",
// },
// {
// name: "5) Rename TestIDs: Rename test id prefixes based on position in the directory tree",
// value: "derive",
// },
// {
// name: "6) Quit Application",
// value: "quit",
// },
// ],
// filter(val) {
// return val;
// },
// } as const;
// const answers = await inquirer.prompt(questionList);
const menuChoice = await select({
message: "Main Menu - Please select one of the following:",
default: 0,
pageSize: 12,
choices: !configPresent
? [
{
name: !configPresent
? "1) Initialise: Generate a new Moonwall Config File"
: chalk.dim("1) Initialise: ✅ CONFIG ALREADY GENERATED"),
value: "init",
},
{
name: "2) Artifact Downloader: Fetch artifacts (x86) from GitHub repos",
value: "download",
},
{
name: "3) Quit Application",
value: "quit",
},
]
: [
{
name: "1) Execute Script: Run scripts placed in your config defined script directory",
value: "exec",
},
{
name: "2) Network Launcher & Toolbox: Launch network, access tools: tail logs, interactive tests etc",
value: "run",
},
{
name: "3) Test Suite Execution: Run automated tests, start network if needed",
value: "test",
},
{
name: "4) Artifact Downloader: Fetch artifacts (x86) from GitHub repos",
value: "download",
},
{
name: "5) Rename TestIDs: Rename test id prefixes based on position in the directory tree",
value: "derive",
},
{
name: "6) Quit Application",
value: "quit",
},
],
});
switch (menuChoice) {
case "init":
await generateConfig({});
await createFolders();
return false;
case "run": {
if (!config) {
throw new Error("Config not defined, this is a defect please raise it.");
}
const chosenRunEnv = await chooseRunEnv(config);
process.env.MOON_RUN_SCRIPTS = "true";
if (chosenRunEnv.envName !== "back") {
await runNetworkCmd(chosenRunEnv);
}
return true;
}
case "test": {
if (!config) {
throw new Error("Config not defined, this is a defect please raise it.");
}
const chosenTestEnv = await chooseTestEnv(config);
if (chosenTestEnv.envName !== "back") {
process.env.MOON_RUN_SCRIPTS = "true";
await testCmd(chosenTestEnv.envName);
await input({
message: `ℹ️ Test run for ${chalk.bgWhiteBright.black(chosenTestEnv.envName)} has been completed. Press any key to continue...\n`,
});
// await inquirer.prompt({
// name: "test complete",
// type: "press-to-continue",
// anyKey: true,
// pressToContinueMessage: `ℹ️ Test run for ${chalk.bgWhiteBright.black(
// chosenTestEnv.envName
// )} has been completed. Press any key to continue...\n`,
// });
}
return true;
}
case "download":
await resolveDownloadChoice();
return false;
case "quit":
return await resolveQuitChoice();
case "exec": {
if (!config) {
throw new Error("Config not defined, this is a defect please raise it.");
}
return await resolveExecChoice(config);
}
case "derive": {
clear();
const rootDir = await input({
message: "Enter the root testSuites directory to process:",
default: "suites",
});
await deriveTestIds({ rootDir });
await input({
message: `ℹ️ Renaming task for ${chalk.bold(`/${rootDir}`)} has been completed. Press any key to continue...\n`,
});
return false;
}
default:
throw new Error("Invalid choice");
}
}
async function resolveExecChoice(config) {
const scriptDir = config.scriptsDir;
if (!scriptDir) {
await input({
message: `ℹ️ No scriptDir property defined at ${chalk.bgWhiteBright.black("moonwall.config.json")}\n Press any key to continue...\n`,
});
return false;
}
if (!fs.existsSync(scriptDir)) {
await input({
message: `ℹ️ No scriptDir found at at ${chalk.bgWhiteBright.black(path.join(process.cwd(), scriptDir))}\n Press any key to continue...\n`,
});
return false;
}
const files = await fs.promises.readdir(scriptDir);
if (!files) {
await input({
message: `ℹ️ No scripts found at ${chalk.bgWhiteBright.black(path.join(process.cwd(), config.scriptsDir || ""))}\n Press any key to continue...\n`,
});
}
const choices = files.map((file) => {
const ext = getExtString(file);
return { name: `${ext}: ${path.basename(file, "")}`, value: file };
});
for (;;) {
const selections = await checkbox({
message: "Select which scripts you'd like to run (press ↩️ with none selected to go 🔙)\n",
choices,
});
if (selections.length === 0) {
const noneSelected = await confirm({
message: "No scripts have been selected to run, do you wish to exit?",
default: true,
});
if (noneSelected) {
return false;
}
continue;
}
for (const script of selections) {
const args = await input({
message: `Enter any arguments for ${chalk.bgWhiteBright.black(script)} (press enter for none)`,
});
await executeScript(script, args);
}
await input({
message: "Press any key to continue...\n",
});
return false;
}
}
async function resolveDownloadChoice() {
const repos = (await configExists()) ? await allReposAsync() : standardRepos();
const binList = repos.reduce((acc, curr) => {
acc.push(...curr.binaries.flatMap((bin) => bin.name));
acc.push(new Separator());
acc.push("Back");
acc.push(new Separator());
return acc;
}, []);
for (;;) {
const firstChoice = await select({
message: "Download - which artifact?",
choices: binList,
});
if (firstChoice === "Back") {
return;
}
const versions = await getVersions(firstChoice, firstChoice.includes("runtime"));
const chooseversion = await select({
default: "latest",
message: "Download - which version?",
choices: [...versions, new Separator(), "Back", new Separator()],
});
if (chooseversion === "Back") {
continue;
}
const chooseLocation = await input({
message: "Download - where would you like it placed?",
default: "./tmp",
});
const result = await confirm({
message: `You are about to download ${chalk.bgWhite.blackBright(firstChoice)} v-${chalk.bgWhite.blackBright(chooseversion)} to: ${chalk.bgWhite.blackBright(chooseLocation)}.\n Would you like to continue? `,
default: true,
});
if (result === false) {
continue;
}
await fetchArtifact({
bin: firstChoice,
ver: chooseversion,
path: chooseLocation,
});
return;
}
}
const chooseTestEnv = async (config) => {
const envs = config.environments
.map((a) => ({
name: `[${a.foundation.type}] ${a.name}${a.description ? `: \t\t${a.description}` : ""}`,
value: a.name,
disabled: false,
}))
.sort((a, b) => (a.name > b.name ? -1 : +1));
envs.push(...[new Separator(), { name: "Back", value: "back" }, new Separator()]);
const envName = (await select({
message: "Select a environment to run",
pageSize: 12,
choices: envs,
}));
return { envName };
};
const chooseRunEnv = async (config) => {
const envs = config.environments.map((a) => {
const result = { name: "", value: a.name, disabled: false };
if (a.foundation.type === "dev" ||
a.foundation.type === "chopsticks" ||
a.foundation.type === "zombie") {
result.name = `[${a.foundation.type}] ${a.name}${a.description ? `: \t\t${a.description}` : ""}`;
}
else {
result.name = chalk.dim(`[${a.foundation.type}] ${a.name} NO NETWORK TO RUN`);
result.disabled = true;
}
return result;
});
const choices = [
...envs.filter(({ disabled }) => disabled === false).sort((a, b) => (a.name > b.name ? 1 : -1)),
new Separator(),
...envs.filter(({ disabled }) => disabled === true).sort((a, b) => (a.name > b.name ? 1 : -1)),
new Separator(),
{ name: "Back", value: "back" },
new Separator(),
];
const envName = (await select({
message: "Select a environment to run",
pageSize: 12,
choices,
}));
return { envName };
};
const resolveQuitChoice = async () => {
const result = await confirm({
message: "Are you sure you want to Quit?",
default: false,
});
return result;
};
const printIntro = async () => {
const currentVersion = new SemVer(pkg.version);
let remoteVersion = "";
try {
const releases = await octokit.rest.repos.listReleases({
owner: "moonsong-labs",
repo: "moonwall",
});
if (releases.status !== 200 || releases.data.length === 0) {
throw new Error("No releases found for moonsong-labs.moonwall, try again later.");
}
const json = releases.data;
remoteVersion =
json.find((a) => a.tag_name.includes("@moonwall/cli@"))?.tag_name.split("@")[2] || "unknown";
}
catch (error) {
remoteVersion = "unknown";
console.error(`Fetch Error: ${error}`);
}
cfonts.say("Moonwall", {
gradient: ["#FF66FF", "#9966FF", "#99CCFF", "#99FFFF", "#33FFFF", "#3366FF"],
transitionGradient: true,
lineHeight: 4,
});
const versionText = remoteVersion !== "unknown" && lt(currentVersion, new SemVer(remoteVersion))
? `V${currentVersion.version} (New version ${remoteVersion} available!) ${currentVersion.version}`
: `V${currentVersion.version}`;
const dividerLength = 90;
const leftPadding = Math.floor((dividerLength - versionText.length) / 2);
const rightPadding = dividerLength - versionText.length - leftPadding;
const formattedDivider = `${colors.rainbow("=".repeat(leftPadding))}${chalk.bgCyan.grey(versionText)}${colors.rainbow("=".repeat(rightPadding))}\n`;
console.log(formattedDivider);
};
const getExtString = (file) => {
const ext = path.extname(file);
switch (ext) {
case ".js":
return chalk.bgYellow.black(ext);
case ".ts":
return chalk.bgBlue.black(ext);
case ".sh":
return chalk.bgGreen.black(ext);
default:
return chalk.bgRed.black(ext);
}
};
//# sourceMappingURL=main.js.map