4bnode
Version:
4bnode is a CLI-powered backend development platform with a built-in visual dashboard to generate, manage, and test Node.js/Express APIs faster.
126 lines (113 loc) • 4.34 kB
JavaScript
import fs from "fs";
import path from "path";
import { checkbox, confirm } from "@inquirer/prompts";
import { getProjectRoot, listModels, getModelSchemaDetailed } from "./lib/project.js";
import { addImport, insertBeforeRoutes, insertBeforeListen } from "./lib/indexFile.js";
import {
showHeader,
showTaskDone,
showError,
showInfo,
showFileAction,
installDeps,
} from "./lib/ui.js";
import {
buildErrorHandlerFile,
buildRolesGuardFile,
buildValidateFile,
buildZodSchemaFile,
securityWiring,
} from "./lib/codegen.js";
function writeFileSafe(relPath, content) {
const full = path.join(getProjectRoot(), relPath);
fs.mkdirSync(path.dirname(full), { recursive: true });
if (fs.existsSync(full)) {
showInfo(`${relPath} already exists — skipped.`);
return false;
}
fs.writeFileSync(full, content, "utf8");
showFileAction("created", relPath);
return true;
}
async function main() {
showHeader("add-security", "Harden your app with security middleware");
const features = await checkbox({
message: "Select protections to add:",
choices: [
{ name: "Helmet Secure HTTP response headers", value: "helmet", checked: true },
{ name: "Rate limiting Throttle requests per IP (express-rate-limit)", value: "rateLimit", checked: true },
{ name: "Error handler Central 404 + error JSON responses", value: "errorHandler", checked: true },
{ name: "RBAC roles roles('admin') route guard", value: "roles" },
{ name: "Validation Zod request validation middleware", value: "validation" },
],
validate: (v) => (v.length > 0 ? true : "Select at least one protection."),
});
console.log();
const deps = new Set();
const summary = [];
// ── App-level wiring (helmet / rate limit / error handler) ──
const appLevel = features.filter((f) => ["helmet", "rateLimit", "errorHandler"].includes(f));
if (appLevel.length) {
const wiring = securityWiring(appLevel);
wiring.imports.forEach((imp) => addImport(imp));
if (wiring.beforeRoutes) insertBeforeRoutes(wiring.beforeRoutes);
if (wiring.beforeListen) insertBeforeListen(wiring.beforeListen);
showFileAction("updated", "index.js");
}
if (features.includes("helmet")) { deps.add("helmet"); summary.push("Helmet security headers"); }
if (features.includes("rateLimit")) { deps.add("express-rate-limit"); summary.push("Rate limiting (100 req / 15 min per IP)"); }
// ── Middleware files ──
if (features.includes("errorHandler")) {
writeFileSafe("src/middleware/errorHandler.js", buildErrorHandlerFile());
summary.push("Central error handler");
}
if (features.includes("roles")) {
writeFileSafe("src/middleware/roles.js", buildRolesGuardFile());
summary.push("RBAC roles() guard");
}
// ── Validation (Zod) ──
if (features.includes("validation")) {
deps.add("zod");
writeFileSafe("src/middleware/validate.js", buildValidateFile());
const models = listModels().map((f) => path.basename(f, ".js"));
let generatedValidators = 0;
if (models.length) {
const wantValidators = await confirm({
message: "Generate Zod validators from existing models?",
default: true,
});
if (wantValidators) {
const chosen = await checkbox({
message: "Select models to generate validators for:",
choices: models.map((m) => ({ name: m, value: m, checked: true })),
});
for (const m of chosen) {
const fields = getModelSchemaDetailed(m);
if (!fields.length) {
showInfo(`${m}: no fields found — skipped.`);
continue;
}
if (writeFileSafe(`src/validators/${m}.js`, buildZodSchemaFile(m, fields))) {
generatedValidators++;
}
}
}
}
summary.push(
generatedValidators > 0
? `Zod validation (+ ${generatedValidators} model validator${generatedValidators > 1 ? "s" : ""})`
: "Zod validation middleware"
);
}
if (deps.size) {
console.log();
await installDeps([...deps]);
}
showTaskDone("Security & middleware configured", summary);
}
main().catch((err) => {
if (err.name === "ExitPromptError") process.exit(0);
showError(err.message);
process.exit(1);
});