diff --git a/src/cli.ts b/src/cli.ts index d57ed01..5af8b34 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -16,6 +16,7 @@ import { createProject } from "./new"; import { addProviderCMD } from "./providers"; import { createExternalProviderCMD } from "./providers/create/cli"; import { printError } from "./utils/cli-ui"; +import { scriptsCommand } from "./scripts"; stdout.write(`\n${[chalk.bold.green("🐎 Expressots")]}\n\n`); @@ -28,6 +29,7 @@ yargs(hideBin(process.argv)) .command(createExternalProviderCMD()) .command(addProviderCMD()) .command(generateProject()) + .command(scriptsCommand()) .command(infoProject()) .command(helpCommand()) .demandCommand(1, "You need at least one command before moving on") @@ -35,7 +37,6 @@ yargs(hideBin(process.argv)) .fail((msg, err, yargs) => { if (msg) { if (msg.includes("Unknown argument")) { - // Get the command name const command = process.argv[2]; if (command === "run") { diff --git a/src/help/form.ts b/src/help/form.ts index 4fae5a8..a027853 100644 --- a/src/help/form.ts +++ b/src/help/form.ts @@ -15,6 +15,7 @@ const helpForm = async (): Promise => { ["new project", "new", "Generate a new project"], ["info", "i", "Provides project information"], ["resources", "r", "Displays cli commands and resources"], + ["scripts", "scripts", "Run scripts list or specific scripts"], ["help", "h", "Show command help"], [ "service", diff --git a/src/scripts/cli.ts b/src/scripts/cli.ts new file mode 100644 index 0000000..ed4a192 --- /dev/null +++ b/src/scripts/cli.ts @@ -0,0 +1,24 @@ +import { CommandModule } from "yargs"; +import { scriptsForm } from "./form"; + +const scriptsCommand = (): CommandModule => { + return { + command: "scripts [scripts..]", + describe: "Run scripts list or specific scripts", + builder: (yargs) => { + return yargs.positional("scripts", { + describe: "The names of the scripts to run", + type: "string", + array: true, + }); + }, + handler: async (argv) => { + const scripts = Array.isArray(argv.scripts) + ? argv.scripts.filter((script) => typeof script === "string") + : []; + await scriptsForm(scripts); + }, + }; +}; + +export { scriptsCommand }; diff --git a/src/scripts/form.ts b/src/scripts/form.ts new file mode 100644 index 0000000..5eb83be --- /dev/null +++ b/src/scripts/form.ts @@ -0,0 +1,133 @@ +import { ExecSyncOptions, execSync } from "child_process"; +import fs from "fs"; +import inquirer from "inquirer"; +import path from "path"; +import { printError, printWarning } from "../utils/cli-ui"; + +const cwd = process.cwd(); +const packageJsonPath = path.join(cwd, "package.json"); + +interface PackageJson { + scripts?: Record; +} + +function readPackageJson(): PackageJson { + try { + return JSON.parse(fs.readFileSync(packageJsonPath, "utf8")); + } catch (e) { + printError(`Error reading package.json`, "scripts-command"); + process.exit(1); + } +} + +function listScripts(packageJson: PackageJson): Record { + const scripts = packageJson.scripts || {}; + if (Object.keys(scripts).length === 0) { + printWarning("No scripts found in package.json", "scripts-command"); + process.exit(0); + } + return scripts; +} + +async function promptUserToSelectScripts( + scripts: Record, +): Promise<{ selectedScripts: string[] }> { + const scriptChoices = Object.keys(scripts).map((key) => ({ + name: `${key}`, + value: key, + })); + + let selectionOrder: string[] = []; + + const answers = await inquirer.prompt([ + { + type: "checkbox", + name: "selectedScripts", + message: "Select scripts to run:", + choices: scriptChoices, + filter: (selected: string[]) => { + selectionOrder = selected; + return selected; + }, + loop: false, + }, + ]); + + return answers; +} + +function executeScripts( + scripts: Record, + selectedScripts: string[], + runner: string, +): void { + selectedScripts.forEach((script) => { + console.log(`Running ${script}...`); + try { + const command = `${runner} run ${script}`; + const options: ExecSyncOptions = { + stdio: "inherit", + env: { ...process.env }, + }; + + execSync(command, options); + } catch (e) { + printWarning( + `Command ${script} cancelled or failed - ${e}`, + "scripts-command", + ); + } + }); +} + +process.stdin.on("keypress", (ch, key) => { + if (key && key.name === "escape") { + console.log("Exiting..."); + process.exit(0); + } +}); + +export const scriptsForm = async (scriptArgs: string[] = []): Promise => { + const packageJson = readPackageJson(); + const scripts = listScripts(packageJson); + + const runner = fs.existsSync("package-lock.json") + ? "npm" + : fs.existsSync("yarn.lock") + ? "yarn" + : fs.existsSync("pnpm-lock.yaml") + ? "pnpm" + : null; + + if (!runner) { + printError( + "No package manager found! Please ensure you have npm, yarn, or pnpm installed.", + "scripts-command", + ); + process.exit(1); + } + + if (scriptArgs.length > 0) { + const validScripts = scriptArgs.filter((script) => scripts[script]); + const invalidScripts = scriptArgs.filter((script) => !scripts[script]); + + if (invalidScripts.length > 0) { + console.error( + `Scripts not found in package.json: ${invalidScripts.join(", ")}`, + ); + } + + if (validScripts.length > 0) { + executeScripts(scripts, validScripts, runner); + } + } else { + const { selectedScripts } = await promptUserToSelectScripts(scripts); + + if (selectedScripts.length === 0) { + console.log("No scripts selected."); + process.exit(0); + } + + executeScripts(scripts, selectedScripts, runner); + } +}; diff --git a/src/scripts/index.ts b/src/scripts/index.ts new file mode 100644 index 0000000..c1d55cf --- /dev/null +++ b/src/scripts/index.ts @@ -0,0 +1 @@ +export * from "./cli";