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
JavaScript
// 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);
}