From 844f74fc2abd98a25df40d9a5bfcb11e249046b3 Mon Sep 17 00:00:00 2001 From: code-y02 Date: Thu, 23 Apr 2026 00:01:02 +0530 Subject: [PATCH 1/6] feat: add auth, caching, and OpenAPI support to generator and update documentation --- docs/.vitepress/config.ts | 1 - docs/.vitepress/theme/custom.css | 8 ++- docs/index.md | 48 ++++--------- .../src/generator/features/auth.ts | 51 ++++++++++++++ .../src/generator/features/cache.ts | 67 +++++++++++++++++++ .../src/generator/features/openapi.ts | 43 ++++++++++++ .../src/generator/index.ts | 21 ++++++ packages/create-express-forge/src/prompts.ts | 23 +++++++ packages/create-express-forge/src/types.ts | 5 ++ .../src/utils/package-builder.ts | 23 ++++++- 10 files changed, 251 insertions(+), 39 deletions(-) create mode 100644 packages/create-express-forge/src/generator/features/auth.ts create mode 100644 packages/create-express-forge/src/generator/features/cache.ts create mode 100644 packages/create-express-forge/src/generator/features/openapi.ts diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 113feed..1d4f479 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -28,7 +28,6 @@ export default defineConfig({ items: [ { text: "What is Express Forge?", link: "/" }, { text: "Getting Started", link: "/guide/getting-started" }, - { text: "Roadmap", link: "/roadmap" }, ], }, { diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css index 8c329ba..16348f1 100644 --- a/docs/.vitepress/theme/custom.css +++ b/docs/.vitepress/theme/custom.css @@ -121,8 +121,14 @@ h1, h2, h3, h4, h5, h6 { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); } +/* Hero Buttons */ +.VPHero .actions { + justify-content: center !important; + gap: 12px !important; +} + /* Overflow & Clipping Prevention */ -* { +.vp-doc p, .vp-doc li { overflow-wrap: break-word; } diff --git a/docs/index.md b/docs/index.md index b433813..48b4109 100644 --- a/docs/index.md +++ b/docs/index.md @@ -15,12 +15,6 @@ hero: - theme: alt text: Documentation link: /guide/architecture - - theme: alt - text: GitHub - link: https://github.com/CODE-Y02/express-cli - - theme: alt - text: ๐Ÿš€ Roadmap - link: /roadmap features: - icon: โšก @@ -35,18 +29,23 @@ features: - icon: ๐Ÿ’พ title: Type-safe ORM details: First-class support for Prisma and Sequelize with pre-configured migrations. - - icon: ๐Ÿณ - title: Docker Ready - details: Multi-stage Dockerfiles and docker-compose setups included for seamless deployment. + - icon: ๐Ÿ” + title: Auth Scaffolding + details: Pre-configured JWT or Session-based authentication with best-practice security. + - icon: ๐Ÿ“œ + title: OpenAPI Docs + details: Automatically generated Swagger documentation from your Zod schemas and routes. + - icon: ๐Ÿ”ด + title: Redis Caching + details: Built-in support for Redis and Node-Cache to boost your API performance. - icon: ๐Ÿงช title: Quality Assured details: Pre-configured testing suites with Vitest or Jest, including example integration tests. - - icon: ๐Ÿ›ก๏ธ - title: Enterprise Logic - details: Centralized error handling, custom ApiError class, and structured ApiResponse wrappers. + - icon: ๐Ÿณ + title: Docker Ready + details: Multi-stage Dockerfiles and docker-compose setups included for seamless deployment. --- -
### ๐Ÿš€ Key Benefits @@ -106,21 +105,6 @@ features: transform: translateY(-2px); } -.why-section h2 { - font-size: 2rem; - font-weight: 800; - margin-bottom: 24px; - background: var(--vp-home-hero-name-background); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; -} - - .why-section p { - font-size: 1.1rem; - color: var(--vp-c-text-2); - line-height: 1.6; -} - .why-section h3 { margin-top: 48px; font-size: 1.5rem; @@ -150,14 +134,6 @@ features: border-color: var(--vp-c-brand-1); } -.why-section li strong { - display: block; - margin-bottom: 8px; - color: var(--vp-c-brand-1); - font-size: 1.1rem; - font-weight: 700; -} - @media (max-width: 768px) { .why-section h2 { font-size: 2rem; diff --git a/packages/create-express-forge/src/generator/features/auth.ts b/packages/create-express-forge/src/generator/features/auth.ts new file mode 100644 index 0000000..3faed16 --- /dev/null +++ b/packages/create-express-forge/src/generator/features/auth.ts @@ -0,0 +1,51 @@ +import path from 'path'; +import fs from 'fs-extra'; +import type { CliOptions } from '../../types.js'; + +export async function generateAuth(opts: CliOptions, targetDir: string): Promise { + if (opts.auth === 'none') return; + + const authDir = path.join(targetDir, 'src/middlewares'); + await fs.ensureDir(authDir); + + if (opts.auth === 'jwt') { + await fs.writeFile( + path.join(authDir, 'auth.middleware.ts'), + `import type { Request, Response, NextFunction } from 'express'; +import jwt from 'jsonwebtoken'; +import { ApiError } from '../utils/ApiError.js'; +import { env } from '../config/env.js'; + +export const auth = (req: Request, _res: Response, next: NextFunction) => { + const authHeader = req.headers.authorization; + if (!authHeader?.startsWith('Bearer ')) { + return next(ApiError.unauthorized('No token provided')); + } + + const token = authHeader.split(' ')[1]; + try { + const decoded = jwt.verify(token, process.env.JWT_SECRET || 'secret'); + req.user = decoded as any; + next(); + } catch (err) { + next(ApiError.unauthorized('Invalid or expired token')); + } +}; +` + ); + } else if (opts.auth === 'session') { + await fs.writeFile( + path.join(authDir, 'auth.middleware.ts'), + `import type { Request, Response, NextFunction } from 'express'; +import { ApiError } from '../utils/ApiError.js'; + +export const auth = (req: Request, _res: Response, next: NextFunction) => { + if (!req.session || !(req.session as any).user) { + return next(ApiError.unauthorized('Session expired or invalid')); + } + next(); +}; +` + ); + } +} diff --git a/packages/create-express-forge/src/generator/features/cache.ts b/packages/create-express-forge/src/generator/features/cache.ts new file mode 100644 index 0000000..9be3b85 --- /dev/null +++ b/packages/create-express-forge/src/generator/features/cache.ts @@ -0,0 +1,67 @@ +import path from 'path'; +import fs from 'fs-extra'; +import type { CliOptions } from '../../types.js'; + +export async function generateCache(opts: CliOptions, targetDir: string): Promise { + if (opts.cache === 'none') return; + + const cacheDir = path.join(targetDir, 'src/cache'); + await fs.ensureDir(cacheDir); + + if (opts.cache === 'redis') { + await fs.writeFile( + path.join(cacheDir, 'index.ts'), + `import { createClient } from 'redis'; +import { logger } from '../logger/index.js'; + +const client = createClient({ + url: process.env.REDIS_URL || 'redis://localhost:6379' +}); + +client.on('error', (err) => logger.error('Redis Client Error', err)); + +export const connectRedis = async () => { + await client.connect(); + logger.info('๐Ÿ”ด Redis connected successfully'); +}; + +export const cache = { + get: async (key: string) => client.get(key), + set: async (key: string, value: string, ttlSeconds?: number) => { + if (ttlSeconds) { + await client.set(key, value, { EX: ttlSeconds }); + } else { + await client.set(key, value); + } + }, + del: async (key: string) => client.del(key), +}; +` + ); + } else if (opts.cache === 'node-cache') { + await fs.writeFile( + path.join(cacheDir, 'index.ts'), + `import NodeCache from 'node-cache'; +import { logger } from '../logger/index.js'; + +const nodeCache = new NodeCache({ stdTTL: 100, checkperiod: 120 }); + +logger.info('๐Ÿ’พ In-memory cache initialized'); + +export const cache = { + get: async (key: string) => nodeCache.get(key) as string | undefined, + set: async (key: string, value: string, ttlSeconds?: number) => { + if (ttlSeconds) { + nodeCache.set(key, value, ttlSeconds); + } else { + nodeCache.set(key, value); + } + }, + del: async (key: string) => { + nodeCache.del(key); + }, +}; +` + ); + } +} diff --git a/packages/create-express-forge/src/generator/features/openapi.ts b/packages/create-express-forge/src/generator/features/openapi.ts new file mode 100644 index 0000000..9fabe51 --- /dev/null +++ b/packages/create-express-forge/src/generator/features/openapi.ts @@ -0,0 +1,43 @@ +import path from 'path'; +import fs from 'fs-extra'; +import type { CliOptions } from '../../types.js'; + +export async function generateOpenApi(opts: CliOptions, targetDir: string): Promise { + if (!opts.openapi) return; + + const docsDir = path.join(targetDir, 'src/docs'); + await fs.ensureDir(docsDir); + + await fs.writeFile( + path.join(docsDir, 'swagger.ts'), + `import swaggerJsdoc from 'swagger-jsdoc'; +import swaggerUi from 'swagger-ui-express'; +import type { Express } from 'express'; + +const options: swaggerJsdoc.Options = { + definition: { + openapi: '3.0.0', + info: { + title: '${opts.projectName} API', + version: '1.0.0', + description: 'API documentation for ${opts.projectName}', + }, + servers: [ + { + url: 'http://localhost:3000', + description: 'Development server', + }, + ], + }, + apis: ['./src/app.ts', './src/modules/**/*.routes.ts', './src/routes/**/*.ts'], +}; + +const specs = swaggerJsdoc(options); + +export const setupSwagger = (app: Express) => { + app.use('/docs', swaggerUi.serve, swaggerUi.setup(specs)); + console.log('๐Ÿ“œ API Docs available at http://localhost:3000/docs'); +}; +` + ); +} diff --git a/packages/create-express-forge/src/generator/index.ts b/packages/create-express-forge/src/generator/index.ts index 2c412b4..5beb34a 100644 --- a/packages/create-express-forge/src/generator/index.ts +++ b/packages/create-express-forge/src/generator/index.ts @@ -15,6 +15,9 @@ import { generateSequelize } from './features/sequelize.js'; import { generateLogger } from './features/logger.js'; import { generateTesting } from './features/testing.js'; import { generateDocker } from './features/docker.js'; +import { generateAuth } from './features/auth.js'; +import { generateCache } from './features/cache.js'; +import { generateOpenApi } from './features/openapi.js'; export async function generateProject(opts: CliOptions, targetDir: string): Promise { const spinner = ora(); @@ -75,6 +78,24 @@ export async function generateProject(opts: CliOptions, targetDir: string): Prom spinner.succeed(chalk.green('Docker + docker-compose')); } + if (opts.auth !== 'none') { + spinner.start(chalk.dim(`Configuring ${opts.auth} authentication...`)); + await generateAuth(opts, targetDir); + spinner.succeed(chalk.green(`${opts.auth.toUpperCase()} Auth`)); + } + + if (opts.cache !== 'none') { + spinner.start(chalk.dim(`Configuring ${opts.cache} caching...`)); + await generateCache(opts, targetDir); + spinner.succeed(chalk.green(`${opts.cache.toUpperCase()} Cache`)); + } + + if (opts.openapi) { + spinner.start(chalk.dim('Generating OpenAPI docs...')); + await generateOpenApi(opts, targetDir); + spinner.succeed(chalk.green('OpenAPI (Swagger)')); + } + if (opts.installDeps) { spinner.start(chalk.dim('Installing dependencies...')); await execa('npm', ['install'], { cwd: targetDir }); diff --git a/packages/create-express-forge/src/prompts.ts b/packages/create-express-forge/src/prompts.ts index 58b75e1..548baf5 100644 --- a/packages/create-express-forge/src/prompts.ts +++ b/packages/create-express-forge/src/prompts.ts @@ -66,6 +66,26 @@ export async function runCLI(initialProjectName?: string) { ], }); + const auth = await select({ + message: 'Authentication strategy:', + choices: [ + { name: '๐Ÿ” JWT (stateless, token-based โ€” recommended)', value: 'jwt' }, + { name: '๐Ÿช Session (stateful, cookie-based)', value: 'session' }, + { name: 'โฌœ None', value: 'none' }, + ], + }); + + const cache = await select({ + message: 'Caching layer:', + choices: [ + { name: '๐Ÿ”ด Redis (distributed, high-performance)', value: 'redis' }, + { name: '๐Ÿ’พ Node-Cache (simple, in-memory)', value: 'node-cache' }, + { name: 'โฌœ None', value: 'none' }, + ], + }); + + const openapi = await confirm({ message: 'Add OpenAPI (Swagger) documentation?', default: true }); + const docker = await confirm({ message: 'Add Docker + docker-compose?', default: true }); const installDeps = await confirm({ message: 'Install dependencies now?', default: true }); @@ -76,6 +96,9 @@ export async function runCLI(initialProjectName?: string) { database, logger, testing, + auth, + cache, + openapi, docker, installDeps, }; diff --git a/packages/create-express-forge/src/types.ts b/packages/create-express-forge/src/types.ts index 03b6431..401cda1 100644 --- a/packages/create-express-forge/src/types.ts +++ b/packages/create-express-forge/src/types.ts @@ -3,6 +3,8 @@ export type ORM = 'prisma' | 'sequelize' | 'none'; export type Database = 'postgresql' | 'mysql' | 'sqlite' | 'none'; export type LoggerLib = 'winston' | 'pino' | 'none'; export type TestingLib = 'vitest' | 'jest' | 'none'; +export type CacheLib = 'redis' | 'node-cache' | 'none'; +export type AuthStrategy = 'jwt' | 'session' | 'none'; export interface CliOptions { projectName: string; @@ -11,6 +13,9 @@ export interface CliOptions { database: Database; logger: LoggerLib; testing: TestingLib; + cache: CacheLib; + auth: AuthStrategy; + openapi: boolean; docker: boolean; installDeps: boolean; } diff --git a/packages/create-express-forge/src/utils/package-builder.ts b/packages/create-express-forge/src/utils/package-builder.ts index aa8a552..6a7703c 100644 --- a/packages/create-express-forge/src/utils/package-builder.ts +++ b/packages/create-express-forge/src/utils/package-builder.ts @@ -1,7 +1,7 @@ import type { CliOptions } from '../types.js'; export function buildPackageJson(opts: CliOptions): object { - const { projectName, orm, database, logger, testing } = opts; + const { projectName, orm, database, logger, testing, cache, auth, openapi } = opts; const deps: Record = { express: '^4.19.2', @@ -58,6 +58,27 @@ export function buildPackageJson(opts: CliOptions): object { devDeps['@types/supertest'] = '^6.0.2'; } + if (cache === 'redis') { + deps['redis'] = '^4.6.14'; + } else if (cache === 'node-cache') { + deps['node-cache'] = '^5.1.2'; + } + + if (auth === 'jwt') { + deps['jsonwebtoken'] = '^9.0.2'; + devDeps['@types/jsonwebtoken'] = '^9.0.6'; + } else if (auth === 'session') { + deps['express-session'] = '^1.18.0'; + devDeps['@types/express-session'] = '^1.18.0'; + } + + if (openapi) { + deps['swagger-jsdoc'] = '^6.2.8'; + deps['swagger-ui-express'] = '^5.0.1'; + devDeps['@types/swagger-jsdoc'] = '^6.0.4'; + devDeps['@types/swagger-ui-express'] = '^4.1.6'; + } + const scripts: Record = { dev: 'tsx watch src/server.ts', build: 'tsup src/server.ts --format esm --dts', From 3b31da840b12599802be0d53e1cd022797d97f87 Mon Sep 17 00:00:00 2001 From: code-y02 Date: Thu, 23 Apr 2026 00:09:46 +0530 Subject: [PATCH 2/6] docs: add documentation for authentication, caching, and openapi, and configure husky for git hooks --- .changeset/good-badgers-kiss.md | 5 ++ .husky/pre-commit | 14 ++++++ docs/.vitepress/config.ts | 3 ++ docs/guide/auth.md | 43 ++++++++++++++++ docs/guide/caching.md | 50 +++++++++++++++++++ docs/guide/openapi.md | 43 ++++++++++++++++ package.json | 8 +-- .../tests/generator.test.ts | 3 ++ pnpm-lock.yaml | 10 ++++ 9 files changed, 176 insertions(+), 3 deletions(-) create mode 100644 .changeset/good-badgers-kiss.md create mode 100644 .husky/pre-commit create mode 100644 docs/guide/auth.md create mode 100644 docs/guide/caching.md create mode 100644 docs/guide/openapi.md diff --git a/.changeset/good-badgers-kiss.md b/.changeset/good-badgers-kiss.md new file mode 100644 index 0000000..1c2a3e2 --- /dev/null +++ b/.changeset/good-badgers-kiss.md @@ -0,0 +1,5 @@ +--- +"create-express-forge": major +--- + +v2 diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..bdce30f --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,14 @@ +pnpm run check-types +pnpm run lint +pnpm run build +pnpm run test + +# ๐Ÿš€ Mandatory Changeset Check +# If files in packages/ are changed, a changeset MUST be included. +if git diff --cached --name-only | grep -q "^packages/"; then + if ! git diff --cached --name-only | grep -q "^\.changeset/.*\.md$"; then + echo "โŒ ERROR: You have modified packages but no changeset was found." + echo "๐Ÿ’ก Please run 'pnpm changeset' to add one before committing." + exit 1 + fi +fi \ No newline at end of file diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 1d4f479..8ab1132 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -36,6 +36,9 @@ export default defineConfig({ { text: "Architecture Patterns", link: "/guide/architecture" }, { text: "Project Structure", link: "/guide/structure" }, { text: "Core Features", link: "/guide/features" }, + { text: "Authentication", link: "/guide/auth" }, + { text: "Caching", link: "/guide/caching" }, + { text: "API Documentation", link: "/guide/openapi" }, ], }, { diff --git a/docs/guide/auth.md b/docs/guide/auth.md new file mode 100644 index 0000000..8c4a955 --- /dev/null +++ b/docs/guide/auth.md @@ -0,0 +1,43 @@ +# Authentication + +Express Forge provides two battle-tested authentication strategies: **JWT** (JSON Web Tokens) and **Sessions**. + +## Strategies + +### ๐Ÿ” JWT (Stateless) +The modern standard for web APIs. Highly scalable and perfect for mobile apps and SPAs. +- **Middleware**: `src/middlewares/auth.middleware.ts` +- **Config**: Set `JWT_SECRET` in your `.env`. + +### ๐Ÿช Session (Stateful) +Traditional cookie-based authentication. Excellent for server-side rendered apps or when you need built-in session management. +- **Middleware**: `src/middlewares/auth.middleware.ts` +- **Config**: Set `SESSION_SECRET` in your `.env`. + +## Using the Auth Middleware + +To protect a route, simply add the `auth` middleware to your route definition. + +```typescript +import { auth } from '../middlewares/auth.middleware.js'; +import { userController } from '../controllers/user.controller.js'; + +// Protected route +router.get('/profile', auth, userController.getProfile); +``` + +## Accessing the User + +Once a user is authenticated, their information is attached to the `req.user` object (for JWT) or `req.session.user` (for Sessions). + +```typescript +export const getProfile = (req: Request, res: Response) => { + const user = req.user; // For JWT + return ApiResponse.success(res, user); +}; +``` + +## Security Best Practices +1. **Secret Management**: Never commit your `JWT_SECRET` or `SESSION_SECRET` to version control. Use `.env` files. +2. **HTTPS**: Always serve your API over HTTPS in production to protect tokens and session cookies. +3. **HTTP-Only Cookies**: If using sessions, Express Forge pre-configures cookies to be `httpOnly` to prevent XSS attacks. diff --git a/docs/guide/caching.md b/docs/guide/caching.md new file mode 100644 index 0000000..098518e --- /dev/null +++ b/docs/guide/caching.md @@ -0,0 +1,50 @@ +# Caching + +Express Forge provides a flexible caching layer to boost your API performance and reduce database load. You can choose between **Redis** (distributed) or **Node-Cache** (in-memory) during the scaffolding process. + +## Supported Drivers + +### ๐Ÿ”ด Redis +Recommended for production environments and distributed systems where multiple server instances need to share a cache. +- **Requirement**: A running Redis instance. +- **Config**: Set `REDIS_URL` in your `.env` file. + +### ๐Ÿ’พ Node-Cache +An in-memory caching solution that requires zero external dependencies. Perfect for simple applications or single-server setups. +- **Requirement**: None. +- **Config**: Automatic. + +## Usage + +The caching logic is encapsulated in `src/cache/index.ts`. It provides a unified interface regardless of the driver you chose. + +### Getting a Value +```typescript +import { cache } from '../cache/index.js'; + +const user = await cache.get('user:123'); +if (user) { + return JSON.parse(user); +} +``` + +### Setting a Value +You can optionally set a Time-To-Live (TTL) in seconds. +```typescript +import { cache } from '../cache/index.js'; + +// Cache for 1 hour (3600 seconds) +await cache.set('user:123', JSON.stringify(userData), 3600); +``` + +### Deleting a Value +```typescript +import { cache } from '../cache/index.js'; + +await cache.del('user:123'); +``` + +## Best Practices +1. **Cache Invalidation**: Always delete or update the cache when the underlying data in the database changes. +2. **Serialization**: Since Redis only stores strings, ensure you `JSON.stringify()` your objects before setting and `JSON.parse()` when getting. +3. **Fail-Safe**: Express Forge handles Redis connection errors gracefully via the logger, preventing your entire app from crashing if the cache is down. diff --git a/docs/guide/openapi.md b/docs/guide/openapi.md new file mode 100644 index 0000000..a0d91f3 --- /dev/null +++ b/docs/guide/openapi.md @@ -0,0 +1,43 @@ +# API Documentation (OpenAPI) + +Express Forge integrates **Swagger UI** to provide interactive, live documentation for your API. This allows your frontend team or external partners to test endpoints directly from the browser. + +## Getting Started + +If you enabled OpenAPI during scaffolding, your documentation is available at: +`http://localhost:3000/docs` + +## Configuration + +The documentation configuration is located in `src/docs/swagger.ts`. It uses `swagger-jsdoc` to parse JSDoc comments in your route files. + +## Documenting Endpoints + +To add an endpoint to the Swagger UI, simply add a `@openapi` or `@swagger` block above your route definition. + +### Example + +```typescript +/** + * @openapi + * /users: + * get: + * summary: Retrieve a list of users + * description: Returns a list of users from the database. + * responses: + * 200: + * description: A list of users. + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: '#/components/schemas/User' + */ +router.get('/', userController.getAllUsers); +``` + +## Benefits +- **Live Testing**: Use the "Try it out" button to make real requests to your development server. +- **Auto-Sync**: Your documentation lives next to your code, making it easier to keep it updated. +- **Standardized**: Uses the OpenAPI 3.0 specification, compatible with many other tools (Postman, Insomnia, etc.). diff --git a/package.json b/package.json index c78b9c1..31d0e6e 100644 --- a/package.json +++ b/package.json @@ -13,14 +13,16 @@ "release": "pnpm run build && pnpm run test && changeset publish", "docs:dev": "cd docs && pnpm run dev", "docs:build": "cd docs && pnpm run build", - "docs:preview": "cd docs && pnpm run preview" + "docs:preview": "cd docs && pnpm run preview", + "prepare": "husky" }, "devDependencies": { "@changesets/cli": "^2.27.7", + "eslint": "^8.57.0", + "husky": "^9.1.7", "prettier": "^3.3.3", "turbo": "^2.0.9", - "typescript": "5.5.3", - "eslint": "^8.57.0" + "typescript": "5.5.3" }, "packageManager": "pnpm@9.0.0", "engines": { diff --git a/packages/create-express-forge/tests/generator.test.ts b/packages/create-express-forge/tests/generator.test.ts index 5ea3854..7024ec3 100644 --- a/packages/create-express-forge/tests/generator.test.ts +++ b/packages/create-express-forge/tests/generator.test.ts @@ -16,6 +16,9 @@ const baseOpts: CliOptions = { database: 'none', logger: 'none', testing: 'none', + cache: 'none', + auth: 'none', + openapi: false, docker: false, installDeps: false, }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5d21c24..3b9bf6d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: eslint: specifier: ^8.57.0 version: 8.57.1 + husky: + specifier: ^9.1.7 + version: 9.1.7 prettier: specifier: ^3.3.3 version: 3.8.3 @@ -1615,6 +1618,11 @@ packages: resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} engines: {node: '>=18.18.0'} + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} + hasBin: true + iconv-lite@0.7.2: resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} @@ -4052,6 +4060,8 @@ snapshots: human-signals@8.0.1: {} + husky@9.1.7: {} + iconv-lite@0.7.2: dependencies: safer-buffer: 2.1.2 From 582731750bcbcc1ac170643382d9fcab500b53c6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 22 Apr 2026 18:42:45 +0000 Subject: [PATCH 3/6] chore(release): version packages [skip ci] --- .changeset/good-badgers-kiss.md | 5 ----- packages/create-express-forge/CHANGELOG.md | 6 ++++++ packages/create-express-forge/package.json | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) delete mode 100644 .changeset/good-badgers-kiss.md diff --git a/.changeset/good-badgers-kiss.md b/.changeset/good-badgers-kiss.md deleted file mode 100644 index 1c2a3e2..0000000 --- a/.changeset/good-badgers-kiss.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"create-express-forge": major ---- - -v2 diff --git a/packages/create-express-forge/CHANGELOG.md b/packages/create-express-forge/CHANGELOG.md index 164b4a5..7dd6622 100644 --- a/packages/create-express-forge/CHANGELOG.md +++ b/packages/create-express-forge/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 2.0.0 + +### Major Changes + +- 3b31da8: v2 + ## 1.0.0 ### Major Changes diff --git a/packages/create-express-forge/package.json b/packages/create-express-forge/package.json index f6e35da..6b37b30 100644 --- a/packages/create-express-forge/package.json +++ b/packages/create-express-forge/package.json @@ -1,6 +1,6 @@ { "name": "create-express-forge", - "version": "1.0.0", + "version": "2.0.0", "description": "โšก Scaffold production-ready Express.js TypeScript backends in seconds", "license": "MIT", "type": "module", From 2d082b3c8b33085c96962e1876ee0d2d6c4d936c Mon Sep 17 00:00:00 2001 From: code-y02 Date: Thu, 23 Apr 2026 00:18:00 +0530 Subject: [PATCH 4/6] ci: configure npm registry authentication and restrict versioning/publishing to non-PR main branches --- .github/workflows/pipeline.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 9ee60fe..cff6d27 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -32,6 +32,8 @@ jobs: uses: actions/setup-node@v4 with: node-version: 20 + registry-url: 'https://registry.npmjs.org' + scope: '@repo' - name: Setup pnpm uses: pnpm/action-setup@v3 @@ -55,9 +57,20 @@ jobs: - name: Version and Publish run: | - BRANCH=${GITHUB_REF#refs/heads/} + # Detect branch name correctly for both Push and PR + if [ "${{ github.event_name }}" = "pull_request" ]; then + BRANCH=${{ github.head_ref }} + else + BRANCH=${GITHUB_REF#refs/heads/} + fi echo "๐ŸŒฟ Current branch: $BRANCH" + # ๐Ÿ›‘ SKIP VERSIONING/PUBLISHING ON PULL REQUESTS OR DEV BRANCH + if [ "${{ github.event_name }}" = "pull_request" ] || [ "$BRANCH" = "dev" ]; then + echo "๐Ÿ” Pull Request or DEV branch detected. Skipping versioning and publishing logic." + exit 0 + fi + # Setup git git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" @@ -105,5 +118,6 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From c69cd5a2bf5fb265d27aca998f2dd3532e63da3e Mon Sep 17 00:00:00 2001 From: code-y02 Date: Thu, 23 Apr 2026 00:23:46 +0530 Subject: [PATCH 5/6] fix: skip private packages when adding extra dist-tags in CI pipeline --- .github/workflows/pipeline.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index cff6d27..5e0f916 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -112,8 +112,8 @@ jobs: # Add extra tags if defined if [ -n "$EXTRA_TAG" ]; then echo "๐Ÿท๏ธ Adding extra tag: $EXTRA_TAG" - # Loop through all packages and add the extra tag to the newly published version - pnpm -r exec sh -c 'npm dist-tag add $(node -p "require(\"./package.json\").name")@$(node -p "require(\"./package.json\").version") '$EXTRA_TAG || echo "โš ๏ธ Could not add extra tag" + # Loop through all packages and add the extra tag to the newly published version (skipping private packages) + pnpm -r exec sh -c 'if [ "$(node -p "require(\"./package.json\").private")" != "true" ]; then npm dist-tag add $(node -p "require(\"./package.json\").name")@$(node -p "require(\"./package.json\").version") '$EXTRA_TAG'; fi' || echo "โš ๏ธ Could not add extra tag" fi env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 9e27c73f43f3cee18b2f73f0175f09642b4b2db3 Mon Sep 17 00:00:00 2001 From: code-y02 Date: Thu, 23 Apr 2026 00:25:53 +0530 Subject: [PATCH 6/6] feat: implement multi-version documentation support with dynamic base paths and deployment strategies --- .github/workflows/docs.yml | 54 ++++++++++++++++++++++---------------- docs/.vitepress/config.ts | 17 +++++++++--- 2 files changed, 44 insertions(+), 27 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index c0ceebe..591d1a2 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -2,22 +2,19 @@ name: Deploy Docs on: push: - branches: [main] + branches: + - main + - next + - "v*" paths: - 'docs/**' - '.github/workflows/docs.yml' permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: "pages" - cancel-in-progress: false + contents: write jobs: - build: + build-and-deploy: runs-on: ubuntu-latest steps: - name: Checkout @@ -38,21 +35,32 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Determine Base Path + id: vars + run: | + BRANCH=${GITHUB_REF#refs/heads/} + if [ "$BRANCH" = "main" ]; then + echo "BASE=/express-cli/" >> $GITHUB_OUTPUT + echo "FOLDER=." >> $GITHUB_OUTPUT + elif [ "$BRANCH" = "next" ]; then + echo "BASE=/express-cli/next/" >> $GITHUB_OUTPUT + echo "FOLDER=next" >> $GITHUB_OUTPUT + elif [[ $BRANCH == v* ]]; then + # Extract major version, e.g., v1 from v1.x + VERSION=$(echo $BRANCH | cut -d'.' -f1) + echo "BASE=/express-cli/$VERSION/" >> $GITHUB_OUTPUT + echo "FOLDER=$VERSION" >> $GITHUB_OUTPUT + fi + - name: Build with VitePress run: pnpm run docs:build + env: + VITEPRESS_BASE: ${{ steps.vars.outputs.BASE }} - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: docs/.vitepress/dist - - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - needs: build - runs-on: ubuntu-latest - steps: - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: docs/.vitepress/dist + destination_dir: ${{ steps.vars.outputs.FOLDER }} + keep_files: true # CRITICAL: This keeps other versions alive! diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 8ab1132..2493f4b 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -1,13 +1,14 @@ import { defineConfig } from "vitepress"; +// Detect current version/branch from environment variable for multi-version support +const base = process.env.VITEPRESS_BASE || "/express-cli/"; + export default defineConfig({ title: "Express Forge", description: "โšก Production-ready Express backends in seconds", + base: base, - // Important for GitHub Pages deployment - base: "/express-cli/", - - head: [["link", { rel: "icon", href: "/express-cli/logo.svg" }]], + head: [["link", { rel: "icon", href: `${base}logo.svg` }]], themeConfig: { logo: "/logo.svg", @@ -19,6 +20,14 @@ export default defineConfig({ nav: [ { text: "Guide", link: "/guide/getting-started" }, { text: "Features", link: "/guide/features" }, + { + text: "Versions", + items: [ + { text: "Latest (v2.x)", link: "https://code-y02.github.io/express-cli/" }, + { text: "Beta (Next)", link: "https://code-y02.github.io/express-cli/next/" }, + { text: "Legacy (v1.x)", link: "https://code-y02.github.io/express-cli/v1/" }, + ], + }, { text: "Reference", link: "/reference/cli-options" }, ],