express-ignite-cli
Version:
Powerful Express.js CLI to generate boilerplate projects with auth, swagger, testing, and more!
464 lines (358 loc) • 13.9 kB
JavaScript
// --- DYNAMIC AUTH GENERATION MODULE FOR CLI ---
import fs from "fs";
import path from "path";
import chalk from "chalk";
import { execSync } from "child_process";
import { imp, exp } from './helper.js';
function toExt(isTS) {
return isTS ? "ts" : "js";
}
function write(filePath, content) {
fs.writeFileSync(filePath, content.trim());
console.log(chalk.green("✔ Created:"), filePath);
}
function generateModels(projectPath, isTS, db, orm, isESM) {
const ext = toExt(isTS);
const modelsDir = path.join(projectPath, "models");
if (db === "MongoDB") {
write(path.join(modelsDir, `userModel.${ext}`), `
${imp("mongoose", "mongoose", isESM)}
const userSchema = new mongoose.Schema({
name: String,
role: String,
});
const User = mongoose.model('User', userSchema);
${exp("User", isESM, true)}
`);
write(path.join(modelsDir, `authModel.${ext}`), `
${imp("mongoose", "mongoose", isESM)}
const authSchema = new mongoose.Schema({
email: { type: String, required: true, unique: true },
password: String,
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }
});
const Auth = mongoose.model('Auth', authSchema);
${exp("Auth", isESM, true)}
`);
} else if (orm === "Sequelize") {
write(path.join(modelsDir, `userModel.${ext}`), `
// ${imp("Sequelize", "sequelize", isESM)}
${imp("{ DataTypes }", "sequelize", isESM)}
${imp("{ sequelize }", `../configs/connectDb.${ext}`, isESM)}
const User = sequelize.define('User', {
name: DataTypes.STRING,
role: DataTypes.STRING,
});
${exp("User", isESM, true)}
`);
write(path.join(modelsDir, `authModel.${ext}`), `
// ${imp("Sequelize", "sequelize", isESM)}
${imp("{ DataTypes }", "sequelize", isESM)}
${imp("{ sequelize }", `../configs/connectDb.${ext}`, isESM)}
const Auth = sequelize.define('Auth', {
email: { type: DataTypes.STRING, unique: true },
password: DataTypes.STRING,
});
${exp("Auth", isESM, true)}
${imp("User", `./userModel.${ext}`, isESM)}
User.hasOne(Auth);
Auth.belongsTo(User);`);
} else if (orm === "Prisma") {
const prismaSchemaPath = path.join(projectPath, "prisma", "schema.prisma");
const schema = `
model User {
id Int @id @default(autoincrement())
name String
role String
auth Auth?
}
model Auth {
id Int @id @default(autoincrement())
email String @unique
password String
userId Int @unique
user User @relation(fields: [userId], references: [id])
}`;
fs.appendFileSync(prismaSchemaPath, "\n\n" + schema);
console.log(chalk.green("✔ Updated Prisma schema with Auth/User models"));
} else if (orm === "TypeORM") {
write(path.join(modelsDir, `userModel.${ext}`), `
${imp("{ Entity, PrimaryGeneratedColumn, Column, OneToOne }", "typeorm", isESM)}
${imp("Auth", `./authModel.${ext}`, isESM)}
@Entity()
class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
role: string;
@OneToOne(() => Auth, (auth) => auth.user)
auth: Auth;
}
${exp("User", isESM)}
`);
write(path.join(modelsDir, `authModel.${ext}`), `
${imp("{ Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn }", "typeorm", isESM)}
${imp("User", `./userModel.${ext}`, isESM)}
@Entity()
class Auth {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
email: string;
@Column()
password: string;
@OneToOne(() => User)
@JoinColumn()
user: User;
}
${exp("Auth", isESM)}
`);
}
}
function generateSwaggerDocs(projectPath, isTS, isESM) {
const ext = toExt(isTS);
const swaggerPath = path.join(projectPath, `configs/swagger.${ext}`);
execSync("npm install swagger-ui-express swagger-jsdoc", {
cwd: projectPath,
stdio: "inherit",
});
write(swaggerPath, `
${imp("swaggerJsdoc", "swagger-jsdoc", isESM)}
${imp("swaggerUi", "swagger-ui-express", isESM)}
const options = {
definition: {
openapi: '3.0.0',
info: {
title: 'Express API with Swagger',
version: '1.0.0',
},
},
apis: ['./routes/*.${ext}'],
};
const specs = swaggerJsdoc(options);
const setupSwagger = (app) => {
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));
};
${exp("setupSwagger", isESM, true)}
`);
}
function generateAuthModule(projectPath, isTS, db, orm, isESM) {
const ext = toExt(isTS);
const modelsPath = path.join(projectPath, "models");
const routesPath = path.join(projectPath, "routes");
const controllersPath = path.join(projectPath, "controllers");
const validationsPath = path.join(projectPath, "validations");
const servicesPath = path.join(projectPath, "services");
const middlewaresPath = path.join(projectPath, "middlewares");
const utilsPath = path.join(projectPath, "utils");
const testsPath = path.join(projectPath, "__tests__");
fs.mkdirSync(testsPath);
execSync("npm install express-validator", { cwd: projectPath, stdio: "inherit" });
execSync(`npm install --save-dev jest supertest${isTS ? " @types/jest ts-jest @types/supertest" : ""}`,
{ cwd: projectPath, stdio: "inherit" });
write(
path.join(testsPath, `auth.test.${ext}`),
// `${isTS ? `import request from 'supertest';\nimport app from '../app';` : "const request = require('supertest');\nconst app = require('../app');"}
`${imp("request", "swagger-supertest", isESM)}
${imp("app", `../app.${ext}`, isESM)}
describe('Auth API', () => {
it('should return 201 for successful registration', async () => {
const res = await request(app)
.post('/api/auth/register')
.send({ name: 'John', email: 'john@test.com', password: '123456' });
expect(res.statusCode).toBe(201);
expect(res.body.token).toBeDefined();
});
it('should return 200 for successful login', async () => {
const res = await request(app)
.post('/api/auth/login')
.send({ email: 'john@test.com', password: '123456' });
expect(res.statusCode).toBe(200);
expect(res.body.token).toBeDefined();
});
});`
);
write(
path.join(routesPath, `authRoutes.${ext}`),
`${imp("express", "express", isESM)}
${imp("register", `../controllers/authController.${ext}`, isESM, true)}
${imp("login", `../controllers/authController.${ext}`, isESM, true)}
${imp("validateRegister", `../validations/authValidation.${ext}`, isESM, true)}
${imp("validateLogin", `../validations/authValidation.${ext}`, isESM, true)}
${imp("validateRequest", `../middlewares/validateRequest.${ext}`, isESM, true)}
const router = express.Router();
router.post('/register', validateRegister, validateRequest, register);
router.post('/login', validateLogin, validateRequest, login);
${exp("router", isESM, true)}`
);
write(
path.join(middlewaresPath, `validateRequest.${ext}`),
`${imp("validationResult", "express-validator", isESM)}
${imp("AppError", `../utils/AppError.${ext}`, isESM, true)}
const validateRequest = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return next(new AppError('Validation failed', 400));
}
next();
};
${exp("validateRequest", isESM)}`
);
write(
path.join(middlewaresPath, `errorHandler.${ext}`),
`const errorHandler = (err, req, res, next) => {
const statusCode = err.statusCode || 500;
const message = err.message || 'Something went wrong';
res.status(statusCode).json({ message });
};
${exp("errorHandler", isESM, true)}`
);
write(
path.join(utilsPath, `AppError.${ext}`),
`class AppError extends Error {
constructor(message, statusCode = 500) {
super(message);
this.statusCode = statusCode;
}
}
${exp("AppError", isESM)}`
);
write(
path.join(validationsPath, `authValidation.${ext}`),
`${imp("check", "express-validator", isESM, true)}
const validateRegister = [
check('name', 'Name is required').not().isEmpty(),
check('email', 'Please include a valid email').isEmail(),
check('password', 'Password must be 6+ chars').isLength({ min: 6 })
];
const validateLogin = [
check('email', 'Email is required').isEmail(),
check('password', 'Password is required').exists()
];
${exp(["validateRegister", "validateLogin"], isESM)}`
);
let authServiceContent = `
${imp("hashPassword", `../utils/passwords.${ext}`, isESM, true)}
${imp("comparePasswords", `../utils/passwords.${ext}`, isESM, true)}
${imp("generateToken", `../utils/generateToken.${ext}`, isESM, true)}
${imp("AppError", `../utils/AppError.${ext}`, isESM, true)}
`;
if (db === "MongoDB") {
authServiceContent += `
${imp("Auth", `../models/authModel.${ext}`, isESM)}
${imp("User", `../models/userModel.${ext}`, isESM)}
const registerService = async (name, email, password) => {
const existing = await Auth.findOne({ email });
if (existing) throw new AppError('User already exists', 400);
const hashedPassword = await hashPassword(password);
const user = await User.create({ name, role: 'user' });
const auth = await Auth.create({ email, password: hashedPassword, user: user._id });
return generateToken(user._id);
};
const loginService = async (email, password) => {
const auth = await Auth.findOne({ email }).populate('user');
if (!auth) throw new AppError('Invalid credentials', 401);
const isMatch = await comparePasswords(password, auth.password);
if (!isMatch) throw new AppError('Invalid credentials', 401);
return generateToken(auth.user._id);
};
${exp(["registerService", "loginService"], isESM)}
`;
} else if (orm === "Sequelize") {
authServiceContent += `
${imp("Auth", `../models/authModel.${ext}`, isESM)}
${imp("User", `../models/userModel.${ext}`, isESM)}
const registerService = async (name, email, password) => {
const existing = await Auth.findOne({ where: { email } });
if (existing) throw new AppError('User already exists', 400);
const hashedPassword = await hashPassword(password);
const user = await User.create({ name, role: 'user' });
const auth = await Auth.create({ email, password: hashedPassword, UserId: user.id });
return generateToken(user.id);
};
const loginService = async (email, password) => {
const auth = await Auth.findOne({ where: { email }, include: User });
if (!auth) throw new AppError('Invalid credentials', 401);
const isMatch = await comparePasswords(password, auth.password);
if (!isMatch) throw new AppError('Invalid credentials', 401);
return generateToken(auth.User.id);
};
${exp(["registerService", "loginService"], isESM)}
`;
} else if (orm === "Prisma") {
authServiceContent += `
${imp("prisma", `../configs/connectDb.${ext}`, isESM)}
const registerService = async (name, email, password) => {
const existing = await prisma.auth.findUnique({ where: { email } });
if (existing) throw new AppError('User already exists', 400);
const hashedPassword = await hashPassword(password);
const user = await prisma.user.create({ data: { name, role: 'user' } });
await prisma.auth.create({ data: { email, password: hashedPassword, userId: user.id } });
return generateToken(user.id);
};
const loginService = async (email, password) => {
const auth = await prisma.auth.findUnique({ where: { email }, include: { user: true } });
if (!auth) throw new AppError('Invalid credentials', 401);
const isMatch = await comparePasswords(password, auth.password);
if (!isMatch) throw new AppError('Invalid credentials', 401);
return generateToken(auth.user.id);
};
${exp(["registerService", "loginService"], isESM)}
`;
} else if (orm === "TypeORM") {
authServiceContent += `
${imp("AppDataSource", `../configs/connectDb.${ext}`, isESM)}
${imp("Auth", `../models/authModel.${ext}`, isESM)}
${imp("User", `../models/userModel.${ext}`, isESM)}
const authRepo = AppDataSource.getRepository(Auth);
const userRepo = AppDataSource.getRepository(User);
const registerService = async (name, email, password) => {
const existing = await authRepo.findOneBy({ email });
if (existing) throw new AppError('User already exists', 400);
const hashedPassword = await hashPassword(password);
const user = userRepo.create({ name, role: 'user' });
await userRepo.save(user);
const auth = authRepo.create({ email, password: hashedPassword, user });
await authRepo.save(auth);
return generateToken(user.id);
};
const loginService = async (email, password) => {
const auth = await authRepo.findOne({ where: { email }, relations: ['user'] });
if (!auth) throw new AppError('Invalid credentials', 401);
const isMatch = await comparePasswords(password, auth.password);
if (!isMatch) throw new AppError('Invalid credentials', 401);
return generateToken(auth.user.id);
};
${exp(["registerService", "loginService"], isESM)}
`;
}
write(path.join(servicesPath, `authService.${ext}`), authServiceContent);
write(
path.join(controllersPath, `authController.${ext}`),
`${imp("registerService", `../services/authService.${ext}`, isESM, true)}
${imp("loginService", `../services/authService.${ext}`, isESM, true)}
const register = async (req, res, next) => {
const { name, email, password } = req.body;
try {
const token = await registerService(name, email, password);
res.status(201).json({ token });
} catch (err) {
next(err);
}
};
const login = async (req, res, next) => {
const { email, password } = req.body;
try {
const token = await loginService(email, password);
res.json({ token });
} catch (err) {
next(err);
}
};
${exp(["register", "login"], isESM)}`
);
generateModels(projectPath, isTS, db, orm, isESM);
}
export { generateAuthModule };