UNPKG

actionhero

Version:

The reusable, scalable, and quick node.js API server for stateless and stateful applications

226 lines (193 loc) 6.42 kB
#!/usr/bin/env node import * as path from "path"; import * as fs from "fs"; import { program, InvalidArgumentError } from "commander"; import { typescript } from "../classes/process/typescript"; import { projectRoot } from "../classes/process/projectRoot"; import { ensureNoTsHeaderOrSpecFiles } from "../modules/utils/ensureNoTsHeaderOrSpecFiles"; import { CLI } from "../classes/cli"; import { PackageJson } from "type-fest"; // load explicitly to find the type changes for the config module import "../config/api"; import "../config/plugins"; import "../config/logger"; import "../config/routes"; import { safeGlobSync } from "../modules/utils/safeGlob"; export namespace ActionheroCLIRunner { export async function run() { program.storeOptionsAsProperties(false); program.version(getVersion()); let pathsLoaded: string[] = []; try { const { config } = await import("../index"); // this project for (const p of config.general.paths.cli) { await loadDirectory(path.join(p), pathsLoaded); } // plugins for (const plugin of Object.values(config.plugins)) { if (plugin.cli !== false) { // old plugins await loadDirectory(path.join(plugin.path, "bin"), pathsLoaded); // new plugins await loadDirectory( path.join(plugin.path, "dist", "bin"), pathsLoaded, ); } } // core if (config.general.cliIncludeInternal !== false) { await loadDirectory(__dirname, pathsLoaded); } } catch (e) { // we are trying to build a new project, only load the generate command await loadDirectory(path.join(__dirname), pathsLoaded, "generate"); } program.parse(process.argv); } // --- Utils --- // export async function loadDirectory( dir: string, pathsLoaded: string[], match = "*", ) { if (!fs.existsSync(dir)) return; const realpath = fs.realpathSync(dir); if (pathsLoaded.includes(realpath)) return; pathsLoaded.push(realpath); const matcher = `${realpath}/**/+(${ typescript ? `${match}.js|*.ts` : `${match}.js` })`; const files = ensureNoTsHeaderOrSpecFiles(safeGlobSync(matcher)); for (const i in files) { const collection = await import(files[i]); for (const j in collection) { const command = collection[j]; convertCLIToCommanderAction(command); } } } export async function convertCLIToCommanderAction( cliConstructor: new () => CLI, ) { if ( Object.getPrototypeOf(cliConstructor?.prototype?.constructor || {}) .name !== "CLI" ) { return; } const instance: CLI = new cliConstructor(); const command = program .command(instance.name) .description(instance.description) .action(async (_arg1, _arg2, _arg3, _arg4, _arg5) => { await runCommand(instance, _arg1, _arg2, _arg3, _arg4, _arg5); }) .on("--help", () => { if (instance.example) { console.log(""); console.log("Example: \r\n" + " " + instance.example); } if (typeof instance.help === "function") instance.help(); }); for (const key in instance.inputs) { const input = instance.inputs[key]; if (input.flag && !input.letter) { throw new Error( `flag inputs require a short letter (${JSON.stringify(input)})`, ); } const separators = input.required || input.requiredValue ? ["<", ">"] : ["[", "]"]; const methodName = input.required ? "requiredOption" : "option"; const argString = `${input.letter ? `-${input.letter}, ` : ""}--${key} ${ input.flag ? "" : `${separators[0]}${input.placeholder || key}${ input.variadic ? "..." : "" }${separators[1]}` }`; const argProcessor = ( value: string, accumulator?: unknown[], ): unknown => { try { if (typeof input.formatter === "function") { value = input.formatter(value); } if (typeof input.validator === "function") { input.validator(value); } if (input.variadic) { if (!Array.isArray(accumulator)) accumulator = []; accumulator.push(value); return accumulator; } return value; } catch (error) { throw new InvalidArgumentError(error?.message ?? error); } }; command[methodName]( argString, input.description, argProcessor, input.default, ); } } export async function runCommand( instance: CLI, _arg1: any, _arg2: any, _arg3: any, _arg4: any, _arg5: any, ) { let toStop = false; let _arguments: string[] = []; let params: Record<string, string[]> = {}; [_arg1, _arg2, _arg3, _arg4, _arg5].forEach((arg) => { if (typeof arg?.opts === "function") { params = arg.opts(); } else if (arg !== null && arg !== undefined && typeof arg !== "object") { _arguments.push(arg); } }); params["_arguments"] = _arguments; if (instance.initialize === false && instance.start === false) { toStop = await instance.run({ params }); } else { try { const { Process } = await import("../index"); const actionHeroProcess = new Process(); if (instance.initialize) await actionHeroProcess.initialize(); if (instance.start) await actionHeroProcess.start(); toStop = await instance.run({ params }); } catch (error) { console.error(error.toString()); process.exit(1); } } if (toStop || toStop === null || toStop === undefined) { setTimeout(process.exit, 500, 0); } } export function readPackageJSON(file: string) { return JSON.parse(fs.readFileSync(file).toString()); } export function getVersion(): string { const parentPackageJSON = path.join(projectRoot, "package.json"); if (fs.existsSync(parentPackageJSON)) { const pkg: PackageJson = readPackageJSON(parentPackageJSON); return pkg.version; } else { const pkg: PackageJson = readPackageJSON( path.join(__dirname, "..", "..", "package.json"), ); return pkg.version; } } } ActionheroCLIRunner.run();