UNPKG

run-project-commands

Version:

A powerful CLI toolkit for developers to run, manage, and automate project commands across JavaScript/TypeScript projects with task automation and workflow management

390 lines (380 loc) 14.1 kB
#!/usr/bin/env node // bin/cli.ts import { Command } from "commander"; // src/index.ts import path from "path"; import fs from "fs"; import { fileURLToPath } from "url"; var __filename = fileURLToPath(import.meta.url); var __dirname = path.dirname(__filename); function findPackageJson(startPath) { let currentPath = startPath; for (let i = 0; i < 5; i++) { const packagePath = path.join(currentPath, "package.json"); if (fs.existsSync(packagePath)) { return packagePath; } const parentPath = path.dirname(currentPath); if (parentPath === currentPath) { break; } currentPath = parentPath; } throw new Error("Could not find package.json in any parent directory"); } var packageJsonPath = findPackageJson(__dirname); var packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")); var VERSION = packageJson.version; function getPackageInfo() { return { name: packageJson.name, version: VERSION, description: packageJson.description }; } // src/commands/doctor.ts import { execSync } from "child_process"; import chalk from "chalk"; import ora from "ora"; import path2 from "path"; import fs2 from "fs"; import { fileURLToPath as fileURLToPath2 } from "url"; // src/utils/config.ts var CONFIG = { minNodeVersion: "v16.0.0", packageName: "run-project-commands", githubRepoUrl: "https://github.com/AdarshHatkar/run-project-commands" }; // src/commands/doctor.ts function findPackageJson2(startPath) { let currentPath = startPath; for (let i = 0; i < 5; i++) { const packagePath = path2.join(currentPath, "package.json"); if (fs2.existsSync(packagePath)) { return packagePath; } const parentPath = path2.dirname(currentPath); if (parentPath === currentPath) { break; } currentPath = parentPath; } throw new Error("Could not find package.json in any parent directory"); } function getLocalVersion() { try { const __filename3 = fileURLToPath2(import.meta.url); const __dirname3 = path2.dirname(__filename3); const packageJsonPath2 = findPackageJson2(__dirname3); const packageJson2 = JSON.parse(fs2.readFileSync(packageJsonPath2, "utf8")); return packageJson2.version; } catch (error) { console.error("Error reading package.json:", error); return null; } } async function getLatestVersion() { try { const output = execSync(`npm show ${CONFIG.packageName} version`, { encoding: "utf8" }); return output.trim(); } catch (error) { return null; } } function isGloballyInstalled() { try { const output = execSync(`npm list -g ${CONFIG.packageName}`, { encoding: "utf8" }); return !output.includes("empty"); } catch (error) { return false; } } async function doctorCommand() { console.log(chalk.blue.bold("\n\u{1FA7A} RPC Doctor - Checking system...\n")); const installSpinner = ora("Checking installation status...").start(); const isGlobal = isGloballyInstalled(); if (isGlobal) { installSpinner.succeed(chalk.green("Run Project Commands is properly installed globally.")); } else { installSpinner.warn(chalk.yellow("Run Project Commands does not appear to be installed globally. Some features may not work as expected.")); console.log(chalk.yellow(" Tip: Install globally using ") + chalk.white(`npm install -g ${CONFIG.packageName}`)); } const versionSpinner = ora("Checking for updates...").start(); const localVersion = getLocalVersion(); const latestVersion = await getLatestVersion(); if (!localVersion) { versionSpinner.fail(chalk.red("Could not determine local version.")); } else if (!latestVersion) { versionSpinner.fail(chalk.red("Could not check for updates. You may be offline.")); } else { if (localVersion === latestVersion) { versionSpinner.succeed(chalk.green(`You are running the latest version (${localVersion}).`)); } else { versionSpinner.warn(chalk.yellow(`Update available: ${localVersion} \u2192 ${latestVersion}`)); console.log(chalk.yellow(" Tip: Update using ") + chalk.white(`npm install -g ${CONFIG.packageName}@latest`)); } } const nodeSpinner = ora("Checking Node.js environment...").start(); const nodeVersion = process.version; const requiredVersion = CONFIG.minNodeVersion; if (compareVersions(nodeVersion, requiredVersion) >= 0) { nodeSpinner.succeed(chalk.green(`Node.js ${nodeVersion} (meets minimum requirement of ${requiredVersion})`)); } else { nodeSpinner.warn(chalk.yellow(`Node.js ${nodeVersion} (below minimum requirement of ${requiredVersion})`)); console.log(chalk.yellow(" Tip: Update Node.js from ") + chalk.white("https://nodejs.org/")); } console.log(chalk.blue.bold("\n\u{1FA7A} Doctor Summary:")); console.log(chalk.gray("If you encounter any issues, please report them at:")); console.log(chalk.white(`${CONFIG.githubRepoUrl}/issues `)); } function compareVersions(a, b) { const verA = a.replace("v", "").split(".").map(Number); const verB = b.replace("v", "").split(".").map(Number); for (let i = 0; i < Math.max(verA.length, verB.length); i++) { const numA = verA[i] || 0; const numB = verB[i] || 0; if (numA > numB) return 1; if (numA < numB) return -1; } return 0; } // src/commands/run.ts import path3 from "path"; import fs3 from "fs"; import chalk2 from "chalk"; import { spawn } from "child_process"; import inquirer from "inquirer"; import ora2 from "ora"; var BUILT_IN_COMMANDS = ["doctor", "run", "help"]; function handleGracefulExit(message = "Process was interrupted", exitCode = 130) { console.log(chalk2.yellow(` ${message}`)); process.exit(exitCode); } process.on("SIGINT", () => { handleGracefulExit("Operation cancelled by user (Ctrl+C)"); }); function findPackageJson3(dir = process.cwd()) { try { const packagePath = path3.join(dir, "package.json"); if (fs3.existsSync(packagePath)) { const content = JSON.parse(fs3.readFileSync(packagePath, "utf8")); return { path: packagePath, content }; } return null; } catch (error) { return null; } } function getAvailableScripts(packageJson2) { if (packageJson2 && packageJson2.scripts && Object.keys(packageJson2.scripts).length > 0) { return packageJson2.scripts; } return null; } function executeScript(scriptName, scriptCommand) { return new Promise((resolve, reject) => { console.log(chalk2.blue(` > Executing: ${chalk2.bold(scriptName)} (${scriptCommand}) `)); const packageManager = fs3.existsSync(path3.join(process.cwd(), "yarn.lock")) ? "yarn" : fs3.existsSync(path3.join(process.cwd(), "pnpm-lock.yaml")) ? "pnpm" : "npm"; let command; let args; if (packageManager === "yarn") { command = packageManager; args = [scriptName]; } else { command = packageManager; args = ["run", scriptName]; } const childProcess = spawn(command, args, { stdio: "inherit", shell: true }); childProcess.on("close", (code) => { if (code === 0) { console.log(chalk2.green(` \u2713 Script ${chalk2.bold(scriptName)} completed successfully`)); resolve(); } else { console.log(chalk2.red(` \u2717 Script ${chalk2.bold(scriptName)} failed with exit code ${code}`)); reject(new Error(`Script failed with exit code ${code}`)); } }); childProcess.on("error", (err) => { console.error(chalk2.red(` \u2717 Failed to execute script: ${err.message}`)); reject(err); }); }); } async function selectAndRunScript(scripts) { const scriptNames = Object.keys(scripts); try { const { selectedScript } = await inquirer.prompt([ { type: "list", name: "selectedScript", message: "Select a script to run:", choices: scriptNames.map((name, index) => ({ name: `${index + 1}. ${chalk2.green(name)} ${chalk2.dim(`\u2192 ${scripts[name]}`)}`, value: name })), loop: false, pageSize: scriptNames.length // Show all scripts without pagination } ]); await executeScript(selectedScript, scripts[selectedScript]); } catch (error) { if (error?.name === "ExitPromptError" || error?.message?.includes("SIGINT")) { handleGracefulExit(); } else { console.error(chalk2.red(`Error selecting script: ${error?.message || "Unknown error"}`)); process.exit(1); } } } async function resolveCommandConflict(scriptName) { try { const { choice } = await inquirer.prompt([ { type: "list", name: "choice", message: `'${scriptName}' exists as both a script and an RPC command. Which would you like to run?`, choices: [ { name: `1. Run as a script (from package.json)`, value: "script" }, { name: `2. Run as an RPC command`, value: "command" } ], loop: false, pageSize: 2 // Show all options without pagination } ]); return choice; } catch (error) { if (error?.name === "ExitPromptError" || error?.message?.includes("SIGINT")) { handleGracefulExit(); return "command"; } else { console.error(chalk2.red(`Error resolving command conflict: ${error?.message || "Unknown error"}`)); process.exit(1); } } } async function isScriptName(name) { const pkg = findPackageJson3(); if (!pkg) return false; const scripts = getAvailableScripts(pkg.content); if (!scripts) return false; return !!scripts[name]; } function checkCommandConflict(scriptName) { return BUILT_IN_COMMANDS.includes(scriptName); } async function runCommand(scriptName, forceAsScript = false) { try { if (!scriptName) { console.log(chalk2.blue.bold("\n\u{1F680} Run Project Commands (RPC)")); } const showSpinner = !scriptName; const spinner = showSpinner ? ora2("Looking for scripts in package.json...").start() : null; const pkg = findPackageJson3(); if (!pkg) { if (spinner) spinner.fail(chalk2.red("No package.json found in the current directory.")); else console.error(chalk2.red("No package.json found in the current directory.")); return; } const scripts = getAvailableScripts(pkg.content); if (!scripts || Object.keys(scripts).length === 0) { if (spinner) spinner.fail(chalk2.red("No scripts found in package.json.")); else console.error(chalk2.red("No scripts found in package.json.")); return; } if (spinner) spinner.succeed(chalk2.green(`Found ${Object.keys(scripts).length} script${Object.keys(scripts).length === 1 ? "" : "s"} in package.json`)); if (scriptName) { if (!forceAsScript && checkCommandConflict(scriptName) && scripts[scriptName]) { const choice = await resolveCommandConflict(scriptName); if (choice === "script") { try { await executeScript(scriptName, scripts[scriptName]); } catch (error) { console.error(chalk2.red(`Error executing script: ${error?.message || "Unknown error"}`)); } } else { console.log(chalk2.blue(`Running '${scriptName}' as an RPC command...`)); return; } } else if (scripts[scriptName]) { try { await executeScript(scriptName, scripts[scriptName]); } catch (error) { console.error(chalk2.red(`Error executing script: ${error?.message || "Unknown error"}`)); } } else { console.error(chalk2.red(`Script '${scriptName}' not found in package.json`)); console.log(chalk2.yellow("Available scripts:")); Object.keys(scripts).forEach((name) => { console.log(` ${chalk2.cyan(name)}: ${scripts[name]}`); }); process.exit(1); } return; } await selectAndRunScript(scripts); } catch (error) { if (error?.name === "ExitPromptError" || error?.message?.includes("SIGINT")) { handleGracefulExit(); } else { console.error(chalk2.red(`Error in runCommand: ${error?.message || "Unknown error"}`)); process.exit(1); } } } // bin/cli.ts var helpExplicitlyRequested = false; var program = new Command(); var info = getPackageInfo(); program.name("rpc").description(info.description).version(`v${info.version}`, "-v, --version", "Output the current version").usage("[script|command] [options]").helpOption("-h, --help", "display help for command").on("option:help", function() { helpExplicitlyRequested = true; }); program.command("doctor").description("Check if package is installed properly and if updates are available").action(async () => { if (await isScriptName("doctor")) { const choice = await resolveCommandConflict("doctor"); if (choice === "script") { await runCommand("doctor", true); return; } } await doctorCommand(); }); program.command("run [script]").description("Run scripts from package.json (interactive if no script name provided)").action(async (script) => { await runCommand(script); }); program.on("command:*", async (operands) => { const unknownCommand = operands[0]; await runCommand(unknownCommand, true); }); if (process.argv.length === 2 || process.argv.length >= 2 && !helpExplicitlyRequested && !process.argv.includes("help")) { program.helpInformation = function() { return ""; }; runCommand(); } program.addHelpText("after", ` Script Execution: Run a script directly: rpc <script-name> Interactive script menu: rpc Examples: rpc dev Run the "dev" script from package.json rpc run test Run the "test" script from package.json rpc doctor Check installation status and available updates Note: When no arguments are provided, RPC shows available scripts from package.json. Use 'rpc help' to see this help information about other commands.`); if (helpExplicitlyRequested || process.argv.includes("help") || process.argv.length > 2 && !["run", "doctor"].includes(process.argv[2]) && process.argv[2].startsWith("-")) { program.parse(process.argv); } else if (process.argv.length > 2) { program.parse(process.argv); }