UNPKG

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
#!/usr/bin/env node 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); });