UNPKG

ns-init

Version:

Scaffold a ready-made CLI for Node.js + Express server (JavaScript or TypeScript)

268 lines (267 loc) • 12.9 kB
#!/usr/bin/env node "use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const commander_1 = require("commander"); const inquirer_1 = __importDefault(require("inquirer")); const fs_extra_1 = __importDefault(require("fs-extra")); const path_1 = __importDefault(require("path")); const cli_spinner_1 = require("cli-spinner"); const child_process_1 = require("child_process"); const chalk_1 = __importDefault(require("chalk")); const package_json_1 = __importDefault(require("../package.json")); /* ----------------- CLI Logic ----------------- */ /** * Runs a shell command in the specified directory. * @param {string} cmd - The command to run. * @param {string} cwd - The current working directory. */ function run(cmd, cwd) { try { (0, child_process_1.execSync)(cmd, { cwd, stdio: process.env.DEBUG ? "inherit" : "pipe" }); } catch (err) { console.error(red(`\nāŒ Failed to run: ${cmd}`)); if (process.env.DEBUG) { console.error(gray(err.stack || err.message)); } process.exit(1); } } const { yellow, green, blue, cyan, red, magenta, gray } = chalk_1.default; async function runCLI() { const program = new commander_1.Command(); program .name("ns-init") .description("Scaffold a ready-made CLI for Node.js + Express server (JavaScript or TypeScript)") .version(`v${package_json_1.default.version}`) .argument("[project-name]", "Name of the new project directory") .option("--ts", "Use the TypeScript template") .option("--js", "Use the JavaScript template") .option("--log", "Include node-js-api-response logger") .action(async (projectName, opts) => { console.log(chalk_1.default.cyan.bold("šŸš€ Welcome to Node Server Initializer CLI šŸš€\n")); if (!projectName) { const answers = await inquirer_1.default.prompt([ { type: "input", name: "projectName", message: "Enter your project name:", default: "my-node-server", validate: (input) => input.trim() !== "" || "Project name cannot be empty", }, ]); projectName = answers.projectName; } const targetDir = projectName ? path_1.default.resolve(process.cwd(), projectName) : process.cwd(); if (await fs_extra_1.default.pathExists(targetDir)) { console.error(red(`\nāŒ Project Directory already exists: ${yellow(targetDir)}`)); process.exit(1); } // Decide template via flags or interactive prompt let template; let includeLogger = false; if (opts.ts && opts.js) { console.error(red("Cannot use both --typescript and --javascript. Pick one.")); process.exit(1); } if (opts.ts) template = "TypeScript"; if (opts.js) template = "JavaScript"; if (opts.log) includeLogger = true; if (!template) { const answers = await inquirer_1.default.prompt([ { type: "list", name: "template", message: "Which template do you want to use?", choices: ["TypeScript", "JavaScript"], default: "TypeScript", }, ]); template = answers.template; } if (!includeLogger) { const answers = await inquirer_1.default.prompt([ { type: "confirm", name: "includeLogger", message: "Do you want to include node-js-api-response logger ?", default: true, // default answer "yes" }, ]); includeLogger = answers.includeLogger; } console.log(yellow("⚔ Initializing Node Server...")); const status = new cli_spinner_1.Spinner(yellow("ā³ Initializing Node Server, please wait... %s")); status.setSpinnerString("|/-\\"); status.start(); const templateDir = path_1.default.join(__dirname, `../templates/${template?.toLowerCase()}-server`); await fs_extra_1.default.copy(templateDir, targetDir); status.stop(true); console.log(cyan("šŸ›  Setting up project structure...")); console.log(green(`šŸ“ Creating a new ${cyan(template)} server in: ${yellow(targetDir)}`)); // set package name to projectName const pkgPath = path_1.default.join(targetDir, "package.json"); const pkg = await fs_extra_1.default.readJSON(pkgPath); pkg.name = projectName.trim().toLowerCase().replace(/\s+/g, "-"); await fs_extra_1.default.writeJSON(pkgPath, pkg, { spaces: 4 }); const installSpinner = new cli_spinner_1.Spinner(blue("ā³ Installing dependencies... %s")); installSpinner.setSpinnerString("|/-\\"); installSpinner.start(); run(`npm install express ${includeLogger ? "node-js-api-response express-useragent" : ""}`, targetDir); const devDeps = template === "JavaScript" ? "nodemon" : "typescript ts-node nodemon @types/node @types/express"; run(`npm install -D ${devDeps}`, targetDir); installSpinner.stop(true); console.log(green("šŸ“¦ Dependencies Installed!\n")); if (includeLogger) { console.log(magenta("🧩 Adding logger (node-js-api-response + express-useragent)...")); console.log(magenta(`šŸ“ Logger integration is ready — check ${cyan('`src/app`')} and ${cyan('`src/config/db.config`')}!\n`)); if (template === "JavaScript") { // --- Update app.js --- const appPath = path_1.default.join(targetDir, "src", "app.js"); // let appCode = await fs.readFile(appPath, "utf8"); // appCode = replaceConsole(appCode); // appCode = appCode // .replace(/const app = express\(\);/, // `import useragent from 'express-useragent';\n\nconst app = express();\napp.use(useragent.express());\napp.use(requestResponseLogger);` // ) // appCode = mergeImports(appCode, ["requestResponseLogger", "appLogger"]); // await fs.writeFile(appPath, appCode, "utf8"); await updateFile(appPath, ["requestResponseLogger", "appLogger", "errorLogger"], (code) => code.replace(/const app = express\(\);/, `import useragent from 'express-useragent';\n\nconst app = express();\n\napp.use(useragent.express());\napp.use(requestResponseLogger);`)); // --- Update index.js --- // const indexPath = path.join(targetDir, "src", "index.js"); // if (fs.existsSync(indexPath)) { // let indexCode = await fs.readFile(indexPath, "utf8"); // indexCode = replaceConsole(indexCode); // indexCode = mergeImports(indexCode, ["appLogger", "errorLogger"]); // await fs.writeFile(indexPath, indexCode, "utf8"); // } await updateFile(path_1.default.join(targetDir, "src", "index.js"), [ "appLogger", "errorLogger", ]); // --- Update db.config.js --- // const dbConfigPath = path.join(targetDir, "src", "configs", "db.config.js"); // if (fs.existsSync(dbConfigPath)) { // let dbConfigCode = await fs.readFile(dbConfigPath, "utf8"); // dbConfigCode = replaceConsole(dbConfigCode); // dbConfigCode = mergeImports(dbConfigCode, ["appLogger", "errorLogger"]); // await fs.writeFile(dbConfigPath, dbConfigCode, "utf8"); // } await updateFile(path_1.default.join(targetDir, "src", "config", "db.config.js"), [ "appLogger", "errorLogger", ]); } else { run("npm i --save-dev @types/express-useragent", targetDir); const entryPath = path_1.default.join(targetDir, "src/app.ts"); // --- Update app.ts --- await updateFile(entryPath, ["requestResponseLogger", "appLogger", "errorLogger"], (code) => code.replace(/const app: Application = express\(\);/, `import useragent from 'express-useragent';\n\nconst app:Application = express();\n\napp.use(useragent.express());\napp.use(requestResponseLogger);`)); // --- Update index.ts --- await updateFile(path_1.default.join(targetDir, "src/index.ts"), [ "appLogger", "errorLogger", ]); // --- Update db.config.ts --- await updateFile(path_1.default.join(targetDir, "src/config/db.config.ts"), [ "appLogger", "errorLogger", ]); // console.log(blue("\nšŸ“¦ Building TypeScript files...")); const buildSpinner = new cli_spinner_1.Spinner(blue("ā³ Building TypeScript files... %s")); buildSpinner.setSpinnerString("|/-\\"); buildSpinner.start(); try { run("npm run build", targetDir); buildSpinner.stop(true); console.log(green("āš™ļø Build completed successfully!")); } catch (error) { buildSpinner.stop(true); console.error(red("āŒ Build failed"), error); process.exit(1); } } } await setupEnvFiles(targetDir); await initGit(targetDir); console.log(green(`🌐 Node server initialized successfully!\n`)); console.log(yellow("šŸŽ‰ Happy hacking!\n")); console.log(`šŸ‘‰ Next steps:\n ${cyan(` cd ${projectName}`)}\n ${cyan("npm run dev")}`); }); await program.parseAsync(process.argv); } runCLI().catch((err) => { console.error(red("\nšŸ’„ Oops! Something went wrong.\n")); console.error(gray(err.stack || err.message)); process.exit(1); }); /* ----------------- Env Files ----------------- */ async function setupEnvFiles(targetDir) { const envFiles = [ ".env.local", ".env.development", ".env.staging", ".env.production", ]; for (const file of envFiles) { const srcPath = path_1.default.join(__dirname, "../templates/ecoSystem", file); const destPath = path_1.default.join(targetDir, file); if (!fs_extra_1.default.existsSync(destPath)) { await fs_extra_1.default.copy(srcPath, destPath); } } } /* ----------------- Git Integration ----------------- */ // initialize git repository async function initGit(targetDir) { try { run("git init", targetDir); run("git add .", targetDir); run('git commit -m "Initial commit from ns-init"', targetDir); } catch (error) { if (process.env.DEBUG) console.error(gray(error.message)); } } /* ----------------- Helpers ----------------- */ // replace console.* with logger equivalents function replaceConsole(code) { return code .replace(/console\.log/g, "appLogger.info") .replace(/console\.info/g, "appLogger.info") .replace(/console\.warn/g, "appLogger.warn") .replace(/console\.error/g, "errorLogger.error"); } // merge needed imports into existing import block (or add if missing) function mergeImports(code, needed, pkg = "node-js-api-response") { const importRegex = new RegExp(`import\\s*{([^}]*)}\\s*from\\s*["']${pkg}["']`); if (importRegex.test(code)) { return code.replace(importRegex, (match, imports) => { const existing = imports.split(",").map((i) => i.trim()); const merged = Array.from(new Set([...existing, ...needed])); return `import { ${merged.join(", ")} } from "${pkg}"`; }); } return `import { ${needed.join(", ")} } from "${pkg}";\n${code}`; } // update file with logger imports & console replacement async function updateFile(file, needed, transform) { if (await fs_extra_1.default.pathExists(file)) { let code = await fs_extra_1.default.readFile(file, "utf8"); code = replaceConsole(code); code = mergeImports(code, needed); if (transform) code = transform(code); // šŸ‘ˆ apply extra updates await fs_extra_1.default.writeFile(file, code, "utf8"); } }