ns-init
Version:
Scaffold a ready-made CLI for Node.js + Express server (JavaScript or TypeScript)
268 lines (267 loc) ⢠12.9 kB
JavaScript
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");
}
}
;