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.
127 lines (105 loc) • 3.58 kB
JavaScript
import path from "path";
import { select, checkbox } from "@inquirer/prompts";
import { toPascalCase } from "./lib/names.js";
import { listRoutes, listModels, getModelSchema } from "./lib/project.js";
import {
getRouteFilePath,
addImportToRoute,
insertCodeIntoRoute,
detectHttpMethod,
getRequestDataSource,
} from "./lib/routeFile.js";
import {
showHeader,
showTaskDone,
showError,
showFileAction,
installDeps,
} from "./lib/ui.js";
import {
buildInsertCode,
buildReadCode,
buildUpdateCode,
buildDeleteCode,
} from "./lib/codegen.js";
async function main() {
showHeader("add-crud", "Add CRUD operations to a route");
const routeFiles = listRoutes();
if (routeFiles.length === 0) {
showError("No route files found in src/routes.");
process.exit(1);
}
const modelFiles = listModels();
if (modelFiles.length === 0) {
showError("No model files found in src/models.");
process.exit(1);
}
const selectedRoute = await select({
message: "Select route file:",
choices: routeFiles.map((f) => ({ name: f, value: f })),
});
const selectedModelFile = await select({
message: "Select model file:",
choices: modelFiles.map((f) => ({ name: f, value: f })),
});
const operations = await checkbox({
message: "Select CRUD operations:",
choices: [
{ name: "Create (POST /create)", value: "create", checked: true },
{ name: "Read (GET / and GET /:id)", value: "read", checked: true },
{ name: "Update (PUT /:id)", value: "update", checked: true },
{ name: "Delete (DELETE /:id)", value: "delete", checked: true },
],
validate: (v) => (v.length > 0 ? true : "Select at least one operation."),
});
const modelName = path.basename(selectedModelFile, ".js");
const pascalName = toPascalCase(modelName);
const fields = getModelSchema(modelName);
if (fields.length === 0) {
showError("No fields found in the selected model.");
process.exit(1);
}
console.log();
const routeFilePath = getRouteFilePath(selectedRoute);
const method = detectHttpMethod(routeFilePath);
const dataSource = getRequestDataSource(method);
const needsBcrypt = fields.includes("password") && (operations.includes("create") || operations.includes("update"));
// Build import
let importBlock = `import ${pascalName} from '../models/${modelName}.js';`;
if (needsBcrypt) {
importBlock = `import bcrypt from 'bcrypt';\n${importBlock}`;
}
addImportToRoute(routeFilePath, importBlock);
// Add selected operations
const added = [];
if (operations.includes("create")) {
insertCodeIntoRoute(routeFilePath, buildInsertCode(pascalName, fields, dataSource));
added.push("POST /create");
}
if (operations.includes("read")) {
insertCodeIntoRoute(routeFilePath, buildReadCode(pascalName));
added.push("GET / and GET /:id");
}
if (operations.includes("update")) {
insertCodeIntoRoute(routeFilePath, buildUpdateCode(pascalName, fields, dataSource));
added.push("PUT /:id");
}
if (operations.includes("delete")) {
insertCodeIntoRoute(routeFilePath, buildDeleteCode(pascalName));
added.push("DELETE /:id");
}
showFileAction("updated", `src/routes/${selectedRoute}`);
if (needsBcrypt) {
await installDeps("bcrypt");
}
showTaskDone(`CRUD operations added to ${selectedRoute}`, [
`Model: ${modelName}`,
...added.map((a) => `Route: ${a}`),
]);
}
main().catch((err) => {
if (err.name === "ExitPromptError") process.exit(0);
showError(err.message);
process.exit(1);
});