@re-shell/cli
Version:
Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja
1,342 lines (1,198 loc) ⢠36.8 kB
JavaScript
"use strict";
/**
* Hono Framework Template Generator
* Small, simple, and ultrafast web framework for Bun
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.HonoGenerator = void 0;
const bun_base_generator_1 = require("./bun-base-generator");
const fs_1 = require("fs");
const path = __importStar(require("path"));
class HonoGenerator extends bun_base_generator_1.BunBackendGenerator {
constructor() {
super('Hono');
}
async generateFrameworkFiles(projectPath, options) {
// Update package.json with Hono dependencies
await this.updatePackageJson(projectPath);
// Generate main application
await this.generateMainApp(projectPath, options);
// Generate app factory
await this.generateApp(projectPath);
// Generate routes
await this.generateRoutes(projectPath);
// Generate controllers
await this.generateControllers(projectPath);
// Generate middleware
await this.generateMiddleware(projectPath);
// Generate services
await this.generateServices(projectPath);
// Generate models
await this.generateModels(projectPath);
// Generate utilities
await this.generateUtilities(projectPath);
// Generate types
await this.generateTypes(projectPath);
// Generate configuration
await this.generateConfig(projectPath, options);
// Generate validation
await this.generateValidation(projectPath);
}
async updatePackageJson(projectPath) {
const packageJsonPath = path.join(projectPath, 'package.json');
const packageJson = JSON.parse(await fs_1.promises.readFile(packageJsonPath, 'utf-8'));
packageJson.dependencies = {
...packageJson.dependencies,
"hono": "^4.0.1",
"@hono/zod-validator": "^0.2.1",
"@hono/swagger-ui": "^0.2.1",
"@hono/cors": "^0.2.1",
"@hono/jwt": "^0.2.1",
"@hono/logger": "^0.2.1",
"@hono/timing": "^0.2.1",
"@hono/etag": "^0.2.1",
"@hono/compress": "^0.2.1",
"@hono/pretty-json": "^0.2.1",
"bcryptjs": "^2.4.3",
"@prisma/client": "^5.13.0",
"zod": "^3.22.4",
"ioredis": "^5.3.2"
};
packageJson.devDependencies = {
...packageJson.devDependencies,
"prisma": "^5.13.0",
"@types/bcryptjs": "^2.4.6"
};
await fs_1.promises.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
}
async generateMainApp(projectPath, options) {
const mainContent = `import { Hono } from 'hono';
import { serve } from 'bun';
import { cors } from 'hono/cors';
import { logger } from 'hono/logger';
import { timing } from 'hono/timing';
import { etag } from 'hono/etag';
import { compress } from 'hono/compress';
import { prettyJSON } from 'hono/pretty-json';
import { swaggerUI } from '@hono/swagger-ui';
import { config } from './config';
import { errorHandler } from './middleware/error-handler';
import { rateLimiter } from './middleware/rate-limiter';
import { authMiddleware } from './middleware/auth';
import { healthRoutes } from './routes/health';
import { authRoutes } from './routes/auth';
import { userRoutes } from './routes/users';
import { apiRoutes } from './routes/api';
import { connectDatabase } from './utils/database';
import { generateOpenAPISpec } from './utils/openapi';
// Create Hono app
const app = new Hono();
// Global middleware
app.use('*', timing());
app.use('*', logger());
app.use('*', etag());
app.use('*', compress());
app.use('*', prettyJSON());
app.use('*', cors({
origin: config.CORS_ORIGIN.split(','),
credentials: true
}));
// Rate limiting
app.use('*', rateLimiter());
// Error handling
app.onError(errorHandler);
// API Documentation
app.get('/swagger', swaggerUI({ url: '/doc' }));
app.get('/doc', (c) => c.json(generateOpenAPISpec()));
// Routes
app.route('/health', healthRoutes);
app.route('/api/v1/auth', authRoutes);
app.route('/api/v1/users', userRoutes);
app.route('/api', apiRoutes);
// 404 handler
app.notFound((c) => {
return c.json({
error: 'Not Found',
message: 'The requested resource was not found',
statusCode: 404
}, 404);
});
// Initialize database
await connectDatabase();
// Start server
const server = serve({
fetch: app.fetch,
port: config.PORT,
hostname: config.HOST
});
console.log(\`š„ Hono is running at http://\${config.HOST}:\${config.PORT}\`);
console.log(\`š Swagger UI available at http://\${config.HOST}:\${config.PORT}/swagger\`);
// Graceful shutdown
process.on('SIGINT', () => {
console.log('\\nš Shutting down gracefully...');
server.stop();
process.exit(0);
});
// Export app for testing
export default app;
export type AppType = typeof app;
`;
await fs_1.promises.writeFile(path.join(projectPath, 'src', 'index.ts'), mainContent);
}
async generateApp(projectPath) {
const appContent = `import { Hono } from 'hono';
import { HTTPException } from 'hono/http-exception';
import type { User } from './types';
// Custom app context
export type AppBindings = {
Variables: {
user: User | null;
startTime: number;
};
};
// Create app factory
export function createApp() {
const app = new Hono<AppBindings>();
// Add global variables
app.use('*', async (c, next) => {
c.set('startTime', Date.now());
await next();
});
return app;
}
// Custom error classes
export class ValidationError extends HTTPException {
constructor(message: string, details?: any) {
super(400, { message, details });
}
}
export class UnauthorizedError extends HTTPException {
constructor(message: string = 'Unauthorized') {
super(401, { message });
}
}
export class ForbiddenError extends HTTPException {
constructor(message: string = 'Forbidden') {
super(403, { message });
}
}
export class NotFoundError extends HTTPException {
constructor(message: string = 'Not found') {
super(404, { message });
}
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'src', 'app.ts'), appContent);
}
async generateRoutes(projectPath) {
const routesDir = path.join(projectPath, 'src', 'routes');
// Health routes
const healthRoutesContent = `import { Hono } from 'hono';
import { db } from '../utils/database';
export const healthRoutes = new Hono()
.get('/', (c) => {
return c.json({
status: 'healthy',
timestamp: new Date().toISOString(),
version: '1.0.0',
uptime: process.uptime(),
memory: process.memoryUsage()
});
})
.get('/ready', async (c) => {
try {
// Check database connection
await db.$queryRaw\`SELECT 1\`;
return c.json({
status: 'ready',
checks: {
database: 'ok',
cache: 'ok'
}
});
} catch (error) {
return c.json({
status: 'not ready',
checks: {
database: 'error',
cache: 'ok'
}
}, 503);
}
});
`;
await fs_1.promises.writeFile(path.join(routesDir, 'health.ts'), healthRoutesContent);
// Auth routes
const authRoutesContent = `import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { authController } from '../controllers/auth';
import { loginSchema, registerSchema } from '../schemas/auth';
import { authMiddleware } from '../middleware/auth';
export const authRoutes = new Hono()
.post('/register',
zValidator('json', registerSchema),
authController.register
)
.post('/login',
zValidator('json', loginSchema),
authController.login
)
.post('/refresh',
authMiddleware,
authController.refresh
)
.post('/logout',
authMiddleware,
authController.logout
);
`;
await fs_1.promises.writeFile(path.join(routesDir, 'auth.ts'), authRoutesContent);
// User routes
const userRoutesContent = `import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { userController } from '../controllers/users';
import { updateUserSchema, userQuerySchema } from '../schemas/user';
import { authMiddleware } from '../middleware/auth';
import { requireRole } from '../middleware/rbac';
export const userRoutes = new Hono()
// All routes require authentication
.use('*', authMiddleware)
.get('/',
requireRole('admin'),
zValidator('query', userQuerySchema),
userController.list
)
.get('/me',
userController.getCurrentUser
)
.get('/:id',
userController.getById
)
.put('/:id',
zValidator('json', updateUserSchema),
userController.update
)
.delete('/:id',
requireRole('admin'),
userController.delete
);
`;
await fs_1.promises.writeFile(path.join(routesDir, 'users.ts'), userRoutesContent);
// API routes aggregator
const apiRoutesContent = `import { Hono } from 'hono';
import { AppBindings } from '../app';
export const apiRoutes = new Hono<AppBindings>()
.get('/', (c) => {
return c.json({
message: 'Welcome to Hono API',
version: 'v1',
documentation: '/swagger'
});
})
.get('/stats', async (c) => {
const user = c.get('user');
return c.json({
uptime: process.uptime(),
memory: process.memoryUsage(),
authenticated: !!user,
timestamp: new Date().toISOString()
});
});
`;
await fs_1.promises.writeFile(path.join(routesDir, 'api.ts'), apiRoutesContent);
}
async generateControllers(projectPath) {
const controllersDir = path.join(projectPath, 'src', 'controllers');
// Auth controller
const authControllerContent = `import { Context } from 'hono';
import bcrypt from 'bcryptjs';
import { authService } from '../services/auth';
import { generateToken, verifyToken } from '../utils/jwt';
import { ValidationError, UnauthorizedError } from '../app';
export const authController = {
async register(c: Context) {
const { email, password, name } = c.req.valid('json' as never);
// Check if user exists
const existingUser = await authService.findByEmail(email);
if (existingUser) {
throw new ValidationError('Email already registered');
}
// Hash password
const hashedPassword = await bcrypt.hash(password, 10);
// Create user
const user = await authService.createUser({
email,
password: hashedPassword,
name
});
// Generate token
const token = await generateToken({
sub: user.id,
email: user.email,
role: user.role
});
return c.json({
user: {
id: user.id,
email: user.email,
name: user.name,
role: user.role
},
token
}, 201);
},
async login(c: Context) {
const { email, password } = c.req.valid('json' as never);
// Find user
const user = await authService.findByEmail(email);
if (!user) {
throw new UnauthorizedError('Invalid credentials');
}
// Verify password
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) {
throw new UnauthorizedError('Invalid credentials');
}
// Generate token
const token = await generateToken({
sub: user.id,
email: user.email,
role: user.role
});
return c.json({
user: {
id: user.id,
email: user.email,
name: user.name,
role: user.role
},
token
});
},
async refresh(c: Context) {
const user = c.get('user');
if (!user) {
throw new UnauthorizedError('Invalid token');
}
const token = await generateToken({
sub: user.id,
email: user.email,
role: user.role
});
return c.json({ token });
},
async logout(c: Context) {
// In production, you might want to blacklist the token
return c.json({ message: 'Logged out successfully' });
}
};
`;
await fs_1.promises.writeFile(path.join(controllersDir, 'auth.ts'), authControllerContent);
// Users controller
const usersControllerContent = `import { Context } from 'hono';
import { userService } from '../services/users';
import { NotFoundError, ForbiddenError } from '../app';
export const userController = {
async list(c: Context) {
const query = c.req.valid('query' as never);
const { page = 1, limit = 10, search } = query || {};
const users = await userService.findMany({
page: Number(page),
limit: Number(limit),
search: search as string
});
return c.json(users);
},
async getCurrentUser(c: Context) {
const user = c.get('user');
if (!user) {
throw new UnauthorizedError('Not authenticated');
}
const currentUser = await userService.findById(user.id);
if (!currentUser) {
throw new NotFoundError('User not found');
}
return c.json({
id: currentUser.id,
email: currentUser.email,
name: currentUser.name,
role: currentUser.role,
createdAt: currentUser.createdAt,
updatedAt: currentUser.updatedAt
});
},
async getById(c: Context) {
const id = c.req.param('id');
const currentUser = c.get('user');
// Check permissions
if (currentUser?.id !== id && currentUser?.role !== 'admin') {
throw new ForbiddenError('Access denied');
}
const user = await userService.findById(id);
if (!user) {
throw new NotFoundError('User not found');
}
return c.json({
id: user.id,
email: user.email,
name: user.name,
role: user.role,
createdAt: user.createdAt,
updatedAt: user.updatedAt
});
},
async update(c: Context) {
const id = c.req.param('id');
const body = c.req.valid('json' as never);
const currentUser = c.get('user');
// Check permissions
if (currentUser?.id !== id && currentUser?.role !== 'admin') {
throw new ForbiddenError('Access denied');
}
const user = await userService.update(id, body);
if (!user) {
throw new NotFoundError('User not found');
}
return c.json({
id: user.id,
email: user.email,
name: user.name,
role: user.role,
updatedAt: user.updatedAt
});
},
async delete(c: Context) {
const id = c.req.param('id');
const currentUser = c.get('user');
// Prevent self-deletion
if (currentUser?.id === id) {
throw new ForbiddenError('Cannot delete your own account');
}
const deleted = await userService.delete(id);
if (!deleted) {
throw new NotFoundError('User not found');
}
c.status(204);
return c.body(null);
}
};
`;
await fs_1.promises.writeFile(path.join(controllersDir, 'users.ts'), usersControllerContent);
}
async generateMiddleware(projectPath) {
const middlewareDir = path.join(projectPath, 'src', 'middleware');
// Error handler
const errorHandlerContent = `import { Context } from 'hono';
import { HTTPException } from 'hono/http-exception';
import { ZodError } from 'zod';
export async function errorHandler(err: Error, c: Context) {
// Handle Zod validation errors
if (err instanceof ZodError) {
return c.json({
error: 'Validation Error',
message: 'Invalid request data',
details: err.errors,
statusCode: 400
}, 400);
}
// Handle HTTP exceptions
if (err instanceof HTTPException) {
const status = err.status;
return c.json({
error: err.message,
statusCode: status
}, status);
}
// Log unexpected errors
console.error('Unhandled error:', err);
// Default error response
return c.json({
error: 'Internal Server Error',
message: 'An unexpected error occurred',
statusCode: 500
}, 500);
}
`;
await fs_1.promises.writeFile(path.join(middlewareDir, 'error-handler.ts'), errorHandlerContent);
// Auth middleware
const authMiddlewareContent = `import { Context, Next } from 'hono';
import { verifyToken } from '../utils/jwt';
import { UnauthorizedError } from '../app';
export async function authMiddleware(c: Context, next: Next) {
const authHeader = c.req.header('Authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) {
c.set('user', null);
throw new UnauthorizedError('Missing authorization header');
}
const token = authHeader.substring(7);
try {
const payload = await verifyToken(token);
// In production, fetch full user from database
const user = {
id: payload.sub as string,
email: payload.email as string,
role: payload.role as string
};
c.set('user', user);
await next();
} catch (error) {
c.set('user', null);
throw new UnauthorizedError('Invalid token');
}
}
// Optional auth middleware - doesn't throw if no token
export async function optionalAuth(c: Context, next: Next) {
const authHeader = c.req.header('Authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) {
c.set('user', null);
await next();
return;
}
const token = authHeader.substring(7);
try {
const payload = await verifyToken(token);
const user = {
id: payload.sub as string,
email: payload.email as string,
role: payload.role as string
};
c.set('user', user);
} catch {
c.set('user', null);
}
await next();
}
`;
await fs_1.promises.writeFile(path.join(middlewareDir, 'auth.ts'), authMiddlewareContent);
// RBAC middleware
const rbacMiddlewareContent = `import { Context, Next } from 'hono';
import { ForbiddenError } from '../app';
export function requireRole(...roles: string[]) {
return async (c: Context, next: Next) => {
const user = c.get('user');
if (!user) {
throw new ForbiddenError('Authentication required');
}
if (!roles.includes(user.role)) {
throw new ForbiddenError('Insufficient permissions');
}
await next();
};
}
export function requireOwnership(userIdParam = 'id') {
return async (c: Context, next: Next) => {
const user = c.get('user');
const resourceUserId = c.req.param(userIdParam);
if (!user) {
throw new ForbiddenError('Authentication required');
}
if (user.id !== resourceUserId && user.role !== 'admin') {
throw new ForbiddenError('Access denied');
}
await next();
};
}
`;
await fs_1.promises.writeFile(path.join(middlewareDir, 'rbac.ts'), rbacMiddlewareContent);
// Rate limiter
const rateLimiterContent = `import { Context, Next } from 'hono';
interface RateLimitStore {
[key: string]: {
count: number;
resetTime: number;
};
}
const store: RateLimitStore = {};
export function rateLimiter(options = {
windowMs: 60 * 1000, // 1 minute
maxRequests: 100
}) {
return async (c: Context, next: Next) => {
const ip = c.req.header('x-forwarded-for') ||
c.req.header('x-real-ip') ||
'unknown';
const now = Date.now();
const key = \`\${ip}:\${c.req.path}\`;
if (!store[key] || store[key].resetTime < now) {
store[key] = {
count: 1,
resetTime: now + options.windowMs
};
} else {
store[key].count++;
}
if (store[key].count > options.maxRequests) {
return c.json({
error: 'Too Many Requests',
message: 'Rate limit exceeded',
statusCode: 429
}, 429);
}
// Add rate limit headers
c.header('X-RateLimit-Limit', options.maxRequests.toString());
c.header('X-RateLimit-Remaining', (options.maxRequests - store[key].count).toString());
c.header('X-RateLimit-Reset', new Date(store[key].resetTime).toISOString());
await next();
};
}
// Cleanup old entries periodically
setInterval(() => {
const now = Date.now();
for (const key in store) {
if (store[key].resetTime < now) {
delete store[key];
}
}
}, 60 * 1000);
`;
await fs_1.promises.writeFile(path.join(middlewareDir, 'rate-limiter.ts'), rateLimiterContent);
}
async generateServices(projectPath) {
const servicesDir = path.join(projectPath, 'src', 'services');
// Auth service
const authServiceContent = `import { db } from '../utils/database';
import type { User } from '@prisma/client';
export const authService = {
async findByEmail(email: string): Promise<User | null> {
return db.user.findUnique({
where: { email }
});
},
async createUser(data: {
email: string;
password: string;
name: string;
}): Promise<User> {
return db.user.create({
data: {
...data,
role: 'user'
}
});
}
};
`;
await fs_1.promises.writeFile(path.join(servicesDir, 'auth.ts'), authServiceContent);
// Users service
const usersServiceContent = `import { db } from '../utils/database';
import type { User } from '@prisma/client';
interface FindManyOptions {
page: number;
limit: number;
search?: string;
}
export const userService = {
async findMany({ page, limit, search }: FindManyOptions) {
const where = search
? {
OR: [
{ name: { contains: search, mode: 'insensitive' } },
{ email: { contains: search, mode: 'insensitive' } }
]
}
: {};
const [users, total] = await Promise.all([
db.user.findMany({
where,
skip: (page - 1) * limit,
take: limit,
select: {
id: true,
email: true,
name: true,
role: true,
createdAt: true,
updatedAt: true
}
}),
db.user.count({ where })
]);
return {
data: users,
meta: {
page,
limit,
total,
totalPages: Math.ceil(total / limit)
}
};
},
async findById(id: string): Promise<User | null> {
return db.user.findUnique({
where: { id }
});
},
async update(id: string, data: Partial<{ name: string; email: string }>): Promise<User | null> {
try {
return await db.user.update({
where: { id },
data
});
} catch (error) {
return null;
}
},
async delete(id: string): Promise<boolean> {
try {
await db.user.delete({
where: { id }
});
return true;
} catch (error) {
return false;
}
}
};
`;
await fs_1.promises.writeFile(path.join(servicesDir, 'users.ts'), usersServiceContent);
}
async generateModels(projectPath) {
const modelsDir = path.join(projectPath, 'src', 'models');
// User model
const userModelContent = `import { z } from 'zod';
// User schemas
export const userSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
name: z.string(),
role: z.enum(['user', 'admin']),
createdAt: z.date(),
updatedAt: z.date()
});
export const createUserSchema = z.object({
email: z.string().email(),
password: z.string().min(6),
name: z.string().min(1)
});
export const updateUserSchema = z.object({
email: z.string().email().optional(),
name: z.string().min(1).optional()
});
// Types
export type User = z.infer<typeof userSchema>;
export type CreateUser = z.infer<typeof createUserSchema>;
export type UpdateUser = z.infer<typeof updateUserSchema>;
`;
await fs_1.promises.writeFile(path.join(modelsDir, 'user.ts'), userModelContent);
// Prisma schema
const prismaDir = path.join(projectPath, 'prisma');
await fs_1.promises.mkdir(prismaDir, { recursive: true });
const prismaSchemaContent = `// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = "file:../data/app.db"
}
model User {
id String
email String
password String
name String
role String
createdAt DateTime
updatedAt DateTime
sessions Session[]
}
model Session {
id String
userId String
token String
expiresAt DateTime
createdAt DateTime
user User
}
`;
await fs_1.promises.writeFile(path.join(prismaDir, 'schema.prisma'), prismaSchemaContent);
}
async generateUtilities(projectPath) {
const utilsDir = path.join(projectPath, 'src', 'utils');
// Database utility
const databaseContent = `import { PrismaClient } from '@prisma/client';
const globalForPrisma = global as unknown as {
prisma: PrismaClient | undefined;
};
export const db = globalForPrisma.prisma ?? new PrismaClient({
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error']
});
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = db;
}
export async function connectDatabase() {
try {
await db.$connect();
console.log('ā
Database connected');
} catch (error) {
console.error('ā Database connection failed:', error);
process.exit(1);
}
}
`;
await fs_1.promises.writeFile(path.join(utilsDir, 'database.ts'), databaseContent);
// JWT utility
const jwtContent = `import { SignJWT, jwtVerify } from 'jose';
import { config } from '../config';
const secret = new TextEncoder().encode(config.JWT_SECRET);
export async function generateToken(payload: any) {
const jwt = await new SignJWT(payload)
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('7d')
.sign(secret);
return jwt;
}
export async function verifyToken(token: string) {
try {
const { payload } = await jwtVerify(token, secret);
return payload;
} catch (error) {
throw new Error('Invalid token');
}
}
`;
await fs_1.promises.writeFile(path.join(utilsDir, 'jwt.ts'), jwtContent);
// OpenAPI utility
const openapiContent = `export function generateOpenAPISpec() {
return {
openapi: '3.0.0',
info: {
title: 'Hono API',
version: '1.0.0',
description: 'API documentation for Hono application'
},
servers: [
{
url: 'http://localhost:3000',
description: 'Development server'
}
],
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT'
}
}
},
security: [
{
bearerAuth: []
}
],
paths: {
'/health': {
get: {
summary: 'Health check',
tags: ['Health'],
responses: {
'200': {
description: 'Service is healthy',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
status: { type: 'string' },
timestamp: { type: 'string' },
version: { type: 'string' }
}
}
}
}
}
}
}
},
'/api/v1/auth/register': {
post: {
summary: 'Register a new user',
tags: ['Auth'],
security: [],
requestBody: {
content: {
'application/json': {
schema: {
type: 'object',
required: ['email', 'password', 'name'],
properties: {
email: { type: 'string', format: 'email' },
password: { type: 'string', minLength: 6 },
name: { type: 'string', minLength: 1 }
}
}
}
}
},
responses: {
'201': {
description: 'User created successfully'
},
'400': {
description: 'Validation error'
}
}
}
},
'/api/v1/auth/login': {
post: {
summary: 'Login',
tags: ['Auth'],
security: [],
requestBody: {
content: {
'application/json': {
schema: {
type: 'object',
required: ['email', 'password'],
properties: {
email: { type: 'string', format: 'email' },
password: { type: 'string' }
}
}
}
}
},
responses: {
'200': {
description: 'Login successful'
},
'401': {
description: 'Invalid credentials'
}
}
}
}
}
};
}
`;
await fs_1.promises.writeFile(path.join(utilsDir, 'openapi.ts'), openapiContent);
// Validation utility
const validationContent = `export function isValidEmail(email: string): boolean {
const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;
return emailRegex.test(email);
}
export function isValidPassword(password: string): boolean {
return password.length >= 6;
}
export function sanitizeInput(input: string): string {
return input.trim().replace(/<[^>]*>?/gm, '');
}
export function paginate(page: number, limit: number) {
const offset = (page - 1) * limit;
return { offset, limit };
}
export function generateSlug(text: string): string {
return text
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
}
`;
await fs_1.promises.writeFile(path.join(utilsDir, 'validation.ts'), validationContent);
}
async generateTypes(projectPath) {
const typesDir = path.join(projectPath, 'src', 'types');
// Global types
const indexTypesContent = `import type { Context as HonoContext } from 'hono';
// User type
export interface User {
id: string;
email: string;
name: string;
role: string;
createdAt?: Date;
updatedAt?: Date;
}
// Context with user
export type Context = HonoContext<{
Variables: {
user: User | null;
};
}>;
// Generic API response
export interface ApiResponse<T = any> {
data?: T;
error?: {
message: string;
code?: string;
details?: any;
};
meta?: {
page?: number;
limit?: number;
total?: number;
totalPages?: number;
};
}
// Pagination
export interface PaginationQuery {
page?: number;
limit?: number;
sort?: string;
order?: 'asc' | 'desc';
}
`;
await fs_1.promises.writeFile(path.join(typesDir, 'index.ts'), indexTypesContent);
// Environment types
const envTypesContent = `declare global {
namespace NodeJS {
interface ProcessEnv {
NODE_ENV: 'development' | 'production' | 'test';
PORT: string;
HOST: string;
DATABASE_URL: string;
JWT_SECRET: string;
CORS_ORIGIN: string;
LOG_LEVEL: 'debug' | 'info' | 'warn' | 'error';
REDIS_URL?: string;
}
}
}
export {};
`;
await fs_1.promises.writeFile(path.join(typesDir, 'env.d.ts'), envTypesContent);
}
async generateConfig(projectPath, options) {
const configDir = path.join(projectPath, 'src', 'config');
// Main config
const configContent = `import { z } from 'zod';
import dotenv from 'dotenv';
// Load environment variables
dotenv.config();
const configSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
PORT: z.string().transform(Number).default('${options.port || 3000}'),
HOST: z.string().default('0.0.0.0'),
DATABASE_URL: z.string().default('file:../data/app.db'),
JWT_SECRET: z.string().min(32),
CORS_ORIGIN: z.string().default('*'),
LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
REDIS_URL: z.string().optional()
});
// Validate and parse environment variables
export const config = (() => {
try {
const parsed = configSchema.parse(process.env);
return {
...parsed,
isDevelopment: parsed.NODE_ENV === 'development',
isProduction: parsed.NODE_ENV === 'production',
isTest: parsed.NODE_ENV === 'test'
};
} catch (error) {
console.error('ā Configuration validation failed:');
if (error instanceof z.ZodError) {
console.error(error.errors);
}
process.exit(1);
}
})();
export type Config = typeof config;
`;
await fs_1.promises.writeFile(path.join(configDir, 'index.ts'), configContent);
}
async generateValidation(projectPath) {
const schemasDir = path.join(projectPath, 'src', 'schemas');
await fs_1.promises.mkdir(schemasDir, { recursive: true });
// Auth schemas
const authSchemasContent = `import { z } from 'zod';
export const loginSchema = z.object({
email: z.string().email('Invalid email format'),
password: z.string().min(1, 'Password is required')
});
export const registerSchema = z.object({
email: z.string().email('Invalid email format'),
password: z.string().min(6, 'Password must be at least 6 characters'),
name: z.string().min(1, 'Name is required').max(100, 'Name is too long')
});
export type LoginInput = z.infer<typeof loginSchema>;
export type RegisterInput = z.infer<typeof registerSchema>;
`;
await fs_1.promises.writeFile(path.join(schemasDir, 'auth.ts'), authSchemasContent);
// User schemas
const userSchemasContent = `import { z } from 'zod';
export const updateUserSchema = z.object({
email: z.string().email('Invalid email format').optional(),
name: z.string().min(1, 'Name is required').max(100, 'Name is too long').optional()
});
export const userQuerySchema = z.object({
page: z.coerce.number().min(1).default(1),
limit: z.coerce.number().min(1).max(100).default(10),
search: z.string().optional(),
sort: z.enum(['createdAt', 'updatedAt', 'name', 'email']).optional(),
order: z.enum(['asc', 'desc']).default('desc')
});
export type UpdateUserInput = z.infer<typeof updateUserSchema>;
export type UserQuery = z.infer<typeof userQuerySchema>;
`;
await fs_1.promises.writeFile(path.join(schemasDir, 'user.ts'), userSchemasContent);
// Common schemas
const commonSchemasContent = `import { z } from 'zod';
// UUID schema
export const uuidSchema = z.string().uuid('Invalid UUID format');
// Date schema
export const dateSchema = z.string().datetime('Invalid date format');
// Pagination schema
export const paginationSchema = z.object({
page: z.coerce.number().min(1).default(1),
limit: z.coerce.number().min(1).max(100).default(10)
});
// Response schemas
export const errorResponseSchema = z.object({
error: z.string(),
message: z.string(),
statusCode: z.number(),
details: z.any().optional()
});
export const successResponseSchema = <T extends z.ZodTypeAny>(dataSchema: T) =>
z.object({
data: dataSchema,
meta: z.object({
page: z.number(),
limit: z.number(),
total: z.number(),
totalPages: z.number()
}).optional()
});
`;
await fs_1.promises.writeFile(path.join(schemasDir, 'common.ts'), commonSchemasContent);
}
}
exports.HonoGenerator = HonoGenerator;