From d617dbf531405a1df4eb526146317ad2df934f22 Mon Sep 17 00:00:00 2001 From: code-y02 Date: Mon, 27 Apr 2026 22:46:10 +0530 Subject: [PATCH 01/16] feat: implement comprehensive scaffold templates for mvc and modular architectures in create-express-forge --- .changeset/easy-pans-wink.md | 7 + .changeset/shy-papers-peel.md | 5 + .changeset/tidy-mammals-stay.md | 6 + .eslintrc.cjs | 8 +- biome.json | 14 + docs/.vitepress/config.ts | 31 +- docs/.vitepress/theme/custom.css | 110 ++- docs/.vitepress/theme/index.ts | 2 +- package.json | 5 +- packages/create-express-forge/package.json | 10 +- .../src/generator/base.ts | 134 +-- .../src/generator/features/auth.ts | 78 +- .../src/generator/features/cache.ts | 84 +- .../src/generator/features/docker.ts | 155 +--- .../src/generator/features/logger.ts | 37 +- .../src/generator/features/openapi.ts | 70 +- .../src/generator/features/prisma.ts | 133 +-- .../src/generator/features/sequelize.ts | 54 +- .../src/generator/features/testing.ts | 43 +- .../src/generator/index.ts | 184 ++-- .../src/generator/structure/modular.ts | 143 ++- .../src/generator/structure/mvc.ts | 115 +-- .../src/generator/templates.ts | 339 ------- packages/create-express-forge/src/index.ts | 43 +- packages/create-express-forge/src/prompts.ts | 276 ++++-- packages/create-express-forge/src/types.ts | 19 +- .../create-express-forge/src/utils/display.ts | 74 +- .../create-express-forge/src/utils/file.ts | 13 +- .../src/utils/package-builder.ts | 187 ++-- .../src/utils/template-manager.ts | 275 +----- .../templates/base/.env.example.eta | 33 + .../templates/base/.gitignore.eta | 11 + .../templates/base/README.md.eta | 23 + .../templates/base/src/app.ts.eta | 66 ++ .../templates/base/src/config/env.ts.eta | 35 + .../base/src/middleware/errorHandler.ts.eta | 38 + .../base/src/middleware/notFound.ts.eta | 6 + .../base/src/middleware/rateLimiter.ts.eta | 10 + .../base/src/middleware/validate.ts.eta | 18 + .../templates/base/src/server.ts.eta | 58 ++ .../templates/base/src/types/express.d.ts.eta | 7 + .../templates/base/src/utils/ApiError.ts.eta | 21 + .../base/src/utils/ApiResponse.ts.eta | 14 + .../templates/base/tsconfig.json.eta | 20 + .../features/auth/jwt-auth.middleware.ts.eta | 29 + .../auth/session-auth.middleware.ts.eta | 9 + .../features/cache/node-cache.ts.eta | 3 + .../templates/features/cache/redis.ts.eta | 12 + .../templates/features/docker/Dockerfile.eta | 43 + .../features/docker/docker-compose.yml.eta | 56 ++ .../templates/features/logger/pino.ts.eta | 19 + .../templates/features/logger/winston.ts.eta | 30 + .../templates/features/openapi/swagger.ts.eta | 34 + .../templates/features/prisma/database.ts.eta | 5 + .../features/prisma/schema.prisma.eta | 47 + .../templates/features/prisma/seed.ts.eta | 36 + .../features/sequelize/.sequelizerc.eta | 7 + .../templates/features/sequelize/User.ts.eta | 11 + .../features/sequelize/database.ts.eta | 17 + .../features/sequelize/sequelize.cjs.eta | 6 + .../features/testing/jest.config.js.eta | 7 + .../features/testing/smoke.test.ts.eta | 9 + .../features/testing/vitest.config.ts.eta | 9 + .../modules/auth/auth.controller.ts.eta | 16 + .../modular/modules/auth/auth.routes.ts.eta | 32 + .../modular/modules/auth/auth.schema.ts.eta | 8 + .../modules/health/health.controller.ts.eta | 5 + .../modules/health/health.routes.ts.eta | 19 + .../modules/todos/todos.controller.ts.eta | 37 + .../modular/modules/todos/todos.routes.ts.eta | 45 + .../modular/modules/todos/todos.schema.ts.eta | 20 + .../modules/todos/todos.service.ts.eta | 41 + .../mvc/controllers/auth.controller.ts.eta | 16 + .../mvc/controllers/health.controller.ts.eta | 5 + .../mvc/controllers/todo.controller.ts.eta | 29 + .../structure/mvc/routes/auth.routes.ts.eta | 32 + .../structure/mvc/routes/health.routes.ts.eta | 19 + .../structure/mvc/routes/index.ts.eta | 12 + .../structure/mvc/routes/todo.routes.ts.eta | 45 + .../structure/mvc/schemas/auth.schema.ts.eta | 8 + .../structure/mvc/schemas/todo.schema.ts.eta | 20 + .../mvc/services/todo.service.ts.eta | 41 + .../tests/generator.test.ts | 89 -- .../create-express-forge/tests/main.test.ts | 99 ++ .../tests/smoke.integration.test.ts | 84 +- packages/create-express-forge/tsup.config.ts | 16 +- .../create-express-forge/vitest.config.ts | 8 +- packages/eslint-config/index.js | 12 - packages/eslint-config/package.json | 11 - packages/lint-config/biome.base.json | 24 + packages/lint-config/package.json | 8 + pnpm-lock.yaml | 848 +++--------------- turbo.json | 3 +- 93 files changed, 2533 insertions(+), 2522 deletions(-) create mode 100644 .changeset/easy-pans-wink.md create mode 100644 .changeset/shy-papers-peel.md create mode 100644 .changeset/tidy-mammals-stay.md create mode 100644 biome.json delete mode 100644 packages/create-express-forge/src/generator/templates.ts create mode 100644 packages/create-express-forge/templates/base/.env.example.eta create mode 100644 packages/create-express-forge/templates/base/.gitignore.eta create mode 100644 packages/create-express-forge/templates/base/README.md.eta create mode 100644 packages/create-express-forge/templates/base/src/app.ts.eta create mode 100644 packages/create-express-forge/templates/base/src/config/env.ts.eta create mode 100644 packages/create-express-forge/templates/base/src/middleware/errorHandler.ts.eta create mode 100644 packages/create-express-forge/templates/base/src/middleware/notFound.ts.eta create mode 100644 packages/create-express-forge/templates/base/src/middleware/rateLimiter.ts.eta create mode 100644 packages/create-express-forge/templates/base/src/middleware/validate.ts.eta create mode 100644 packages/create-express-forge/templates/base/src/server.ts.eta create mode 100644 packages/create-express-forge/templates/base/src/types/express.d.ts.eta create mode 100644 packages/create-express-forge/templates/base/src/utils/ApiError.ts.eta create mode 100644 packages/create-express-forge/templates/base/src/utils/ApiResponse.ts.eta create mode 100644 packages/create-express-forge/templates/base/tsconfig.json.eta create mode 100644 packages/create-express-forge/templates/features/auth/jwt-auth.middleware.ts.eta create mode 100644 packages/create-express-forge/templates/features/auth/session-auth.middleware.ts.eta create mode 100644 packages/create-express-forge/templates/features/cache/node-cache.ts.eta create mode 100644 packages/create-express-forge/templates/features/cache/redis.ts.eta create mode 100644 packages/create-express-forge/templates/features/docker/Dockerfile.eta create mode 100644 packages/create-express-forge/templates/features/docker/docker-compose.yml.eta create mode 100644 packages/create-express-forge/templates/features/logger/pino.ts.eta create mode 100644 packages/create-express-forge/templates/features/logger/winston.ts.eta create mode 100644 packages/create-express-forge/templates/features/openapi/swagger.ts.eta create mode 100644 packages/create-express-forge/templates/features/prisma/database.ts.eta create mode 100644 packages/create-express-forge/templates/features/prisma/schema.prisma.eta create mode 100644 packages/create-express-forge/templates/features/prisma/seed.ts.eta create mode 100644 packages/create-express-forge/templates/features/sequelize/.sequelizerc.eta create mode 100644 packages/create-express-forge/templates/features/sequelize/User.ts.eta create mode 100644 packages/create-express-forge/templates/features/sequelize/database.ts.eta create mode 100644 packages/create-express-forge/templates/features/sequelize/sequelize.cjs.eta create mode 100644 packages/create-express-forge/templates/features/testing/jest.config.js.eta create mode 100644 packages/create-express-forge/templates/features/testing/smoke.test.ts.eta create mode 100644 packages/create-express-forge/templates/features/testing/vitest.config.ts.eta create mode 100644 packages/create-express-forge/templates/structure/modular/modules/auth/auth.controller.ts.eta create mode 100644 packages/create-express-forge/templates/structure/modular/modules/auth/auth.routes.ts.eta create mode 100644 packages/create-express-forge/templates/structure/modular/modules/auth/auth.schema.ts.eta create mode 100644 packages/create-express-forge/templates/structure/modular/modules/health/health.controller.ts.eta create mode 100644 packages/create-express-forge/templates/structure/modular/modules/health/health.routes.ts.eta create mode 100644 packages/create-express-forge/templates/structure/modular/modules/todos/todos.controller.ts.eta create mode 100644 packages/create-express-forge/templates/structure/modular/modules/todos/todos.routes.ts.eta create mode 100644 packages/create-express-forge/templates/structure/modular/modules/todos/todos.schema.ts.eta create mode 100644 packages/create-express-forge/templates/structure/modular/modules/todos/todos.service.ts.eta create mode 100644 packages/create-express-forge/templates/structure/mvc/controllers/auth.controller.ts.eta create mode 100644 packages/create-express-forge/templates/structure/mvc/controllers/health.controller.ts.eta create mode 100644 packages/create-express-forge/templates/structure/mvc/controllers/todo.controller.ts.eta create mode 100644 packages/create-express-forge/templates/structure/mvc/routes/auth.routes.ts.eta create mode 100644 packages/create-express-forge/templates/structure/mvc/routes/health.routes.ts.eta create mode 100644 packages/create-express-forge/templates/structure/mvc/routes/index.ts.eta create mode 100644 packages/create-express-forge/templates/structure/mvc/routes/todo.routes.ts.eta create mode 100644 packages/create-express-forge/templates/structure/mvc/schemas/auth.schema.ts.eta create mode 100644 packages/create-express-forge/templates/structure/mvc/schemas/todo.schema.ts.eta create mode 100644 packages/create-express-forge/templates/structure/mvc/services/todo.service.ts.eta delete mode 100644 packages/create-express-forge/tests/generator.test.ts create mode 100644 packages/create-express-forge/tests/main.test.ts delete mode 100644 packages/eslint-config/index.js delete mode 100644 packages/eslint-config/package.json create mode 100644 packages/lint-config/biome.base.json create mode 100644 packages/lint-config/package.json diff --git a/.changeset/easy-pans-wink.md b/.changeset/easy-pans-wink.md new file mode 100644 index 0000000..3aa5569 --- /dev/null +++ b/.changeset/easy-pans-wink.md @@ -0,0 +1,7 @@ +--- +"create-express-forge": major +"@repo/lint-config": minor +"@repo/typescript-config": minor +--- + +### ๐Ÿš€ Core CLI & Codebase Modernization diff --git a/.changeset/shy-papers-peel.md b/.changeset/shy-papers-peel.md new file mode 100644 index 0000000..db38820 --- /dev/null +++ b/.changeset/shy-papers-peel.md @@ -0,0 +1,5 @@ +--- +"create-express-forge": major +--- + +### ๐Ÿš€ Core CLI & Codebase Modernization- **Unified Tooling**: Migrated the entire monorepo to **Biome 2.4**, achieving sub-100ms linting and formatting.- **Generator Refactor**: Re-architected the generator logic into modular, testable components (Base, Structure, Features).- **Pro Testing Suite**: - Separated internal logic tests (`main.test.ts`) from CLI E2E tests (`smoke.integration.test.ts`). - Disabled test caching in Turborepo to ensure 100% reliable smoke runs.- **Modern Templates**: - Updated to **Express 5** (native async error handling) and **Prisma 6**. - Implemented professional **multi-stage Docker builds** (Node 20 Alpine). - Integrated **Biome** as the default linter/formatter for all generated projects.- **Production Hardening**: Added graceful shutdown handlers (SIGTERM/SIGINT) and fail-fast database connection checks on startup.- **Infrastructure**: Renamed and modernized shared monorepo configs (`@repo/lint-config`). diff --git a/.changeset/tidy-mammals-stay.md b/.changeset/tidy-mammals-stay.md new file mode 100644 index 0000000..54be252 --- /dev/null +++ b/.changeset/tidy-mammals-stay.md @@ -0,0 +1,6 @@ +--- +"@repo/lint-config": minor +"@repo/typescript-config": minor +--- + +Internal refactor diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 133f41b..28fe266 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,4 +1,10 @@ module.exports = { root: true, - ignorePatterns: ["dist", "node_modules", "packages/**/*", "docs/**/*", "examples/**/*"], + ignorePatterns: [ + "dist", + "node_modules", + "packages/**/*", + "docs/**/*", + "examples/**/*", + ], }; diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..31e20cd --- /dev/null +++ b/biome.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.4.13/schema.json", + "extends": ["./packages/lint-config/biome.base.json"], + "files": { + "includes": [ + "!**/dist", + "!**/node_modules", + "!**/pnpm-lock.yaml", + "!.turbo", + "!docs/.vitepress/cache", + "!docs/.vitepress/dist" + ] + } +} diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index a336e19..1633c34 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -11,11 +11,31 @@ export default defineConfig({ head: [ ["link", { rel: "icon", href: `${base}logo.svg` }], - ["meta", { name: "keywords", content: "express, typescript, nodejs, backend, api, generator, scaffold, prisma, sequelize, architecture, mvc, modular, rest-api, server-boilerplate" }], + [ + "meta", + { + name: "keywords", + content: + "express, typescript, nodejs, backend, api, generator, scaffold, prisma, sequelize, architecture, mvc, modular, rest-api, server-boilerplate", + }, + ], ["meta", { name: "author", content: "Yatharth Lakhate" }], ["meta", { property: "og:type", content: "website" }], - ["meta", { property: "og:title", content: "Express Forge | The Ultimate Express + TypeScript Generator" }], - ["meta", { property: "og:description", content: "Scaffold production-ready Express.js TypeScript backends in seconds with built-in Auth, ORM, and OpenAPI support." }], + [ + "meta", + { + property: "og:title", + content: "Express Forge | The Ultimate Express + TypeScript Generator", + }, + ], + [ + "meta", + { + property: "og:description", + content: + "Scaffold production-ready Express.js TypeScript backends in seconds with built-in Auth, ORM, and OpenAPI support.", + }, + ], ], themeConfig: { @@ -29,7 +49,10 @@ export default defineConfig({ { text: "Guide", link: "/guide/getting-started" }, { text: "Features", link: "/guide/features" }, { text: "Reference", link: "/reference/cli-options" }, - { text: "โญ Star on GitHub", link: "https://github.com/CODE-Y02/express-cli" }, + { + text: "โญ Star on GitHub", + link: "https://github.com/CODE-Y02/express-cli", + }, ], sidebar: [ diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css index beaa8ab..f2f2249 100644 --- a/docs/.vitepress/theme/custom.css +++ b/docs/.vitepress/theme/custom.css @@ -8,18 +8,26 @@ --vp-c-brand-2: #4f46e5; /* Indigo 600 */ --vp-c-brand-3: #818cf8; /* Indigo 400 */ --vp-c-brand-next: #06b6d4; /* Cyan 500 */ - + --vp-c-brand-soft: rgba(99, 102, 241, 0.12); /* Typography Visibility */ --vp-c-text-1: #1e293b; /* Slate 800 */ --vp-c-text-2: #475569; /* Slate 600 */ - + /* Hero Gradient */ --vp-home-hero-name-color: transparent; - --vp-home-hero-name-background: linear-gradient(135deg, #6366f1 0%, #06b6d4 100%); - - --vp-home-hero-image-background-image: radial-gradient(circle, rgba(99, 102, 241, 0.2) 0%, rgba(6, 182, 212, 0.15) 100%); + --vp-home-hero-name-background: linear-gradient( + 135deg, + #6366f1 0%, + #06b6d4 100% + ); + + --vp-home-hero-image-background-image: radial-gradient( + circle, + rgba(99, 102, 241, 0.2) 0%, + rgba(6, 182, 212, 0.15) 100% + ); --vp-home-hero-image-filter: blur(60px); } @@ -27,10 +35,10 @@ --vp-c-brand-1: #818cf8; --vp-c-brand-2: #6366f1; --vp-c-brand-3: #a5b4fc; - + --vp-c-text-1: #f8fafc; /* Slate 50 */ --vp-c-text-2: #94a3b8; /* Slate 400 */ - + --vp-c-bg: #0f172a; /* Slate 900 - Deep Obsidian */ --vp-c-bg-soft: #1e293b; /* Slate 800 */ --vp-c-bg-mute: #1e293b; @@ -38,97 +46,105 @@ /* Hero Section Enhancements */ .VPHero { - margin-top: calc(var(--vp-nav-height) + 32px) !important; - padding: 0 32px !important; + margin-top: calc(var(--vp-nav-height) + 32px); + padding: 0 32px; } @media (min-width: 640px) { .VPHero { - padding: 0 48px !important; + padding: 0 48px; } } @media (min-width: 960px) { .VPHero { - padding: 0 64px !important; + padding: 0 64px; } } .name { - font-weight: 900 !important; - letter-spacing: -0.02em !important; + font-weight: 900; + letter-spacing: -0.02em; } /* Feature Cards */ .VPFeatures { - padding: 64px 32px !important; + padding: 64px 32px; } .VPFeature { - border: 1px solid var(--vp-c-divider) !important; - background-color: var(--vp-c-bg-soft) !important; - transition: all 0.4s cubic-bezier(0.2, 1, 0.2, 1) !important; - border-radius: 16px !important; + border: 1px solid var(--vp-c-divider); + background-color: var(--vp-c-bg-soft); + transition: all 0.4s cubic-bezier(0.2, 1, 0.2, 1); + border-radius: 16px; } .VPFeature:hover { - border-color: var(--vp-c-brand-1) !important; + border-color: var(--vp-c-brand-1); transform: translateY(-8px) scale(1.02); box-shadow: 0 20px 40px rgba(0, 0, 0, 0.12); - background-color: var(--vp-c-bg) !important; + background-color: var(--vp-c-bg); } /* Dark Mode Overrides */ .dark .VPFeature { - background-color: rgba(30, 41, 59, 0.5) !important; + background-color: rgba(30, 41, 59, 0.5); backdrop-filter: blur(8px); } .dark .VPFeature:hover { - background-color: #1e293b !important; + background-color: #1e293b; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4); } /* Typography & Content Visibility */ :root { - --vp-font-family-base: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; + --vp-font-family-base: + "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, + Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; } -h1, h2, h3, h4, h5, h6 { +h1, +h2, +h3, +h4, +h5, +h6 { letter-spacing: -0.02em; - font-weight: 800 !important; + font-weight: 800; color: var(--vp-c-text-1); } .VPHero .tagline { - color: var(--vp-c-text-2) !important; - font-weight: 500 !important; - font-size: 1.25rem !important; - max-width: 600px !important; - margin-left: auto !important; - margin-right: auto !important; + color: var(--vp-c-text-2); + font-weight: 500; + font-size: 1.25rem; + max-width: 600px; + margin-left: auto; + margin-right: auto; } /* Code Block Visibility */ -.vp-doc [class*='language-'] { - border-radius: 12px !important; - border: 1px solid var(--vp-c-divider) !important; +.vp-doc [class*="language-"] { + border-radius: 12px; + border: 1px solid var(--vp-c-divider); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); } -.dark .vp-doc [class*='language-'] { - background-color: #0f172a !important; +.dark .vp-doc [class*="language-"] { + background-color: #0f172a; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); } /* Hero Buttons */ .VPHero .actions { - justify-content: center !important; - gap: 12px !important; + justify-content: center; + gap: 12px; } /* Overflow & Clipping Prevention */ -.vp-doc p, .vp-doc li { +.vp-doc p, +.vp-doc li { overflow-wrap: break-word; } @@ -138,7 +154,7 @@ h1, h2, h3, h4, h5, h6 { } .VPSidebarItem.level-0 > .item > .text { - font-weight: 700 !important; + font-weight: 700; } /* Table Responsiveness */ @@ -152,22 +168,22 @@ h1, h2, h3, h4, h5, h6 { } /* Code Block Wrapping */ -.vp-doc [class*='language-'] pre { - white-space: pre-wrap !important; - word-break: break-all !important; +.vp-doc [class*="language-"] pre { + white-space: pre-wrap; + word-break: break-all; } /* Hero Section Responsiveness */ @media (max-width: 640px) { .VPHero .name { - font-size: 2.5rem !important; - line-height: 1.1 !important; + font-size: 2.5rem; + line-height: 1.1; } .VPHero .text { - font-size: 1.5rem !important; + font-size: 1.5rem; } .VPHero .tagline { - font-size: 1rem !important; + font-size: 1rem; padding: 0 16px; } } diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts index bdba88a..7913a43 100644 --- a/docs/.vitepress/theme/index.ts +++ b/docs/.vitepress/theme/index.ts @@ -3,7 +3,7 @@ import "./custom.css"; export default { extends: DefaultTheme, - enhanceApp({}) { + enhanceApp() { // register your custom global components }, }; diff --git a/package.json b/package.json index 31d0e6e..40c05b3 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "build": "turbo run build", "dev": "turbo run dev", "lint": "turbo run lint", - "format": "prettier --write \"**/*.{ts,tsx,md,json}\"", + "format": "biome format --write .", "check-types": "turbo run check-types", "test": "turbo run test", "changeset": "changeset", @@ -17,10 +17,9 @@ "prepare": "husky" }, "devDependencies": { + "@biomejs/biome": "^2.4.13", "@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" }, diff --git a/packages/create-express-forge/package.json b/packages/create-express-forge/package.json index b7e40dd..ebd2305 100644 --- a/packages/create-express-forge/package.json +++ b/packages/create-express-forge/package.json @@ -37,6 +37,7 @@ }, "files": [ "dist", + "templates", "README.md", "CHANGELOG.md" ], @@ -47,7 +48,7 @@ "dev": "tsx src/index.ts", "build": "tsup", "check-types": "tsc --noEmit", - "lint": "eslint . --max-warnings 0", + "lint": "biome lint .", "test": "vitest run", "test:smoke": "vitest run tests/smoke.integration.test.ts" }, @@ -55,14 +56,15 @@ "@inquirer/prompts": "^7.1.0", "chalk": "^5.3.0", "commander": "^12.1.0", + "eta": "^3.5.0", "execa": "^9.3.0", "fs-extra": "^11.2.0", - "ora": "^8.1.1" + "ora": "^8.1.1", + "pkg-types": "^1.2.0" }, "devDependencies": { - "@repo/eslint-config": "workspace:*", + "@repo/lint-config": "workspace:*", "@repo/typescript-config": "workspace:*", - "eslint": "^8.57.0", "@types/fs-extra": "^11.0.4", "@types/node": "^20.14.0", "tsup": "^8.1.0", diff --git a/packages/create-express-forge/src/generator/base.ts b/packages/create-express-forge/src/generator/base.ts index da9e787..8ad0c38 100644 --- a/packages/create-express-forge/src/generator/base.ts +++ b/packages/create-express-forge/src/generator/base.ts @@ -1,69 +1,83 @@ -import path from 'path'; -import type { CliOptions } from '../types.js'; -import { writeFile, writeJson } from '../utils/file.js'; +import path from "node:path"; +import type { CliOptions } from "../types.js"; +import type { TemplateManager } from "../utils/template-manager.js"; -function dbExampleUrl(db: string): string { - if (db === 'mysql') return 'mysql://user:password@localhost:3306/mydb'; - if (db === 'sqlite') return 'file:./dev.db'; - return 'postgresql://user:password@localhost:5432/mydb'; -} +export async function generateBaseFiles( + _opts: CliOptions, + dir: string, + tmpl: TemplateManager, +): Promise { + const src = path.join(dir, "src"); -export async function generateBaseFiles(opts: CliOptions, dir: string): Promise { - const { orm, database, logger, testing, docker, projectName } = opts; - const hasDb = orm !== 'none' && database !== 'none'; + // Root files + await tmpl.renderTemplateFile( + "base/tsconfig.json.eta", + path.join(dir, "tsconfig.json"), + ); + await tmpl.renderTemplateFile( + "base/.gitignore.eta", + path.join(dir, ".gitignore"), + ); + await tmpl.renderTemplateFile( + "base/.env.example.eta", + path.join(dir, ".env.example"), + ); + await tmpl.renderTemplateFile( + "base/.env.example.eta", + path.join(dir, ".env"), + ); + await tmpl.renderTemplateFile( + "base/README.md.eta", + path.join(dir, "README.md"), + ); - await writeJson(path.join(dir, 'tsconfig.json'), { - compilerOptions: { - target: 'ES2022', - module: 'NodeNext', - moduleResolution: 'NodeNext', - outDir: './dist', - rootDir: './src', - strict: true, - esModuleInterop: true, - skipLibCheck: true, - declaration: true, - sourceMap: true, - ...(opts.importAlias ? { baseUrl: '.', paths: { '@/*': ['src/*'] } } : {}), - ...(orm === 'sequelize' ? { experimentalDecorators: true, emitDecoratorMetadata: true } : {}), - }, - include: ['src/**/*'], - exclude: ['node_modules', 'dist', '**/*.test.ts'], - }); + // Core src files + await tmpl.renderTemplateFile( + "base/src/app.ts.eta", + path.join(src, "app.ts"), + ); + await tmpl.renderTemplateFile( + "base/src/server.ts.eta", + path.join(src, "server.ts"), + ); - await writeFile( - path.join(dir, '.gitignore'), - `node_modules/\ndist/\n.env\n*.log\nlogs/\ncoverage/\n.DS_Store\n${orm === 'prisma' ? 'prisma/migrations/\n*.db\n' : ''}`, + // Config + await tmpl.renderTemplateFile( + "base/src/config/env.ts.eta", + path.join(src, "config", "env.ts"), ); - const envLines = [ - '# Application', - 'NODE_ENV=development', - 'PORT=3000', - '', - '# CORS', - 'CORS_ORIGIN=http://localhost:3000', - '', - '# Rate limiter', - 'RATE_LIMIT_WINDOW_MS=900000', - 'RATE_LIMIT_MAX=100', - '', - ...(hasDb ? ['# Database', `DATABASE_URL="${dbExampleUrl(database)}"`, ''] : []), - ...(opts.auth === 'jwt' - ? ['# JWT Auth', 'JWT_SECRET=your_super_secret_jwt_key_change_me', 'JWT_EXPIRES_IN=1d', ''] - : []), - ...(opts.auth === 'session' - ? ['# Session Auth', 'SESSION_SECRET=your_session_secret_key_change_me', ''] - : []), - ...(opts.cache === 'redis' - ? ['# Redis Cache', 'REDIS_URL="redis://localhost:6379"', ''] - : []), - ]; - await writeFile(path.join(dir, '.env.example'), envLines.join('\n')); - await writeFile(path.join(dir, '.env'), envLines.join('\n')); + // Utils + await tmpl.renderTemplateFile( + "base/src/utils/ApiError.ts.eta", + path.join(src, "utils", "ApiError.ts"), + ); + await tmpl.renderTemplateFile( + "base/src/utils/ApiResponse.ts.eta", + path.join(src, "utils", "ApiResponse.ts"), + ); + + // Middleware + await tmpl.renderTemplateFile( + "base/src/middleware/errorHandler.ts.eta", + path.join(src, "middleware", "errorHandler.ts"), + ); + await tmpl.renderTemplateFile( + "base/src/middleware/notFound.ts.eta", + path.join(src, "middleware", "notFound.ts"), + ); + await tmpl.renderTemplateFile( + "base/src/middleware/rateLimiter.ts.eta", + path.join(src, "middleware", "rateLimiter.ts"), + ); + await tmpl.renderTemplateFile( + "base/src/middleware/validate.ts.eta", + path.join(src, "middleware", "validate.ts"), + ); - await writeFile( - path.join(dir, 'README.md'), - `# ${projectName}\n\n> Scaffolded with [create-express-forge](https://github.com/CODE-Y02/create-express-forge)\n\n## Stack\n\n- TypeScript + Express.js\n- Zod validation\n${orm === 'prisma' ? `- Prisma ORM + ${database}\n` : ''}${orm === 'sequelize' ? `- Sequelize ORM + ${database}\n` : ''}${logger !== 'none' ? `- ${logger} logger\n` : ''}${testing !== 'none' ? `- ${testing} tests\n` : ''}${docker ? '- Docker + docker-compose\n' : ''}\n## Quick Start\n\n\`\`\`bash\ncp .env.example .env\n${orm === 'prisma' ? `${opts.packageManager === 'npm' ? 'npm run' : opts.packageManager} db:migrate\n` : ''}${opts.packageManager} run dev\n\`\`\`\n`, + // Types + await tmpl.renderTemplateFile( + "base/src/types/express.d.ts.eta", + path.join(src, "types", "express.d.ts"), ); } diff --git a/packages/create-express-forge/src/generator/features/auth.ts b/packages/create-express-forge/src/generator/features/auth.ts index dc09b1e..62472aa 100644 --- a/packages/create-express-forge/src/generator/features/auth.ts +++ b/packages/create-express-forge/src/generator/features/auth.ts @@ -1,61 +1,25 @@ -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/middleware'); - await fs.ensureDir(authDir); - - if (opts.auth === 'jwt') { - const isCookie = opts.jwtStorage === 'cookie'; - 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) => { - let token: string | undefined; - - ${ - isCookie - ? "token = req.cookies?.token;" - : `const authHeader = req.headers.authorization; - if (authHeader?.startsWith('Bearer ')) { - token = authHeader.split(' ')[1]; - }` - } - - if (!token) { - return next(ApiError.unauthorized('No token provided')); - } - - try { - const decoded = jwt.verify(token, env.JWT_SECRET); - req.user = decoded as any; - next(); - } catch (err) { - next(ApiError.unauthorized('Invalid or expired token')); - } -}; -` +import path from "node:path"; +import type { CliOptions } from "../../types.js"; +import type { TemplateManager } from "../../utils/template-manager.js"; + +export async function generateAuth( + opts: CliOptions, + targetDir: string, + tmpl: TemplateManager, +): Promise { + if (opts.auth === "none") return; + + const authDir = path.join(targetDir, "src/middleware"); + + if (opts.auth === "jwt") { + await tmpl.renderTemplateFile( + "features/auth/jwt-auth.middleware.ts.eta", + path.join(authDir, "auth.middleware.ts"), ); - } 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(); -}; -` + } else if (opts.auth === "session") { + await tmpl.renderTemplateFile( + "features/auth/session-auth.middleware.ts.eta", + path.join(authDir, "auth.middleware.ts"), ); } } diff --git a/packages/create-express-forge/src/generator/features/cache.ts b/packages/create-express-forge/src/generator/features/cache.ts index afcbba2..0a8f2f6 100644 --- a/packages/create-express-forge/src/generator/features/cache.ts +++ b/packages/create-express-forge/src/generator/features/cache.ts @@ -1,67 +1,25 @@ -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'; -${opts.logger === 'none' ? '' : "import { logger } from '../logger/index.js';"} - -const client = createClient({ - url: process.env.REDIS_URL || 'redis://localhost:6379' -}); - -client.on('error', (err) => ${opts.logger === 'none' ? "console.error('Redis Client Error', err)" : "logger.error('Redis Client Error', err)"}); - -export const connectRedis = async () => { - await client.connect(); - ${opts.logger === 'none' ? "console.info('๐Ÿ”ด Redis connected successfully');" : "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), -}; -` +import path from "node:path"; +import type { CliOptions } from "../../types.js"; +import type { TemplateManager } from "../../utils/template-manager.js"; + +export async function generateCache( + opts: CliOptions, + targetDir: string, + tmpl: TemplateManager, +): Promise { + if (opts.cache === "none") return; + + const cacheDir = path.join(targetDir, "src/cache"); + + if (opts.cache === "redis") { + await tmpl.renderTemplateFile( + "features/cache/redis.ts.eta", + path.join(cacheDir, "index.ts"), ); - } else if (opts.cache === 'node-cache') { - await fs.writeFile( - path.join(cacheDir, 'index.ts'), - `import NodeCache from 'node-cache'; -${opts.logger === 'none' ? '' : "import { logger } from '../logger/index.js';"} - -const nodeCache = new NodeCache({ stdTTL: 100, checkperiod: 120 }); - -${opts.logger === 'none' ? "console.info('๐Ÿ’พ In-memory cache initialized');" : "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); - }, -}; -` + } else if (opts.cache === "node-cache") { + await tmpl.renderTemplateFile( + "features/cache/node-cache.ts.eta", + path.join(cacheDir, "index.ts"), ); } } diff --git a/packages/create-express-forge/src/generator/features/docker.ts b/packages/create-express-forge/src/generator/features/docker.ts index a54bc53..eac15b4 100644 --- a/packages/create-express-forge/src/generator/features/docker.ts +++ b/packages/create-express-forge/src/generator/features/docker.ts @@ -1,135 +1,22 @@ -import path from 'path'; -import type { CliOptions } from '../../types.js'; -import { writeFile } from '../../utils/file.js'; - -export async function generateDocker(opts: CliOptions, dir: string): Promise { - const hasDb = opts.orm !== 'none' && opts.database !== 'none' && opts.database !== 'sqlite'; - - const pm = opts.packageManager; - const pmInstallGlobal = pm === 'pnpm' ? 'RUN npm install -g pnpm\n' : pm === 'bun' ? 'RUN npm install -g bun\n' : ''; - const pmInstallCmd = pm === 'pnpm' ? 'RUN pnpm install --frozen-lockfile' : pm === 'yarn' ? 'RUN yarn install --frozen-lockfile' : pm === 'bun' ? 'RUN bun install --frozen-lockfile' : 'RUN npm ci'; - const pmProdInstallCmd = pm === 'pnpm' ? 'RUN pnpm install --frozen-lockfile --prod' : pm === 'yarn' ? 'RUN yarn install --production --frozen-lockfile' : pm === 'bun' ? 'RUN bun install --frozen-lockfile --production' : 'RUN npm ci --omit=dev && npm cache clean --force'; - const pmRunBuild = pm === 'pnpm' ? 'pnpm run build' : pm === 'yarn' ? 'yarn run build' : pm === 'bun' ? 'bun run build' : 'npm run build'; - - await writeFile(path.join(dir, 'Dockerfile'), - `# โ”€โ”€ Stage 1: Builder โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -FROM node:20-alpine AS builder -WORKDIR /app -${pmInstallGlobal}COPY package.json pnpm-lock.yaml* yarn.lock* bun.lock* package-lock.json* ./ -${pmInstallCmd} -COPY . . -RUN ${pmRunBuild} -${opts.orm === 'prisma' ? `RUN ${pm === 'bun' ? 'bunx' : pm === 'pnpm' ? 'pnpx' : 'npx'} prisma generate\n` : ''} -# โ”€โ”€ Stage 2: Production โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -FROM node:20-alpine AS production -WORKDIR /app -ENV NODE_ENV=production -${pmInstallGlobal}COPY package.json pnpm-lock.yaml* yarn.lock* bun.lock* package-lock.json* ./ -${pmProdInstallCmd} -COPY --from=builder /app/dist ./dist -${opts.orm === 'prisma' ? 'COPY --from=builder /app/prisma ./prisma\nCOPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma\n' : ''} -EXPOSE 3000 -HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \\ - CMD wget -qO- http://localhost:3000/api/health || exit 1 - -CMD ["node", "dist/server.js"] -`); - - await writeFile(path.join(dir, '.dockerignore'), - `node_modules\ndist\n.env\n*.log\nlogs/\ncoverage\n.git\n.turbo\n`); - - const dbService = buildDbService(opts.database); - const redisService = opts.cache === 'redis' ? buildRedisService() : ''; - - const dependsOn: string[] = []; - if (hasDb) dependsOn.push('db'); - if (opts.cache === 'redis') dependsOn.push('redis'); - - const dependsOnTpl = dependsOn.length > 0 - ? `\n depends_on:${dependsOn.map(s => `\n ${s}:\n condition: service_healthy`).join('')}` - : ''; - - const volumes: string[] = []; - if (hasDb) volumes.push(' db_data:'); - if (opts.cache === 'redis') volumes.push(' redis_data:'); - - const volumesTpl = volumes.length > 0 - ? `\nvolumes:\n${volumes.join('\n')}\n` - : ''; - - await writeFile(path.join(dir, 'docker-compose.yml'), - `version: '3.9' - -services: - app: - build: . - restart: unless-stopped - ports: - - "\${PORT:-3000}:3000" - env_file: .env - environment: - NODE_ENV: \${NODE_ENV:-development}${dependsOnTpl} -${dbService}${redisService}${volumesTpl}`); -} - -function buildDbService(db: string): string { - if (db === 'postgresql') { - return ` - db: - image: postgres:16-alpine - restart: unless-stopped - environment: - POSTGRES_USER: \${POSTGRES_USER:-user} - POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:-password} - POSTGRES_DB: \${POSTGRES_DB:-mydb} - ports: - - "5432:5432" - volumes: - - db_data:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U \${POSTGRES_USER:-user}"] - interval: 10s - timeout: 5s - retries: 5 -`; - } - if (db === 'mysql') { - return ` - db: - image: mysql:8.0 - restart: unless-stopped - environment: - MYSQL_ROOT_PASSWORD: \${MYSQL_ROOT_PASSWORD:-root} - MYSQL_DATABASE: \${MYSQL_DATABASE:-mydb} - MYSQL_USER: \${MYSQL_USER:-user} - MYSQL_PASSWORD: \${MYSQL_PASSWORD:-password} - ports: - - "3306:3306" - volumes: - - db_data:/var/lib/mysql - healthcheck: - test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] - interval: 10s - timeout: 5s - retries: 5 -`; - } - return ''; -} - -function buildRedisService(): string { - return ` - redis: - image: redis:7-alpine - restart: unless-stopped - ports: - - "6379:6379" - volumes: - - redis_data:/data - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 10s - timeout: 5s - retries: 5 -`; +import path from "node:path"; +import type { CliOptions } from "../../types.js"; +import type { TemplateManager } from "../../utils/template-manager.js"; + +export async function generateDocker( + _opts: CliOptions, + dir: string, + tmpl: TemplateManager, +): Promise { + await tmpl.renderTemplateFile( + "features/docker/Dockerfile.eta", + path.join(dir, "Dockerfile"), + ); + await tmpl.renderTemplateFile( + "features/docker/docker-compose.yml.eta", + path.join(dir, "docker-compose.yml"), + ); + + const dockerIgnore = `node_modules\ndist\n.env\n*.log\nlogs/\ncoverage\n.git\n.turbo\n`; + const { writeFile } = await import("../../utils/file.js"); + await writeFile(path.join(dir, ".dockerignore"), dockerIgnore); } diff --git a/packages/create-express-forge/src/generator/features/logger.ts b/packages/create-express-forge/src/generator/features/logger.ts index d7f715e..685f54d 100644 --- a/packages/create-express-forge/src/generator/features/logger.ts +++ b/packages/create-express-forge/src/generator/features/logger.ts @@ -1,16 +1,27 @@ -import path from 'path'; -import type { CliOptions } from '../../types.js'; -import { writeFile } from '../../utils/file.js'; +import path from "node:path"; +import type { CliOptions } from "../../types.js"; +import type { TemplateManager } from "../../utils/template-manager.js"; -export async function generateLogger(opts: CliOptions, dir: string): Promise { - const loggerDir = path.join(dir, 'src', 'logger'); - if (opts.logger === 'winston') { - await writeFile(path.join(loggerDir, 'index.ts'), - `import winston from 'winston';\nimport 'winston-daily-rotate-file';\nconst { combine, timestamp, printf, colorize, errors } = winston.format;\nconst devFmt = combine(colorize({ all: true }), timestamp({ format: 'HH:mm:ss' }), errors({ stack: true }), printf(({ level, message, timestamp: ts, stack }) => \`\${ts as string} [\${level}]: \${(stack as string | undefined) ?? (message as string)}\`));\nconst prodFmt = combine(timestamp(), errors({ stack: true }), winston.format.json());\nexport const logger = winston.createLogger({\n level: process.env.NODE_ENV === 'production' ? 'warn' : 'debug',\n format: process.env.NODE_ENV === 'production' ? prodFmt : devFmt,\n transports: [new winston.transports.Console(), ...(process.env.NODE_ENV === 'production' ? [new winston.transports.DailyRotateFile({ filename: 'logs/app-%DATE%.log', datePattern: 'YYYY-MM-DD', zippedArchive: true, maxSize: '20m', maxFiles: '14d' })] : [])],\n exceptionHandlers: [new winston.transports.Console()],\n rejectionHandlers: [new winston.transports.Console()],\n});\n`); - } else if (opts.logger === 'pino') { - await writeFile(path.join(loggerDir, 'index.ts'), - `import pino from 'pino';\nexport const logger = pino({\n level: process.env.NODE_ENV === 'production' ? 'warn' : 'debug',\n ...(process.env.NODE_ENV !== 'production' && { transport: { target: 'pino-pretty', options: { colorize: true, translateTime: 'HH:MM:ss Z', ignore: 'pid,hostname' } } }),\n});\n`); - await writeFile(path.join(dir, 'src', 'middleware', 'httpLogger.ts'), - `import pinoHttp from 'pino-http';\nimport { logger } from '../logger/index.js';\nexport const httpLogger = pinoHttp({ logger });\n`); +export async function generateLogger( + opts: CliOptions, + dir: string, + tmpl: TemplateManager, +): Promise { + const loggerDir = path.join(dir, "src", "logger"); + + if (opts.logger === "winston") { + await tmpl.renderTemplateFile( + "features/logger/winston.ts.eta", + path.join(loggerDir, "index.ts"), + ); + } else if (opts.logger === "pino") { + await tmpl.renderTemplateFile( + "features/logger/pino.ts.eta", + path.join(loggerDir, "index.ts"), + ); + await tmpl.renderTemplateFile( + "features/logger/pino.ts.eta", + path.join(dir, "src", "middleware", "httpLogger.ts"), + ); } } diff --git a/packages/create-express-forge/src/generator/features/openapi.ts b/packages/create-express-forge/src/generator/features/openapi.ts index 3abb264..9ed455a 100644 --- a/packages/create-express-forge/src/generator/features/openapi.ts +++ b/packages/create-express-forge/src/generator/features/openapi.ts @@ -1,63 +1,17 @@ -import path from 'path'; -import fs from 'fs-extra'; -import type { CliOptions } from '../../types.js'; +import path from "node:path"; +import type { CliOptions } from "../../types.js"; +import type { TemplateManager } from "../../utils/template-manager.js"; -export async function generateOpenApi(opts: CliOptions, targetDir: string): Promise { +export async function generateOpenApi( + opts: CliOptions, + targetDir: string, + tmpl: TemplateManager, +): 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', - }, - ], - components: { - securitySchemes: { - ${ - opts.auth === 'jwt' - ? opts.jwtStorage === 'cookie' - ? "cookieAuth: { type: 'apiKey', in: 'cookie', name: 'token' }" - : "bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' }" - : '' - } - } - }, - ${opts.auth === 'jwt' ? `security: [{ ${opts.jwtStorage === 'cookie' ? "'cookieAuth'" : "'bearerAuth'"}: [] }],` : ''} - }, - apis: ['./src/app.ts', './src/modules/**/*.routes.ts', './src/routes/**/*.ts'], -}; - -const specs = swaggerJsdoc(options); - -export const setupSwagger = (app: Express) => { - // Always expose the OpenAPI spec as JSON for debugging or external tools - app.get('/docs.json', (_req, res) => res.json(specs)); - - ${ - opts.openapiUI - ? `app.use('/docs', swaggerUi.serve, swaggerUi.setup(specs)); - console.log('๐Ÿ“œ API Docs available at http://localhost:3000/docs'); - console.log('๐Ÿ“œ API Spec available at http://localhost:3000/docs.json');` - : `console.log('๐Ÿ“œ API Spec available at http://localhost:3000/docs.json (UI disabled)');` - } -}; -` + const docsDir = path.join(targetDir, "src/docs"); + await tmpl.renderTemplateFile( + "features/openapi/swagger.ts.eta", + path.join(docsDir, "swagger.ts"), ); } diff --git a/packages/create-express-forge/src/generator/features/prisma.ts b/packages/create-express-forge/src/generator/features/prisma.ts index 660f16b..66d4fe2 100644 --- a/packages/create-express-forge/src/generator/features/prisma.ts +++ b/packages/create-express-forge/src/generator/features/prisma.ts @@ -1,110 +1,25 @@ -import path from 'path'; -import { execa } from 'execa'; -import fs from 'fs-extra'; -import type { CliOptions } from '../../types.js'; -import { writeFile } from '../../utils/file.js'; - -export async function generatePrisma(opts: CliOptions, dir: string): Promise { - const provider = opts.database === 'mysql' ? 'mysql' : opts.database === 'sqlite' ? 'sqlite' : 'postgresql'; - const dbUrl = opts.database === 'sqlite' ? 'file:./dev.db' : opts.database === 'mysql' ? 'mysql://user:password@localhost:3306/mydb' : 'postgresql://user:password@localhost:5432/mydb'; - - try { - // Initialize prisma folder using latest CLI pattern - const npxCmd = opts.packageManager === 'bun' ? 'bunx' : 'npx'; - await execa(npxCmd, ['prisma', 'init', '--datasource-provider', provider], { cwd: dir }); - } catch (err) { - // Fallback - await fs.ensureDir(path.join(dir, 'prisma')); - } - - await writeFile(path.join(dir, 'prisma', 'schema.prisma'), - `generator client { - provider = "prisma-client-js" -} - -datasource db { - provider = "${provider}" - url = env("DATABASE_URL") -} -${opts.auth !== 'none' ? ` -model User { - id String @id @default(uuid()) - name String - email String @unique - password String - role Role @default(USER) - todos Todo[] - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - @@map("users") -} - -model Todo { - id String @id @default(cuid()) - title String - description String? - completed Boolean @default(false) - userId String - user User @relation(fields: [userId], references: [id]) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - @@map("todos") -} - -enum Role { - USER - ADMIN -}` : ''} -`); - - await writeFile(path.join(dir, 'prisma', 'seed.ts'), - `import { PrismaClient } from '@prisma/client'; - -const prisma = new PrismaClient(); - -async function main() { - console.log('๐ŸŒฑ Seeding database...'); - - const admin = await prisma.user.upsert({ - where: { email: 'admin@example.com' }, - update: {}, - create: { - email: 'admin@example.com', - name: 'Admin User', - password: 'password123', // In real app, hash this! - role: 'ADMIN', - }, - }); - - console.log({ admin }); - console.log('โœ… Seeding completed.'); -} - -main() - .catch((e) => { - console.error(e); - process.exit(1); - }) - .finally(async () => { - await prisma.$disconnect(); - }); -`); - - await writeFile(path.join(dir, 'src', 'config', 'database.ts'), - `import { PrismaClient } from '@prisma/client';\n\nexport const prisma = new PrismaClient({\n log: process.env.NODE_ENV === 'development' ? ['query', 'warn', 'error'] : ['error'],\n});\n`); - - // Patch .env with correct DATABASE_URL if init didn't do it right or we want to ensure our format - const { readFile, writeFile: fsWrite } = await import('fs/promises'); - for (const f of [path.join(dir, '.env'), path.join(dir, '.env.example')]) { - try { - const c = await readFile(f, 'utf-8'); - if (c.includes('DATABASE_URL=')) { - await fsWrite(f, c.replace(/DATABASE_URL=.*/, `DATABASE_URL="${dbUrl}"`), 'utf-8'); - } else { - await fsWrite(f, c + `\nDATABASE_URL="${dbUrl}"\n`, 'utf-8'); - } - } catch { /* ignore */ } - } +import path from "node:path"; +import type { CliOptions } from "../../types.js"; +import type { TemplateManager } from "../../utils/template-manager.js"; + +export async function generatePrisma( + _opts: CliOptions, + dir: string, + tmpl: TemplateManager, +): Promise { + // We no longer need to run `npx prisma init` during scaffolding which slows it down. + // We just copy the configured files directly. + + await tmpl.renderTemplateFile( + "features/prisma/schema.prisma.eta", + path.join(dir, "prisma", "schema.prisma"), + ); + await tmpl.renderTemplateFile( + "features/prisma/seed.ts.eta", + path.join(dir, "prisma", "seed.ts"), + ); + await tmpl.renderTemplateFile( + "features/prisma/database.ts.eta", + path.join(dir, "src", "config", "database.ts"), + ); } diff --git a/packages/create-express-forge/src/generator/features/sequelize.ts b/packages/create-express-forge/src/generator/features/sequelize.ts index 7485ea7..488eba1 100644 --- a/packages/create-express-forge/src/generator/features/sequelize.ts +++ b/packages/create-express-forge/src/generator/features/sequelize.ts @@ -1,33 +1,29 @@ -import path from 'path'; -import type { CliOptions } from '../../types.js'; -import { writeFile } from '../../utils/file.js'; +import path from "node:path"; +import type { CliOptions } from "../../types.js"; +import type { TemplateManager } from "../../utils/template-manager.js"; -export async function generateSequelize(opts: CliOptions, dir: string): Promise { - const dialect = opts.database === 'mysql' ? 'mysql' : opts.database === 'sqlite' ? 'sqlite' : 'postgres'; - - await writeFile(path.join(dir, '.sequelizerc'), - `const path = require('path'); -module.exports = { - config: path.resolve('src', 'config', 'sequelize.cjs'), - 'models-path': path.resolve('src', 'models'), - 'seeders-path': path.resolve('db', 'seeders'), - 'migrations-path': path.resolve('db', 'migrations'), -}; -`); +export async function generateSequelize( + opts: CliOptions, + dir: string, + tmpl: TemplateManager, +): Promise { + await tmpl.renderTemplateFile( + "features/sequelize/.sequelizerc.eta", + path.join(dir, ".sequelizerc"), + ); + await tmpl.renderTemplateFile( + "features/sequelize/sequelize.cjs.eta", + path.join(dir, "src", "config", "sequelize.cjs"), + ); + await tmpl.renderTemplateFile( + "features/sequelize/database.ts.eta", + path.join(dir, "src", "config", "database.ts"), + ); - await writeFile(path.join(dir, 'src', 'config', 'sequelize.cjs'), - `module.exports = { - development: { use_env_variable: 'DATABASE_URL', dialect: '${dialect}', logging: console.log }, - test: { use_env_variable: 'DATABASE_URL', dialect: '${dialect}', logging: false }, - production: { use_env_variable: 'DATABASE_URL', dialect: '${dialect}', logging: false }, -}; -`); - - await writeFile(path.join(dir, 'src', 'config', 'database.ts'), - `import { Sequelize } from 'sequelize-typescript';\nimport { env } from './env.js';\n${opts.auth !== 'none' ? "import { User } from '../models/User.js';\n" : ""}export const sequelize = new Sequelize(env.DATABASE_URL, { dialect: '${dialect}' as const, logging: env.NODE_ENV === 'development' ? console.log : false, models: [${opts.auth !== 'none' ? 'User' : ''}] });\nexport async function connectDB() { await sequelize.authenticate(); console.log('โœ… Database connected'); }\n`); - - if (opts.auth !== 'none') { - await writeFile(path.join(dir, 'src', 'models', 'User.ts'), - `import { Table, Column, Model, DataType, CreatedAt, UpdatedAt, PrimaryKey, Default, Unique } from 'sequelize-typescript';\n@Table({ tableName: 'users', timestamps: true })\nexport class User extends Model {\n @PrimaryKey @Default(DataType.UUIDV4) @Column(DataType.UUID) declare id: string;\n @Column({ type: DataType.STRING, allowNull: false }) declare name: string;\n @Unique @Column({ type: DataType.STRING, allowNull: false }) declare email: string;\n @Column({ type: DataType.STRING, allowNull: false }) declare password: string;\n @CreatedAt declare createdAt: Date;\n @UpdatedAt declare updatedAt: Date;\n}\n`); + if (opts.auth !== "none") { + await tmpl.renderTemplateFile( + "features/sequelize/User.ts.eta", + path.join(dir, "src", "models", "User.ts"), + ); } } diff --git a/packages/create-express-forge/src/generator/features/testing.ts b/packages/create-express-forge/src/generator/features/testing.ts index 6d106e7..f12aa66 100644 --- a/packages/create-express-forge/src/generator/features/testing.ts +++ b/packages/create-express-forge/src/generator/features/testing.ts @@ -1,20 +1,31 @@ -import path from 'path'; -import type { CliOptions } from '../../types.js'; -import { writeFile } from '../../utils/file.js'; +import path from "node:path"; +import type { CliOptions } from "../../types.js"; +import type { TemplateManager } from "../../utils/template-manager.js"; -export async function generateTesting(opts: CliOptions, dir: string): Promise { - const testDir = path.join(dir, 'src', '__tests__'); - const healthPath = opts.pattern === 'modular' ? '/api/health' : '/api/v1/health'; +export async function generateTesting( + opts: CliOptions, + dir: string, + tmpl: TemplateManager, +): Promise { + const testDir = path.join(dir, "src", "__tests__"); - if (opts.testing === 'vitest') { - await writeFile(path.join(dir, 'vitest.config.ts'), - `import { defineConfig } from 'vitest/config';\nexport default defineConfig({ test: { globals: true, environment: 'node', coverage: { provider: 'v8', reporter: ['text', 'html', 'lcov'] } } });\n`); - await writeFile(path.join(testDir, 'health.test.ts'), - `import { describe, it, expect } from 'vitest';\nimport request from 'supertest';\nimport { app } from '../app.js';\n\ndescribe('Health', () => {\n it('GET ${healthPath} โ†’ 200', async () => {\n const res = await request(app).get('${healthPath}');\n expect(res.status).toBe(200);\n expect(res.body.success).toBe(true);\n expect(res.body.data.status).toBe('ok');\n });\n});\n`); - } else if (opts.testing === 'jest') { - await writeFile(path.join(dir, 'jest.config.ts'), - `import type { Config } from 'jest';\nconst config: Config = { preset: 'ts-jest/presets/default-esm', testEnvironment: 'node', extensionsToTreatAsEsm: ['.ts'], moduleNameMapper: { '^(\\.{1,2}/.*)\\.js$': '$1' } };\nexport default config;\n`); - await writeFile(path.join(testDir, 'health.test.ts'), - `import request from 'supertest';\nimport { app } from '../app.js';\n\ndescribe('Health', () => {\n it('GET ${healthPath} โ†’ 200', async () => {\n const res = await request(app).get('${healthPath}');\n expect(res.status).toBe(200);\n expect(res.body.success).toBe(true);\n });\n});\n`); + if (opts.testing === "vitest") { + await tmpl.renderTemplateFile( + "features/testing/vitest.config.ts.eta", + path.join(dir, "vitest.config.ts"), + ); + await tmpl.renderTemplateFile( + "features/testing/smoke.test.ts.eta", + path.join(testDir, "smoke.test.ts"), + ); + } else if (opts.testing === "jest") { + await tmpl.renderTemplateFile( + "features/testing/jest.config.js.eta", + path.join(dir, "jest.config.js"), + ); + await tmpl.renderTemplateFile( + "features/testing/smoke.test.ts.eta", + path.join(testDir, "smoke.test.ts"), + ); } } diff --git a/packages/create-express-forge/src/generator/index.ts b/packages/create-express-forge/src/generator/index.ts index 77ba35b..c536ad6 100644 --- a/packages/create-express-forge/src/generator/index.ts +++ b/packages/create-express-forge/src/generator/index.ts @@ -1,31 +1,36 @@ -import ora from 'ora'; -import chalk from 'chalk'; -import path from 'path'; -import { execa } from 'execa'; -import fs from 'fs-extra'; -import type { CliOptions } from '../types.js'; -import { writeJson } from '../utils/file.js'; -import { buildPackageJson } from '../utils/package-builder.js'; -import { displaySuccess, displayError } from '../utils/display.js'; -import { generateBaseFiles } from './base.js'; -import { generateModularStructure } from './structure/modular.js'; -import { generateMvcStructure } from './structure/mvc.js'; -import { generatePrisma } from './features/prisma.js'; -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 { +import path from "node:path"; +import chalk from "chalk"; +import { execa } from "execa"; +import fs from "fs-extra"; +import ora from "ora"; +import type { CliOptions } from "../types.js"; +import { displayError, displaySuccess } from "../utils/display.js"; +import { writeJson } from "../utils/file.js"; +import { buildPackageJson } from "../utils/package-builder.js"; +import { generateBaseFiles } from "./base.js"; +import { generateAuth } from "./features/auth.js"; +import { generateCache } from "./features/cache.js"; +import { generateDocker } from "./features/docker.js"; +import { generateLogger } from "./features/logger.js"; +import { generateOpenApi } from "./features/openapi.js"; +import { generatePrisma } from "./features/prisma.js"; +import { generateSequelize } from "./features/sequelize.js"; +import { generateTesting } from "./features/testing.js"; +import { generateModularStructure } from "./structure/modular.js"; +import { generateMvcStructure } from "./structure/mvc.js"; + +export async function generateProject( + opts: CliOptions, + targetDir: string, +): Promise { const spinner = ora(); if (await fs.pathExists(targetDir)) { const files = await fs.readdir(targetDir); if (files.length > 0) { - displayError(`Directory "${opts.projectName}" already exists and is not empty.`); + displayError( + `Directory "${opts.projectName}" already exists and is not empty.`, + ); process.exit(1); } } @@ -34,90 +39,135 @@ export async function generateProject(opts: CliOptions, targetDir: string): Prom console.log(); try { - spinner.start(chalk.dim('Creating package.json...')); - await writeJson(path.join(targetDir, 'package.json'), buildPackageJson(opts)); - spinner.succeed(chalk.green('package.json')); + spinner.start(chalk.dim("Creating package.json...")); + await writeJson( + path.join(targetDir, "package.json"), + buildPackageJson(opts), + ); + spinner.succeed(chalk.green("package.json")); - spinner.start(chalk.dim('Setting up base config...')); - await generateBaseFiles(opts, targetDir); - spinner.succeed(chalk.green('Base config files')); + const { TemplateManager } = await import("../utils/template-manager.js"); + const tmpl = new TemplateManager(opts); + + spinner.start(chalk.dim("Setting up base config...")); + await generateBaseFiles(opts, targetDir, tmpl); + spinner.succeed(chalk.green("Base config files")); spinner.start(chalk.dim(`Scaffolding ${opts.pattern} architecture...`)); - if (opts.pattern === 'modular') { - await generateModularStructure(opts, targetDir); + if (opts.pattern === "modular") { + await generateModularStructure(opts, targetDir, tmpl); } else { - await generateMvcStructure(opts, targetDir); + await generateMvcStructure(opts, targetDir, tmpl); } spinner.succeed(chalk.green(`${opts.pattern.toUpperCase()} architecture`)); - if (opts.orm === 'prisma') { - spinner.start(chalk.dim('Configuring Prisma...')); - await generatePrisma(opts, targetDir); - spinner.succeed(chalk.green('Prisma')); - } else if (opts.orm === 'sequelize') { - spinner.start(chalk.dim('Configuring Sequelize...')); - await generateSequelize(opts, targetDir); - spinner.succeed(chalk.green('Sequelize')); + if (opts.orm === "prisma") { + spinner.start(chalk.dim("Configuring Prisma...")); + await generatePrisma(opts, targetDir, tmpl); + spinner.succeed(chalk.green("Prisma")); + } else if (opts.orm === "sequelize") { + spinner.start(chalk.dim("Configuring Sequelize...")); + await generateSequelize(opts, targetDir, tmpl); + spinner.succeed(chalk.green("Sequelize")); } - if (opts.logger !== 'none') { + if (opts.logger !== "none") { spinner.start(chalk.dim(`Configuring ${opts.logger}...`)); - await generateLogger(opts, targetDir); + await generateLogger(opts, targetDir, tmpl); spinner.succeed(chalk.green(`${opts.logger} logger`)); } - if (opts.testing !== 'none') { + if (opts.testing !== "none") { spinner.start(chalk.dim(`Configuring ${opts.testing}...`)); - await generateTesting(opts, targetDir); + await generateTesting(opts, targetDir, tmpl); spinner.succeed(chalk.green(opts.testing)); } if (opts.docker) { - spinner.start(chalk.dim('Adding Docker files...')); - await generateDocker(opts, targetDir); - spinner.succeed(chalk.green('Docker + docker-compose')); + spinner.start(chalk.dim("Adding Docker files...")); + await generateDocker(opts, targetDir, tmpl); + spinner.succeed(chalk.green("Docker + docker-compose")); } - if (opts.auth !== 'none') { + if (opts.auth !== "none") { spinner.start(chalk.dim(`Configuring ${opts.auth} authentication...`)); - await generateAuth(opts, targetDir); + await generateAuth(opts, targetDir, tmpl); spinner.succeed(chalk.green(`${opts.auth.toUpperCase()} Auth`)); } - if (opts.cache !== 'none') { + if (opts.cache !== "none") { spinner.start(chalk.dim(`Configuring ${opts.cache} caching...`)); - await generateCache(opts, targetDir); + await generateCache(opts, targetDir, tmpl); 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)')); + spinner.start(chalk.dim("Generating OpenAPI docs...")); + await generateOpenApi(opts, targetDir, tmpl); + spinner.succeed(chalk.green("OpenAPI (Swagger)")); + } + + if (!opts.skipPolish) { + // Final polish: Format everything with Biome + spinner.start(chalk.dim("Polishing code with Biome...")); + try { + await execa( + "npx", + ["--yes", "@biomejs/biome", "format", "--write", "."], + { cwd: targetDir }, + ); + spinner.succeed(chalk.green("Code polished and formatted")); + } catch (_err) { + spinner.warn(chalk.yellow("Polishing skipped (Biome failed)")); + } } if (opts.installDeps) { - spinner.start(chalk.dim(`Installing dependencies with ${opts.packageManager}...`)); - await execa(opts.packageManager, ['install'], { cwd: targetDir }); - spinner.succeed(chalk.green('Dependencies installed')); + spinner.start( + chalk.dim(`Installing dependencies with ${opts.packageManager}...`), + ); + try { + await execa(opts.packageManager, ["install"], { + cwd: targetDir, + env: { ...process.env, NODE_ENV: undefined }, // Ensure we install devDeps + }); + spinner.succeed(chalk.green("Dependencies installed")); + } catch (err) { + spinner.fail( + chalk.red( + `Failed to install dependencies with ${opts.packageManager}`, + ), + ); + console.error(chalk.dim((err as Error).message)); + throw err; + } - if (opts.orm === 'prisma') { - spinner.start(chalk.dim('Generating Prisma client...')); + if (opts.orm === "prisma") { + spinner.start(chalk.dim("Generating Prisma client...")); try { - const npxCmd = opts.packageManager === 'bun' ? 'bunx' : opts.packageManager === 'pnpm' ? 'pnpm' : 'npx'; - const args = opts.packageManager === 'pnpm' ? ['exec', 'prisma', 'generate'] : ['prisma', 'generate']; - await execa(npxCmd, args, { cwd: targetDir }); - spinner.succeed(chalk.green('Prisma client generated')); - } catch (err) { - spinner.warn(chalk.yellow('Prisma client generation skipped (network/env issue)')); - console.log(chalk.dim(' You can run "npx prisma generate" manually once your network is stable.')); + // Use npx --yes to ensure it runs without prompts and is reliable across environments + await execa("npx", ["--yes", "prisma", "generate"], { + cwd: targetDir, + }); + spinner.succeed(chalk.green("Prisma client generated")); + } catch (_err) { + spinner.warn( + chalk.yellow( + "Prisma client generation skipped (manual run required)", + ), + ); + console.log( + chalk.dim( + ' You can run "npx prisma generate" manually once your network is stable.', + ), + ); } } } displaySuccess(opts.projectName, opts.packageManager, opts.installDeps); } catch (err) { - spinner.fail(chalk.red('Scaffolding failed')); + spinner.fail(chalk.red("Scaffolding failed")); displayError((err as Error).message); process.exit(1); } diff --git a/packages/create-express-forge/src/generator/structure/modular.ts b/packages/create-express-forge/src/generator/structure/modular.ts index 4ddfd4b..c24795c 100644 --- a/packages/create-express-forge/src/generator/structure/modular.ts +++ b/packages/create-express-forge/src/generator/structure/modular.ts @@ -1,96 +1,61 @@ -import path from 'path'; -import type { CliOptions } from '../../types.js'; -import { writeFile } from '../../utils/file.js'; -import { - tplEnvConfig, tplApiError, tplApiResponse, tplAsyncHandler, - tplErrorHandler, tplNotFound, tplRateLimiter, tplValidate, - tplExpressTypes, tplServerTs, tplAppTs, -} from '../templates.js'; -import { TemplateManager } from '../../utils/template-manager.js'; - -export async function generateModularStructure(opts: CliOptions, dir: string): Promise { - const src = path.join(dir, 'src'); - const tm = new TemplateManager(opts); - - await writeFile(path.join(src, 'config', 'env.ts'), tplEnvConfig(opts)); - await writeFile(path.join(src, 'types', 'express.d.ts'), tplExpressTypes()); - await writeFile(path.join(src, 'utils', 'ApiError.ts'), tplApiError()); - await writeFile(path.join(src, 'utils', 'ApiResponse.ts'), tplApiResponse()); - await writeFile(path.join(src, 'utils', 'asyncHandler.ts'), tplAsyncHandler()); - await writeFile(path.join(src, 'middleware', 'errorHandler.ts'), tplErrorHandler()); - await writeFile(path.join(src, 'middleware', 'notFound.ts'), tplNotFound()); - await writeFile(path.join(src, 'middleware', 'rateLimiter.ts'), tplRateLimiter()); - await writeFile(path.join(src, 'middleware', 'validate.ts'), tplValidate()); - if (opts.auth !== 'none') { - await writeFile(path.join(src, 'middleware', 'auth.middleware.ts'), tm.renderAuthMiddleware(1)); +import path from "node:path"; +import type { CliOptions } from "../../types.js"; +import type { TemplateManager } from "../../utils/template-manager.js"; + +export async function generateModularStructure( + opts: CliOptions, + dir: string, + tmpl: TemplateManager, +): Promise { + const src = path.join(dir, "src"); + + if (opts.auth !== "none") { + await tmpl.renderTemplateFile( + "features/auth/jwt-auth.middleware.ts.eta", + path.join(src, "middleware", "auth.middleware.ts"), + ); } // Health module - await writeFile(path.join(src, 'modules', 'health', 'health.routes.ts'), - `import { Router } from 'express';\nimport { getHealth } from './health.controller.js';\nconst router = Router();\n/**\n * @openapi\n * /api/health:\n * get:\n * tags:\n * - Health\n * description: Responds if the app is up and running\n * responses:\n * 200:\n * description: App is up and running\n */\nrouter.get('/', getHealth);\nexport { router as healthRouter };\n`); - await writeFile(path.join(src, 'modules', 'health', 'health.controller.ts'), - `import type { Request, Response } from 'express';\nimport { ApiResponse } from '../../utils/ApiResponse.js';\nexport const getHealth = (_req: Request, res: Response) =>\n ApiResponse.success(res, { status: 'ok', timestamp: new Date().toISOString() }, 'Service healthy');\n`); + await tmpl.renderTemplateFile( + "structure/modular/modules/health/health.routes.ts.eta", + path.join(src, "modules", "health", "health.routes.ts"), + ); + await tmpl.renderTemplateFile( + "structure/modular/modules/health/health.controller.ts.eta", + path.join(src, "modules", "health", "health.controller.ts"), + ); // Todos module - await writeFile(path.join(src, 'modules', 'todos', 'todos.schema.ts'), - `import { z } from 'zod';\n\nexport const createTodoSchema = z.object({\n body: z.object({\n title: z.string().min(1).max(100),\n description: z.string().max(500).optional(),\n }),\n});\n\nexport const updateTodoSchema = z.object({\n params: z.object({ id: z.string() }),\n body: z.object({\n title: z.string().min(1).max(100).optional(),\n description: z.string().max(500).optional(),\n completed: z.boolean().optional(),\n }),\n});\n\nexport type CreateTodoDto = z.infer['body'];\nexport type UpdateTodoDto = z.infer['body'];\n`); - - await writeFile(path.join(src, 'modules', 'todos', 'todos.service.ts'), - `import { ApiError } from '../../utils/ApiError.js';\n\nexport interface Todo { id: string; title: string; description?: string; completed: boolean; ${opts.auth === 'none' ? '' : 'userId: string; '}}\n\nconst store: Todo[] = [];\n\nexport const TodosService = {\n findAll: async (${opts.auth === 'none' ? '' : 'userId: string'}) => store${opts.auth === 'none' ? '' : '.filter(t => t.userId === userId)'},\n findById: async (id: string${opts.auth === 'none' ? '' : ', userId: string'}) => {\n const todo = store.find((t) => t.id === id${opts.auth === 'none' ? '' : ' && t.userId === userId'});\n if (!todo) throw ApiError.notFound('Todo not found');\n return todo;\n },\n create: async (data: Omit) => {\n const todo = { id: Math.random().toString(36).substring(7), ...data };\n store.push(todo);\n return todo;\n },\n update: async (id: string, ${opts.auth === 'none' ? '' : 'userId: string, '}data: Partial) => {\n const todo = store.find((t) => t.id === id${opts.auth === 'none' ? '' : ' && t.userId === userId'});\n if (!todo) throw ApiError.notFound('Todo not found');\n Object.assign(todo, data);\n return todo;\n },\n remove: async (id: string${opts.auth === 'none' ? '' : ', userId: string'}) => {\n const idx = store.findIndex((t) => t.id === id${opts.auth === 'none' ? '' : ' && t.userId === userId'});\n if (idx === -1) throw ApiError.notFound('Todo not found');\n store.splice(idx, 1);\n },\n};\n`); - - await writeFile(path.join(src, 'modules', 'todos', 'todos.controller.ts'), - `import type { Request, Response } from 'express';\nimport { asyncHandler } from '../../utils/asyncHandler.js';\nimport { ApiResponse } from '../../utils/ApiResponse.js';\nimport { TodosService } from './todos.service.js';\nimport type { CreateTodoDto, UpdateTodoDto } from './todos.schema.js';\n\nexport const getTodos = asyncHandler(async (req, res: Response) => {\n const todos = await TodosService.findAll(${opts.auth === 'none' ? '' : "req.user?.id || 'guest'"});\n return ApiResponse.success(res, todos, 'Todos fetched');\n});\n\nexport const getTodoById = asyncHandler(async (req: Request, res: Response) => {\n const todo = await TodosService.findById(req.params.id || ''${opts.auth === 'none' ? '' : ", req.user?.id || 'guest'"});\n return ApiResponse.success(res, todo);\n});\n\nexport const createTodo = asyncHandler(async (req: Request, res: Response) => {\n const todo = await TodosService.create({ ...(req.body as CreateTodoDto), completed: false${opts.auth === 'none' ? '' : ", userId: req.user?.id || 'guest'" } });\n return ApiResponse.created(res, todo, 'Todo created');\n});\n\nexport const updateTodo = asyncHandler(async (req: Request, res: Response) => {\n const todo = await TodosService.update(req.params.id || '', ${opts.auth === 'none' ? '' : "req.user?.id || 'guest', "}req.body as UpdateTodoDto);\n return ApiResponse.success(res, todo, 'Todo updated');\n});\n\nexport const deleteTodo = asyncHandler(async (req: Request, res: Response) => {\n await TodosService.remove(req.params.id || ''${opts.auth === 'none' ? '' : ", req.user?.id || 'guest'"});\n return ApiResponse.noContent(res);\n});\n`); - - await writeFile(path.join(src, 'modules', 'todos', 'todos.routes.ts'), - `import { Router } from 'express';\nimport { getTodos, getTodoById, createTodo, updateTodo, deleteTodo } from './todos.controller.js';\nimport { validate } from '../../middleware/validate.js';\nimport { createTodoSchema, updateTodoSchema } from './todos.schema.js';\n${opts.auth !== 'none' ? "import { auth } from '../../middleware/auth.middleware.js';\n" : ""} -const router = Router(); -${opts.auth !== 'none' ? 'router.use(auth);' : ''} - -/** - * @openapi - * /api/v1/todos: - * get: - * tags: - * - Todos - * responses: - * 200: - * description: Success - * post: - * tags: - * - Todos - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: [title] - * properties: - * title: - * type: string - * description: - * type: string - * responses: - * 201: - * description: Created - */ -router.get('/', getTodos); -router.get('/:id', getTodoById); -router.post('/', validate(createTodoSchema), createTodo); -router.patch('/:id', validate(updateTodoSchema), updateTodo); -router.delete('/:id', deleteTodo); -export { router as todosRouter };\n`); - - if (opts.auth === 'jwt') { - await writeFile(path.join(src, 'modules', 'auth', 'auth.schema.ts'), - `import { z } from 'zod';\nexport const loginSchema = z.object({ body: z.object({ email: z.string().email(), password: z.string().min(8) }) });\n`); - await writeFile(path.join(src, 'modules', 'auth', 'auth.controller.ts'), - `import type { Request, Response } from 'express';\nimport jwt from 'jsonwebtoken';\nimport { asyncHandler } from '../../utils/asyncHandler.js';\nimport { ApiResponse } from '../../utils/ApiResponse.js';\nimport { env } from '../../config/env.js';\n\nexport const login = asyncHandler(async (req, res) => {\n // Demo logic: accept any valid email/password\n const token = jwt.sign({ email: req.body.email }, env.JWT_SECRET, { expiresIn: env.JWT_EXPIRES_IN as jwt.SignOptions['expiresIn'] });\n ${opts.jwtStorage === 'cookie' ? "res.cookie('token', token, { httpOnly: true, secure: env.NODE_ENV === 'production' });\n return ApiResponse.success(res, { token }, 'Logged in successfully');" : "return ApiResponse.success(res, { token }, 'Logged in successfully');"}\n});\n`); - await writeFile(path.join(src, 'modules', 'auth', 'auth.routes.ts'), - `import { Router } from 'express';\nimport { login } from './auth.controller.js';\nimport { validate } from '../../middleware/validate.js';\nimport { loginSchema } from './auth.schema.js';\nconst router = Router();\n/**\n * @openapi\n * /api/v1/auth/login:\n * post:\n * tags:\n * - Auth\n * requestBody:\n * required: true\n * content:\n * application/json:\n * schema:\n * type: object\n * required: [email, password]\n * properties:\n * email:\n * type: string\n * password:\n * type: string\n * responses:\n * 200:\n * description: Logged in successfully\n */\nrouter.post('/login', validate(loginSchema), login);\nexport { router as authRouter };\n`); + await tmpl.renderTemplateFile( + "structure/modular/modules/todos/todos.schema.ts.eta", + path.join(src, "modules", "todos", "todos.schema.ts"), + ); + await tmpl.renderTemplateFile( + "structure/modular/modules/todos/todos.service.ts.eta", + path.join(src, "modules", "todos", "todos.service.ts"), + ); + await tmpl.renderTemplateFile( + "structure/modular/modules/todos/todos.controller.ts.eta", + path.join(src, "modules", "todos", "todos.controller.ts"), + ); + await tmpl.renderTemplateFile( + "structure/modular/modules/todos/todos.routes.ts.eta", + path.join(src, "modules", "todos", "todos.routes.ts"), + ); + + if (opts.auth === "jwt") { + await tmpl.renderTemplateFile( + "structure/modular/modules/auth/auth.schema.ts.eta", + path.join(src, "modules", "auth", "auth.schema.ts"), + ); + await tmpl.renderTemplateFile( + "structure/modular/modules/auth/auth.controller.ts.eta", + path.join(src, "modules", "auth", "auth.controller.ts"), + ); + await tmpl.renderTemplateFile( + "structure/modular/modules/auth/auth.routes.ts.eta", + path.join(src, "modules", "auth", "auth.routes.ts"), + ); } - - await writeFile(path.join(src, 'app.ts'), tplAppTs(opts)); - - await writeFile(path.join(src, 'server.ts'), tplServerTs(opts.logger)); } diff --git a/packages/create-express-forge/src/generator/structure/mvc.ts b/packages/create-express-forge/src/generator/structure/mvc.ts index 227e2d0..44f9a04 100644 --- a/packages/create-express-forge/src/generator/structure/mvc.ts +++ b/packages/create-express-forge/src/generator/structure/mvc.ts @@ -1,61 +1,62 @@ -import path from 'path'; -import type { CliOptions } from '../../types.js'; -import { writeFile } from '../../utils/file.js'; -import { - tplEnvConfig, tplApiError, tplApiResponse, tplAsyncHandler, - tplErrorHandler, tplNotFound, tplRateLimiter, tplValidate, - tplExpressTypes, tplServerTs, tplAppTs, -} from '../templates.js'; -import { TemplateManager } from '../../utils/template-manager.js'; - -export async function generateMvcStructure(opts: CliOptions, dir: string): Promise { - const src = path.join(dir, 'src'); - const tm = new TemplateManager(opts); - - await writeFile(path.join(src, 'config', 'env.ts'), tplEnvConfig(opts)); - await writeFile(path.join(src, 'types', 'express.d.ts'), tplExpressTypes()); - await writeFile(path.join(src, 'utils', 'ApiError.ts'), tplApiError()); - await writeFile(path.join(src, 'utils', 'ApiResponse.ts'), tplApiResponse()); - await writeFile(path.join(src, 'utils', 'asyncHandler.ts'), tplAsyncHandler()); - await writeFile(path.join(src, 'middleware', 'errorHandler.ts'), tplErrorHandler()); - await writeFile(path.join(src, 'middleware', 'notFound.ts'), tplNotFound()); - await writeFile(path.join(src, 'middleware', 'rateLimiter.ts'), tplRateLimiter()); - await writeFile(path.join(src, 'middleware', 'validate.ts'), tplValidate()); - if (opts.auth !== 'none') { - await writeFile(path.join(src, 'middleware', 'auth.middleware.ts'), tm.renderAuthMiddleware(1)); +import path from "node:path"; +import type { CliOptions } from "../../types.js"; +import type { TemplateManager } from "../../utils/template-manager.js"; + +export async function generateMvcStructure( + opts: CliOptions, + dir: string, + tmpl: TemplateManager, +): Promise { + const src = path.join(dir, "src"); + + if (opts.auth !== "none") { + await tmpl.renderTemplateFile( + "features/auth/jwt-auth.middleware.ts.eta", + path.join(src, "middleware", "auth.middleware.ts"), + ); } - await writeFile(path.join(src, 'schemas', 'todo.schema.ts'), - `import { z } from 'zod';\nexport const createTodoSchema = z.object({ body: z.object({ title: z.string().min(1).max(100), description: z.string().max(500).optional() }) });\nexport const updateTodoSchema = z.object({ params: z.object({ id: z.string() }), body: z.object({ title: z.string().min(1).max(100).optional(), description: z.string().max(500).optional(), completed: z.boolean().optional() }) });\nexport type CreateTodoDto = z.infer['body'];\nexport type UpdateTodoDto = z.infer['body'];\n`); - - await writeFile(path.join(src, 'services', 'todo.service.ts'), - `import { ApiError } from '../utils/ApiError.js';\nexport interface Todo { id: string; title: string; description?: string; completed: boolean; ${opts.auth === 'none' ? '' : 'userId: string; '}}\nconst store: Todo[] = [];\nexport const TodoService = {\n findAll: async (${opts.auth === 'none' ? '' : 'userId: string'}) => store${opts.auth === 'none' ? '' : '.filter(t => t.userId === userId)'},\n findById: async (id: string${opts.auth === 'none' ? '' : ', userId: string'}) => { const t = store.find((t) => t.id === id${opts.auth === 'none' ? '' : ' && t.userId === userId'}); if (!t) throw ApiError.notFound('Todo not found'); return t; },\n create: async (data: Omit) => { const t = { id: Math.random().toString(36).substring(7), ...data }; store.push(t); return t; },\n update: async (id: string, ${opts.auth === 'none' ? '' : 'userId: string, '}data: Partial) => { const t = store.find((t) => t.id === id${opts.auth === 'none' ? '' : ' && t.userId === userId'}); if (!t) throw ApiError.notFound('Todo not found'); Object.assign(t, data); return t; },\n remove: async (id: string${opts.auth === 'none' ? '' : ', userId: string'}) => { const i = store.findIndex((t) => t.id === id${opts.auth === 'none' ? '' : ' && t.userId === userId'}); if (i === -1) throw ApiError.notFound('Todo not found'); store.splice(i, 1); },\n};\n`); - - await writeFile(path.join(src, 'controllers', 'health.controller.ts'), - `import type { Request, Response } from 'express';\nimport { ApiResponse } from '../utils/ApiResponse.js';\nexport const getHealth = (_req: Request, res: Response) => ApiResponse.success(res, { status: 'ok', timestamp: new Date().toISOString() }, 'Service healthy');\n`); - - await writeFile(path.join(src, 'controllers', 'todo.controller.ts'), - `import type { Request, Response } from 'express';\nimport { asyncHandler } from '../utils/asyncHandler.js';\nimport { ApiResponse } from '../utils/ApiResponse.js';\nimport { TodoService } from '../services/todo.service.js';\nimport type { CreateTodoDto, UpdateTodoDto } from '../schemas/todo.schema.js';\nexport const getTodos = asyncHandler(async (req, res: Response) => ApiResponse.success(res, await TodoService.findAll(${opts.auth === 'none' ? '' : "req.user?.id || 'guest'"}), 'Todos fetched'));\nexport const getTodoById = asyncHandler(async (req: Request, res: Response) => ApiResponse.success(res, await TodoService.findById(req.params.id || ''${opts.auth === 'none' ? '' : ", req.user?.id || 'guest'"})));\nexport const createTodo = asyncHandler(async (req: Request, res: Response) => ApiResponse.created(res, await TodoService.create({ ...(req.body as CreateTodoDto), completed: false${opts.auth === 'none' ? '' : ", userId: req.user?.id || 'guest'"} }), 'Todo created'));\nexport const updateTodo = asyncHandler(async (req: Request, res: Response) => ApiResponse.success(res, await TodoService.update(req.params.id || '', ${opts.auth === 'none' ? '' : "req.user?.id || 'guest', "}req.body as UpdateTodoDto), 'Todo updated'));\nexport const deleteTodo = asyncHandler(async (req: Request, res: Response) => { await TodoService.remove(req.params.id || ''${opts.auth === 'none' ? '' : ", req.user?.id || 'guest'"}); return ApiResponse.noContent(res); });\n`); - - await writeFile(path.join(src, 'routes', 'index.ts'), - `import { Router } from 'express';\nimport { healthRouter } from './health.routes.js';\nimport { todoRouter } from './todo.routes.js';\n${opts.auth === 'jwt' ? "import { authRouter } from './auth.routes.js';\n" : ""}const router = Router();\nrouter.use('/health', healthRouter);\nrouter.use('/todos', todoRouter);\n${opts.auth === 'jwt' ? "router.use('/auth', authRouter);\n" : ""}export { router };\n`); - - await writeFile(path.join(src, 'routes', 'health.routes.ts'), - `import { Router } from 'express';\nimport { getHealth } from '../controllers/health.controller.js';\nconst router = Router();\n/**\n * @openapi\n * /api/health:\n * get:\n * tags:\n * - Health\n * description: Responds if the app is up and running\n * responses:\n * 200:\n * description: App is up and running\n */\nrouter.get('/', getHealth);\nexport { router as healthRouter };\n`); - - await writeFile(path.join(src, 'routes', 'todo.routes.ts'), - `import { Router } from 'express';\nimport { getTodos, getTodoById, createTodo, updateTodo, deleteTodo } from '../controllers/todo.controller.js';\nimport { validate } from '../middleware/validate.js';\nimport { createTodoSchema, updateTodoSchema } from '../schemas/todo.schema.js';\n${opts.auth !== 'none' ? "import { auth } from '../middleware/auth.middleware.js';\n" : ""}const router = Router();\n${opts.auth !== 'none' ? 'router.use(auth);\n' : ''}/**\n * @openapi\n * /api/v1/todos:\n * get:\n * tags:\n * - Todos\n * responses:\n * 200:\n * description: Success\n * post:\n * tags:\n * - Todos\n * requestBody:\n * required: true\n * content:\n * application/json:\n * schema:\n * type: object\n * required: [title]\n * properties:\n * title:\n * type: string\n * description:\n * type: string\n * responses:\n * 201:\n * description: Created\n */\nrouter.get('/', getTodos);\nrouter.get('/:id', getTodoById);\nrouter.post('/', validate(createTodoSchema), createTodo);\nrouter.patch('/:id', validate(updateTodoSchema), updateTodo);\nrouter.delete('/:id', deleteTodo);\nexport { router as todoRouter };\n`); - - if (opts.auth === 'jwt') { - await writeFile(path.join(src, 'schemas', 'auth.schema.ts'), - `import { z } from 'zod';\nexport const loginSchema = z.object({ body: z.object({ email: z.string().email(), password: z.string().min(8) }) });\n`); - await writeFile(path.join(src, 'controllers', 'auth.controller.ts'), - `import type { Request, Response } from 'express';\nimport jwt from 'jsonwebtoken';\nimport { asyncHandler } from '../utils/asyncHandler.js';\nimport { ApiResponse } from '../utils/ApiResponse.js';\nimport { env } from '../config/env.js';\n\nexport const login = asyncHandler(async (req, res) => {\n // Demo logic: accept any valid email/password\n const token = jwt.sign({ email: req.body.email }, env.JWT_SECRET, { expiresIn: env.JWT_EXPIRES_IN as jwt.SignOptions['expiresIn'] });\n ${opts.jwtStorage === 'cookie' ? "res.cookie('token', token, { httpOnly: true, secure: env.NODE_ENV === 'production' });\n return ApiResponse.success(res, { token }, 'Logged in successfully');" : "return ApiResponse.success(res, { token }, 'Logged in successfully');"}\n});\n`); - await writeFile(path.join(src, 'routes', 'auth.routes.ts'), - `import { Router } from 'express';\nimport { login } from '../controllers/auth.controller.js';\nimport { validate } from '../middleware/validate.js';\nimport { loginSchema } from '../schemas/auth.schema.js';\nconst router = Router();\n/**\n * @openapi\n * /api/v1/auth/login:\n * post:\n * tags:\n * - Auth\n * requestBody:\n * required: true\n * content:\n * application/json:\n * schema:\n * type: object\n * required: [email, password]\n * properties:\n * email:\n * type: string\n * password:\n * type: string\n * responses:\n * 200:\n * description: Logged in successfully\n */\nrouter.post('/login', validate(loginSchema), login);\nexport { router as authRouter };\n`); + await tmpl.renderTemplateFile( + "structure/mvc/schemas/todo.schema.ts.eta", + path.join(src, "schemas", "todo.schema.ts"), + ); + await tmpl.renderTemplateFile( + "structure/mvc/services/todo.service.ts.eta", + path.join(src, "services", "todo.service.ts"), + ); + await tmpl.renderTemplateFile( + "structure/mvc/controllers/health.controller.ts.eta", + path.join(src, "controllers", "health.controller.ts"), + ); + await tmpl.renderTemplateFile( + "structure/mvc/controllers/todo.controller.ts.eta", + path.join(src, "controllers", "todo.controller.ts"), + ); + await tmpl.renderTemplateFile( + "structure/mvc/routes/index.ts.eta", + path.join(src, "routes", "index.ts"), + ); + await tmpl.renderTemplateFile( + "structure/mvc/routes/health.routes.ts.eta", + path.join(src, "routes", "health.routes.ts"), + ); + await tmpl.renderTemplateFile( + "structure/mvc/routes/todo.routes.ts.eta", + path.join(src, "routes", "todo.routes.ts"), + ); + + if (opts.auth === "jwt") { + await tmpl.renderTemplateFile( + "structure/mvc/schemas/auth.schema.ts.eta", + path.join(src, "schemas", "auth.schema.ts"), + ); + await tmpl.renderTemplateFile( + "structure/mvc/controllers/auth.controller.ts.eta", + path.join(src, "controllers", "auth.controller.ts"), + ); + await tmpl.renderTemplateFile( + "structure/mvc/routes/auth.routes.ts.eta", + path.join(src, "routes", "auth.routes.ts"), + ); } - - await writeFile(path.join(src, 'app.ts'), tplAppTs(opts)); - - await writeFile(path.join(src, 'server.ts'), tplServerTs(opts.logger)); } diff --git a/packages/create-express-forge/src/generator/templates.ts b/packages/create-express-forge/src/generator/templates.ts deleted file mode 100644 index 263fff1..0000000 --- a/packages/create-express-forge/src/generator/templates.ts +++ /dev/null @@ -1,339 +0,0 @@ -import type { CliOptions } from '../types.js'; - -// All template strings for files written into the user's generated project - -export function tplEnvConfig(opts: CliOptions): string { - const hasDb = opts.orm !== 'none' && opts.database !== 'none'; - const isJwt = opts.auth === 'jwt'; - const isSession = opts.auth === 'session'; - - return `import { z } from 'zod'; - -const envSchema = z.object({ - NODE_ENV: z.enum(['development', 'production', 'test']).default('development'), - PORT: z.coerce.number().default(3000), - CORS_ORIGIN: z.string().default('*'), - RATE_LIMIT_WINDOW_MS: z.coerce.number().default(900_000), - RATE_LIMIT_MAX: z.coerce.number().default(100), -${hasDb ? ` DATABASE_URL: z.string().url('DATABASE_URL must be a valid URL'),` : " // DATABASE_URL: z.string(),"} -${isJwt ? ` JWT_SECRET: z.string().min(1, 'JWT_SECRET is required'),\n JWT_EXPIRES_IN: z.string().default('1d'),` : ''}${isSession ? ` SESSION_SECRET: z.string().min(1, 'SESSION_SECRET is required'),` : ''} -${opts.cache === 'redis' ? ` REDIS_URL: z.string().url().default('redis://localhost:6379'),` : ''} -}); - -const parsed = envSchema.safeParse(process.env); - -if (!parsed.success) { - console.error('\\nโŒ Invalid environment variables:'); - console.error(JSON.stringify(parsed.error.format(), null, 2)); - process.exit(1); -} - -export const env = parsed.data; -export type Env = typeof env; -`; -} - -export function tplApiError(): string { - return `export class ApiError extends Error { - public readonly statusCode: number; - public readonly errors: unknown[]; - public readonly isOperational: boolean; - - constructor(statusCode: number, message: string, errors: unknown[] = [], isOperational = true) { - super(message); - this.statusCode = statusCode; - this.errors = errors; - this.isOperational = isOperational; - Object.setPrototypeOf(this, ApiError.prototype); - Error.captureStackTrace(this, this.constructor); - } - - static badRequest(message = 'Bad Request', errors: unknown[] = []) { return new ApiError(400, message, errors); } - static unauthorized(message = 'Unauthorized') { return new ApiError(401, message); } - static forbidden(message = 'Forbidden') { return new ApiError(403, message); } - static notFound(message = 'Not Found') { return new ApiError(404, message); } - static conflict(message = 'Conflict') { return new ApiError(409, message); } - static internal(message = 'Internal Server Error') { return new ApiError(500, message, [], false); } -} -`; -} - -export function tplApiResponse(): string { - return `import type { Response } from 'express'; - -export class ApiResponse { - static success(res: Response, data: T, message = 'Success', statusCode = 200) { - return res.status(statusCode).json({ success: true, message, data }); - } - static created(res: Response, data: T, message = 'Created') { - return ApiResponse.success(res, data, message, 201); - } - static noContent(res: Response) { return res.status(204).send(); } - static paginated(res: Response, data: T[], pagination: { total: number; page: number; limit: number; pages: number }, message = 'Success') { - return res.status(200).json({ success: true, message, data, pagination }); - } -} -`; -} - -export function tplAsyncHandler(): string { - return `import type { Request, Response, NextFunction, RequestHandler } from 'express'; - -type AsyncFn = (req: Request, res: Response, next: NextFunction) => Promise; - -/** Wraps async route handlers โ€” errors forwarded to global error handler */ -export const asyncHandler = (fn: AsyncFn): RequestHandler => - (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); }; -`; -} - -export function tplErrorHandler(): string { - return `import type { Request, Response, NextFunction } from 'express'; -import { ZodError } from 'zod'; -import { ApiError } from '../utils/ApiError.js'; -import { env } from '../config/env.js'; - -/** - * Global centralized error handler โ€” must be the LAST middleware registered. - * Handles: ApiError, ZodError, and unknown errors. - */ -export const errorHandler = (err: Error, _req: Request, res: Response, _next: NextFunction): void => { - const isDev = env.NODE_ENV === 'development'; - - if (err instanceof ApiError) { - res.status(err.statusCode).json({ - success: false, - message: err.message, - ...(err.errors.length > 0 && { errors: err.errors }), - ...(isDev && { stack: err.stack }), - }); - return; - } - - if (err instanceof ZodError) { - res.status(400).json({ - success: false, - message: 'Validation failed', - errors: err.errors.map((e) => ({ path: e.path.join('.'), message: e.message })), - }); - return; - } - - console.error('[UnhandledError]', err); - res.status(500).json({ - success: false, - message: 'Internal Server Error', - ...(isDev && { stack: err.stack }), - }); -}; -`; -} - -export function tplNotFound(): string { - return `import type { Request, Response, NextFunction } from 'express'; -import { ApiError } from '../utils/ApiError.js'; - -export const notFound = (req: Request, _res: Response, next: NextFunction): void => { - next(ApiError.notFound(\`Route \${req.method} \${req.url} not found\`)); -}; -`; -} - -export function tplRateLimiter(): string { - return `import rateLimit from 'express-rate-limit'; -import { env } from '../config/env.js'; - -export const rateLimiter = rateLimit({ - windowMs: env.RATE_LIMIT_WINDOW_MS, - max: env.RATE_LIMIT_MAX, - standardHeaders: true, - legacyHeaders: false, - message: { success: false, message: 'Too many requests, please try again later.' }, -}); -`; -} - -export function tplValidate(): string { - return `import type { Request, Response, NextFunction } from 'express'; -import { type AnyZodObject, ZodError } from 'zod'; -import { ApiError } from '../utils/ApiError.js'; - -/** Validates req.body / req.query / req.params against a Zod schema */ -export const validate = (schema: AnyZodObject) => - async (req: Request, _res: Response, next: NextFunction): Promise => { - try { - const validated = await schema.parseAsync({ body: req.body, query: req.query, params: req.params }); - req.body = validated.body; - req.query = validated.query; - req.params = validated.params; - next(); - } catch (err) { - if (err instanceof ZodError) next(ApiError.badRequest('Validation failed', err.errors)); - else next(err); - } - }; -`; -} - -export function tplExpressTypes(): string { - return `import 'express'; - -declare module 'express' { - interface Request { - user?: { id: string; email: string; role: string }; - } -} -`; -} - -export function tplAppTs(opts: CliOptions): string { - const isModular = opts.pattern === 'modular'; - const hasSwagger = opts.openapi; - const needsCookies = opts.jwtStorage === 'cookie' || opts.auth === 'session'; - - return `import express from 'express'; -import cors from 'cors'; -import helmet from 'helmet'; -import compression from 'compression'; -${needsCookies ? "import cookieParser from 'cookie-parser';" : ''} -${opts.auth === 'session' ? "import session from 'express-session';" : ''} -import { env } from './config/env.js'; -import { rateLimiter } from './middleware/rateLimiter.js'; -${opts.logger === 'pino' ? "import { httpLogger } from './middleware/httpLogger.js';" : ''} -import { notFound } from './middleware/notFound.js'; -import { errorHandler } from './middleware/errorHandler.js'; -${hasSwagger ? "import { setupSwagger } from './docs/swagger.js';" : ''} -${isModular ? `import { healthRouter } from './modules/health/health.routes.js'; -import { todosRouter } from './modules/todos/todos.routes.js'; -${opts.auth !== 'none' ? "import { authRouter } from './modules/auth/auth.routes.js';" : ''}` : "import { router } from './routes/index.js';"} - -const app = express(); - -app.use(helmet()); -app.use(cors({ origin: env.CORS_ORIGIN, credentials: true })); -${needsCookies ? 'app.use(cookieParser());' : ''} -${opts.auth === 'session' ? "app.use(session({ secret: env.SESSION_SECRET, resave: false, saveUninitialized: false }));" : ''} -app.use(express.json({ limit: '10mb' })); -app.use(express.urlencoded({ extended: true })); -app.use(compression()); -app.use(rateLimiter); -${opts.logger === 'pino' ? 'app.use(httpLogger);' : ''} - -${hasSwagger ? 'setupSwagger(app);' : ''} - -${ - isModular - ? `app.use('/api/health', healthRouter); -app.use('/api/v1/todos', todosRouter); -${opts.auth !== 'none' ? "app.use('/api/v1/auth', authRouter);" : ''}` - : "app.use('/api/v1', router);" -} - -app.use(notFound); -app.use(errorHandler); - -export { app }; -`; -} - -export function tplServerTs(loggerLib: string): string { - const hasLogger = loggerLib !== 'none'; - const logImport = hasLogger ? `import { logger } from './logger/index.js';\n` : ''; - const log = hasLogger ? 'logger.info' : 'console.log'; - return `import 'dotenv/config'; -${logImport}import { app } from './app.js'; -import { env } from './config/env.js'; - -const server = app.listen(env.PORT, () => { - ${log}(\`๐Ÿš€ Server running on port \${env.PORT} in \${env.NODE_ENV} mode\`); -}); - -const shutdown = (signal: string) => { - ${log}(\`\${signal} received โ€” shutting down gracefully\`); - server.close(() => process.exit(0)); -}; - -process.on('SIGTERM', () => shutdown('SIGTERM')); -process.on('SIGINT', () => shutdown('SIGINT')); -process.on('uncaughtException', (err) => { console.error('Uncaught:', err); process.exit(1); }); -process.on('unhandledRejection', (r) => { console.error('Unhandled:', r); process.exit(1); }); -`; -} - -export function tplTodosService(opts: CliOptions): string { - if (opts.orm === 'prisma') { - return `import { prisma } from '../../config/database.js'; -import { ApiError } from '../../utils/ApiError.js'; -import type { CreateTodoDto, UpdateTodoDto } from './todos.schema.js'; - -export const TodosService = { - findAll: async (userId: string) => { - return prisma.user.findUnique({ - where: { id: userId } - }).todos(); // Assuming a relation exists, or just filter: - // return prisma.todo.findMany({ where: { userId } }); - }, - - findById: async (id: string, userId: string) => { - const todo = await prisma.todo.findFirst({ where: { id, userId } }); - if (!todo) throw ApiError.notFound('Todo not found'); - return todo; - }, - - create: async (userId: string, data: CreateTodoDto) => { - return prisma.todo.create({ - data: { ...data, userId } - }); - }, - - update: async (id: string, userId: string, data: UpdateTodoDto) => { - const todo = await prisma.todo.findFirst({ where: { id, userId } }); - if (!todo) throw ApiError.notFound('Todo not found'); - return prisma.todo.update({ - where: { id }, - data - }); - }, - - remove: async (id: string, userId: string) => { - const todo = await prisma.todo.findFirst({ where: { id, userId } }); - if (!todo) throw ApiError.notFound('Todo not found'); - return prisma.todo.delete({ where: { id } }); - }, -}; -`; - } - - // Default in-memory store for 'none' or simple demo - return `import { ApiError } from '../../utils/ApiError.js'; - -export interface Todo { id: string; title: string; description?: string; completed: boolean; userId: string; } - -const store: Todo[] = []; - -export const TodosService = { - findAll: async (userId: string) => store.filter(t => t.userId === userId), - findById: async (id: string, userId: string) => { - const todo = store.find((t) => t.id === id && t.userId === userId); - if (!todo) throw ApiError.notFound('Todo not found'); - return todo; - }, - create: async (userId: string, data: any) => { - const todo = { id: Math.random().toString(36).substring(7), ...data, userId, completed: false }; - store.push(todo); - return todo; - }, - update: async (id: string, userId: string, data: any) => { - const todo = store.find((t) => t.id === id && t.userId === userId); - if (!todo) throw ApiError.notFound('Todo not found'); - Object.assign(todo, data); - return todo; - }, - remove: async (id: string, userId: string) => { - const idx = store.findIndex((t) => t.id === id && t.userId === userId); - if (idx === -1) throw ApiError.notFound('Todo not found'); - store.splice(idx, 1); - }, -}; -`; -} diff --git a/packages/create-express-forge/src/index.ts b/packages/create-express-forge/src/index.ts index 1b3e56f..0ccefe7 100644 --- a/packages/create-express-forge/src/index.ts +++ b/packages/create-express-forge/src/index.ts @@ -1,29 +1,36 @@ -import { Command } from 'commander'; -import { createRequire } from 'module'; +import { createRequire } from "node:module"; +import { Command } from "commander"; const require = createRequire(import.meta.url); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -const pkg = require('../package.json') as { version: string }; +const pkg = require("../package.json") as { version: string }; -import { runCLI } from './prompts.js'; +import { runCLI } from "./prompts.js"; const program = new Command(); program - .name('create-express-forge') - .description('โšก Scaffold production-ready Express.js TypeScript backends in seconds') + .name("create-express-forge") + .description( + "โšก Scaffold production-ready Express.js TypeScript backends in seconds", + ) .version(pkg.version) - .option('-y, --yes', 'Use default options for all prompts') - .option('--pattern ', 'Architecture pattern (modular, mvc)') - .option('--orm ', 'ORM to use (prisma, sequelize, none)') - .option('--db ', 'Database type (postgresql, mysql, sqlite, none)') - .option('--logger ', 'Logging library (winston, pino, none)') - .option('--test ', 'Testing framework (vitest, jest, none)') - .option('--docker ', 'Include Docker setup (true, false)') - .option('--install ', 'Auto-install dependencies (true, false)') - .argument('[project-name]', 'Name of the project') - .action(async (projectName?: string, options?: Record) => { - await runCLI(projectName, options); - }); + .option("-y, --yes", "Use default options for all prompts") + .option("--pattern ", "Architecture pattern (modular, mvc)") + .option("--orm ", "ORM to use (prisma, sequelize, none)") + .option("--db ", "Database type (postgresql, mysql, sqlite, none)") + .option("--logger ", "Logging library (winston, pino, none)") + .option("--test ", "Testing framework (vitest, jest, none)") + .option("--docker ", "Include Docker setup (true, false)") + .option("--install ", "Auto-install dependencies (true, false)") + .argument("[project-name]", "Name of the project") + .action( + async ( + projectName?: string, + options?: Record, + ) => { + await runCLI(projectName, options); + }, + ); program.parse(process.argv); diff --git a/packages/create-express-forge/src/prompts.ts b/packages/create-express-forge/src/prompts.ts index 231cea1..e14f649 100644 --- a/packages/create-express-forge/src/prompts.ts +++ b/packages/create-express-forge/src/prompts.ts @@ -1,25 +1,45 @@ -import { input, select, confirm } from '@inquirer/prompts'; -import path from 'path'; -import type { CliOptions, Pattern, ORM, Database, PackageManager, LoggerLib, TestingLib, AuthStrategy, JwtStorage, CacheLib } from './types.js'; -import { displayBanner } from './utils/display.js'; -import { generateProject } from './generator/index.js'; - -export async function runCLI(initialProjectName?: string, cmdOptions: Record = {}) { +import path from "node:path"; +import { confirm, input, select } from "@inquirer/prompts"; +import { generateProject } from "./generator/index.js"; +import type { + AuthStrategy, + CacheLib, + CliOptions, + Database, + JwtStorage, + LoggerLib, + ORM, + PackageManager, + Pattern, + TestingLib, +} from "./types.js"; +import { displayBanner } from "./utils/display.js"; + +export async function runCLI( + initialProjectName?: string, + cmdOptions: Record = {}, +) { displayBanner(); if (cmdOptions.yes) { - const projectName = initialProjectName ?? 'my-express-app'; + const projectName = initialProjectName ?? "my-express-app"; + + // Detect package manager (default to pnpm if we are in this workspace) + const packageManager = process.env.npm_config_user_agent?.includes("pnpm") + ? "pnpm" + : "npm"; + const options: CliOptions = { projectName, - pattern: 'modular', - orm: 'prisma', - database: 'postgresql', - packageManager: 'npm', - logger: 'winston', - testing: 'vitest', - auth: 'jwt', - jwtStorage: 'cookie', - cache: 'redis', + pattern: "modular", + orm: "prisma", + database: "postgresql", + packageManager, + logger: "winston", + testing: "vitest", + auth: "jwt", + jwtStorage: "cookie", + cache: "redis", importAlias: true, openapi: true, openapiUI: true, @@ -34,111 +54,175 @@ export async function runCLI(initialProjectName?: string, cmdOptions: Record /^[a-z0-9-_]+$/.test(v) ? true - : 'Use lowercase letters, numbers, hyphens, or underscores', + : "Use lowercase letters, numbers, hyphens, or underscores", })); - - const packageManager = await select({ - message: 'Package manager:', - choices: [ - { name: '๐Ÿ“ฆ npm', value: 'npm' }, - { name: '๐Ÿš€ pnpm', value: 'pnpm' }, - { name: '๐Ÿงถ yarn', value: 'yarn' }, - { name: '๐Ÿž bun', value: 'bun' }, - ], - }); - const pattern = cmdOptions.pattern ?? await select({ - message: 'Architecture pattern:', + const packageManager = await select({ + message: "Package manager:", choices: [ - { name: '๐Ÿ“ฆ Modular โ€” feature-based modules (recommended)', value: 'modular' }, - { name: '๐Ÿ—๏ธ MVC โ€” Model / View / Controller', value: 'mvc' }, + { name: "๐Ÿ“ฆ npm", value: "npm" }, + { name: "๐Ÿš€ pnpm", value: "pnpm" }, + { name: "๐Ÿงถ yarn", value: "yarn" }, + { name: "๐Ÿž bun", value: "bun" }, ], }); - const orm = cmdOptions.orm ?? await select({ - message: 'ORM / Database layer:', - choices: [ - { name: '๐Ÿ”ท Prisma (type-safe, modern โ€” recommended)', value: 'prisma' }, - { name: '๐Ÿ”ถ Sequelize (battle-tested)', value: 'sequelize' }, - { name: 'โฌœ None (configure later)', value: 'none' }, - ], - }); + const pattern = + cmdOptions.pattern ?? + (await select({ + message: "Architecture pattern:", + choices: [ + { + name: "๐Ÿ“ฆ Modular โ€” feature-based modules (recommended)", + value: "modular", + }, + { name: "๐Ÿ—๏ธ MVC โ€” Model / View / Controller", value: "mvc" }, + ], + })); + + const orm = + cmdOptions.orm ?? + (await select({ + message: "ORM / Database layer:", + choices: [ + { + name: "๐Ÿ”ท Prisma (type-safe, modern โ€” recommended)", + value: "prisma", + }, + { name: "๐Ÿ”ถ Sequelize (battle-tested)", value: "sequelize" }, + { name: "โฌœ None (configure later)", value: "none" }, + ], + })); - const database: CliOptions['database'] = (cmdOptions.db as Database) ?? ( - orm !== 'none' - ? await select({ - message: 'Database:', + const database: CliOptions["database"] = + (cmdOptions.db as Database) ?? + (orm !== "none" + ? await select({ + message: "Database:", choices: [ - { name: '๐Ÿ˜ PostgreSQL', value: 'postgresql' }, - { name: '๐Ÿฌ MySQL', value: 'mysql' }, - { name: '๐Ÿชถ SQLite', value: 'sqlite' }, - { name: 'โฌœ None (configure later)', value: 'none' }, + { name: "๐Ÿ˜ PostgreSQL", value: "postgresql" }, + { name: "๐Ÿฌ MySQL", value: "mysql" }, + { name: "๐Ÿชถ SQLite", value: "sqlite" }, + { name: "โฌœ None (configure later)", value: "none" }, ], }) - : 'none'); - - const logger = cmdOptions.logger ?? await select({ - message: 'Logger:', - choices: [ - { name: '๐Ÿชต Winston (feature-rich, transport-based)', value: 'winston' }, - { name: 'โšก Pino (ultra-fast, low-overhead)', value: 'pino' }, - { name: 'โฌœ None (console.log)', value: 'none' }, - ], - }); + : "none"); + + const logger = + cmdOptions.logger ?? + (await select({ + message: "Logger:", + choices: [ + { + name: "๐Ÿชต Winston (feature-rich, transport-based)", + value: "winston", + }, + { name: "โšก Pino (ultra-fast, low-overhead)", value: "pino" }, + { name: "โฌœ None (console.log)", value: "none" }, + ], + })); - const testing = cmdOptions.test ?? await select({ - message: 'Testing framework:', - choices: [ - { name: 'โšก Vitest (fast, ESM-native โ€” recommended)', value: 'vitest' }, - { name: '๐Ÿƒ Jest (widely-used)', value: 'jest' }, - { name: 'โฌœ None', value: 'none' }, - ], - }); + const testing = + cmdOptions.test ?? + (await select({ + message: "Testing framework:", + choices: [ + { + name: "โšก Vitest (fast, ESM-native โ€” recommended)", + value: "vitest", + }, + { name: "๐Ÿƒ Jest (widely-used)", value: "jest" }, + { name: "โฌœ None", value: "none" }, + ], + })); - const auth = cmdOptions.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 auth = + cmdOptions.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 jwtStorage = cmdOptions.jwtStorage ?? ( - auth === 'jwt' - ? await select({ - message: 'JWT storage location:', + const jwtStorage = + cmdOptions.jwtStorage ?? + (auth === "jwt" + ? await select({ + message: "JWT storage location:", choices: [ - { name: '๐Ÿช Cookie (more secure against XSS)', value: 'cookie' }, - { name: '๐Ÿ“จ Header (Authorization: Bearer )', value: 'header' }, + { + name: "๐Ÿช Cookie (more secure against XSS)", + value: "cookie", + }, + { + name: "๐Ÿ“จ Header (Authorization: Bearer )", + value: "header", + }, ], }) : undefined); - const cache = cmdOptions.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 importAlias = cmdOptions.importAlias !== undefined ? cmdOptions.importAlias === 'true' : await confirm({ message: 'Configure import alias (@/)?', default: true }); + const cache = + cmdOptions.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 = cmdOptions.openapi !== undefined ? cmdOptions.openapi === 'true' : await confirm({ message: 'Add OpenAPI (Swagger) documentation?', default: true }); + const importAlias = + cmdOptions.importAlias !== undefined + ? cmdOptions.importAlias === "true" + : await confirm({ + message: "Configure import alias (@/)?", + default: true, + }); + + const openapi = + cmdOptions.openapi !== undefined + ? cmdOptions.openapi === "true" + : await confirm({ + message: "Add OpenAPI (Swagger) documentation?", + default: true, + }); const openapiUI = openapi - ? (cmdOptions.openapiUI !== undefined ? cmdOptions.openapiUI === 'true' : await confirm({ message: 'Add Swagger UI for documentation?', default: true })) + ? cmdOptions.openapiUI !== undefined + ? cmdOptions.openapiUI === "true" + : await confirm({ + message: "Add Swagger UI for documentation?", + default: true, + }) : false; - const docker = cmdOptions.docker !== undefined ? cmdOptions.docker === 'true' : await confirm({ message: 'Add Docker + docker-compose?', default: true }); - const installDeps = cmdOptions.install !== undefined ? cmdOptions.install === 'true' : await confirm({ message: 'Install dependencies now?', default: true }); + const docker = + cmdOptions.docker !== undefined + ? cmdOptions.docker === "true" + : await confirm({ + message: "Add Docker + docker-compose?", + default: true, + }); + const installDeps = + cmdOptions.install !== undefined + ? cmdOptions.install === "true" + : await confirm({ message: "Install dependencies now?", default: true }); const options: CliOptions = { projectName, diff --git a/packages/create-express-forge/src/types.ts b/packages/create-express-forge/src/types.ts index a461356..d2994e6 100644 --- a/packages/create-express-forge/src/types.ts +++ b/packages/create-express-forge/src/types.ts @@ -1,12 +1,12 @@ -export type Pattern = 'modular' | 'mvc'; -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 type JwtStorage = 'cookie' | 'header'; -export type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun'; +export type Pattern = "modular" | "mvc"; +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 type JwtStorage = "cookie" | "header"; +export type PackageManager = "npm" | "pnpm" | "yarn" | "bun"; export interface CliOptions { projectName: string; @@ -24,4 +24,5 @@ export interface CliOptions { openapiUI?: boolean; docker: boolean; installDeps: boolean; + skipPolish?: boolean; } diff --git a/packages/create-express-forge/src/utils/display.ts b/packages/create-express-forge/src/utils/display.ts index 0c9765c..bf9f981 100644 --- a/packages/create-express-forge/src/utils/display.ts +++ b/packages/create-express-forge/src/utils/display.ts @@ -1,50 +1,74 @@ -import chalk from 'chalk'; +import chalk from "chalk"; export function displayBanner(): void { console.log(); - console.log(chalk.bold.hex('#7C3AED')(' โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—')); console.log( - chalk.bold.hex('#7C3AED')(' โ•‘') + - chalk.bold.white(' โšก create-express-forge ') + - chalk.bold.hex('#7C3AED')('โ•‘'), + chalk.bold.hex("#7C3AED")(" โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—"), ); console.log( - chalk.bold.hex('#7C3AED')(' โ•‘') + - chalk.dim(' Production-ready backends. Fast. ') + - chalk.bold.hex('#7C3AED')('โ•‘'), + chalk.bold.hex("#7C3AED")(" โ•‘") + + chalk.bold.white(" โšก create-express-forge ") + + chalk.bold.hex("#7C3AED")("โ•‘"), + ); + console.log( + chalk.bold.hex("#7C3AED")(" โ•‘") + + chalk.dim(" Production-ready backends. Fast. ") + + chalk.bold.hex("#7C3AED")("โ•‘"), + ); + console.log( + chalk.bold.hex("#7C3AED")(" โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•"), ); - console.log(chalk.bold.hex('#7C3AED')(' โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•')); console.log(); } -export function displaySuccess(projectName: string, packageManager: string, installDeps: boolean): void { +export function displaySuccess( + projectName: string, + packageManager: string, + installDeps: boolean, +): void { const pm = packageManager; - const run = pm === 'npm' ? 'npm run' : pm; + const run = pm === "npm" ? "npm run" : pm; console.log(); - console.log(chalk.bold.green(' โœจ Success! Your project is ready at ') + chalk.cyan(`./${projectName}`)); - console.log(chalk.dim(' โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€')); + console.log( + chalk.bold.green(" โœจ Success! Your project is ready at ") + + chalk.cyan(`./${projectName}`), + ); + console.log( + chalk.dim( + " โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€", + ), + ); console.log(); - console.log(chalk.bold(' Next steps to get started:\n')); - + console.log(chalk.bold(" Next steps to get started:\n")); + let step = 1; console.log(chalk.white(` ${step++}. `) + chalk.cyan(`cd ${projectName}`)); - + if (!installDeps) { console.log(chalk.white(` ${step++}. `) + chalk.cyan(`${pm} install`)); } - - console.log(chalk.white(` ${step++}. `) + chalk.cyan('cp .env.example .env')); + + console.log( + chalk.white(` ${step++}. `) + chalk.cyan("cp .env.example .env"), + ); console.log(chalk.white(` ${step++}. `) + chalk.cyan(`${run} dev`)); - + console.log(); - console.log(chalk.bold(' Useful commands:\n')); - console.log(chalk.white(` โ€ข ${run} build `) + chalk.dim('โ€” Compile the project')); - console.log(chalk.white(` โ€ข ${run} test `) + chalk.dim('โ€” Run the test suite')); - console.log(chalk.white(` โ€ข ${run} lint `) + chalk.dim('โ€” Run linting checks')); - + console.log(chalk.bold(" Useful commands:\n")); + console.log( + chalk.white(` โ€ข ${run} build `) + + chalk.dim("โ€” Compile the project"), + ); + console.log( + chalk.white(` โ€ข ${run} test `) + chalk.dim("โ€” Run the test suite"), + ); + console.log( + chalk.white(` โ€ข ${run} lint `) + chalk.dim("โ€” Run linting checks"), + ); + console.log(); - console.log(chalk.bold.hex('#7C3AED')(' Happy building! ๐Ÿš€\n')); + console.log(chalk.bold.hex("#7C3AED")(" Happy building! ๐Ÿš€\n")); } export function displayError(message: string): void { diff --git a/packages/create-express-forge/src/utils/file.ts b/packages/create-express-forge/src/utils/file.ts index 1c33b71..43ecfa0 100644 --- a/packages/create-express-forge/src/utils/file.ts +++ b/packages/create-express-forge/src/utils/file.ts @@ -1,12 +1,15 @@ -import fs from 'fs-extra'; -import path from 'path'; +import path from "node:path"; +import fs from "fs-extra"; -export async function writeFile(filePath: string, content: string): Promise { +export async function writeFile( + filePath: string, + content: string, +): Promise { await fs.ensureDir(path.dirname(filePath)); - await fs.writeFile(filePath, content, 'utf-8'); + await fs.writeFile(filePath, content, "utf-8"); } export async function writeJson(filePath: string, data: object): Promise { await fs.ensureDir(path.dirname(filePath)); - await fs.writeFile(filePath, JSON.stringify(data, null, 2) + '\n', 'utf-8'); + await fs.writeFile(filePath, `${JSON.stringify(data, null, 2)}\n`, "utf-8"); } diff --git a/packages/create-express-forge/src/utils/package-builder.ts b/packages/create-express-forge/src/utils/package-builder.ts index b41e6ca..afb1946 100644 --- a/packages/create-express-forge/src/utils/package-builder.ts +++ b/packages/create-express-forge/src/utils/package-builder.ts @@ -1,129 +1,134 @@ -import type { CliOptions } from '../types.js'; +import type { PackageJson } from "pkg-types"; +import type { CliOptions } from "../types.js"; -export function buildPackageJson(opts: CliOptions): object { - const { projectName, orm, database, logger, testing, cache, auth, openapi } = opts; +export function buildPackageJson(opts: CliOptions): PackageJson { + const { projectName, orm, database, logger, testing, cache, auth, openapi } = + opts; const deps: Record = { - express: '^4.19.2', - cors: '^2.8.5', - helmet: '^7.1.0', - compression: '^1.7.4', - dotenv: '^16.4.5', - 'express-rate-limit': '^7.3.1', - zod: '^3.23.8', + express: "^5.2.1", + cors: "^2.8.5", + helmet: "^8.0.0", + compression: "^1.7.5", + dotenv: "^16.4.5", + "express-rate-limit": "^7.4.0", + zod: "^3.24.1", }; const devDeps: Record = { - typescript: '^5.5.3', - tsx: '^4.15.0', - tsup: '^8.1.0', - '@types/node': '^20.14.0', - '@types/express': '^4.17.21', - '@types/cors': '^2.8.17', - '@types/compression': '^1.7.5', + typescript: "^5.6.2", + tsx: "^4.19.1", + tsup: "^8.3.0", + "@types/node": "^22.7.5", + "@types/express": "^5.0.0", + "@types/cors": "^2.8.17", + "@types/compression": "^1.7.5", + "@biomejs/biome": "^1.9.4", }; - if (orm === 'prisma') { - deps['@prisma/client'] = '^6.0.0'; - devDeps['prisma'] = '^6.0.0'; - } else if (orm === 'sequelize') { - deps['sequelize'] = '^6.37.3'; - deps['sequelize-typescript'] = '^2.1.6'; - deps['reflect-metadata'] = '^0.2.2'; - devDeps['sequelize-cli'] = '^6.6.2'; - if (database === 'postgresql') deps['pg'] = '^8.12.0'; - else if (database === 'mysql') deps['mysql2'] = '^3.10.1'; - else if (database === 'sqlite') deps['sqlite3'] = '^5.1.7'; + if (orm === "prisma") { + deps["@prisma/client"] = "^6.2.1"; + devDeps.prisma = "^6.2.1"; + } else if (orm === "sequelize") { + deps.sequelize = "^6.37.3"; + deps["sequelize-typescript"] = "^2.1.6"; + deps["reflect-metadata"] = "^0.2.2"; + devDeps["sequelize-cli"] = "^6.6.2"; + if (database === "postgresql") deps.pg = "^8.13.0"; + else if (database === "mysql") deps.mysql2 = "^3.11.2"; + else if (database === "sqlite") deps.sqlite3 = "^5.1.7"; } - if (logger === 'winston') { - deps['winston'] = '^3.13.0'; - deps['winston-daily-rotate-file'] = '^5.0.0'; - } else if (logger === 'pino') { - deps['pino'] = '^9.3.1'; - deps['pino-http'] = '^10.2.0'; - devDeps['pino-pretty'] = '^11.2.1'; + if (logger === "winston") { + deps.winston = "^3.19.0"; + deps["winston-daily-rotate-file"] = "^5.0.0"; + } else if (logger === "pino") { + deps.pino = "^10.3.1"; + deps["pino-http"] = "^10.3.0"; + devDeps["pino-pretty"] = "^11.2.2"; } - if (testing === 'vitest') { - devDeps['vitest'] = '^1.6.0'; - devDeps['@vitest/coverage-v8'] = '^1.6.0'; - devDeps['supertest'] = '^7.0.0'; - devDeps['@types/supertest'] = '^6.0.2'; - } else if (testing === 'jest') { - devDeps['jest'] = '^29.7.0'; - devDeps['ts-jest'] = '^29.2.2'; - devDeps['@types/jest'] = '^29.5.12'; - devDeps['supertest'] = '^7.0.0'; - devDeps['@types/supertest'] = '^6.0.2'; + if (testing === "vitest") { + devDeps.vitest = "^4.1.5"; + devDeps["@vitest/coverage-v8"] = "^4.1.5"; + devDeps.supertest = "^7.0.0"; + devDeps["@types/supertest"] = "^6.0.2"; + } else if (testing === "jest") { + devDeps.jest = "^29.7.0"; + devDeps["ts-jest"] = "^29.2.5"; + devDeps["@types/jest"] = "^29.5.13"; + devDeps.supertest = "^7.0.0"; + 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 (cache === "redis") { + deps.redis = "^4.7.0"; + } else if (cache === "node-cache") { + deps["node-cache"] = "^5.1.2"; } - if (auth === 'jwt' || auth === 'session') { - deps['bcrypt'] = '^5.1.1'; - devDeps['@types/bcrypt'] = '^5.0.2'; + if (auth === "jwt" || auth === "session") { + deps.bcrypt = "^5.1.1"; + devDeps["@types/bcrypt"] = "^5.0.2"; } - if (auth === 'jwt') { - deps['jsonwebtoken'] = '^9.0.2'; - devDeps['@types/jsonwebtoken'] = '^9.0.6'; - if (opts.jwtStorage === 'cookie') { - deps['cookie-parser'] = '^1.4.6'; - devDeps['@types/cookie-parser'] = '^1.4.7'; + if (auth === "jwt") { + deps.jsonwebtoken = "^9.0.2"; + devDeps["@types/jsonwebtoken"] = "^9.0.7"; + if (opts.jwtStorage === "cookie") { + deps["cookie-parser"] = "^1.4.6"; + devDeps["@types/cookie-parser"] = "^1.4.7"; } - } else if (auth === 'session') { - deps['express-session'] = '^1.18.0'; - deps['cookie-parser'] = '^1.4.6'; - devDeps['@types/express-session'] = '^1.18.0'; - devDeps['@types/cookie-parser'] = '^1.4.7'; + } else if (auth === "session") { + deps["express-session"] = "^1.18.0"; + deps["cookie-parser"] = "^1.4.6"; + devDeps["@types/express-session"] = "^1.18.0"; + devDeps["@types/cookie-parser"] = "^1.4.7"; } 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'; + 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', - start: 'node dist/server.js', - 'check-types': 'tsc --noEmit', - lint: 'tsc --noEmit', + dev: "tsx watch src/server.ts", + build: "tsup src/server.ts --format esm", + start: "node dist/server.js", + "check-types": "tsc --noEmit", + lint: "biome lint .", + format: "biome format --write .", }; - if (orm === 'prisma') { - scripts['db:generate'] = 'prisma generate'; - scripts['db:migrate'] = 'prisma migrate dev'; - scripts['db:push'] = 'prisma db push'; - scripts['db:studio'] = 'prisma studio'; - scripts['db:seed'] = 'tsx prisma/seed.ts'; + if (orm === "prisma") { + scripts["db:generate"] = "prisma generate"; + scripts["db:migrate"] = "prisma migrate dev"; + scripts["db:push"] = "prisma db push"; + scripts["db:studio"] = "prisma studio"; + scripts["db:seed"] = "tsx prisma/seed.ts"; + scripts.postinstall = "prisma generate"; } - if (testing === 'vitest') { - scripts['test'] = 'vitest run'; - scripts['test:watch'] = 'vitest'; - scripts['test:coverage'] = 'vitest run --coverage'; - } else if (testing === 'jest') { - scripts['test'] = 'jest'; - scripts['test:watch'] = 'jest --watch'; - scripts['test:coverage'] = 'jest --coverage'; + if (testing === "vitest") { + scripts.test = "vitest run"; + scripts["test:watch"] = "vitest"; + scripts["test:coverage"] = "vitest run --coverage"; + } else if (testing === "jest") { + scripts.test = "jest"; + scripts["test:watch"] = "jest --watch"; + scripts["test:coverage"] = "jest --coverage"; } return { name: projectName, - version: '0.1.0', - description: '', - type: 'module', + version: "0.1.0", + description: "", + type: "module", scripts, dependencies: deps, devDependencies: devDeps, - engines: { node: '>=18.0.0' }, + engines: { node: ">=18.0.0" }, }; } diff --git a/packages/create-express-forge/src/utils/template-manager.ts b/packages/create-express-forge/src/utils/template-manager.ts index 0d50df1..ab055b1 100644 --- a/packages/create-express-forge/src/utils/template-manager.ts +++ b/packages/create-express-forge/src/utils/template-manager.ts @@ -1,238 +1,61 @@ -import type { CliOptions } from '../types.js'; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { Eta } from "eta"; +import fs from "fs-extra"; +import type { CliOptions } from "../types.js"; -/** - * Advanced Template Manager to handle code generation with consistency. - * This avoids giant template strings and allows for more structured logic. - */ -export class TemplateManager { - constructor(private opts: CliOptions) {} - - renderTodoService(depth = 2): string { - const rel = '../'.repeat(depth); - const { orm } = this.opts; - - if (orm === 'prisma') { - return `import { prisma } from '${rel}config/database.js'; -import { ApiError } from '${rel}utils/ApiError.js'; +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); -export const TodosService = { - findAll: async (userId: string) => { - return prisma.todo.findMany({ where: { userId } }); - }, +// In development (src/utils/...), templates are at ../../templates +// In production (dist/...), templates are at ./templates (copied by tsup) +const devPath = path.resolve(__dirname, "../../templates"); +const prodPath = path.resolve(__dirname, "./templates"); - findById: async (id: string, userId: string) => { - const todo = await prisma.todo.findFirst({ where: { id, userId } }); - if (!todo) throw ApiError.notFound('Todo not found'); - return todo; - }, +const TEMPLATE_DIR = fs.existsSync(devPath) ? devPath : prodPath; - create: async (userId: string, data: { title: string; description?: string }) => { - return prisma.todo.create({ - data: { ...data, userId } - }); - }, +export class TemplateManager { + private eta: Eta; - update: async (id: string, userId: string, data: any) => { - const todo = await prisma.todo.findFirst({ where: { id, userId } }); - if (!todo) throw ApiError.notFound('Todo not found'); - return prisma.todo.update({ - where: { id }, - data + constructor(private opts: CliOptions) { + this.eta = new Eta({ + views: TEMPLATE_DIR, + cache: false, + autoEscape: false, }); - }, - - remove: async (id: string, userId: string) => { - const todo = await prisma.todo.findFirst({ where: { id, userId } }); - if (!todo) throw ApiError.notFound('Todo not found'); - return prisma.todo.delete({ where: { id } }); - }, -}; -`; - } - - if (orm === 'sequelize') { - return `import { Todo } from '${rel}models/Todo.model.js'; -import { ApiError } from '${rel}utils/ApiError.js'; - -export const TodosService = { - findAll: async (userId: string) => { - return Todo.findAll({ where: { userId } }); - }, - - findById: async (id: string, userId: string) => { - const todo = await Todo.findOne({ where: { id, userId } }); - if (!todo) throw ApiError.notFound('Todo not found'); - return todo; - }, - - create: async (userId: string, data: any) => { - return Todo.create({ ...data, userId }); - }, - - update: async (id: string, userId: string, data: any) => { - const todo = await Todo.findOne({ where: { id, userId } }); - if (!todo) throw ApiError.notFound('Todo not found'); - return todo.update(data); - }, - - remove: async (id: string, userId: string) => { - const todo = await Todo.findOne({ where: { id, userId } }); - if (!todo) throw ApiError.notFound('Todo not found'); - return todo.destroy(); - }, -}; -`; - } - - return `import { ApiError } from '${rel}utils/ApiError.js'; - -export interface Todo { id: string; title: string; description?: string; completed: boolean; userId: string; } - -const store: Todo[] = []; - -export const TodosService = { - findAll: async (userId: string) => store.filter(t => t.userId === userId), - findById: async (id: string, userId: string) => { - const todo = store.find((t) => t.id === id && t.userId === userId); - if (!todo) throw ApiError.notFound('Todo not found'); - return todo; - }, - create: async (userId: string, data: any) => { - const todo = { id: Math.random().toString(36).substring(7), ...data, userId, completed: false }; - store.push(todo); - return todo; - }, - update: async (id: string, userId: string, data: any) => { - const todo = store.find((t) => t.id === id && t.userId === userId); - if (!todo) throw ApiError.notFound('Todo not found'); - Object.assign(todo, data); - return todo; - }, - remove: async (id: string, userId: string) => { - const idx = store.findIndex((t) => t.id === id && t.userId === userId); - if (idx === -1) throw ApiError.notFound('Todo not found'); - store.splice(idx, 1); - }, -}; -`; } - renderAuthController(depth = 2): string { - const rel = '../'.repeat(depth); - const { orm, jwtStorage } = this.opts; - - return `import jwt from 'jsonwebtoken'; -import bcrypt from 'bcrypt'; -import { asyncHandler } from '${rel}utils/asyncHandler.js'; -import { ApiResponse } from '${rel}utils/ApiResponse.js'; -import { ApiError } from '${rel}utils/ApiError.js'; -import { env } from '${rel}config/env.js'; -${orm === 'prisma' ? `import { prisma } from '${rel}config/database.js';` : ''} -${orm === 'sequelize' ? `import { User } from '${rel}models/User.model.js';` : ''} - -export const register = asyncHandler(async (req, res) => { - const { email, password, name } = req.body; - - const hashedPassword = await bcrypt.hash(password, 10); - - ${ - orm === 'prisma' - ? `const existingUser = await prisma.user.findUnique({ where: { email } }); - if (existingUser) throw ApiError.conflict('User already exists'); - - const user = await prisma.user.create({ - data: { email, name, password: hashedPassword } - });` - : orm === 'sequelize' - ? `const existingUser = await User.findOne({ where: { email } }); - if (existingUser) throw ApiError.conflict('User already exists'); - - const user = await User.create({ email, name, password: hashedPassword });` - : `// Demo logic for 'none' or in-memory - const user = { id: Math.random().toString(36).substring(7), email, name };` - } - - return ApiResponse.created(res, { id: user.id, email: user.email }, 'User registered successfully'); -}); - -export const login = asyncHandler(async (req, res) => { - const { email, password } = req.body; - - ${ - orm === 'prisma' - ? `const user = await prisma.user.findUnique({ where: { email } }); - if (!user || !(await bcrypt.compare(password, user.password))) { - throw ApiError.unauthorized('Invalid email or password'); - }` - : orm === 'sequelize' - ? `const user = await User.findOne({ where: { email } }); - if (!user || !(await bcrypt.compare(password, user.password))) { - throw ApiError.unauthorized('Invalid email or password'); - }` - : `// Demo logic: accept any valid email/password - const user = { id: 'demo-user-id', email, role: 'USER' };` - } - - const token = jwt.sign( - { id: user.id, email: user.email, role: (user as any).role || 'USER' }, - env.JWT_SECRET, - { expiresIn: env.JWT_EXPIRES_IN as any } - ); - - ${ - jwtStorage === 'cookie' - ? `res.cookie('token', token, { - httpOnly: true, - secure: env.NODE_ENV === 'production', - sameSite: 'lax', - maxAge: 24 * 60 * 60 * 1000 // 1 day - });` - : '' - } - - return ApiResponse.success(res, { - user: { id: user.id, email: user.email }, - ${jwtStorage === 'header' ? 'token' : ''} - }, 'Logged in successfully'); -}); - -export const logout = asyncHandler(async (_req, res) => { - ${jwtStorage === 'cookie' ? "res.clearCookie('token');" : ''} - return ApiResponse.success(res, null, 'Logged out successfully'); -}); -`; - } - - renderAuthMiddleware(depth = 1): string { - const rel = '../'.repeat(depth); - const { jwtStorage } = this.opts; - - return `import jwt from 'jsonwebtoken'; -import { asyncHandler } from '${rel}utils/asyncHandler.js'; -import { ApiError } from '${rel}utils/ApiError.js'; -import { env } from '${rel}config/env.js'; - -export const auth = asyncHandler(async (req, _res, next) => { - let token: string | undefined; - - ${ - jwtStorage === 'cookie' - ? "token = req.cookies?.token;" - : "if (req.headers.authorization?.startsWith('Bearer ')) { token = req.headers.authorization.split(' ')[1]; }" - } - - if (!token) { - throw ApiError.unauthorized('Authentication required'); + /** + * Render an Eta template string or file to a destination. + * If sourcePath is provided, it reads from templates/. + */ + async renderTemplateFile( + templatePath: string, + destPath: string, + ): Promise { + const rendered = await this.eta.renderAsync(templatePath, this.opts); + await fs.ensureDir(path.dirname(destPath)); + await fs.writeFile(destPath, rendered, "utf-8"); + } + + /** + * Render a raw Eta template string directly. + */ + async renderString( + templateString: string, + additionalData: Record = {}, + ): Promise { + return this.eta.renderStringAsync(templateString, { + ...this.opts, + ...additionalData, + }); } - try { - const decoded = jwt.verify(token, env.JWT_SECRET) as any; - req.user = decoded; - next(); - } catch (err) { - throw ApiError.unauthorized('Invalid or expired token'); - } -}); -`; + /** + * Bulk copy an entire directory without templating (for static assets) + */ + async copyStaticDir(sourceDir: string, destDir: string): Promise { + const fullSource = path.join(TEMPLATE_DIR, sourceDir); + await fs.copy(fullSource, destDir, { overwrite: true }); } } diff --git a/packages/create-express-forge/templates/base/.env.example.eta b/packages/create-express-forge/templates/base/.env.example.eta new file mode 100644 index 0000000..952c877 --- /dev/null +++ b/packages/create-express-forge/templates/base/.env.example.eta @@ -0,0 +1,33 @@ +# Application +NODE_ENV=development +PORT=3000 + +# CORS +CORS_ORIGIN=http://localhost:3000 + +# Rate limiter +RATE_LIMIT_WINDOW_MS=900000 +RATE_LIMIT_MAX=100 + +<% if (it.orm !== 'none' && it.database !== 'none') { %> +# Database +DATABASE_URL="<% + if (it.database === 'mysql') { %>mysql://user:password@localhost:3306/mydb<% } + else if (it.database === 'sqlite') { %>file:./dev.db<% } + else { %>postgresql://user:password@localhost:5432/mydb<% } +%>" +<% } %> + +<% if (it.auth === 'jwt') { %> +# JWT Auth +JWT_SECRET=your_super_secret_jwt_key_change_me +JWT_EXPIRES_IN=1d +<% } else if (it.auth === 'session') { %> +# Session Auth +SESSION_SECRET=your_session_secret_key_change_me +<% } %> + +<% if (it.cache === 'redis') { %> +# Redis Cache +REDIS_URL="redis://localhost:6379" +<% } %> diff --git a/packages/create-express-forge/templates/base/.gitignore.eta b/packages/create-express-forge/templates/base/.gitignore.eta new file mode 100644 index 0000000..9738812 --- /dev/null +++ b/packages/create-express-forge/templates/base/.gitignore.eta @@ -0,0 +1,11 @@ +node_modules/ +dist/ +.env +*.log +logs/ +coverage/ +.DS_Store +<% if (it.orm === 'prisma') { %> +prisma/migrations/ +*.db +<% } %> diff --git a/packages/create-express-forge/templates/base/README.md.eta b/packages/create-express-forge/templates/base/README.md.eta new file mode 100644 index 0000000..504ca2b --- /dev/null +++ b/packages/create-express-forge/templates/base/README.md.eta @@ -0,0 +1,23 @@ +# <%= it.projectName %> + +> Scaffolded with [create-express-forge](https://github.com/CODE-Y02/create-express-forge) + +## Stack + +- TypeScript + Express.js +- Zod validation +<% if (it.orm === 'prisma') { %>- Prisma ORM + <%= it.database %><% } %> +<% if (it.orm === 'sequelize') { %>- Sequelize ORM + <%= it.database %><% } %> +<% if (it.logger !== 'none') { %>- <%= it.logger %> logger<% } %> +<% if (it.testing !== 'none') { %>- <%= it.testing %> tests<% } %> +<% if (it.docker) { %>- Docker + docker-compose<% } %> + +## Quick Start + +```bash +cp .env.example .env +<% if (it.orm === 'prisma') { %> +<%= it.packageManager === 'npm' ? 'npm run' : it.packageManager %> db:migrate +<% } %> +<%= it.packageManager %> run dev +``` diff --git a/packages/create-express-forge/templates/base/src/app.ts.eta b/packages/create-express-forge/templates/base/src/app.ts.eta new file mode 100644 index 0000000..d20b69d --- /dev/null +++ b/packages/create-express-forge/templates/base/src/app.ts.eta @@ -0,0 +1,66 @@ +import express from 'express'; +import cors from 'cors'; +import helmet from 'helmet'; +import compression from 'compression'; +<% if (it.jwtStorage === 'cookie' || it.auth === 'session') { %> +import cookieParser from 'cookie-parser'; +<% } %> +<% if (it.auth === 'session') { %> +import session from 'express-session'; +<% } %> +import { env } from './config/env.js'; +import { rateLimiter } from './middleware/rateLimiter.js'; +<% if (it.logger === 'pino') { %> +import { httpLogger } from './middleware/httpLogger.js'; +<% } %> +import { notFound } from './middleware/notFound.js'; +import { errorHandler } from './middleware/errorHandler.js'; +<% if (it.openapi) { %> +import { setupSwagger } from './docs/swagger.js'; +<% } %> +<% if (it.pattern === 'modular') { %> +import { healthRouter } from './modules/health/health.routes.js'; +import { todosRouter } from './modules/todos/todos.routes.js'; +<% if (it.auth !== 'none') { %> +import { authRouter } from './modules/auth/auth.routes.js'; +<% } %> +<% } else { %> +import { router } from './routes/index.js'; +<% } %> + +const app = express(); + +app.use(helmet()); +app.use(cors({ origin: env.CORS_ORIGIN, credentials: true })); +<% if (it.jwtStorage === 'cookie' || it.auth === 'session') { %> +app.use(cookieParser()); +<% } %> +<% if (it.auth === 'session') { %> +app.use(session({ secret: env.SESSION_SECRET, resave: false, saveUninitialized: false })); +<% } %> +app.use(express.json({ limit: '10mb' })); +app.use(express.urlencoded({ extended: true })); +app.use(compression()); +app.use(rateLimiter); +<% if (it.logger === 'pino') { %> +app.use(httpLogger); +<% } %> + +<% if (it.openapi) { %> +setupSwagger(app); +<% } %> + +<% if (it.pattern === 'modular') { %> +app.use('/api/health', healthRouter); +app.use('/api/v1/todos', todosRouter); +<% if (it.auth !== 'none') { %> +app.use('/api/v1/auth', authRouter); +<% } %> +<% } else { %> +app.use('/api/v1', router); +<% } %> + +app.use(notFound); +app.use(errorHandler); + +export { app }; diff --git a/packages/create-express-forge/templates/base/src/config/env.ts.eta b/packages/create-express-forge/templates/base/src/config/env.ts.eta new file mode 100644 index 0000000..66d0c06 --- /dev/null +++ b/packages/create-express-forge/templates/base/src/config/env.ts.eta @@ -0,0 +1,35 @@ +import { z } from 'zod'; + +const envSchema = z.object({ + NODE_ENV: z.enum(['development', 'production', 'test']).default('development'), + PORT: z.coerce.number().default(3000), + CORS_ORIGIN: z.string().default('*'), + RATE_LIMIT_WINDOW_MS: z.coerce.number().default(900_000), + RATE_LIMIT_MAX: z.coerce.number().default(100), +<% if (it.orm !== 'none' && it.database !== 'none') { %> + DATABASE_URL: z.string().url('DATABASE_URL must be a valid URL'), +<% } else { %> + // DATABASE_URL: z.string(), +<% } %> +<% if (it.auth === 'jwt') { %> + JWT_SECRET: z.string().min(1, 'JWT_SECRET is required'), + JWT_EXPIRES_IN: z.string().default('1d'), +<% } %> +<% if (it.auth === 'session') { %> + SESSION_SECRET: z.string().min(1, 'SESSION_SECRET is required'), +<% } %> +<% if (it.cache === 'redis') { %> + REDIS_URL: z.string().url().default('redis://localhost:6379'), +<% } %> +}); + +const parsed = envSchema.safeParse(process.env); + +if (!parsed.success) { + console.error('\nโŒ Invalid environment variables:'); + console.error(JSON.stringify(parsed.error.format(), null, 2)); + process.exit(1); +} + +export const env = parsed.data; +export type Env = typeof env; diff --git a/packages/create-express-forge/templates/base/src/middleware/errorHandler.ts.eta b/packages/create-express-forge/templates/base/src/middleware/errorHandler.ts.eta new file mode 100644 index 0000000..fb287ff --- /dev/null +++ b/packages/create-express-forge/templates/base/src/middleware/errorHandler.ts.eta @@ -0,0 +1,38 @@ +import type { Request, Response, NextFunction } from 'express'; +import { ZodError } from 'zod'; +import { ApiError } from '../utils/ApiError.js'; +import { env } from '../config/env.js'; + +/** + * Global centralized error handler โ€” must be the LAST middleware registered. + * Handles: ApiError, ZodError, and unknown errors. + */ +export const errorHandler = (err: Error, _req: Request, res: Response, _next: NextFunction): void => { + const isDev = env.NODE_ENV === 'development'; + + if (err instanceof ApiError) { + res.status(err.statusCode).json({ + success: false, + message: err.message, + ...(err.errors.length > 0 && { errors: err.errors }), + ...(isDev && { stack: err.stack }), + }); + return; + } + + if (err instanceof ZodError) { + res.status(400).json({ + success: false, + message: 'Validation failed', + errors: err.errors.map((e) => ({ path: e.path.join('.'), message: e.message })), + }); + return; + } + + console.error('[UnhandledError]', err); + res.status(500).json({ + success: false, + message: 'Internal Server Error', + ...(isDev && { stack: err.stack }), + }); +}; diff --git a/packages/create-express-forge/templates/base/src/middleware/notFound.ts.eta b/packages/create-express-forge/templates/base/src/middleware/notFound.ts.eta new file mode 100644 index 0000000..a0f2269 --- /dev/null +++ b/packages/create-express-forge/templates/base/src/middleware/notFound.ts.eta @@ -0,0 +1,6 @@ +import type { Request, Response, NextFunction } from 'express'; +import { ApiError } from '../utils/ApiError.js'; + +export const notFound = (req: Request, _res: Response, next: NextFunction): void => { + next(ApiError.notFound(`Route ${req.method} ${req.url} not found`)); +}; diff --git a/packages/create-express-forge/templates/base/src/middleware/rateLimiter.ts.eta b/packages/create-express-forge/templates/base/src/middleware/rateLimiter.ts.eta new file mode 100644 index 0000000..0fc703e --- /dev/null +++ b/packages/create-express-forge/templates/base/src/middleware/rateLimiter.ts.eta @@ -0,0 +1,10 @@ +import rateLimit from 'express-rate-limit'; +import { env } from '../config/env.js'; + +export const rateLimiter = rateLimit({ + windowMs: env.RATE_LIMIT_WINDOW_MS, + max: env.RATE_LIMIT_MAX, + standardHeaders: true, + legacyHeaders: false, + message: { success: false, message: 'Too many requests, please try again later.' }, +}); diff --git a/packages/create-express-forge/templates/base/src/middleware/validate.ts.eta b/packages/create-express-forge/templates/base/src/middleware/validate.ts.eta new file mode 100644 index 0000000..f6210ab --- /dev/null +++ b/packages/create-express-forge/templates/base/src/middleware/validate.ts.eta @@ -0,0 +1,18 @@ +import type { Request, Response, NextFunction } from 'express'; +import { type AnyZodObject, ZodError } from 'zod'; +import { ApiError } from '../utils/ApiError.js'; + +/** Validates req.body / req.query / req.params against a Zod schema */ +export const validate = (schema: AnyZodObject) => + async (req: Request, _res: Response, next: NextFunction): Promise => { + try { + const validated = await schema.parseAsync({ body: req.body, query: req.query, params: req.params }); + req.body = validated.body; + req.query = validated.query; + req.params = validated.params; + next(); + } catch (err) { + if (err instanceof ZodError) next(ApiError.badRequest('Validation failed', err.errors)); + else next(err); + } + }; diff --git a/packages/create-express-forge/templates/base/src/server.ts.eta b/packages/create-express-forge/templates/base/src/server.ts.eta new file mode 100644 index 0000000..3676d10 --- /dev/null +++ b/packages/create-express-forge/templates/base/src/server.ts.eta @@ -0,0 +1,58 @@ +import 'dotenv/config'; +<% if (it.logger !== 'none') { %> +import { logger } from './logger/index.js'; +<% } %> +import { app } from './app.js'; +import { env } from './config/env.js'; + +<% if (it.orm === 'prisma') { %> +import { prisma } from './config/database.js'; +try { + await prisma.$connect(); + <% if (it.logger !== 'none') { %>logger.info('โœ… Database connected');<% } %> +} catch (err) { + <% if (it.logger !== 'none') { %>logger.error('โŒ Database connection failed', err);<% } %> + process.exit(1); +} +<% } %> +<% if (it.orm === 'sequelize') { %> +import { connectDB } from './config/database.js'; +await connectDB(); +<% } %> + +const server = app.listen(env.PORT, () => { + <% if (it.logger !== 'none') { %> + logger.info(`๐Ÿš€ Server running on port ${env.PORT} in ${env.NODE_ENV} mode`); + <% } else { %> + console.log(`๐Ÿš€ Server running on port ${env.PORT} in ${env.NODE_ENV} mode`); + <% } %> +}); + +const shutdown = async (signal: string) => { + <% if (it.logger !== 'none') { %> + logger.info(`${signal} received โ€” shutting down gracefully`); + <% } else { %> + console.log(`${signal} received โ€” shutting down gracefully`); + <% } %> + + server.close(async () => { + <% if (it.orm === 'prisma') { %> + await prisma.$disconnect(); + <% } %> + process.exit(0); + }); + + // Force shutdown after 10s + setTimeout(() => process.exit(1), 10000); +}; + +process.on('SIGTERM', () => shutdown('SIGTERM')); +process.on('SIGINT', () => shutdown('SIGINT')); +process.on('uncaughtException', (err) => { + <% if (it.logger !== 'none') { %>logger.error('Uncaught Exception', err);<% } else { %>console.error('Uncaught Exception', err);<% } %> + process.exit(1); +}); +process.on('unhandledRejection', (reason) => { + <% if (it.logger !== 'none') { %>logger.error('Unhandled Rejection', reason);<% } else { %>console.error('Unhandled Rejection', reason);<% } %> + process.exit(1); +}); diff --git a/packages/create-express-forge/templates/base/src/types/express.d.ts.eta b/packages/create-express-forge/templates/base/src/types/express.d.ts.eta new file mode 100644 index 0000000..f79c3d9 --- /dev/null +++ b/packages/create-express-forge/templates/base/src/types/express.d.ts.eta @@ -0,0 +1,7 @@ +import 'express'; + +declare module 'express' { + interface Request { + user?: { id: string; email: string; role: string }; + } +} diff --git a/packages/create-express-forge/templates/base/src/utils/ApiError.ts.eta b/packages/create-express-forge/templates/base/src/utils/ApiError.ts.eta new file mode 100644 index 0000000..8942286 --- /dev/null +++ b/packages/create-express-forge/templates/base/src/utils/ApiError.ts.eta @@ -0,0 +1,21 @@ +export class ApiError extends Error { + public readonly statusCode: number; + public readonly errors: unknown[]; + public readonly isOperational: boolean; + + constructor(statusCode: number, message: string, errors: unknown[] = [], isOperational = true) { + super(message); + this.statusCode = statusCode; + this.errors = errors; + this.isOperational = isOperational; + Object.setPrototypeOf(this, ApiError.prototype); + Error.captureStackTrace(this, this.constructor); + } + + static badRequest(message = 'Bad Request', errors: unknown[] = []) { return new ApiError(400, message, errors); } + static unauthorized(message = 'Unauthorized') { return new ApiError(401, message); } + static forbidden(message = 'Forbidden') { return new ApiError(403, message); } + static notFound(message = 'Not Found') { return new ApiError(404, message); } + static conflict(message = 'Conflict') { return new ApiError(409, message); } + static internal(message = 'Internal Server Error') { return new ApiError(500, message, [], false); } +} diff --git a/packages/create-express-forge/templates/base/src/utils/ApiResponse.ts.eta b/packages/create-express-forge/templates/base/src/utils/ApiResponse.ts.eta new file mode 100644 index 0000000..437e027 --- /dev/null +++ b/packages/create-express-forge/templates/base/src/utils/ApiResponse.ts.eta @@ -0,0 +1,14 @@ +import type { Response } from 'express'; + +export class ApiResponse { + static success(res: Response, data: T, message = 'Success', statusCode = 200) { + return res.status(statusCode).json({ success: true, message, data }); + } + static created(res: Response, data: T, message = 'Created') { + return ApiResponse.success(res, data, message, 201); + } + static noContent(res: Response) { return res.status(204).send(); } + static paginated(res: Response, data: T[], pagination: { total: number; page: number; limit: number; pages: number }, message = 'Success') { + return res.status(200).json({ success: true, message, data, pagination }); + } +} diff --git a/packages/create-express-forge/templates/base/tsconfig.json.eta b/packages/create-express-forge/templates/base/tsconfig.json.eta new file mode 100644 index 0000000..9169df9 --- /dev/null +++ b/packages/create-express-forge/templates/base/tsconfig.json.eta @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "declaration": false, + "sourceMap": true<% if (it.importAlias) { %>, + "baseUrl": ".", + "paths": { "@/*": ["src/*"] }<% } %><% if (it.orm === 'sequelize') { %>, + "experimentalDecorators": true, + "emitDecoratorMetadata": true<% } %> + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "**/*.test.ts"] +} diff --git a/packages/create-express-forge/templates/features/auth/jwt-auth.middleware.ts.eta b/packages/create-express-forge/templates/features/auth/jwt-auth.middleware.ts.eta new file mode 100644 index 0000000..5786854 --- /dev/null +++ b/packages/create-express-forge/templates/features/auth/jwt-auth.middleware.ts.eta @@ -0,0 +1,29 @@ +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) => { + let token: string | undefined; + +<% if (it.jwtStorage === 'cookie') { %> + token = req.cookies?.token; +<% } else { %> + const authHeader = req.headers.authorization; + if (authHeader?.startsWith('Bearer ')) { + token = authHeader.split(' ')[1]; + } +<% } %> + + if (!token) { + return next(ApiError.unauthorized('No token provided')); + } + + try { + const decoded = jwt.verify(token, env.JWT_SECRET); + req.user = decoded as any; + next(); + } catch (err) { + next(ApiError.unauthorized('Invalid or expired token')); + } +}; diff --git a/packages/create-express-forge/templates/features/auth/session-auth.middleware.ts.eta b/packages/create-express-forge/templates/features/auth/session-auth.middleware.ts.eta new file mode 100644 index 0000000..bc73f73 --- /dev/null +++ b/packages/create-express-forge/templates/features/auth/session-auth.middleware.ts.eta @@ -0,0 +1,9 @@ +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/templates/features/cache/node-cache.ts.eta b/packages/create-express-forge/templates/features/cache/node-cache.ts.eta new file mode 100644 index 0000000..4c3628d --- /dev/null +++ b/packages/create-express-forge/templates/features/cache/node-cache.ts.eta @@ -0,0 +1,3 @@ +import NodeCache from 'node-cache'; + +export const cache = new NodeCache({ stdTTL: 600, checkperiod: 120 }); diff --git a/packages/create-express-forge/templates/features/cache/redis.ts.eta b/packages/create-express-forge/templates/features/cache/redis.ts.eta new file mode 100644 index 0000000..6895f6c --- /dev/null +++ b/packages/create-express-forge/templates/features/cache/redis.ts.eta @@ -0,0 +1,12 @@ +import { createClient } from 'redis'; +import { env } from '../config/env.js'; +<% if (it.logger !== 'none') { %>import { logger } from '../logger/index.js';<% } %> + +export const redisClient = createClient({ url: env.REDIS_URL }); + +redisClient.on('error', (err) => <% if (it.logger !== 'none') { %>logger.error('Redis Client Error', err)<% } else { %>console.error('Redis Client Error', err)<% } %>); +redisClient.on('connect', () => <% if (it.logger !== 'none') { %>logger.info('โœ… Redis connected')<% } else { %>console.log('โœ… Redis connected')<% } %>); + +export async function connectRedis() { + await redisClient.connect(); +} diff --git a/packages/create-express-forge/templates/features/docker/Dockerfile.eta b/packages/create-express-forge/templates/features/docker/Dockerfile.eta new file mode 100644 index 0000000..1678cc3 --- /dev/null +++ b/packages/create-express-forge/templates/features/docker/Dockerfile.eta @@ -0,0 +1,43 @@ +# Stage 1: Build +FROM node:20-alpine AS builder +WORKDIR /app + +# Copy dependency files +COPY package*.json ./ +<% if (it.orm === 'prisma') { %>COPY prisma ./prisma/ <% } %> + +# Install all dependencies (including devDependencies) +RUN <%= it.packageManager %> install + +# Copy source and build +COPY . . +RUN <%= it.packageManager %> run build + +<% if (it.orm === 'prisma') { %> +# Generate Prisma Client +RUN npx prisma generate +<% } %> + +# Prune devDependencies to keep the image small +# Note: This keeps the generated Prisma client in node_modules +RUN npm prune --production + +# Stage 2: Run +FROM node:20-alpine AS runner +WORKDIR /app + +# Set production environment +ENV NODE_ENV=production + +# Copy built assets and pruned node_modules +COPY --from=builder /app/package*.json ./ +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/dist ./dist +<% if (it.orm === 'prisma') { %>COPY --from=builder /app/prisma ./prisma<% } %> + +EXPOSE 3000 + +# Using a non-root user for security +USER node + +CMD ["node", "dist/server.js"] diff --git a/packages/create-express-forge/templates/features/docker/docker-compose.yml.eta b/packages/create-express-forge/templates/features/docker/docker-compose.yml.eta new file mode 100644 index 0000000..21adf5a --- /dev/null +++ b/packages/create-express-forge/templates/features/docker/docker-compose.yml.eta @@ -0,0 +1,56 @@ +version: '3.8' +services: + api: + build: . + ports: + - "3000:3000" + environment: + - NODE_ENV=production + - PORT=3000 +<% if (it.database === 'postgresql') { %> + - DATABASE_URL=postgresql://user:password@db:5432/mydb +<% } else if (it.database === 'mysql') { %> + - DATABASE_URL=mysql://user:password@db:3306/mydb +<% } %> +<% if (it.cache === 'redis') { %> + - REDIS_URL=redis://redis:6379 +<% } %> + depends_on: +<% if (it.database !== 'sqlite' && it.database !== 'none') { %> - db<% } %> +<% if (it.cache === 'redis') { %> - redis<% } %> + +<% if (it.database === 'postgresql') { %> + db: + image: postgres:15-alpine + environment: + POSTGRES_USER: user + POSTGRES_PASSWORD: password + POSTGRES_DB: mydb + ports: + - "5432:5432" + volumes: + - pgdata:/var/lib/postgresql/data +<% } else if (it.database === 'mysql') { %> + db: + image: mysql:8.0 + environment: + MYSQL_USER: user + MYSQL_PASSWORD: password + MYSQL_DATABASE: mydb + MYSQL_ROOT_PASSWORD: root_password + ports: + - "3306:3306" + volumes: + - mysqldata:/var/lib/mysql +<% } %> + +<% if (it.cache === 'redis') { %> + redis: + image: redis:7-alpine + ports: + - "6379:6379" +<% } %> + +volumes: +<% if (it.database === 'postgresql') { %> pgdata:<% } %> +<% if (it.database === 'mysql') { %> mysqldata:<% } %> diff --git a/packages/create-express-forge/templates/features/logger/pino.ts.eta b/packages/create-express-forge/templates/features/logger/pino.ts.eta new file mode 100644 index 0000000..a867be3 --- /dev/null +++ b/packages/create-express-forge/templates/features/logger/pino.ts.eta @@ -0,0 +1,19 @@ +import pino from 'pino'; +import pinoHttp from 'pino-http'; + +export const logger = pino({ + level: process.env.NODE_ENV === 'production' ? 'info' : 'debug', + transport: process.env.NODE_ENV === 'production' ? undefined : { + target: 'pino-pretty', + options: { colorize: true, translateTime: 'SYS:standard' } + } +}); + +export const httpLogger = pinoHttp({ + logger, + customLogLevel: (req, res, err) => { + if (res.statusCode >= 500 || err) return 'error'; + if (res.statusCode >= 400) return 'warn'; + return 'info'; + } +}); diff --git a/packages/create-express-forge/templates/features/logger/winston.ts.eta b/packages/create-express-forge/templates/features/logger/winston.ts.eta new file mode 100644 index 0000000..b10a567 --- /dev/null +++ b/packages/create-express-forge/templates/features/logger/winston.ts.eta @@ -0,0 +1,30 @@ +import winston from 'winston'; +import 'winston-daily-rotate-file'; + +const { combine, timestamp, printf, colorize, errors } = winston.format; + +const logFormat = printf(({ level, message, timestamp, stack }) => { + return `${timestamp} ${level}: ${stack || message}`; +}); + +export const logger = winston.createLogger({ + level: process.env.NODE_ENV === 'production' ? 'info' : 'debug', + format: combine( + errors({ stack: true }), + timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + logFormat + ), + transports: [ + new winston.transports.Console({ + format: combine(colorize(), logFormat) + }), + new winston.transports.DailyRotateFile({ + dirname: 'logs', + filename: 'application-%DATE%.log', + datePattern: 'YYYY-MM-DD', + maxFiles: '14d', + maxSize: '20m', + zippedArchive: true, + }) + ] +}); diff --git a/packages/create-express-forge/templates/features/openapi/swagger.ts.eta b/packages/create-express-forge/templates/features/openapi/swagger.ts.eta new file mode 100644 index 0000000..442620a --- /dev/null +++ b/packages/create-express-forge/templates/features/openapi/swagger.ts.eta @@ -0,0 +1,34 @@ +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: '<%= it.projectName %> API', + version: '0.1.0', + description: 'API documentation for <%= it.projectName %>', + }, + servers: [{ url: '/api/v1' }], +<% if (it.auth === 'jwt') { %> + components: { + securitySchemes: { + bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' } + } + }, + security: [{ bearerAuth: [] }] +<% } %> + }, + apis: ['./src/routes/*.ts', './src/modules/**/*.routes.ts', './src/controllers/*.ts'], +}; + +const swaggerSpec = swaggerJsdoc(options); + +export function setupSwagger(app: Express) { + app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec)); + app.get('/api-docs.json', (req, res) => { + res.setHeader('Content-Type', 'application/json'); + res.send(swaggerSpec); + }); +} diff --git a/packages/create-express-forge/templates/features/prisma/database.ts.eta b/packages/create-express-forge/templates/features/prisma/database.ts.eta new file mode 100644 index 0000000..897167c --- /dev/null +++ b/packages/create-express-forge/templates/features/prisma/database.ts.eta @@ -0,0 +1,5 @@ +import { PrismaClient } from '@prisma/client'; + +export const prisma = new PrismaClient({ + log: process.env.NODE_ENV === 'development' ? ['query', 'warn', 'error'] : ['error'], +}); diff --git a/packages/create-express-forge/templates/features/prisma/schema.prisma.eta b/packages/create-express-forge/templates/features/prisma/schema.prisma.eta new file mode 100644 index 0000000..693d9b1 --- /dev/null +++ b/packages/create-express-forge/templates/features/prisma/schema.prisma.eta @@ -0,0 +1,47 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { +<% if (it.database === 'sqlite') { %> + provider = "sqlite" +<% } else if (it.database === 'mysql') { %> + provider = "mysql" +<% } else { %> + provider = "postgresql" +<% } %> + url = env("DATABASE_URL") +} + +<% if (it.auth !== 'none') { %> +model User { + id String @id @default(uuid()) + name String + email String @unique + password String + role Role @default(USER) + todos Todo[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@map("users") +} + +model Todo { + id String @id @default(cuid()) + title String + description String? + completed Boolean @default(false) + userId String + user User @relation(fields: [userId], references: [id]) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@map("todos") +} + +enum Role { + USER + ADMIN +} +<% } %> diff --git a/packages/create-express-forge/templates/features/prisma/seed.ts.eta b/packages/create-express-forge/templates/features/prisma/seed.ts.eta new file mode 100644 index 0000000..0341b81 --- /dev/null +++ b/packages/create-express-forge/templates/features/prisma/seed.ts.eta @@ -0,0 +1,36 @@ +import { PrismaClient } from '@prisma/client'; + +const prisma = new PrismaClient(); + +async function main() { + console.log('๐ŸŒฑ Seeding database...'); + +<% if (it.auth !== 'none') { %> + const admin = await prisma.user.upsert({ + where: { email: 'admin@example.com' }, + update: {}, + create: { + email: 'admin@example.com', + name: 'Admin User', + password: 'password123', // In real app, hash this! + role: 'ADMIN', + }, + }); + + console.log({ admin }); +<% } else { %> + // Add seed data here + console.log('Seeding models...'); +<% } %> + + console.log('โœ… Seeding completed.'); +} + +main() + .catch((e) => { + console.error(e); + process.exit(1); + }) + .finally(async () => { + await prisma.$disconnect(); + }); diff --git a/packages/create-express-forge/templates/features/sequelize/.sequelizerc.eta b/packages/create-express-forge/templates/features/sequelize/.sequelizerc.eta new file mode 100644 index 0000000..5ae0181 --- /dev/null +++ b/packages/create-express-forge/templates/features/sequelize/.sequelizerc.eta @@ -0,0 +1,7 @@ +const path = require('path'); +module.exports = { + config: path.resolve('src', 'config', 'sequelize.cjs'), + 'models-path': path.resolve('src', 'models'), + 'seeders-path': path.resolve('db', 'seeders'), + 'migrations-path': path.resolve('db', 'migrations'), +}; diff --git a/packages/create-express-forge/templates/features/sequelize/User.ts.eta b/packages/create-express-forge/templates/features/sequelize/User.ts.eta new file mode 100644 index 0000000..1bca274 --- /dev/null +++ b/packages/create-express-forge/templates/features/sequelize/User.ts.eta @@ -0,0 +1,11 @@ +import { Table, Column, Model, DataType, CreatedAt, UpdatedAt, PrimaryKey, Default, Unique } from 'sequelize-typescript'; + +@Table({ tableName: 'users', timestamps: true }) +export class User extends Model { + @PrimaryKey @Default(DataType.UUIDV4) @Column(DataType.UUID) declare id: string; + @Column({ type: DataType.STRING, allowNull: false }) declare name: string; + @Unique @Column({ type: DataType.STRING, allowNull: false }) declare email: string; + @Column({ type: DataType.STRING, allowNull: false }) declare password: string; + @CreatedAt declare createdAt: Date; + @UpdatedAt declare updatedAt: Date; +} diff --git a/packages/create-express-forge/templates/features/sequelize/database.ts.eta b/packages/create-express-forge/templates/features/sequelize/database.ts.eta new file mode 100644 index 0000000..1f5fc47 --- /dev/null +++ b/packages/create-express-forge/templates/features/sequelize/database.ts.eta @@ -0,0 +1,17 @@ +<% const dialect = it.database === 'mysql' ? 'mysql' : it.database === 'sqlite' ? 'sqlite' : 'postgres'; %> +import { Sequelize } from 'sequelize-typescript'; +import { env } from './env.js'; +<% if (it.auth !== 'none') { %> +import { User } from '../models/User.js'; +<% } %> + +export const sequelize = new Sequelize(env.DATABASE_URL, { + dialect: '<%= dialect %>' as const, + logging: env.NODE_ENV === 'development' ? console.log : false, + models: [<% if (it.auth !== 'none') { %>'User'<% } %>] +}); + +export async function connectDB() { + await sequelize.authenticate(); + console.log('โœ… Database connected'); +} diff --git a/packages/create-express-forge/templates/features/sequelize/sequelize.cjs.eta b/packages/create-express-forge/templates/features/sequelize/sequelize.cjs.eta new file mode 100644 index 0000000..766746f --- /dev/null +++ b/packages/create-express-forge/templates/features/sequelize/sequelize.cjs.eta @@ -0,0 +1,6 @@ +<% const dialect = it.database === 'mysql' ? 'mysql' : it.database === 'sqlite' ? 'sqlite' : 'postgres'; %> +module.exports = { + development: { use_env_variable: 'DATABASE_URL', dialect: '<%= dialect %>', logging: console.log }, + test: { use_env_variable: 'DATABASE_URL', dialect: '<%= dialect %>', logging: false }, + production: { use_env_variable: 'DATABASE_URL', dialect: '<%= dialect %>', logging: false }, +}; diff --git a/packages/create-express-forge/templates/features/testing/jest.config.js.eta b/packages/create-express-forge/templates/features/testing/jest.config.js.eta new file mode 100644 index 0000000..28caa9d --- /dev/null +++ b/packages/create-express-forge/templates/features/testing/jest.config.js.eta @@ -0,0 +1,7 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + moduleNameMapper: { '^@/(.*)$': '/src/$1' }, + collectCoverageFrom: ['src/**/*.{ts,js}', '!src/**/*.d.ts'], +}; diff --git a/packages/create-express-forge/templates/features/testing/smoke.test.ts.eta b/packages/create-express-forge/templates/features/testing/smoke.test.ts.eta new file mode 100644 index 0000000..7ee02d7 --- /dev/null +++ b/packages/create-express-forge/templates/features/testing/smoke.test.ts.eta @@ -0,0 +1,9 @@ +import request from 'supertest'; +import { app } from '../src/app.js'; + +describe('Smoke Test', () => { + it('should return 404 for unknown route', async () => { + const res = await request(app).get('/api/unknown-route-123'); + expect(res.status).toBe(404); + }); +}); diff --git a/packages/create-express-forge/templates/features/testing/vitest.config.ts.eta b/packages/create-express-forge/templates/features/testing/vitest.config.ts.eta new file mode 100644 index 0000000..7c33155 --- /dev/null +++ b/packages/create-express-forge/templates/features/testing/vitest.config.ts.eta @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'node', + globals: true, + coverage: { provider: 'v8', reporter: ['text', 'lcov'] } + } +}); diff --git a/packages/create-express-forge/templates/structure/modular/modules/auth/auth.controller.ts.eta b/packages/create-express-forge/templates/structure/modular/modules/auth/auth.controller.ts.eta new file mode 100644 index 0000000..5b02504 --- /dev/null +++ b/packages/create-express-forge/templates/structure/modular/modules/auth/auth.controller.ts.eta @@ -0,0 +1,16 @@ +import type { Request, Response } from 'express'; +import jwt from 'jsonwebtoken'; +import { ApiResponse } from '../../utils/ApiResponse.js'; +import { env } from '../../config/env.js'; + +export const login = async (req: Request, res: Response) => { + // Demo logic: accept any valid email/password + const token = jwt.sign({ email: req.body.email }, env.JWT_SECRET, { expiresIn: env.JWT_EXPIRES_IN as any }); + +<% if (it.jwtStorage === 'cookie') { %> + res.cookie('token', token, { httpOnly: true, secure: env.NODE_ENV === 'production' }); + return ApiResponse.success(res, { token }, 'Logged in successfully'); +<% } else { %> + return ApiResponse.success(res, { token }, 'Logged in successfully'); +<% } %> +}; diff --git a/packages/create-express-forge/templates/structure/modular/modules/auth/auth.routes.ts.eta b/packages/create-express-forge/templates/structure/modular/modules/auth/auth.routes.ts.eta new file mode 100644 index 0000000..b298dda --- /dev/null +++ b/packages/create-express-forge/templates/structure/modular/modules/auth/auth.routes.ts.eta @@ -0,0 +1,32 @@ +import { Router } from 'express'; +import { login } from './auth.controller.js'; +import { validate } from '../../middleware/validate.js'; +import { loginSchema } from './auth.schema.js'; + +const router = Router(); + +/** + * @openapi + * /api/v1/auth/login: + * post: + * tags: + * - Auth + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: [email, password] + * properties: + * email: + * type: string + * password: + * type: string + * responses: + * 200: + * description: Logged in successfully + */ +router.post('/login', validate(loginSchema), login); + +export { router as authRouter }; diff --git a/packages/create-express-forge/templates/structure/modular/modules/auth/auth.schema.ts.eta b/packages/create-express-forge/templates/structure/modular/modules/auth/auth.schema.ts.eta new file mode 100644 index 0000000..2a7b752 --- /dev/null +++ b/packages/create-express-forge/templates/structure/modular/modules/auth/auth.schema.ts.eta @@ -0,0 +1,8 @@ +import { z } from 'zod'; + +export const loginSchema = z.object({ + body: z.object({ + email: z.string().email(), + password: z.string().min(8) + }) +}); diff --git a/packages/create-express-forge/templates/structure/modular/modules/health/health.controller.ts.eta b/packages/create-express-forge/templates/structure/modular/modules/health/health.controller.ts.eta new file mode 100644 index 0000000..bb9396d --- /dev/null +++ b/packages/create-express-forge/templates/structure/modular/modules/health/health.controller.ts.eta @@ -0,0 +1,5 @@ +import type { Request, Response } from 'express'; +import { ApiResponse } from '../../utils/ApiResponse.js'; + +export const getHealth = (_req: Request, res: Response) => + ApiResponse.success(res, { status: 'ok', timestamp: new Date().toISOString() }, 'Service healthy'); diff --git a/packages/create-express-forge/templates/structure/modular/modules/health/health.routes.ts.eta b/packages/create-express-forge/templates/structure/modular/modules/health/health.routes.ts.eta new file mode 100644 index 0000000..ca895e2 --- /dev/null +++ b/packages/create-express-forge/templates/structure/modular/modules/health/health.routes.ts.eta @@ -0,0 +1,19 @@ +import { Router } from 'express'; +import { getHealth } from './health.controller.js'; + +const router = Router(); + +/** + * @openapi + * /api/health: + * get: + * tags: + * - Health + * description: Responds if the app is up and running + * responses: + * 200: + * description: App is up and running + */ +router.get('/', getHealth); + +export { router as healthRouter }; diff --git a/packages/create-express-forge/templates/structure/modular/modules/todos/todos.controller.ts.eta b/packages/create-express-forge/templates/structure/modular/modules/todos/todos.controller.ts.eta new file mode 100644 index 0000000..a5d54db --- /dev/null +++ b/packages/create-express-forge/templates/structure/modular/modules/todos/todos.controller.ts.eta @@ -0,0 +1,37 @@ +import type { Request, Response } from 'express'; +import { ApiResponse } from '../../utils/ApiResponse.js'; +import { TodosService } from './todos.service.js'; +import type { CreateTodoDto, UpdateTodoDto } from './todos.schema.js'; + +export const getTodos = async (req: Request, res: Response) => { + const todos = await TodosService.findAll(<% if (it.auth !== 'none') { %>req.user?.id || 'guest'<% } %>); + return ApiResponse.success(res, todos, 'Todos fetched'); +}; + +export const getTodoById = async (req: Request, res: Response) => { + const todo = await TodosService.findById(req.params.id as string<% if (it.auth !== 'none') { %>, req.user?.id || 'guest'<% } %>); + return ApiResponse.success(res, todo); +}; + +export const createTodo = async (req: Request, res: Response) => { + const todo = await TodosService.create({ + ...(req.body as CreateTodoDto), + completed: false<% if (it.auth !== 'none') { %>, + userId: req.user?.id || 'guest'<% } %> + }); + return ApiResponse.created(res, todo, 'Todo created'); +}; + +export const updateTodo = async (req: Request, res: Response) => { + const todo = await TodosService.update( + req.params.id as string, + <% if (it.auth !== 'none') { %>req.user?.id || 'guest', <% } %> + req.body as UpdateTodoDto + ); + return ApiResponse.success(res, todo, 'Todo updated'); +}; + +export const deleteTodo = async (req: Request, res: Response) => { + await TodosService.remove(req.params.id as string<% if (it.auth !== 'none') { %>, req.user?.id || 'guest'<% } %>); + return ApiResponse.noContent(res); +}; diff --git a/packages/create-express-forge/templates/structure/modular/modules/todos/todos.routes.ts.eta b/packages/create-express-forge/templates/structure/modular/modules/todos/todos.routes.ts.eta new file mode 100644 index 0000000..ea030c1 --- /dev/null +++ b/packages/create-express-forge/templates/structure/modular/modules/todos/todos.routes.ts.eta @@ -0,0 +1,45 @@ +import { Router } from 'express'; +import { getTodos, getTodoById, createTodo, updateTodo, deleteTodo } from './todos.controller.js'; +import { validate } from '../../middleware/validate.js'; +import { createTodoSchema, updateTodoSchema } from './todos.schema.js'; +<% if (it.auth !== 'none') { %>import { auth } from '../../middleware/auth.middleware.js';<% } %> + +const router = Router(); + +<% if (it.auth !== 'none') { %>router.use(auth);<% } %> + +/** + * @openapi + * /api/v1/todos: + * get: + * tags: + * - Todos + * responses: + * 200: + * description: Success + * post: + * tags: + * - Todos + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: [title] + * properties: + * title: + * type: string + * description: + * type: string + * responses: + * 201: + * description: Created + */ +router.get('/', getTodos); +router.get('/:id', getTodoById); +router.post('/', validate(createTodoSchema), createTodo); +router.patch('/:id', validate(updateTodoSchema), updateTodo); +router.delete('/:id', deleteTodo); + +export { router as todosRouter }; diff --git a/packages/create-express-forge/templates/structure/modular/modules/todos/todos.schema.ts.eta b/packages/create-express-forge/templates/structure/modular/modules/todos/todos.schema.ts.eta new file mode 100644 index 0000000..c861cdb --- /dev/null +++ b/packages/create-express-forge/templates/structure/modular/modules/todos/todos.schema.ts.eta @@ -0,0 +1,20 @@ +import { z } from 'zod'; + +export const createTodoSchema = z.object({ + body: z.object({ + title: z.string().min(1).max(100), + description: z.string().max(500).optional(), + }), +}); + +export const updateTodoSchema = z.object({ + params: z.object({ id: z.string() }), + body: z.object({ + title: z.string().min(1).max(100).optional(), + description: z.string().max(500).optional(), + completed: z.boolean().optional(), + }), +}); + +export type CreateTodoDto = z.infer['body']; +export type UpdateTodoDto = z.infer['body']; diff --git a/packages/create-express-forge/templates/structure/modular/modules/todos/todos.service.ts.eta b/packages/create-express-forge/templates/structure/modular/modules/todos/todos.service.ts.eta new file mode 100644 index 0000000..5a8ecc1 --- /dev/null +++ b/packages/create-express-forge/templates/structure/modular/modules/todos/todos.service.ts.eta @@ -0,0 +1,41 @@ +import { ApiError } from '../../utils/ApiError.js'; + +export interface Todo { + id: string; + title: string; + description?: string; + completed: boolean; + <% if (it.auth !== 'none') { %>userId: string;<% } %> +} + +const store: Todo[] = []; + +export const TodosService = { + findAll: async (<% if (it.auth !== 'none') { %>userId: string<% } %>) => + store<% if (it.auth !== 'none') { %>.filter(t => t.userId === userId)<% } %>, + + findById: async (id: string<% if (it.auth !== 'none') { %>, userId: string<% } %>) => { + const todo = store.find((t) => t.id === id<% if (it.auth !== 'none') { %> && t.userId === userId<% } %>); + if (!todo) throw ApiError.notFound('Todo not found'); + return todo; + }, + + create: async (data: Omit) => { + const todo = { id: Math.random().toString(36).substring(7), ...data }; + store.push(todo); + return todo; + }, + + update: async (id: string, <% if (it.auth !== 'none') { %>userId: string, <% } %>data: Partial) => { + const todo = store.find((t) => t.id === id<% if (it.auth !== 'none') { %> && t.userId === userId<% } %>); + if (!todo) throw ApiError.notFound('Todo not found'); + Object.assign(todo, data); + return todo; + }, + + remove: async (id: string<% if (it.auth !== 'none') { %>, userId: string<% } %>) => { + const idx = store.findIndex((t) => t.id === id<% if (it.auth !== 'none') { %> && t.userId === userId<% } %>); + if (idx === -1) throw ApiError.notFound('Todo not found'); + store.splice(idx, 1); + }, +}; diff --git a/packages/create-express-forge/templates/structure/mvc/controllers/auth.controller.ts.eta b/packages/create-express-forge/templates/structure/mvc/controllers/auth.controller.ts.eta new file mode 100644 index 0000000..233e5ab --- /dev/null +++ b/packages/create-express-forge/templates/structure/mvc/controllers/auth.controller.ts.eta @@ -0,0 +1,16 @@ +import type { Request, Response } from 'express'; +import jwt from 'jsonwebtoken'; +import { ApiResponse } from '../utils/ApiResponse.js'; +import { env } from '../config/env.js'; + +export const login = async (req: Request, res: Response) => { + // Demo logic: accept any valid email/password + const token = jwt.sign({ email: req.body.email }, env.JWT_SECRET, { expiresIn: env.JWT_EXPIRES_IN as any }); + +<% if (it.jwtStorage === 'cookie') { %> + res.cookie('token', token, { httpOnly: true, secure: env.NODE_ENV === 'production' }); + return ApiResponse.success(res, { token }, 'Logged in successfully'); +<% } else { %> + return ApiResponse.success(res, { token }, 'Logged in successfully'); +<% } %> +}; diff --git a/packages/create-express-forge/templates/structure/mvc/controllers/health.controller.ts.eta b/packages/create-express-forge/templates/structure/mvc/controllers/health.controller.ts.eta new file mode 100644 index 0000000..07e4d6e --- /dev/null +++ b/packages/create-express-forge/templates/structure/mvc/controllers/health.controller.ts.eta @@ -0,0 +1,5 @@ +import type { Request, Response } from 'express'; +import { ApiResponse } from '../utils/ApiResponse.js'; + +export const getHealth = (_req: Request, res: Response) => + ApiResponse.success(res, { status: 'ok', timestamp: new Date().toISOString() }, 'Service healthy'); diff --git a/packages/create-express-forge/templates/structure/mvc/controllers/todo.controller.ts.eta b/packages/create-express-forge/templates/structure/mvc/controllers/todo.controller.ts.eta new file mode 100644 index 0000000..18851d7 --- /dev/null +++ b/packages/create-express-forge/templates/structure/mvc/controllers/todo.controller.ts.eta @@ -0,0 +1,29 @@ +import type { Request, Response } from 'express'; +import { ApiResponse } from '../utils/ApiResponse.js'; +import { TodoService } from '../services/todo.service.js'; +import type { CreateTodoDto, UpdateTodoDto } from '../schemas/todo.schema.js'; + +export const getTodos = async (req: Request, res: Response) => + ApiResponse.success(res, await TodoService.findAll(<% if (it.auth !== 'none') { %>req.user?.id || 'guest'<% } %>), 'Todos fetched'); + +export const getTodoById = async (req: Request, res: Response) => + ApiResponse.success(res, await TodoService.findById(req.params.id as string<% if (it.auth !== 'none') { %>, req.user?.id || 'guest'<% } %>)); + +export const createTodo = async (req: Request, res: Response) => + ApiResponse.created(res, await TodoService.create({ + ...(req.body as CreateTodoDto), + completed: false<% if (it.auth !== 'none') { %>, + userId: req.user?.id || 'guest'<% } %> + }), 'Todo created'); + +export const updateTodo = async (req: Request, res: Response) => + ApiResponse.success(res, await TodoService.update( + req.params.id as string, + <% if (it.auth !== 'none') { %>req.user?.id || 'guest', <% } %> + req.body as UpdateTodoDto + ), 'Todo updated'); + +export const deleteTodo = async (req: Request, res: Response) => { + await TodoService.remove(req.params.id as string<% if (it.auth !== 'none') { %>, req.user?.id || 'guest'<% } %>); + return ApiResponse.noContent(res); +}; diff --git a/packages/create-express-forge/templates/structure/mvc/routes/auth.routes.ts.eta b/packages/create-express-forge/templates/structure/mvc/routes/auth.routes.ts.eta new file mode 100644 index 0000000..e9c101f --- /dev/null +++ b/packages/create-express-forge/templates/structure/mvc/routes/auth.routes.ts.eta @@ -0,0 +1,32 @@ +import { Router } from 'express'; +import { login } from '../controllers/auth.controller.js'; +import { validate } from '../middleware/validate.js'; +import { loginSchema } from '../schemas/auth.schema.js'; + +const router = Router(); + +/** + * @openapi + * /api/v1/auth/login: + * post: + * tags: + * - Auth + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: [email, password] + * properties: + * email: + * type: string + * password: + * type: string + * responses: + * 200: + * description: Logged in successfully + */ +router.post('/login', validate(loginSchema), login); + +export { router as authRouter }; diff --git a/packages/create-express-forge/templates/structure/mvc/routes/health.routes.ts.eta b/packages/create-express-forge/templates/structure/mvc/routes/health.routes.ts.eta new file mode 100644 index 0000000..9332f45 --- /dev/null +++ b/packages/create-express-forge/templates/structure/mvc/routes/health.routes.ts.eta @@ -0,0 +1,19 @@ +import { Router } from 'express'; +import { getHealth } from '../controllers/health.controller.js'; + +const router = Router(); + +/** + * @openapi + * /api/health: + * get: + * tags: + * - Health + * description: Responds if the app is up and running + * responses: + * 200: + * description: App is up and running + */ +router.get('/', getHealth); + +export { router as healthRouter }; diff --git a/packages/create-express-forge/templates/structure/mvc/routes/index.ts.eta b/packages/create-express-forge/templates/structure/mvc/routes/index.ts.eta new file mode 100644 index 0000000..927c317 --- /dev/null +++ b/packages/create-express-forge/templates/structure/mvc/routes/index.ts.eta @@ -0,0 +1,12 @@ +import { Router } from 'express'; +import { healthRouter } from './health.routes.js'; +import { todoRouter } from './todo.routes.js'; +<% if (it.auth === 'jwt') { %>import { authRouter } from './auth.routes.js';<% } %> + +const router = Router(); + +router.use('/health', healthRouter); +router.use('/todos', todoRouter); +<% if (it.auth === 'jwt') { %>router.use('/auth', authRouter);<% } %> + +export { router }; diff --git a/packages/create-express-forge/templates/structure/mvc/routes/todo.routes.ts.eta b/packages/create-express-forge/templates/structure/mvc/routes/todo.routes.ts.eta new file mode 100644 index 0000000..de4cea9 --- /dev/null +++ b/packages/create-express-forge/templates/structure/mvc/routes/todo.routes.ts.eta @@ -0,0 +1,45 @@ +import { Router } from 'express'; +import { getTodos, getTodoById, createTodo, updateTodo, deleteTodo } from '../controllers/todo.controller.js'; +import { validate } from '../middleware/validate.js'; +import { createTodoSchema, updateTodoSchema } from '../schemas/todo.schema.ts'; +<% if (it.auth !== 'none') { %>import { auth } from '../middleware/auth.middleware.js';<% } %> + +const router = Router(); + +<% if (it.auth !== 'none') { %>router.use(auth);<% } %> + +/** + * @openapi + * /api/v1/todos: + * get: + * tags: + * - Todos + * responses: + * 200: + * description: Success + * post: + * tags: + * - Todos + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: [title] + * properties: + * title: + * type: string + * description: + * type: string + * responses: + * 201: + * description: Created + */ +router.get('/', getTodos); +router.get('/:id', getTodoById); +router.post('/', validate(createTodoSchema), createTodo); +router.patch('/:id', validate(updateTodoSchema), updateTodo); +router.delete('/:id', deleteTodo); + +export { router as todoRouter }; diff --git a/packages/create-express-forge/templates/structure/mvc/schemas/auth.schema.ts.eta b/packages/create-express-forge/templates/structure/mvc/schemas/auth.schema.ts.eta new file mode 100644 index 0000000..2a7b752 --- /dev/null +++ b/packages/create-express-forge/templates/structure/mvc/schemas/auth.schema.ts.eta @@ -0,0 +1,8 @@ +import { z } from 'zod'; + +export const loginSchema = z.object({ + body: z.object({ + email: z.string().email(), + password: z.string().min(8) + }) +}); diff --git a/packages/create-express-forge/templates/structure/mvc/schemas/todo.schema.ts.eta b/packages/create-express-forge/templates/structure/mvc/schemas/todo.schema.ts.eta new file mode 100644 index 0000000..ce29481 --- /dev/null +++ b/packages/create-express-forge/templates/structure/mvc/schemas/todo.schema.ts.eta @@ -0,0 +1,20 @@ +import { z } from 'zod'; + +export const createTodoSchema = z.object({ + body: z.object({ + title: z.string().min(1).max(100), + description: z.string().max(500).optional() + }) +}); + +export const updateTodoSchema = z.object({ + params: z.object({ id: z.string() }), + body: z.object({ + title: z.string().min(1).max(100).optional(), + description: z.string().max(500).optional(), + completed: z.boolean().optional() + }) +}); + +export type CreateTodoDto = z.infer['body']; +export type UpdateTodoDto = z.infer['body']; diff --git a/packages/create-express-forge/templates/structure/mvc/services/todo.service.ts.eta b/packages/create-express-forge/templates/structure/mvc/services/todo.service.ts.eta new file mode 100644 index 0000000..ae6bca8 --- /dev/null +++ b/packages/create-express-forge/templates/structure/mvc/services/todo.service.ts.eta @@ -0,0 +1,41 @@ +import { ApiError } from '../utils/ApiError.js'; + +export interface Todo { + id: string; + title: string; + description?: string; + completed: boolean; + <% if (it.auth !== 'none') { %>userId: string;<% } %> +} + +const store: Todo[] = []; + +export const TodoService = { + findAll: async (<% if (it.auth !== 'none') { %>userId: string<% } %>) => + store<% if (it.auth !== 'none') { %>.filter(t => t.userId === userId)<% } %>, + + findById: async (id: string<% if (it.auth !== 'none') { %>, userId: string<% } %>) => { + const t = store.find((t) => t.id === id<% if (it.auth !== 'none') { %> && t.userId === userId<% } %>); + if (!t) throw ApiError.notFound('Todo not found'); + return t; + }, + + create: async (data: Omit) => { + const t = { id: Math.random().toString(36).substring(7), ...data }; + store.push(t); + return t; + }, + + update: async (id: string, <% if (it.auth !== 'none') { %>userId: string, <% } %>data: Partial) => { + const t = store.find((t) => t.id === id<% if (it.auth !== 'none') { %> && t.userId === userId<% } %>); + if (!t) throw ApiError.notFound('Todo not found'); + Object.assign(t, data); + return t; + }, + + remove: async (id: string<% if (it.auth !== 'none') { %>, userId: string<% } %>) => { + const i = store.findIndex((t) => t.id === id<% if (it.auth !== 'none') { %> && t.userId === userId<% } %>); + if (i === -1) throw ApiError.notFound('Todo not found'); + store.splice(i, 1); + }, +}; diff --git a/packages/create-express-forge/tests/generator.test.ts b/packages/create-express-forge/tests/generator.test.ts deleted file mode 100644 index 0c29e2b..0000000 --- a/packages/create-express-forge/tests/generator.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import path from 'path'; -import fs from 'fs-extra'; -import os from 'os'; -import { generateProject } from '../src/generator/index.js'; -import type { CliOptions } from '../src/types.js'; - -async function makeTempDir(): Promise { - return fs.mkdtemp(path.join(os.tmpdir(), 'cef-test-')); -} - -const baseOpts: CliOptions = { - projectName: 'test-app', - pattern: 'modular', - orm: 'none', - database: 'none', - packageManager: 'npm', - logger: 'none', - testing: 'none', - cache: 'none', - auth: 'none', - importAlias: false, - openapi: false, - openapiUI: false, - docker: false, - installDeps: false, -}; - -describe('generateProject โ€“ modular, no extras', () => { - let tmpDir: string; - - beforeEach(async () => { tmpDir = await makeTempDir(); }); - afterEach(async () => { await fs.remove(tmpDir); }); - - it('creates package.json', async () => { - await generateProject(baseOpts, tmpDir); - expect(await fs.pathExists(path.join(tmpDir, 'package.json'))).toBe(true); - }); - - it('creates src/app.ts', async () => { - await generateProject(baseOpts, tmpDir); - expect(await fs.pathExists(path.join(tmpDir, 'src', 'app.ts'))).toBe(true); - }); - - it('creates src/server.ts', async () => { - await generateProject(baseOpts, tmpDir); - expect(await fs.pathExists(path.join(tmpDir, 'src', 'server.ts'))).toBe(true); - }); - - it('creates global error handler', async () => { - await generateProject(baseOpts, tmpDir); - const content = await fs.readFile(path.join(tmpDir, 'src', 'middleware', 'errorHandler.ts'), 'utf-8'); - expect(content).toContain('ApiError'); - expect(content).toContain('ZodError'); - }); - - it('creates .env.example', async () => { - await generateProject(baseOpts, tmpDir); - expect(await fs.pathExists(path.join(tmpDir, '.env.example'))).toBe(true); - }); -}); - -describe('generateProject โ€“ MVC pattern', () => { - let tmpDir: string; - beforeEach(async () => { tmpDir = await makeTempDir(); }); - afterEach(async () => { await fs.remove(tmpDir); }); - - it('creates routes/index.ts', async () => { - await generateProject({ ...baseOpts, pattern: 'mvc' }, tmpDir); - expect(await fs.pathExists(path.join(tmpDir, 'src', 'routes', 'index.ts'))).toBe(true); - }); - - it('creates controllers/', async () => { - await generateProject({ ...baseOpts, pattern: 'mvc' }, tmpDir); - expect(await fs.pathExists(path.join(tmpDir, 'src', 'controllers'))).toBe(true); - }); -}); - -describe('generateProject โ€“ Docker', () => { - let tmpDir: string; - beforeEach(async () => { tmpDir = await makeTempDir(); }); - afterEach(async () => { await fs.remove(tmpDir); }); - - it('creates Dockerfile and docker-compose.yml', async () => { - await generateProject({ ...baseOpts, docker: true }, tmpDir); - expect(await fs.pathExists(path.join(tmpDir, 'Dockerfile'))).toBe(true); - expect(await fs.pathExists(path.join(tmpDir, 'docker-compose.yml'))).toBe(true); - }); -}); diff --git a/packages/create-express-forge/tests/main.test.ts b/packages/create-express-forge/tests/main.test.ts new file mode 100644 index 0000000..7c60b21 --- /dev/null +++ b/packages/create-express-forge/tests/main.test.ts @@ -0,0 +1,99 @@ +import os from "node:os"; +import path from "node:path"; +import fs from "fs-extra"; +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; +import { generateBaseFiles } from "../src/generator/base.js"; +import { generateDocker } from "../src/generator/features/docker.js"; +import { generateModularStructure } from "../src/generator/structure/modular.js"; +import { generateMvcStructure } from "../src/generator/structure/mvc.js"; +import type { CliOptions } from "../src/types.js"; +import { buildPackageJson } from "../src/utils/package-builder.js"; +import { TemplateManager } from "../src/utils/template-manager.js"; + +vi.setConfig({ testTimeout: 10000 }); + +async function makeTempDir(prefix: string): Promise { + return fs.mkdtemp(path.join(os.tmpdir(), `cef-test-${prefix}-`)); +} + +const baseOpts: CliOptions = { + projectName: "test-app", + pattern: "modular", + orm: "none", + database: "none", + packageManager: "npm", + logger: "none", + testing: "none", + cache: "none", + auth: "none", + importAlias: false, + openapi: false, + openapiUI: false, + docker: false, + installDeps: false, +}; + +describe("Codebase Logic: Package Builder", () => { + it("should build a valid package.json object", () => { + const pkg = buildPackageJson(baseOpts); + expect(pkg.name).toBe("test-app"); + expect(pkg.dependencies).toBeDefined(); + expect(pkg.devDependencies).toHaveProperty("@biomejs/biome"); + }); +}); + +describe("Codebase Logic: Template Rendering", () => { + let tmpDir: string; + let tmpl: TemplateManager; + + beforeAll(async () => { + tmpDir = await makeTempDir("logic"); + tmpl = new TemplateManager(baseOpts); + }); + + afterAll(async () => { + if (tmpDir) await fs.remove(tmpDir); + }); + + it("generates base configuration files", async () => { + await generateBaseFiles(baseOpts, tmpDir, tmpl); + expect(await fs.pathExists(path.join(tmpDir, "tsconfig.json"))).toBe(true); + expect(await fs.pathExists(path.join(tmpDir, "src", "app.ts"))).toBe(true); + expect( + await fs.pathExists( + path.join(tmpDir, "src", "middleware", "errorHandler.ts"), + ), + ).toBe(true); + }); + + it("generates modular architecture structure", async () => { + const modularDir = path.join(tmpDir, "modular-test"); + await fs.ensureDir(modularDir); + await generateModularStructure(baseOpts, modularDir, tmpl); + expect( + await fs.pathExists(path.join(modularDir, "src", "modules", "todos")), + ).toBe(true); + }); + + it("generates MVC architecture structure", async () => { + const mvcDir = path.join(tmpDir, "mvc-test"); + await fs.ensureDir(mvcDir); + await generateMvcStructure({ ...baseOpts, pattern: "mvc" }, mvcDir, tmpl); + expect(await fs.pathExists(path.join(mvcDir, "src", "controllers"))).toBe( + true, + ); + expect( + await fs.pathExists(path.join(mvcDir, "src", "routes", "index.ts")), + ).toBe(true); + }); + + it("generates Docker configuration", async () => { + const dockerDir = path.join(tmpDir, "docker-test"); + await fs.ensureDir(dockerDir); + await generateDocker(baseOpts, dockerDir, tmpl); + expect(await fs.pathExists(path.join(dockerDir, "Dockerfile"))).toBe(true); + expect( + await fs.pathExists(path.join(dockerDir, "docker-compose.yml")), + ).toBe(true); + }); +}); diff --git a/packages/create-express-forge/tests/smoke.integration.test.ts b/packages/create-express-forge/tests/smoke.integration.test.ts index db8077a..f6307a0 100644 --- a/packages/create-express-forge/tests/smoke.integration.test.ts +++ b/packages/create-express-forge/tests/smoke.integration.test.ts @@ -1,66 +1,80 @@ -import { describe, it, expect, beforeAll, afterAll } from 'vitest'; -import { execa } from 'execa'; -import path from 'path'; -import fs from 'fs-extra'; -import os from 'os'; +import os from "node:os"; +import path from "node:path"; +import { execa } from "execa"; +import fs from "fs-extra"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; -describe('CLI Integration (Smoke Test)', () => { +describe("CLI Integration (Smoke Test)", () => { let testDir: string; - const projectName = 'smoke-test-app'; + const projectName = "smoke-test-app"; afterAll(async () => { - if (testDir && await fs.pathExists(testDir)) { + if (testDir && (await fs.pathExists(testDir))) { await fs.remove(testDir); } }); beforeAll(async () => { - // 1. Create a clean temp directory for testing - const baseTempDir = path.join(os.tmpdir(), 'cef-smoke-'); + // 1. Create a clean temp directory with unique suffix for testing + const uniqueId = Math.random().toString(36).substring(2, 8); + const baseTempDir = path.join(os.tmpdir(), `cef-smoke-${uniqueId}-`); testDir = await fs.mkdtemp(baseTempDir); - + // 2. Build the CLI to ensure dist/ exists - await execa('npm', ['run', 'build'], { cwd: process.cwd() }); - }, 60000); + await execa("npm", ["run", "build"], { cwd: process.cwd() }); + }, 120000); - it('should scaffold a project with all defaults using --yes flag', async () => { + it("should scaffold a project with all defaults using --yes flag", async () => { const targetPath = path.join(testDir, projectName); // Run the CLI - await execa('node', [ - path.resolve(process.cwd(), 'dist/index.js'), - projectName, - '--yes' - ], { - cwd: testDir, - env: { ...process.env, NODE_ENV: 'test' } - }); + await execa( + "node", + [path.resolve(process.cwd(), "dist/index.js"), projectName, "--yes"], + { + cwd: testDir, + env: { ...process.env, NODE_ENV: "test" }, + }, + ); // Verify critical files - expect(await fs.pathExists(path.join(targetPath, 'package.json'))).toBe(true); - expect(await fs.pathExists(path.join(targetPath, 'src/app.ts'))).toBe(true); - expect(await fs.pathExists(path.join(targetPath, 'src/config/env.ts'))).toBe(true); - expect(await fs.pathExists(path.join(targetPath, 'src/docs/swagger.ts'))).toBe(true); - expect(await fs.pathExists(path.join(targetPath, '.env.example'))).toBe(true); + expect(await fs.pathExists(path.join(targetPath, "package.json"))).toBe( + true, + ); + expect(await fs.pathExists(path.join(targetPath, "src/app.ts"))).toBe(true); + expect( + await fs.pathExists(path.join(targetPath, "src/config/env.ts")), + ).toBe(true); + expect( + await fs.pathExists(path.join(targetPath, "src/docs/swagger.ts")), + ).toBe(true); + expect(await fs.pathExists(path.join(targetPath, ".env.example"))).toBe( + true, + ); // Verify the TODO app boilerplate exists - const todoServicePath = path.join(targetPath, 'src/modules/todos/todos.service.ts'); + const todoServicePath = path.join( + targetPath, + "src/modules/todos/todos.service.ts", + ); expect(await fs.pathExists(todoServicePath)).toBe(true); - const serviceContent = await fs.readFile(todoServicePath, 'utf-8'); - expect(serviceContent).toContain('TodosService'); + const serviceContent = await fs.readFile(todoServicePath, "utf-8"); + expect(serviceContent).toContain("TodosService"); }, 180000); // 3 minutes for scaffolding + npm install - it('should build the generated project successfully', async () => { + it("should build the generated project successfully", async () => { const targetPath = path.join(testDir, projectName); // 1. Run type checking first (more thorough than build) - console.log('Running type check in generated project...'); - const typeCheck = await execa('npm', ['run', 'check-types'], { cwd: targetPath }); + console.log("Running type check in generated project..."); + const typeCheck = await execa("npm", ["run", "check-types"], { + cwd: targetPath, + }); expect(typeCheck.exitCode).toBe(0); // 2. Run the actual build - console.log('Running build in generated project...'); - const build = await execa('npm', ['run', 'build'], { cwd: targetPath }); + console.log("Running build in generated project..."); + const build = await execa("npm", ["run", "build"], { cwd: targetPath }); expect(build.exitCode).toBe(0); }, 240000); // 4 minutes for build + typecheck }); diff --git a/packages/create-express-forge/tsup.config.ts b/packages/create-express-forge/tsup.config.ts index 006ac48..c1517cf 100644 --- a/packages/create-express-forge/tsup.config.ts +++ b/packages/create-express-forge/tsup.config.ts @@ -1,11 +1,17 @@ -import { defineConfig } from 'tsup'; +import path from "node:path"; +import fs from "fs-extra"; +import { defineConfig } from "tsup"; export default defineConfig({ - entry: ['src/index.ts'], - format: ['esm'], + entry: ["src/index.ts"], + format: ["esm"], dts: true, clean: true, - banner: { js: '#!/usr/bin/env node' }, - target: 'es2022', + banner: { js: "#!/usr/bin/env node" }, + target: "es2022", splitting: false, + onSuccess: async () => { + await fs.copy(path.resolve("templates"), path.resolve("dist/templates")); + console.log("โœ… Copied templates to dist/"); + }, }); diff --git a/packages/create-express-forge/vitest.config.ts b/packages/create-express-forge/vitest.config.ts index d3e16d0..dbcb9d6 100644 --- a/packages/create-express-forge/vitest.config.ts +++ b/packages/create-express-forge/vitest.config.ts @@ -1,12 +1,12 @@ -import { defineConfig } from 'vitest/config'; +import { defineConfig } from "vitest/config"; export default defineConfig({ test: { globals: true, - environment: 'node', + environment: "node", coverage: { - provider: 'v8', - reporter: ['text', 'html', 'lcov'], + provider: "v8", + reporter: ["text", "html", "lcov"], }, }, }); diff --git a/packages/eslint-config/index.js b/packages/eslint-config/index.js deleted file mode 100644 index fe438b5..0000000 --- a/packages/eslint-config/index.js +++ /dev/null @@ -1,12 +0,0 @@ -/** @type {import("eslint").Linter.Config} */ -module.exports = { - parser: "@typescript-eslint/parser", - plugins: ["@typescript-eslint"], - extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], - rules: { - "@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }], - "@typescript-eslint/no-explicit-any": "error", - "@typescript-eslint/consistent-type-imports": "warn", - }, - env: { node: true, es2022: true }, -}; diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json deleted file mode 100644 index 1d5efd1..0000000 --- a/packages/eslint-config/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "@repo/eslint-config", - "version": "0.0.0", - "private": true, - "license": "MIT", - "main": "index.js", - "dependencies": { - "@typescript-eslint/eslint-plugin": "^7.0.0", - "@typescript-eslint/parser": "^7.0.0" - } -} diff --git a/packages/lint-config/biome.base.json b/packages/lint-config/biome.base.json new file mode 100644 index 0000000..79dbe5d --- /dev/null +++ b/packages/lint-config/biome.base.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.4.13/schema.json", + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2 + }, + "files": { + "includes": [ + "**", + "!**/dist", + "!**/node_modules", + "!**/pnpm-lock.yaml", + "!docs/.vitepress/cache", + "!docs/.vitepress/dist" + ] + } +} diff --git a/packages/lint-config/package.json b/packages/lint-config/package.json new file mode 100644 index 0000000..c3d8db3 --- /dev/null +++ b/packages/lint-config/package.json @@ -0,0 +1,8 @@ +{ + "name": "@repo/lint-config", + "version": "0.0.0", + "private": true, + "files": [ + "biome.base.json" + ] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3b9bf6d..f5bf5c4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,18 +8,15 @@ importers: .: devDependencies: + '@biomejs/biome': + specifier: ^2.4.13 + version: 2.4.13 '@changesets/cli': specifier: ^2.27.7 version: 2.31.0(@types/node@20.19.39) - 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 turbo: specifier: ^2.0.9 version: 2.9.6 @@ -47,6 +44,9 @@ importers: commander: specifier: ^12.1.0 version: 12.1.0 + eta: + specifier: ^3.5.0 + version: 3.5.0 execa: specifier: ^9.3.0 version: 9.6.1 @@ -56,10 +56,13 @@ importers: ora: specifier: ^8.1.1 version: 8.2.0 + pkg-types: + specifier: ^1.2.0 + version: 1.3.1 devDependencies: - '@repo/eslint-config': + '@repo/lint-config': specifier: workspace:* - version: link:../eslint-config + version: link:../lint-config '@repo/typescript-config': specifier: workspace:* version: link:../typescript-config @@ -69,9 +72,6 @@ importers: '@types/node': specifier: ^20.14.0 version: 20.19.39 - eslint: - specifier: ^8.57.0 - version: 8.57.1 tsup: specifier: ^8.1.0 version: 8.5.1(postcss@8.5.10)(tsx@4.21.0)(typescript@5.5.3) @@ -85,14 +85,7 @@ importers: specifier: ^1.6.0 version: 1.6.1(@types/node@20.19.39) - packages/eslint-config: - dependencies: - '@typescript-eslint/eslint-plugin': - specifier: ^7.0.0 - version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1)(typescript@5.5.3) - '@typescript-eslint/parser': - specifier: ^7.0.0 - version: 7.18.0(eslint@8.57.1)(typescript@5.5.3) + packages/lint-config: {} packages/typescript-config: {} @@ -195,6 +188,59 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@biomejs/biome@2.4.13': + resolution: {integrity: sha512-gLXOwkOBBg0tr7bDsqlkIh4uFeKuMjxvqsrb1Tukww1iDmHcfr4Uu8MoQxp0Rcte+69+osRNWXwHsu/zxT6XqA==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@2.4.13': + resolution: {integrity: sha512-2KImO1jhNFBa2oWConyr0x6flxbQpGKv6902uGXpYM62Xyem8U80j441SyUJ8KyngsmKbQjeIv1q2CQfDkNnYg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@2.4.13': + resolution: {integrity: sha512-BKrJklbaFN4p1Ts4kPBczo+PkbsHQg57kmJ+vON9u2t6uN5okYHaSr7h/MutPCWQgg2lglaWoSmm+zhYW+oOkg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@2.4.13': + resolution: {integrity: sha512-U5MsuBQW25dXaYtqWWSPM3P96H6Y+fHuja3TQpMNnylocHW0tEbtFTDlUj6oM+YJLntvEkQy4grBvQNUD4+RCg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-arm64@2.4.13': + resolution: {integrity: sha512-NzkUDSqfvMBrPplKgVr3aXLHZ2NEELvvF4vZxXulEylKWIGqlvNEcwUcj9OLrn75TD3lJ/GIqCVlBwd1MZCuYQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-x64-musl@2.4.13': + resolution: {integrity: sha512-Z601MienRgTBDza/+u2CH3RSrWoXo9rtr8NK6A4KJzqGgfxx+H3VlyLgTJ4sRo40T3pIsqpTmiOQEvYzQvBRvQ==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-linux-x64@2.4.13': + resolution: {integrity: sha512-Az3ZZedYRBo9EQzNnD9SxFcR1G5QsGo6VEc2hIyVPZ1rdKwee/7E9oeBBZFpE8Z44ekxsDQBqbiWGW5ShOhUSQ==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-win32-arm64@2.4.13': + resolution: {integrity: sha512-Px9PS2B5/Q183bUwy/5VHqp3J2lzdOCeVGzMpphYfl8oSa7VDCqenBdqWpy6DCy/en4Rbf/Y1RieZF6dJPcc9A==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@2.4.13': + resolution: {integrity: sha512-tTcMkXyBrmHi9BfrD2VNHs/5rYIUKETqsBlYOvSAABwBkJhSDVb5e7wPukftsQbO3WzQkXe6kaztC6WtUOXSoQ==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + '@changesets/apply-release-plan@7.1.1': resolution: {integrity: sha512-9qPCm/rLx/xoOFXIHGB229+4GOL76S4MC+7tyOuTsR6+1jYlfFDQORdvwR5hDA6y4FL2BPt3qpbcQIS+dW85LA==} @@ -567,37 +613,6 @@ packages: cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.9.1': - resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - - '@eslint-community/regexpp@4.12.2': - resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - - '@eslint/eslintrc@2.1.4': - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - '@eslint/js@8.57.1': - resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - '@humanwhocodes/config-array@0.13.0': - resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} - engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead - - '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - - '@humanwhocodes/object-schema@2.0.3': - resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} - deprecated: Use @eslint/object-schema instead - '@iconify-json/simple-icons@1.2.79': resolution: {integrity: sha512-aNyO7Fd1qej9oQfIyohYFRv0lhQLaZ+6UkK1c1qwax0MDPUOZOdq65MlU500kow97pD/W+b2u1And3e25eE24Q==} @@ -998,64 +1013,6 @@ packages: '@types/web-bluetooth@0.0.21': resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} - '@typescript-eslint/eslint-plugin@7.18.0': - resolution: {integrity: sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - '@typescript-eslint/parser': ^7.0.0 - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/parser@7.18.0': - resolution: {integrity: sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/scope-manager@7.18.0': - resolution: {integrity: sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==} - engines: {node: ^18.18.0 || >=20.0.0} - - '@typescript-eslint/type-utils@7.18.0': - resolution: {integrity: sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/types@7.18.0': - resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==} - engines: {node: ^18.18.0 || >=20.0.0} - - '@typescript-eslint/typescript-estree@7.18.0': - resolution: {integrity: sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/utils@7.18.0': - resolution: {integrity: sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - eslint: ^8.56.0 - - '@typescript-eslint/visitor-keys@7.18.0': - resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} - engines: {node: ^18.18.0 || >=20.0.0} - '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} @@ -1169,11 +1126,6 @@ packages: '@vueuse/shared@12.8.2': resolution: {integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==} - acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn-walk@8.3.5: resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} engines: {node: '>=0.4.0'} @@ -1183,9 +1135,6 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - ajv@6.14.0: - resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} - algoliasearch@5.50.2: resolution: {integrity: sha512-Tfp26yoNWurUjfgK4GOrVJQhSNXu9tJtHfFFNosgT2YClG+vPyUjX/gbC8rG39qLncnZg8Fj34iarQWpMkqefw==} engines: {node: '>= 14.0.0'} @@ -1226,9 +1175,6 @@ packages: assertion-error@1.1.0: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} @@ -1236,12 +1182,6 @@ packages: birpc@2.9.0: resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} - brace-expansion@1.1.14: - resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} - - brace-expansion@2.1.0: - resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} - braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -1256,10 +1196,6 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -1267,10 +1203,6 @@ packages: resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==} engines: {node: '>=4'} - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - chalk@5.6.2: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} @@ -1321,9 +1253,6 @@ packages: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} @@ -1355,9 +1284,6 @@ packages: resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} engines: {node: '>=6'} - deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -1377,10 +1303,6 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} - doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - emoji-regex-xs@1.0.0: resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} @@ -1408,54 +1330,20 @@ packages: engines: {node: '>=18'} hasBin: true - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - - eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint@8.57.1: - resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. - hasBin: true - - espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} hasBin: true - esquery@1.7.0: - resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} - engines: {node: '>=0.10'} - - esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - - estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} + eta@3.5.0: + resolution: {integrity: sha512-e3x3FBvGzeCIHhF+zhK8FZA2vC5uFn6b4HJjegUbIWrDb4mJ7JjTGMJY9VGIbRVpmSwHopNiaJibhjIr+HfLug==} + engines: {node: '>=6.0.0'} execa@8.0.1: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} @@ -1468,19 +1356,10 @@ packages: extendable-error@0.1.7: resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} - fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - - fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} @@ -1497,10 +1376,6 @@ packages: resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} engines: {node: '>=18'} - file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} - fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -1509,20 +1384,9 @@ packages: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - fix-dts-default-cjs-exports@1.0.1: resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} - flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} - - flatted@3.4.2: - resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} - focus-trap@7.8.0: resolution: {integrity: sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==} @@ -1538,9 +1402,6 @@ packages: resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} engines: {node: '>=6 <7 || >=8'} - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1568,18 +1429,6 @@ packages: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} - glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - - globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} - globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} @@ -1587,13 +1436,6 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - hast-util-to-html@9.0.5: resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} @@ -1631,21 +1473,6 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - import-fresh@3.3.1: - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} - engines: {node: '>=6'} - - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -1666,10 +1493,6 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - is-plain-obj@4.1.0: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} @@ -1720,28 +1543,12 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true - json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - - json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - - json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} jsonfile@6.2.1: resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} - keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - - levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -1761,13 +1568,6 @@ packages: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - lodash.startcase@4.4.0: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} @@ -1821,13 +1621,6 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} - minimatch@3.1.5: - resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - - minimatch@9.0.9: - resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} - engines: {node: '>=16 || 14 >=14.17'} - minisearch@7.2.0: resolution: {integrity: sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==} @@ -1856,9 +1649,6 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - npm-run-path@5.3.0: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1871,9 +1661,6 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - onetime@6.0.0: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} @@ -1885,10 +1672,6 @@ packages: oniguruma-to-es@3.1.1: resolution: {integrity: sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==} - optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} - ora@8.2.0: resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==} engines: {node: '>=18'} @@ -1904,10 +1687,6 @@ packages: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - p-limit@5.0.0: resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} engines: {node: '>=18'} @@ -1916,10 +1695,6 @@ packages: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - p-map@2.1.0: resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} engines: {node: '>=6'} @@ -1931,10 +1706,6 @@ packages: package-manager-detector@0.2.11: resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - parse-ms@4.0.0: resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} engines: {node: '>=18'} @@ -1943,10 +1714,6 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -2018,20 +1785,11 @@ packages: preact@10.29.1: resolution: {integrity: sha512-gQCLc/vWroE8lIpleXtdJhTFDogTdZG9AjMUpVkDf2iTCNwYNWA+u16dL41TqUDJO4gm2IgrcMv3uTpjd4Pwmg==} - prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - prettier@2.8.8: resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} engines: {node: '>=10.13.0'} hasBin: true - prettier@3.8.3: - resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} - engines: {node: '>=14'} - hasBin: true - pretty-format@29.7.0: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2043,10 +1801,6 @@ packages: property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} @@ -2073,10 +1827,6 @@ packages: regex@6.1.0: resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} @@ -2095,11 +1845,6 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - rollup@4.60.2: resolution: {integrity: sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -2203,10 +1948,6 @@ packages: resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} engines: {node: '>=18'} - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - strip-literal@2.1.1: resolution: {integrity: sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==} @@ -2219,10 +1960,6 @@ packages: resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==} engines: {node: '>=16'} - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - tabbable@6.4.0: resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==} @@ -2230,9 +1967,6 @@ packages: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} - text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} @@ -2269,12 +2003,6 @@ packages: trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} - ts-api-utils@1.4.3: - resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} - engines: {node: '>=16'} - peerDependencies: - typescript: '>=4.2.0' - ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -2306,18 +2034,10 @@ packages: resolution: {integrity: sha512-+v2QJey7ZUeUiuigkU+uFfklvNUyPI2VO2vBpMYJA+a1hKFLFiKtUYlRHdb3P9CrAvMzi0upbjI4WT+zKtqkBg==} hasBin: true - type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - type-detect@4.1.0: resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} engines: {node: '>=4'} - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - typescript@5.5.3: resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==} engines: {node: '>=14.17'} @@ -2356,9 +2076,6 @@ packages: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} - uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - vfile-message@4.0.3: resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} @@ -2456,21 +2173,10 @@ packages: engines: {node: '>=8'} hasBin: true - word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} - wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - yocto-queue@1.2.2: resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} engines: {node: '>=12.20'} @@ -2615,6 +2321,41 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@biomejs/biome@2.4.13': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 2.4.13 + '@biomejs/cli-darwin-x64': 2.4.13 + '@biomejs/cli-linux-arm64': 2.4.13 + '@biomejs/cli-linux-arm64-musl': 2.4.13 + '@biomejs/cli-linux-x64': 2.4.13 + '@biomejs/cli-linux-x64-musl': 2.4.13 + '@biomejs/cli-win32-arm64': 2.4.13 + '@biomejs/cli-win32-x64': 2.4.13 + + '@biomejs/cli-darwin-arm64@2.4.13': + optional: true + + '@biomejs/cli-darwin-x64@2.4.13': + optional: true + + '@biomejs/cli-linux-arm64-musl@2.4.13': + optional: true + + '@biomejs/cli-linux-arm64@2.4.13': + optional: true + + '@biomejs/cli-linux-x64-musl@2.4.13': + optional: true + + '@biomejs/cli-linux-x64@2.4.13': + optional: true + + '@biomejs/cli-win32-arm64@2.4.13': + optional: true + + '@biomejs/cli-win32-x64@2.4.13': + optional: true + '@changesets/apply-release-plan@7.1.1': dependencies: '@changesets/config': 3.1.4 @@ -2929,41 +2670,6 @@ snapshots: '@esbuild/win32-x64@0.27.7': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)': - dependencies: - eslint: 8.57.1 - eslint-visitor-keys: 3.4.3 - - '@eslint-community/regexpp@4.12.2': {} - - '@eslint/eslintrc@2.1.4': - dependencies: - ajv: 6.14.0 - debug: 4.4.3 - espree: 9.6.1 - globals: 13.24.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.1.1 - minimatch: 3.1.5 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - '@eslint/js@8.57.1': {} - - '@humanwhocodes/config-array@0.13.0': - dependencies: - '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.3 - minimatch: 3.1.5 - transitivePeerDependencies: - - supports-color - - '@humanwhocodes/module-importer@1.0.1': {} - - '@humanwhocodes/object-schema@2.0.3': {} - '@iconify-json/simple-icons@1.2.79': dependencies: '@iconify/types': 2.0.0 @@ -3318,87 +3024,6 @@ snapshots: '@types/web-bluetooth@0.0.21': {} - '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1)(typescript@5.5.3)': - dependencies: - '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.5.3) - '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.1)(typescript@5.5.3) - '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.5.3) - '@typescript-eslint/visitor-keys': 7.18.0 - eslint: 8.57.1 - graphemer: 1.4.0 - ignore: 5.3.2 - natural-compare: 1.4.0 - ts-api-utils: 1.4.3(typescript@5.5.3) - optionalDependencies: - typescript: 5.5.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.5.3)': - dependencies: - '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.3) - '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.4.3 - eslint: 8.57.1 - optionalDependencies: - typescript: 5.5.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/scope-manager@7.18.0': - dependencies: - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/visitor-keys': 7.18.0 - - '@typescript-eslint/type-utils@7.18.0(eslint@8.57.1)(typescript@5.5.3)': - dependencies: - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.3) - '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.5.3) - debug: 4.4.3 - eslint: 8.57.1 - ts-api-utils: 1.4.3(typescript@5.5.3) - optionalDependencies: - typescript: 5.5.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/types@7.18.0': {} - - '@typescript-eslint/typescript-estree@7.18.0(typescript@5.5.3)': - dependencies: - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.4.3 - globby: 11.1.0 - is-glob: 4.0.3 - minimatch: 9.0.9 - semver: 7.7.4 - ts-api-utils: 1.4.3(typescript@5.5.3) - optionalDependencies: - typescript: 5.5.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@7.18.0(eslint@8.57.1)(typescript@5.5.3)': - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) - '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.3) - eslint: 8.57.1 - transitivePeerDependencies: - - supports-color - - typescript - - '@typescript-eslint/visitor-keys@7.18.0': - dependencies: - '@typescript-eslint/types': 7.18.0 - eslint-visitor-keys: 3.4.3 - '@ungap/structured-clone@1.3.0': {} '@vitejs/plugin-vue@5.2.4(vite@5.4.21(@types/node@20.19.39))(vue@3.5.33(typescript@5.5.3))': @@ -3534,23 +3159,12 @@ snapshots: transitivePeerDependencies: - typescript - acorn-jsx@5.3.2(acorn@8.16.0): - dependencies: - acorn: 8.16.0 - acorn-walk@8.3.5: dependencies: acorn: 8.16.0 acorn@8.16.0: {} - ajv@6.14.0: - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - algoliasearch@5.50.2: dependencies: '@algolia/abtesting': 1.16.2 @@ -3592,23 +3206,12 @@ snapshots: assertion-error@1.1.0: {} - balanced-match@1.0.2: {} - better-path-resolve@1.0.0: dependencies: is-windows: 1.0.2 birpc@2.9.0: {} - brace-expansion@1.1.14: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - brace-expansion@2.1.0: - dependencies: - balanced-match: 1.0.2 - braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -3620,8 +3223,6 @@ snapshots: cac@6.7.14: {} - callsites@3.1.0: {} - ccount@2.0.1: {} chai@4.5.0: @@ -3634,11 +3235,6 @@ snapshots: pathval: 1.1.1 type-detect: 4.1.0 - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - chalk@5.6.2: {} character-entities-html4@2.1.0: {} @@ -3675,8 +3271,6 @@ snapshots: commander@4.1.1: {} - concat-map@0.0.1: {} - confbox@0.1.8: {} consola@3.4.2: {} @@ -3701,8 +3295,6 @@ snapshots: dependencies: type-detect: 4.1.0 - deep-is@0.1.4: {} - dequal@2.0.3: {} detect-indent@6.1.0: {} @@ -3717,10 +3309,6 @@ snapshots: dependencies: path-type: 4.0.0 - doctrine@3.0.0: - dependencies: - esutils: 2.0.3 - emoji-regex-xs@1.0.0: {} emoji-regex@10.6.0: {} @@ -3789,83 +3377,15 @@ snapshots: '@esbuild/win32-ia32': 0.27.7 '@esbuild/win32-x64': 0.27.7 - escape-string-regexp@4.0.0: {} - - eslint-scope@7.2.2: - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - - eslint-visitor-keys@3.4.3: {} - - eslint@8.57.1: - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) - '@eslint-community/regexpp': 4.12.2 - '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.57.1 - '@humanwhocodes/config-array': 0.13.0 - '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.3.0 - ajv: 6.14.0 - chalk: 4.1.2 - cross-spawn: 7.0.6 - debug: 4.4.3 - doctrine: 3.0.0 - escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - esquery: 1.7.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 - find-up: 5.0.0 - glob-parent: 6.0.2 - globals: 13.24.0 - graphemer: 1.4.0 - ignore: 5.3.2 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-yaml: 4.1.1 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.5 - natural-compare: 1.4.0 - optionator: 0.9.4 - strip-ansi: 6.0.1 - text-table: 0.2.0 - transitivePeerDependencies: - - supports-color - - espree@9.6.1: - dependencies: - acorn: 8.16.0 - acorn-jsx: 5.3.2(acorn@8.16.0) - eslint-visitor-keys: 3.4.3 - esprima@4.0.1: {} - esquery@1.7.0: - dependencies: - estraverse: 5.3.0 - - esrecurse@4.3.0: - dependencies: - estraverse: 5.3.0 - - estraverse@5.3.0: {} - estree-walker@2.0.2: {} estree-walker@3.0.3: dependencies: '@types/estree': 1.0.8 - esutils@2.0.3: {} + eta@3.5.0: {} execa@8.0.1: dependencies: @@ -3896,8 +3416,6 @@ snapshots: extendable-error@0.1.7: {} - fast-deep-equal@3.1.3: {} - fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3906,10 +3424,6 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 - fast-json-stable-stringify@2.1.0: {} - - fast-levenshtein@2.0.6: {} - fastq@1.20.1: dependencies: reusify: 1.1.0 @@ -3922,10 +3436,6 @@ snapshots: dependencies: is-unicode-supported: 2.1.0 - file-entry-cache@6.0.1: - dependencies: - flat-cache: 3.2.0 - fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -3935,25 +3445,12 @@ snapshots: locate-path: 5.0.0 path-exists: 4.0.0 - find-up@5.0.0: - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - fix-dts-default-cjs-exports@1.0.1: dependencies: magic-string: 0.30.21 mlly: 1.8.2 rollup: 4.60.2 - flat-cache@3.2.0: - dependencies: - flatted: 3.4.2 - keyv: 4.5.4 - rimraf: 3.0.2 - - flatted@3.4.2: {} - focus-trap@7.8.0: dependencies: tabbable: 6.4.0 @@ -3976,8 +3473,6 @@ snapshots: jsonfile: 4.0.0 universalify: 0.1.2 - fs.realpath@1.0.0: {} - fsevents@2.3.3: optional: true @@ -4000,23 +3495,6 @@ snapshots: dependencies: is-glob: 4.0.3 - glob-parent@6.0.2: - dependencies: - is-glob: 4.0.3 - - glob@7.2.3: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.5 - once: 1.4.0 - path-is-absolute: 1.0.1 - - globals@13.24.0: - dependencies: - type-fest: 0.20.2 - globby@11.1.0: dependencies: array-union: 2.1.0 @@ -4028,10 +3506,6 @@ snapshots: graceful-fs@4.2.11: {} - graphemer@1.4.0: {} - - has-flag@4.0.0: {} - hast-util-to-html@9.0.5: dependencies: '@types/hast': 3.0.4 @@ -4068,20 +3542,6 @@ snapshots: ignore@5.3.2: {} - import-fresh@3.3.1: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - - imurmurhash@0.1.4: {} - - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - - inherits@2.0.4: {} - is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -4094,8 +3554,6 @@ snapshots: is-number@7.0.0: {} - is-path-inside@3.0.3: {} - is-plain-obj@4.1.0: {} is-stream@3.0.0: {} @@ -4129,12 +3587,6 @@ snapshots: dependencies: argparse: 2.0.1 - json-buffer@3.0.1: {} - - json-schema-traverse@0.4.1: {} - - json-stable-stringify-without-jsonify@1.0.1: {} - jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 @@ -4145,15 +3597,6 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 - keyv@4.5.4: - dependencies: - json-buffer: 3.0.1 - - levn@0.4.1: - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} @@ -4169,12 +3612,6 @@ snapshots: dependencies: p-locate: 4.1.0 - locate-path@6.0.0: - dependencies: - p-locate: 5.0.0 - - lodash.merge@4.6.2: {} - lodash.startcase@4.4.0: {} log-symbols@6.0.0: @@ -4234,14 +3671,6 @@ snapshots: mimic-function@5.0.1: {} - minimatch@3.1.5: - dependencies: - brace-expansion: 1.1.14 - - minimatch@9.0.9: - dependencies: - brace-expansion: 2.1.0 - minisearch@7.2.0: {} mitt@3.0.1: {} @@ -4267,8 +3696,6 @@ snapshots: nanoid@3.3.11: {} - natural-compare@1.4.0: {} - npm-run-path@5.3.0: dependencies: path-key: 4.0.0 @@ -4280,10 +3707,6 @@ snapshots: object-assign@4.1.1: {} - once@1.4.0: - dependencies: - wrappy: 1.0.2 - onetime@6.0.0: dependencies: mimic-fn: 4.0.0 @@ -4298,15 +3721,6 @@ snapshots: regex: 6.1.0 regex-recursion: 6.0.2 - optionator@0.9.4: - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - word-wrap: 1.2.5 - ora@8.2.0: dependencies: chalk: 5.6.2 @@ -4329,10 +3743,6 @@ snapshots: dependencies: p-try: 2.2.0 - p-limit@3.1.0: - dependencies: - yocto-queue: 0.1.0 - p-limit@5.0.0: dependencies: yocto-queue: 1.2.2 @@ -4341,10 +3751,6 @@ snapshots: dependencies: p-limit: 2.3.0 - p-locate@5.0.0: - dependencies: - p-limit: 3.1.0 - p-map@2.1.0: {} p-try@2.2.0: {} @@ -4353,16 +3759,10 @@ snapshots: dependencies: quansync: 0.2.11 - parent-module@1.0.1: - dependencies: - callsites: 3.1.0 - parse-ms@4.0.0: {} path-exists@4.0.0: {} - path-is-absolute@1.0.1: {} - path-key@3.1.1: {} path-key@4.0.0: {} @@ -4408,12 +3808,8 @@ snapshots: preact@10.29.1: {} - prelude-ls@1.2.1: {} - prettier@2.8.8: {} - prettier@3.8.3: {} - pretty-format@29.7.0: dependencies: '@jest/schemas': 29.6.3 @@ -4426,8 +3822,6 @@ snapshots: property-information@7.1.0: {} - punycode@2.3.1: {} - quansync@0.2.11: {} queue-microtask@1.2.3: {} @@ -4453,8 +3847,6 @@ snapshots: dependencies: regex-utilities: 2.3.0 - resolve-from@4.0.0: {} - resolve-from@5.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -4468,10 +3860,6 @@ snapshots: rfdc@1.4.1: {} - rimraf@3.0.2: - dependencies: - glob: 7.2.3 - rollup@4.60.2: dependencies: '@types/estree': 1.0.8 @@ -4588,8 +3976,6 @@ snapshots: strip-final-newline@4.0.0: {} - strip-json-comments@3.1.1: {} - strip-literal@2.1.1: dependencies: js-tokens: 9.0.1 @@ -4608,16 +3994,10 @@ snapshots: dependencies: copy-anything: 4.0.5 - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - tabbable@6.4.0: {} term-size@2.2.1: {} - text-table@0.2.0: {} - thenify-all@1.6.0: dependencies: thenify: 3.3.1 @@ -4647,10 +4027,6 @@ snapshots: trim-lines@3.0.1: {} - ts-api-utils@1.4.3(typescript@5.5.3): - dependencies: - typescript: 5.5.3 - ts-interface-checker@0.1.13: {} tsup@8.5.1(postcss@8.5.10)(tsx@4.21.0)(typescript@5.5.3): @@ -4697,14 +4073,8 @@ snapshots: '@turbo/windows-64': 2.9.6 '@turbo/windows-arm64': 2.9.6 - type-check@0.4.0: - dependencies: - prelude-ls: 1.2.1 - type-detect@4.1.0: {} - type-fest@0.20.2: {} - typescript@5.5.3: {} ufo@1.6.3: {} @@ -4740,10 +4110,6 @@ snapshots: universalify@2.0.1: {} - uri-js@4.4.1: - dependencies: - punycode: 2.3.1 - vfile-message@4.0.3: dependencies: '@types/unist': 3.0.3 @@ -4883,18 +4249,12 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 - word-wrap@1.2.5: {} - wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - wrappy@1.0.2: {} - - yocto-queue@0.1.0: {} - yocto-queue@1.2.2: {} yoctocolors-cjs@2.1.3: {} diff --git a/turbo.json b/turbo.json index febf947..3d5d5d7 100644 --- a/turbo.json +++ b/turbo.json @@ -18,7 +18,8 @@ "dependsOn": ["^lint"] }, "test": { - "dependsOn": ["build"] + "dependsOn": ["build"], + "cache": false }, "dev": { "cache": false, From b4864fad7e1e091aa6ea08e4a25c5442eef37674 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 27 Apr 2026 17:23:49 +0000 Subject: [PATCH 02/16] chore(release): version packages [skip ci] --- .changeset/easy-pans-wink.md | 7 ------- .changeset/shy-papers-peel.md | 5 ----- .changeset/tidy-mammals-stay.md | 6 ------ packages/create-express-forge/CHANGELOG.md | 10 ++++++++++ packages/create-express-forge/package.json | 2 +- packages/lint-config/CHANGELOG.md | 8 ++++++++ packages/lint-config/package.json | 2 +- packages/typescript-config/CHANGELOG.md | 8 ++++++++ packages/typescript-config/package.json | 2 +- 9 files changed, 29 insertions(+), 21 deletions(-) delete mode 100644 .changeset/easy-pans-wink.md delete mode 100644 .changeset/shy-papers-peel.md delete mode 100644 .changeset/tidy-mammals-stay.md create mode 100644 packages/lint-config/CHANGELOG.md create mode 100644 packages/typescript-config/CHANGELOG.md diff --git a/.changeset/easy-pans-wink.md b/.changeset/easy-pans-wink.md deleted file mode 100644 index 3aa5569..0000000 --- a/.changeset/easy-pans-wink.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"create-express-forge": major -"@repo/lint-config": minor -"@repo/typescript-config": minor ---- - -### ๐Ÿš€ Core CLI & Codebase Modernization diff --git a/.changeset/shy-papers-peel.md b/.changeset/shy-papers-peel.md deleted file mode 100644 index db38820..0000000 --- a/.changeset/shy-papers-peel.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"create-express-forge": major ---- - -### ๐Ÿš€ Core CLI & Codebase Modernization- **Unified Tooling**: Migrated the entire monorepo to **Biome 2.4**, achieving sub-100ms linting and formatting.- **Generator Refactor**: Re-architected the generator logic into modular, testable components (Base, Structure, Features).- **Pro Testing Suite**: - Separated internal logic tests (`main.test.ts`) from CLI E2E tests (`smoke.integration.test.ts`). - Disabled test caching in Turborepo to ensure 100% reliable smoke runs.- **Modern Templates**: - Updated to **Express 5** (native async error handling) and **Prisma 6**. - Implemented professional **multi-stage Docker builds** (Node 20 Alpine). - Integrated **Biome** as the default linter/formatter for all generated projects.- **Production Hardening**: Added graceful shutdown handlers (SIGTERM/SIGINT) and fail-fast database connection checks on startup.- **Infrastructure**: Renamed and modernized shared monorepo configs (`@repo/lint-config`). diff --git a/.changeset/tidy-mammals-stay.md b/.changeset/tidy-mammals-stay.md deleted file mode 100644 index 54be252..0000000 --- a/.changeset/tidy-mammals-stay.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@repo/lint-config": minor -"@repo/typescript-config": minor ---- - -Internal refactor diff --git a/packages/create-express-forge/CHANGELOG.md b/packages/create-express-forge/CHANGELOG.md index 3f1fde5..9af3f2b 100644 --- a/packages/create-express-forge/CHANGELOG.md +++ b/packages/create-express-forge/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 4.0.0 + +### Major Changes + +- d617dbf: ### ๐Ÿš€ Core CLI & Codebase Modernization +- d617dbf: ### ๐Ÿš€ Core CLI & Codebase Modernization- **Unified Tooling**: Migrated the entire monorepo to **Biome 2.4**, achieving sub-100ms linting and formatting.- **Generator Refactor**: Re-architected the generator logic into modular, testable components (Base, Structure, Features).- **Pro Testing Suite**: - Separated internal logic tests (`main.test.ts`) from CLI E2E tests (`smoke.integration.test.ts`). - Disabled test caching in Turborepo to ensure 100% reliable smoke runs.- **Modern Templates**: - Updated to **Express 5** (native async error handling) and **Prisma 6**. - Implemented professional **multi-stage Docker builds** (Node 20 Alpine). - Integrated **Biome** as the default linter/formatter for all generated projects.- **Production Hardening**: Added graceful shutdown handlers (SIGTERM/SIGINT) and fail-fast database connection checks on startup.- **Infrastructure**: Renamed and modernized shared monorepo configs (`@repo/lint-config`). + ## 3.3.2 ### Patch Changes @@ -32,17 +39,20 @@ ### Major Changes - e1e588f: ## Added + - Support for multiple package managers (npm, pnpm, yarn, bun). - JWT authentication with HttpOnly Cookie and Header options. - Swagger UI integration for interactive API documentation. ## Fixed + - Minor fixes and improvements in the CLI scaffolding process. - Support for multiple package managers (npm, pnpm, yarn, bun). - JWT authentication with HttpOnly Cookie and Header options. - Swagger UI integration for interactive API documentation. ## Fixed + - Minor fixes and improvements in the CLI scaffolding process. ## 2.0.0 diff --git a/packages/create-express-forge/package.json b/packages/create-express-forge/package.json index ebd2305..c4520db 100644 --- a/packages/create-express-forge/package.json +++ b/packages/create-express-forge/package.json @@ -1,6 +1,6 @@ { "name": "create-express-forge", - "version": "3.3.2", + "version": "4.0.0", "description": "โšก Scaffold production-ready Express.js TypeScript backends in seconds", "license": "MIT", "type": "module", diff --git a/packages/lint-config/CHANGELOG.md b/packages/lint-config/CHANGELOG.md new file mode 100644 index 0000000..7990100 --- /dev/null +++ b/packages/lint-config/CHANGELOG.md @@ -0,0 +1,8 @@ +# @repo/lint-config + +## 0.1.0 + +### Minor Changes + +- d617dbf: ### ๐Ÿš€ Core CLI & Codebase Modernization +- d617dbf: Internal refactor diff --git a/packages/lint-config/package.json b/packages/lint-config/package.json index c3d8db3..111aa64 100644 --- a/packages/lint-config/package.json +++ b/packages/lint-config/package.json @@ -1,6 +1,6 @@ { "name": "@repo/lint-config", - "version": "0.0.0", + "version": "0.1.0", "private": true, "files": [ "biome.base.json" diff --git a/packages/typescript-config/CHANGELOG.md b/packages/typescript-config/CHANGELOG.md new file mode 100644 index 0000000..e5347f5 --- /dev/null +++ b/packages/typescript-config/CHANGELOG.md @@ -0,0 +1,8 @@ +# @repo/typescript-config + +## 0.1.0 + +### Minor Changes + +- d617dbf: ### ๐Ÿš€ Core CLI & Codebase Modernization +- d617dbf: Internal refactor diff --git a/packages/typescript-config/package.json b/packages/typescript-config/package.json index 3c3fc92..b01697d 100644 --- a/packages/typescript-config/package.json +++ b/packages/typescript-config/package.json @@ -1,6 +1,6 @@ { "name": "@repo/typescript-config", - "version": "0.0.0", + "version": "0.1.0", "private": true, "license": "MIT" } From 3f3aa6854765082c21b00c8dfee44a6e4d5ec34d Mon Sep 17 00:00:00 2001 From: code-y02 Date: Mon, 27 Apr 2026 23:13:53 +0530 Subject: [PATCH 03/16] refactor: replace ApiResponse class with functional helpers, add Biome configuration, and improve type safety and linting tests. --- .changeset/moody-papayas-fail.md | 5 +++ .../src/generator/base.ts | 4 +++ .../templates/base/biome.json.eta | 33 +++++++++++++++++++ .../base/src/utils/ApiResponse.ts.eta | 32 +++++++++++------- .../features/auth/jwt-auth.middleware.ts.eta | 2 +- .../modules/auth/auth.controller.ts.eta | 12 ++++--- .../modules/health/health.controller.ts.eta | 4 +-- .../modules/todos/todos.controller.ts.eta | 12 +++---- .../mvc/controllers/auth.controller.ts.eta | 12 ++++--- .../mvc/controllers/health.controller.ts.eta | 4 +-- .../mvc/controllers/todo.controller.ts.eta | 12 +++---- .../tests/smoke.integration.test.ts | 8 +++++ 12 files changed, 104 insertions(+), 36 deletions(-) create mode 100644 .changeset/moody-papayas-fail.md create mode 100644 packages/create-express-forge/templates/base/biome.json.eta diff --git a/.changeset/moody-papayas-fail.md b/.changeset/moody-papayas-fail.md new file mode 100644 index 0000000..c7a3ca6 --- /dev/null +++ b/.changeset/moody-papayas-fail.md @@ -0,0 +1,5 @@ +--- +"create-express-forge": minor +--- + +fix linting issues with package diff --git a/packages/create-express-forge/src/generator/base.ts b/packages/create-express-forge/src/generator/base.ts index 8ad0c38..e7669dd 100644 --- a/packages/create-express-forge/src/generator/base.ts +++ b/packages/create-express-forge/src/generator/base.ts @@ -14,6 +14,10 @@ export async function generateBaseFiles( "base/tsconfig.json.eta", path.join(dir, "tsconfig.json"), ); + await tmpl.renderTemplateFile( + "base/biome.json.eta", + path.join(dir, "biome.json"), + ); await tmpl.renderTemplateFile( "base/.gitignore.eta", path.join(dir, ".gitignore"), diff --git a/packages/create-express-forge/templates/base/biome.json.eta b/packages/create-express-forge/templates/base/biome.json.eta new file mode 100644 index 0000000..abfab65 --- /dev/null +++ b/packages/create-express-forge/templates/base/biome.json.eta @@ -0,0 +1,33 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.4.13/schema.json", + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "style": { + "noNonNullAssertion": "warn" + }, + "correctness": { + "noUnusedVariables": "warn" + } + } + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 80 + }, + "javascript": { + "formatter": { + "quoteStyle": "double", + "trailingCommas": "all" + } + }, + "files": { + "ignore": ["dist/**", "node_modules/**"] + } +} diff --git a/packages/create-express-forge/templates/base/src/utils/ApiResponse.ts.eta b/packages/create-express-forge/templates/base/src/utils/ApiResponse.ts.eta index 437e027..3bfe5c6 100644 --- a/packages/create-express-forge/templates/base/src/utils/ApiResponse.ts.eta +++ b/packages/create-express-forge/templates/base/src/utils/ApiResponse.ts.eta @@ -1,14 +1,24 @@ import type { Response } from 'express'; -export class ApiResponse { - static success(res: Response, data: T, message = 'Success', statusCode = 200) { - return res.status(statusCode).json({ success: true, message, data }); - } - static created(res: Response, data: T, message = 'Created') { - return ApiResponse.success(res, data, message, 201); - } - static noContent(res: Response) { return res.status(204).send(); } - static paginated(res: Response, data: T[], pagination: { total: number; page: number; limit: number; pages: number }, message = 'Success') { - return res.status(200).json({ success: true, message, data, pagination }); - } +export interface Pagination { + total: number; + page: number; + limit: number; + pages: number; +} + +export function sendSuccess(res: Response, data: T, message = 'Success', statusCode = 200) { + return res.status(statusCode).json({ success: true, message, data }); +} + +export function sendCreated(res: Response, data: T, message = 'Created') { + return sendSuccess(res, data, message, 201); +} + +export function sendNoContent(res: Response) { + return res.status(204).send(); +} + +export function sendPaginated(res: Response, data: T[], pagination: Pagination, message = 'Success') { + return res.status(200).json({ success: true, message, data, pagination }); } diff --git a/packages/create-express-forge/templates/features/auth/jwt-auth.middleware.ts.eta b/packages/create-express-forge/templates/features/auth/jwt-auth.middleware.ts.eta index 5786854..3c91815 100644 --- a/packages/create-express-forge/templates/features/auth/jwt-auth.middleware.ts.eta +++ b/packages/create-express-forge/templates/features/auth/jwt-auth.middleware.ts.eta @@ -21,7 +21,7 @@ export const auth = (req: Request, _res: Response, next: NextFunction) => { try { const decoded = jwt.verify(token, env.JWT_SECRET); - req.user = decoded as any; + req.user = decoded as { id: string; email: string; role: string }; next(); } catch (err) { next(ApiError.unauthorized('Invalid or expired token')); diff --git a/packages/create-express-forge/templates/structure/modular/modules/auth/auth.controller.ts.eta b/packages/create-express-forge/templates/structure/modular/modules/auth/auth.controller.ts.eta index 5b02504..ba9e582 100644 --- a/packages/create-express-forge/templates/structure/modular/modules/auth/auth.controller.ts.eta +++ b/packages/create-express-forge/templates/structure/modular/modules/auth/auth.controller.ts.eta @@ -1,16 +1,20 @@ import type { Request, Response } from 'express'; import jwt from 'jsonwebtoken'; -import { ApiResponse } from '../../utils/ApiResponse.js'; +import { sendSuccess } from '../../utils/ApiResponse.js'; import { env } from '../../config/env.js'; export const login = async (req: Request, res: Response) => { // Demo logic: accept any valid email/password - const token = jwt.sign({ email: req.body.email }, env.JWT_SECRET, { expiresIn: env.JWT_EXPIRES_IN as any }); + const token = jwt.sign( + { email: req.body.email }, + env.JWT_SECRET, + { expiresIn: env.JWT_EXPIRES_IN as jwt.SignOptions['expiresIn'] } + ); <% if (it.jwtStorage === 'cookie') { %> res.cookie('token', token, { httpOnly: true, secure: env.NODE_ENV === 'production' }); - return ApiResponse.success(res, { token }, 'Logged in successfully'); + return sendSuccess(res, { token }, 'Logged in successfully'); <% } else { %> - return ApiResponse.success(res, { token }, 'Logged in successfully'); + return sendSuccess(res, { token }, 'Logged in successfully'); <% } %> }; diff --git a/packages/create-express-forge/templates/structure/modular/modules/health/health.controller.ts.eta b/packages/create-express-forge/templates/structure/modular/modules/health/health.controller.ts.eta index bb9396d..468def9 100644 --- a/packages/create-express-forge/templates/structure/modular/modules/health/health.controller.ts.eta +++ b/packages/create-express-forge/templates/structure/modular/modules/health/health.controller.ts.eta @@ -1,5 +1,5 @@ import type { Request, Response } from 'express'; -import { ApiResponse } from '../../utils/ApiResponse.js'; +import { sendSuccess } from '../../utils/ApiResponse.js'; export const getHealth = (_req: Request, res: Response) => - ApiResponse.success(res, { status: 'ok', timestamp: new Date().toISOString() }, 'Service healthy'); + sendSuccess(res, { status: 'ok', timestamp: new Date().toISOString() }, 'Service healthy'); diff --git a/packages/create-express-forge/templates/structure/modular/modules/todos/todos.controller.ts.eta b/packages/create-express-forge/templates/structure/modular/modules/todos/todos.controller.ts.eta index a5d54db..0710a16 100644 --- a/packages/create-express-forge/templates/structure/modular/modules/todos/todos.controller.ts.eta +++ b/packages/create-express-forge/templates/structure/modular/modules/todos/todos.controller.ts.eta @@ -1,16 +1,16 @@ import type { Request, Response } from 'express'; -import { ApiResponse } from '../../utils/ApiResponse.js'; +import { sendSuccess, sendCreated, sendNoContent } from '../../utils/ApiResponse.js'; import { TodosService } from './todos.service.js'; import type { CreateTodoDto, UpdateTodoDto } from './todos.schema.js'; export const getTodos = async (req: Request, res: Response) => { const todos = await TodosService.findAll(<% if (it.auth !== 'none') { %>req.user?.id || 'guest'<% } %>); - return ApiResponse.success(res, todos, 'Todos fetched'); + return sendSuccess(res, todos, 'Todos fetched'); }; export const getTodoById = async (req: Request, res: Response) => { const todo = await TodosService.findById(req.params.id as string<% if (it.auth !== 'none') { %>, req.user?.id || 'guest'<% } %>); - return ApiResponse.success(res, todo); + return sendSuccess(res, todo); }; export const createTodo = async (req: Request, res: Response) => { @@ -19,7 +19,7 @@ export const createTodo = async (req: Request, res: Response) => { completed: false<% if (it.auth !== 'none') { %>, userId: req.user?.id || 'guest'<% } %> }); - return ApiResponse.created(res, todo, 'Todo created'); + return sendCreated(res, todo, 'Todo created'); }; export const updateTodo = async (req: Request, res: Response) => { @@ -28,10 +28,10 @@ export const updateTodo = async (req: Request, res: Response) => { <% if (it.auth !== 'none') { %>req.user?.id || 'guest', <% } %> req.body as UpdateTodoDto ); - return ApiResponse.success(res, todo, 'Todo updated'); + return sendSuccess(res, todo, 'Todo updated'); }; export const deleteTodo = async (req: Request, res: Response) => { await TodosService.remove(req.params.id as string<% if (it.auth !== 'none') { %>, req.user?.id || 'guest'<% } %>); - return ApiResponse.noContent(res); + return sendNoContent(res); }; diff --git a/packages/create-express-forge/templates/structure/mvc/controllers/auth.controller.ts.eta b/packages/create-express-forge/templates/structure/mvc/controllers/auth.controller.ts.eta index 233e5ab..a60716a 100644 --- a/packages/create-express-forge/templates/structure/mvc/controllers/auth.controller.ts.eta +++ b/packages/create-express-forge/templates/structure/mvc/controllers/auth.controller.ts.eta @@ -1,16 +1,20 @@ import type { Request, Response } from 'express'; import jwt from 'jsonwebtoken'; -import { ApiResponse } from '../utils/ApiResponse.js'; +import { sendSuccess } from '../utils/ApiResponse.js'; import { env } from '../config/env.js'; export const login = async (req: Request, res: Response) => { // Demo logic: accept any valid email/password - const token = jwt.sign({ email: req.body.email }, env.JWT_SECRET, { expiresIn: env.JWT_EXPIRES_IN as any }); + const token = jwt.sign( + { email: req.body.email }, + env.JWT_SECRET, + { expiresIn: env.JWT_EXPIRES_IN as jwt.SignOptions['expiresIn'] } + ); <% if (it.jwtStorage === 'cookie') { %> res.cookie('token', token, { httpOnly: true, secure: env.NODE_ENV === 'production' }); - return ApiResponse.success(res, { token }, 'Logged in successfully'); + return sendSuccess(res, { token }, 'Logged in successfully'); <% } else { %> - return ApiResponse.success(res, { token }, 'Logged in successfully'); + return sendSuccess(res, { token }, 'Logged in successfully'); <% } %> }; diff --git a/packages/create-express-forge/templates/structure/mvc/controllers/health.controller.ts.eta b/packages/create-express-forge/templates/structure/mvc/controllers/health.controller.ts.eta index 07e4d6e..c1bdf16 100644 --- a/packages/create-express-forge/templates/structure/mvc/controllers/health.controller.ts.eta +++ b/packages/create-express-forge/templates/structure/mvc/controllers/health.controller.ts.eta @@ -1,5 +1,5 @@ import type { Request, Response } from 'express'; -import { ApiResponse } from '../utils/ApiResponse.js'; +import { sendSuccess } from '../utils/ApiResponse.js'; export const getHealth = (_req: Request, res: Response) => - ApiResponse.success(res, { status: 'ok', timestamp: new Date().toISOString() }, 'Service healthy'); + sendSuccess(res, { status: 'ok', timestamp: new Date().toISOString() }, 'Service healthy'); diff --git a/packages/create-express-forge/templates/structure/mvc/controllers/todo.controller.ts.eta b/packages/create-express-forge/templates/structure/mvc/controllers/todo.controller.ts.eta index 18851d7..5d6c4a2 100644 --- a/packages/create-express-forge/templates/structure/mvc/controllers/todo.controller.ts.eta +++ b/packages/create-express-forge/templates/structure/mvc/controllers/todo.controller.ts.eta @@ -1,23 +1,23 @@ import type { Request, Response } from 'express'; -import { ApiResponse } from '../utils/ApiResponse.js'; +import { sendSuccess, sendCreated, sendNoContent } from '../utils/ApiResponse.js'; import { TodoService } from '../services/todo.service.js'; import type { CreateTodoDto, UpdateTodoDto } from '../schemas/todo.schema.js'; export const getTodos = async (req: Request, res: Response) => - ApiResponse.success(res, await TodoService.findAll(<% if (it.auth !== 'none') { %>req.user?.id || 'guest'<% } %>), 'Todos fetched'); + sendSuccess(res, await TodoService.findAll(<% if (it.auth !== 'none') { %>req.user?.id || 'guest'<% } %>), 'Todos fetched'); export const getTodoById = async (req: Request, res: Response) => - ApiResponse.success(res, await TodoService.findById(req.params.id as string<% if (it.auth !== 'none') { %>, req.user?.id || 'guest'<% } %>)); + sendSuccess(res, await TodoService.findById(req.params.id as string<% if (it.auth !== 'none') { %>, req.user?.id || 'guest'<% } %>)); export const createTodo = async (req: Request, res: Response) => - ApiResponse.created(res, await TodoService.create({ + sendCreated(res, await TodoService.create({ ...(req.body as CreateTodoDto), completed: false<% if (it.auth !== 'none') { %>, userId: req.user?.id || 'guest'<% } %> }), 'Todo created'); export const updateTodo = async (req: Request, res: Response) => - ApiResponse.success(res, await TodoService.update( + sendSuccess(res, await TodoService.update( req.params.id as string, <% if (it.auth !== 'none') { %>req.user?.id || 'guest', <% } %> req.body as UpdateTodoDto @@ -25,5 +25,5 @@ export const updateTodo = async (req: Request, res: Response) => export const deleteTodo = async (req: Request, res: Response) => { await TodoService.remove(req.params.id as string<% if (it.auth !== 'none') { %>, req.user?.id || 'guest'<% } %>); - return ApiResponse.noContent(res); + return sendNoContent(res); }; diff --git a/packages/create-express-forge/tests/smoke.integration.test.ts b/packages/create-express-forge/tests/smoke.integration.test.ts index f6307a0..fea6d1d 100644 --- a/packages/create-express-forge/tests/smoke.integration.test.ts +++ b/packages/create-express-forge/tests/smoke.integration.test.ts @@ -77,4 +77,12 @@ describe("CLI Integration (Smoke Test)", () => { const build = await execa("npm", ["run", "build"], { cwd: targetPath }); expect(build.exitCode).toBe(0); }, 240000); // 4 minutes for build + typecheck + + it("should pass linting checks", async () => { + const targetPath = path.join(testDir, projectName); + + console.log("Running lint check in generated project..."); + const lint = await execa("npm", ["run", "lint"], { cwd: targetPath }); + expect(lint.exitCode).toBe(0); + }, 60000); }); From dd8f600c37ff6e58d7137b742c1ad297cc4af217 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 27 Apr 2026 17:45:54 +0000 Subject: [PATCH 04/16] chore(release): version packages [skip ci] --- .changeset/moody-papayas-fail.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/moody-papayas-fail.md diff --git a/.changeset/moody-papayas-fail.md b/.changeset/moody-papayas-fail.md deleted file mode 100644 index c7a3ca6..0000000 --- a/.changeset/moody-papayas-fail.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"create-express-forge": minor ---- - -fix linting issues with package diff --git a/packages/create-express-forge/CHANGELOG.md b/packages/create-express-forge/CHANGELOG.md index 9af3f2b..2f9beb0 100644 --- a/packages/create-express-forge/CHANGELOG.md +++ b/packages/create-express-forge/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 4.1.0 + +### Minor Changes + +- 3f3aa68: fix linting issues with package + ## 4.0.0 ### Major Changes diff --git a/packages/create-express-forge/package.json b/packages/create-express-forge/package.json index c4520db..63d7692 100644 --- a/packages/create-express-forge/package.json +++ b/packages/create-express-forge/package.json @@ -1,6 +1,6 @@ { "name": "create-express-forge", - "version": "4.0.0", + "version": "4.1.0", "description": "โšก Scaffold production-ready Express.js TypeScript backends in seconds", "license": "MIT", "type": "module", From 3752d64137521358f42fd48ac9be7450d66ec5bb Mon Sep 17 00:00:00 2001 From: code-y02 Date: Mon, 27 Apr 2026 23:30:53 +0530 Subject: [PATCH 05/16] feat: add Redis support, improve database connection resilience, and update Docker Compose configuration --- .changeset/stale-clouds-tease.md | 5 +++++ .../templates/base/src/server.ts.eta | 20 +++++++++++++++---- .../features/docker/docker-compose.yml.eta | 20 ++++++++++++++----- 3 files changed, 36 insertions(+), 9 deletions(-) create mode 100644 .changeset/stale-clouds-tease.md diff --git a/.changeset/stale-clouds-tease.md b/.changeset/stale-clouds-tease.md new file mode 100644 index 0000000..285fb0c --- /dev/null +++ b/.changeset/stale-clouds-tease.md @@ -0,0 +1,5 @@ +--- +"create-express-forge": patch +--- + +fix build issues diff --git a/packages/create-express-forge/templates/base/src/server.ts.eta b/packages/create-express-forge/templates/base/src/server.ts.eta index 3676d10..ac07c0a 100644 --- a/packages/create-express-forge/templates/base/src/server.ts.eta +++ b/packages/create-express-forge/templates/base/src/server.ts.eta @@ -12,12 +12,24 @@ try { <% if (it.logger !== 'none') { %>logger.info('โœ… Database connected');<% } %> } catch (err) { <% if (it.logger !== 'none') { %>logger.error('โŒ Database connection failed', err);<% } %> - process.exit(1); + if (env.NODE_ENV === 'production') { + process.exit(1); + } + <% if (it.logger !== 'none') { %>logger.warn('โš ๏ธ Continuing in development mode without database');<% } %> } <% } %> -<% if (it.orm === 'sequelize') { %> -import { connectDB } from './config/database.js'; -await connectDB(); + +<% if (it.cache === 'redis') { %> +import { connectRedis } from './cache/index.js'; +try { + await connectRedis(); +} catch (err) { + <% if (it.logger !== 'none') { %>logger.error('โŒ Redis connection failed', err);<% } %> + if (env.NODE_ENV === 'production') { + process.exit(1); + } + <% if (it.logger !== 'none') { %>logger.warn('โš ๏ธ Continuing in development mode without Redis');<% } %> +} <% } %> const server = app.listen(env.PORT, () => { diff --git a/packages/create-express-forge/templates/features/docker/docker-compose.yml.eta b/packages/create-express-forge/templates/features/docker/docker-compose.yml.eta index 21adf5a..a725e24 100644 --- a/packages/create-express-forge/templates/features/docker/docker-compose.yml.eta +++ b/packages/create-express-forge/templates/features/docker/docker-compose.yml.eta @@ -1,4 +1,3 @@ -version: '3.8' services: api: build: . @@ -15,9 +14,15 @@ services: <% if (it.cache === 'redis') { %> - REDIS_URL=redis://redis:6379 <% } %> +<% if ((it.database !== 'sqlite' && it.database !== 'none') || it.cache === 'redis') { %> depends_on: -<% if (it.database !== 'sqlite' && it.database !== 'none') { %> - db<% } %> -<% if (it.cache === 'redis') { %> - redis<% } %> +<% if (it.database !== 'sqlite' && it.database !== 'none') { %> + - db +<% } %> +<% if (it.cache === 'redis') { %> + - redis +<% } %> +<% } %> <% if (it.database === 'postgresql') { %> db: @@ -51,6 +56,11 @@ services: - "6379:6379" <% } %> +<% if (it.database !== 'sqlite' && it.database !== 'none') { %> volumes: -<% if (it.database === 'postgresql') { %> pgdata:<% } %> -<% if (it.database === 'mysql') { %> mysqldata:<% } %> +<% if (it.database === 'postgresql') { %> + pgdata: +<% } else if (it.database === 'mysql') { %> + mysqldata: +<% } %> +<% } %> From f184c05090e8b1f68276c2b7b94747459732e42c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 27 Apr 2026 18:03:19 +0000 Subject: [PATCH 06/16] chore(release): version packages [skip ci] --- .changeset/stale-clouds-tease.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/stale-clouds-tease.md diff --git a/.changeset/stale-clouds-tease.md b/.changeset/stale-clouds-tease.md deleted file mode 100644 index 285fb0c..0000000 --- a/.changeset/stale-clouds-tease.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"create-express-forge": patch ---- - -fix build issues diff --git a/packages/create-express-forge/CHANGELOG.md b/packages/create-express-forge/CHANGELOG.md index 2f9beb0..406a279 100644 --- a/packages/create-express-forge/CHANGELOG.md +++ b/packages/create-express-forge/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 4.1.1 + +### Patch Changes + +- 3752d64: fix build issues + ## 4.1.0 ### Minor Changes diff --git a/packages/create-express-forge/package.json b/packages/create-express-forge/package.json index 63d7692..a13fed6 100644 --- a/packages/create-express-forge/package.json +++ b/packages/create-express-forge/package.json @@ -1,6 +1,6 @@ { "name": "create-express-forge", - "version": "4.1.0", + "version": "4.1.1", "description": "โšก Scaffold production-ready Express.js TypeScript backends in seconds", "license": "MIT", "type": "module", From 4ffc258e3d73bc277e09f162c1cc2956c37a3dcf Mon Sep 17 00:00:00 2001 From: code-y02 Date: Tue, 28 Apr 2026 00:25:44 +0530 Subject: [PATCH 07/16] feat: integrate zod-to-openapi for automated documentation and remove JSDoc-based OpenAPI definitions --- .changeset/stale-clouds-tease.md | 12 ++++ .../src/generator/features/openapi.ts | 4 ++ .../src/generator/index.ts | 2 +- .../src/generator/structure/modular.ts | 4 ++ .../src/generator/structure/mvc.ts | 4 ++ .../src/utils/package-builder.ts | 1 + .../templates/base/biome.json.eta | 3 - .../templates/base/src/app.ts.eta | 2 +- .../templates/base/src/server.ts.eta | 6 ++ .../features/openapi/registry.ts.eta | 3 + .../templates/features/openapi/swagger.ts.eta | 29 ++++----- .../modular/modules/auth/auth.routes.ts.eta | 22 ------- .../modular/modules/auth/auth.schema.ts.eta | 39 ++++++++++++ .../modules/health/health.routes.ts.eta | 12 +--- .../modules/health/health.schema.ts.eta | 35 +++++++++++ .../modular/modules/todos/todos.routes.ts.eta | 28 --------- .../modular/modules/todos/todos.schema.ts.eta | 61 +++++++++++++++++++ .../structure/mvc/routes/auth.routes.ts.eta | 22 ------- .../structure/mvc/routes/health.routes.ts.eta | 12 +--- .../structure/mvc/routes/todo.routes.ts.eta | 30 +-------- .../structure/mvc/schemas/auth.schema.ts.eta | 39 ++++++++++++ .../mvc/schemas/health.schema.ts.eta | 35 +++++++++++ .../structure/mvc/schemas/todo.schema.ts.eta | 61 +++++++++++++++++++ 23 files changed, 324 insertions(+), 142 deletions(-) create mode 100644 .changeset/stale-clouds-tease.md create mode 100644 packages/create-express-forge/templates/features/openapi/registry.ts.eta create mode 100644 packages/create-express-forge/templates/structure/modular/modules/health/health.schema.ts.eta create mode 100644 packages/create-express-forge/templates/structure/mvc/schemas/health.schema.ts.eta diff --git a/.changeset/stale-clouds-tease.md b/.changeset/stale-clouds-tease.md new file mode 100644 index 0000000..ebde1f3 --- /dev/null +++ b/.changeset/stale-clouds-tease.md @@ -0,0 +1,12 @@ +--- +"create-express-forge": patch +--- + +- **Zod-to-OpenAPI Integration**: Implemented automated, DRY documentation using `@asteasolutions/zod-to-openapi`. Removed all clunky `@openapi` JSDoc comments from route files in favor of pure TypeScript path registration. +- **Unified API Paths**: Standardized all routes and health checks under `/api/v1/` prefix. +- **Improved DX**: Added automatic logging of the API Documentation URL on server startup. +- **Biome 2.4 Integration**: Migrated the monorepo and generated templates to Biome for 20x faster linting and formatting. Templates now pin `@biomejs/biome@2.4.13` for consistent DX across environments. +- **"Pro" Fail-Fast Logic**: Implemented strict connection checks for DB/Redis in production with warnings in development. +- **Template Hardening**: Fixed multiple linting errors in generated templates (auth controllers, ApiResponse, and middleware). +- **Docker Fix**: Resolved `depends_on` formatting and removed obsolete version tags in `docker-compose.yml`. +- **Smoke Test Hardening**: Added mandatory linting checks to the generator test suite. diff --git a/packages/create-express-forge/src/generator/features/openapi.ts b/packages/create-express-forge/src/generator/features/openapi.ts index 9ed455a..5556185 100644 --- a/packages/create-express-forge/src/generator/features/openapi.ts +++ b/packages/create-express-forge/src/generator/features/openapi.ts @@ -14,4 +14,8 @@ export async function generateOpenApi( "features/openapi/swagger.ts.eta", path.join(docsDir, "swagger.ts"), ); + await tmpl.renderTemplateFile( + "features/openapi/registry.ts.eta", + path.join(docsDir, "registry.ts"), + ); } diff --git a/packages/create-express-forge/src/generator/index.ts b/packages/create-express-forge/src/generator/index.ts index c536ad6..8ddef02 100644 --- a/packages/create-express-forge/src/generator/index.ts +++ b/packages/create-express-forge/src/generator/index.ts @@ -113,7 +113,7 @@ export async function generateProject( try { await execa( "npx", - ["--yes", "@biomejs/biome", "format", "--write", "."], + ["--yes", "@biomejs/biome@2.4.13", "format", "--write", "."], { cwd: targetDir }, ); spinner.succeed(chalk.green("Code polished and formatted")); diff --git a/packages/create-express-forge/src/generator/structure/modular.ts b/packages/create-express-forge/src/generator/structure/modular.ts index c24795c..a712977 100644 --- a/packages/create-express-forge/src/generator/structure/modular.ts +++ b/packages/create-express-forge/src/generator/structure/modular.ts @@ -17,6 +17,10 @@ export async function generateModularStructure( } // Health module + await tmpl.renderTemplateFile( + "structure/modular/modules/health/health.schema.ts.eta", + path.join(src, "modules", "health", "health.schema.ts"), + ); await tmpl.renderTemplateFile( "structure/modular/modules/health/health.routes.ts.eta", path.join(src, "modules", "health", "health.routes.ts"), diff --git a/packages/create-express-forge/src/generator/structure/mvc.ts b/packages/create-express-forge/src/generator/structure/mvc.ts index 44f9a04..32d98c3 100644 --- a/packages/create-express-forge/src/generator/structure/mvc.ts +++ b/packages/create-express-forge/src/generator/structure/mvc.ts @@ -16,6 +16,10 @@ export async function generateMvcStructure( ); } + await tmpl.renderTemplateFile( + "structure/mvc/schemas/health.schema.ts.eta", + path.join(src, "schemas", "health.schema.ts"), + ); await tmpl.renderTemplateFile( "structure/mvc/schemas/todo.schema.ts.eta", path.join(src, "schemas", "todo.schema.ts"), diff --git a/packages/create-express-forge/src/utils/package-builder.ts b/packages/create-express-forge/src/utils/package-builder.ts index afb1946..c992055 100644 --- a/packages/create-express-forge/src/utils/package-builder.ts +++ b/packages/create-express-forge/src/utils/package-builder.ts @@ -89,6 +89,7 @@ export function buildPackageJson(opts: CliOptions): PackageJson { if (openapi) { deps["swagger-jsdoc"] = "^6.2.8"; deps["swagger-ui-express"] = "^5.0.1"; + deps["@asteasolutions/zod-to-openapi"] = "^7.3.0"; devDeps["@types/swagger-jsdoc"] = "^6.0.4"; devDeps["@types/swagger-ui-express"] = "^4.1.6"; } diff --git a/packages/create-express-forge/templates/base/biome.json.eta b/packages/create-express-forge/templates/base/biome.json.eta index abfab65..3b680af 100644 --- a/packages/create-express-forge/templates/base/biome.json.eta +++ b/packages/create-express-forge/templates/base/biome.json.eta @@ -1,8 +1,5 @@ { "$schema": "https://biomejs.dev/schemas/2.4.13/schema.json", - "organizeImports": { - "enabled": true - }, "linter": { "enabled": true, "rules": { diff --git a/packages/create-express-forge/templates/base/src/app.ts.eta b/packages/create-express-forge/templates/base/src/app.ts.eta index d20b69d..1ecba59 100644 --- a/packages/create-express-forge/templates/base/src/app.ts.eta +++ b/packages/create-express-forge/templates/base/src/app.ts.eta @@ -51,7 +51,7 @@ setupSwagger(app); <% } %> <% if (it.pattern === 'modular') { %> -app.use('/api/health', healthRouter); +app.use('/api/v1/health', healthRouter); app.use('/api/v1/todos', todosRouter); <% if (it.auth !== 'none') { %> app.use('/api/v1/auth', authRouter); diff --git a/packages/create-express-forge/templates/base/src/server.ts.eta b/packages/create-express-forge/templates/base/src/server.ts.eta index ac07c0a..58533a4 100644 --- a/packages/create-express-forge/templates/base/src/server.ts.eta +++ b/packages/create-express-forge/templates/base/src/server.ts.eta @@ -35,8 +35,14 @@ try { const server = app.listen(env.PORT, () => { <% if (it.logger !== 'none') { %> logger.info(`๐Ÿš€ Server running on port ${env.PORT} in ${env.NODE_ENV} mode`); + <% if (it.openapi) { %> + logger.info(`๐Ÿ“– API Documentation available at http://localhost:${env.PORT}/api-docs`); + <% } %> <% } else { %> console.log(`๐Ÿš€ Server running on port ${env.PORT} in ${env.NODE_ENV} mode`); + <% if (it.openapi) { %> + console.log(`๐Ÿ“– API Documentation available at http://localhost:${env.PORT}/api-docs`); + <% } %> <% } %> }); diff --git a/packages/create-express-forge/templates/features/openapi/registry.ts.eta b/packages/create-express-forge/templates/features/openapi/registry.ts.eta new file mode 100644 index 0000000..7f369e9 --- /dev/null +++ b/packages/create-express-forge/templates/features/openapi/registry.ts.eta @@ -0,0 +1,3 @@ +import { OpenAPIRegistry } from '@asteasolutions/zod-to-openapi'; + +export const registry = new OpenAPIRegistry(); diff --git a/packages/create-express-forge/templates/features/openapi/swagger.ts.eta b/packages/create-express-forge/templates/features/openapi/swagger.ts.eta index 442620a..0fa6d06 100644 --- a/packages/create-express-forge/templates/features/openapi/swagger.ts.eta +++ b/packages/create-express-forge/templates/features/openapi/swagger.ts.eta @@ -1,9 +1,20 @@ -import swaggerJsdoc from 'swagger-jsdoc'; +import { OpenApiGeneratorV3 } from '@asteasolutions/zod-to-openapi'; import swaggerUi from 'swagger-ui-express'; import type { Express } from 'express'; +import { registry } from './registry.js'; -const options: swaggerJsdoc.Options = { - definition: { +export function setupSwagger(app: Express) { +<% if (it.auth === 'jwt') { %> + registry.registerComponent('securitySchemes', 'bearerAuth', { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + }); +<% } %> + + const generator = new OpenApiGeneratorV3(registry.definitions); + + const swaggerSpec = generator.generateDocument({ openapi: '3.0.0', info: { title: '<%= it.projectName %> API', @@ -12,20 +23,10 @@ const options: swaggerJsdoc.Options = { }, servers: [{ url: '/api/v1' }], <% if (it.auth === 'jwt') { %> - components: { - securitySchemes: { - bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' } - } - }, security: [{ bearerAuth: [] }] <% } %> - }, - apis: ['./src/routes/*.ts', './src/modules/**/*.routes.ts', './src/controllers/*.ts'], -}; - -const swaggerSpec = swaggerJsdoc(options); + }); -export function setupSwagger(app: Express) { app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec)); app.get('/api-docs.json', (req, res) => { res.setHeader('Content-Type', 'application/json'); diff --git a/packages/create-express-forge/templates/structure/modular/modules/auth/auth.routes.ts.eta b/packages/create-express-forge/templates/structure/modular/modules/auth/auth.routes.ts.eta index b298dda..578ae40 100644 --- a/packages/create-express-forge/templates/structure/modular/modules/auth/auth.routes.ts.eta +++ b/packages/create-express-forge/templates/structure/modular/modules/auth/auth.routes.ts.eta @@ -5,28 +5,6 @@ import { loginSchema } from './auth.schema.js'; const router = Router(); -/** - * @openapi - * /api/v1/auth/login: - * post: - * tags: - * - Auth - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: [email, password] - * properties: - * email: - * type: string - * password: - * type: string - * responses: - * 200: - * description: Logged in successfully - */ router.post('/login', validate(loginSchema), login); export { router as authRouter }; diff --git a/packages/create-express-forge/templates/structure/modular/modules/auth/auth.schema.ts.eta b/packages/create-express-forge/templates/structure/modular/modules/auth/auth.schema.ts.eta index 2a7b752..05bb54c 100644 --- a/packages/create-express-forge/templates/structure/modular/modules/auth/auth.schema.ts.eta +++ b/packages/create-express-forge/templates/structure/modular/modules/auth/auth.schema.ts.eta @@ -1,4 +1,43 @@ import { z } from 'zod'; +<% if (it.openapi) { %> +import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi'; +import { registry } from '../../docs/registry.js'; + +extendZodWithOpenApi(z); + +export const AuthSchema = registry.register('Auth', z.object({ + token: z.string(), +})); + +registry.registerPath({ + method: 'post', + path: '/auth/login', + tags: ['Auth'], + summary: 'Login to get a JWT token', + request: { + body: { + content: { + 'application/json': { + schema: z.object({ + email: z.string().email(), + password: z.string().min(8), + }), + }, + }, + }, + }, + responses: { + 200: { + description: 'Login successful', + content: { + 'application/json': { + schema: AuthSchema, + }, + }, + }, + }, +}); +<% } %> export const loginSchema = z.object({ body: z.object({ diff --git a/packages/create-express-forge/templates/structure/modular/modules/health/health.routes.ts.eta b/packages/create-express-forge/templates/structure/modular/modules/health/health.routes.ts.eta index ca895e2..a1fba6c 100644 --- a/packages/create-express-forge/templates/structure/modular/modules/health/health.routes.ts.eta +++ b/packages/create-express-forge/templates/structure/modular/modules/health/health.routes.ts.eta @@ -1,19 +1,9 @@ import { Router } from 'express'; import { getHealth } from './health.controller.js'; +import './health.schema.js'; const router = Router(); -/** - * @openapi - * /api/health: - * get: - * tags: - * - Health - * description: Responds if the app is up and running - * responses: - * 200: - * description: App is up and running - */ router.get('/', getHealth); export { router as healthRouter }; diff --git a/packages/create-express-forge/templates/structure/modular/modules/health/health.schema.ts.eta b/packages/create-express-forge/templates/structure/modular/modules/health/health.schema.ts.eta new file mode 100644 index 0000000..9c40dff --- /dev/null +++ b/packages/create-express-forge/templates/structure/modular/modules/health/health.schema.ts.eta @@ -0,0 +1,35 @@ +import { z } from 'zod'; +<% if (it.openapi) { %> +import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi'; +import { registry } from '../../docs/registry.js'; + +extendZodWithOpenApi(z); + +registry.registerPath({ + method: 'get', + path: '/health', + tags: ['Health'], + summary: 'Health check endpoint', + description: 'Responds if the app is up and running', + responses: { + 200: { + description: 'App is up and running', + content: { + 'application/json': { + schema: z.object({ + success: z.boolean(), + message: z.string(), + timestamp: z.string(), + }), + }, + }, + }, + }, +}); +<% } %> + +export const healthResponseSchema = z.object({ + success: z.boolean(), + message: z.string(), + timestamp: z.string(), +}); diff --git a/packages/create-express-forge/templates/structure/modular/modules/todos/todos.routes.ts.eta b/packages/create-express-forge/templates/structure/modular/modules/todos/todos.routes.ts.eta index ea030c1..7ef58b3 100644 --- a/packages/create-express-forge/templates/structure/modular/modules/todos/todos.routes.ts.eta +++ b/packages/create-express-forge/templates/structure/modular/modules/todos/todos.routes.ts.eta @@ -8,34 +8,6 @@ const router = Router(); <% if (it.auth !== 'none') { %>router.use(auth);<% } %> -/** - * @openapi - * /api/v1/todos: - * get: - * tags: - * - Todos - * responses: - * 200: - * description: Success - * post: - * tags: - * - Todos - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: [title] - * properties: - * title: - * type: string - * description: - * type: string - * responses: - * 201: - * description: Created - */ router.get('/', getTodos); router.get('/:id', getTodoById); router.post('/', validate(createTodoSchema), createTodo); diff --git a/packages/create-express-forge/templates/structure/modular/modules/todos/todos.schema.ts.eta b/packages/create-express-forge/templates/structure/modular/modules/todos/todos.schema.ts.eta index c861cdb..679cf3c 100644 --- a/packages/create-express-forge/templates/structure/modular/modules/todos/todos.schema.ts.eta +++ b/packages/create-express-forge/templates/structure/modular/modules/todos/todos.schema.ts.eta @@ -1,4 +1,65 @@ import { z } from 'zod'; +<% if (it.openapi) { %> +import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi'; +import { registry } from '../../docs/registry.js'; + +extendZodWithOpenApi(z); + +export const TodoSchema = registry.register('Todo', z.object({ + id: z.string().uuid(), + title: z.string().min(1).max(100), + description: z.string().max(500).optional(), + completed: z.boolean(), + createdAt: z.date(), + updatedAt: z.date(), +})); + +registry.registerPath({ + method: 'get', + path: '/todos', + tags: ['Todos'], + summary: 'Get all todos', + responses: { + 200: { + description: 'List of todos', + content: { + 'application/json': { + schema: z.array(TodoSchema), + }, + }, + }, + }, +}); + +registry.registerPath({ + method: 'post', + path: '/todos', + tags: ['Todos'], + summary: 'Create a new todo', + request: { + body: { + content: { + 'application/json': { + schema: z.object({ + title: z.string().min(1).max(100), + description: z.string().max(500).optional(), + }), + }, + }, + }, + }, + responses: { + 201: { + description: 'Todo created', + content: { + 'application/json': { + schema: TodoSchema, + }, + }, + }, + }, +}); +<% } %> export const createTodoSchema = z.object({ body: z.object({ diff --git a/packages/create-express-forge/templates/structure/mvc/routes/auth.routes.ts.eta b/packages/create-express-forge/templates/structure/mvc/routes/auth.routes.ts.eta index e9c101f..cd4c3a4 100644 --- a/packages/create-express-forge/templates/structure/mvc/routes/auth.routes.ts.eta +++ b/packages/create-express-forge/templates/structure/mvc/routes/auth.routes.ts.eta @@ -5,28 +5,6 @@ import { loginSchema } from '../schemas/auth.schema.js'; const router = Router(); -/** - * @openapi - * /api/v1/auth/login: - * post: - * tags: - * - Auth - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: [email, password] - * properties: - * email: - * type: string - * password: - * type: string - * responses: - * 200: - * description: Logged in successfully - */ router.post('/login', validate(loginSchema), login); export { router as authRouter }; diff --git a/packages/create-express-forge/templates/structure/mvc/routes/health.routes.ts.eta b/packages/create-express-forge/templates/structure/mvc/routes/health.routes.ts.eta index 9332f45..e27288d 100644 --- a/packages/create-express-forge/templates/structure/mvc/routes/health.routes.ts.eta +++ b/packages/create-express-forge/templates/structure/mvc/routes/health.routes.ts.eta @@ -1,19 +1,9 @@ import { Router } from 'express'; import { getHealth } from '../controllers/health.controller.js'; +import '../schemas/health.schema.js'; const router = Router(); -/** - * @openapi - * /api/health: - * get: - * tags: - * - Health - * description: Responds if the app is up and running - * responses: - * 200: - * description: App is up and running - */ router.get('/', getHealth); export { router as healthRouter }; diff --git a/packages/create-express-forge/templates/structure/mvc/routes/todo.routes.ts.eta b/packages/create-express-forge/templates/structure/mvc/routes/todo.routes.ts.eta index de4cea9..fef2e58 100644 --- a/packages/create-express-forge/templates/structure/mvc/routes/todo.routes.ts.eta +++ b/packages/create-express-forge/templates/structure/mvc/routes/todo.routes.ts.eta @@ -1,41 +1,13 @@ import { Router } from 'express'; import { getTodos, getTodoById, createTodo, updateTodo, deleteTodo } from '../controllers/todo.controller.js'; import { validate } from '../middleware/validate.js'; -import { createTodoSchema, updateTodoSchema } from '../schemas/todo.schema.ts'; +import { createTodoSchema, updateTodoSchema } from '../schemas/todo.schema.js'; <% if (it.auth !== 'none') { %>import { auth } from '../middleware/auth.middleware.js';<% } %> const router = Router(); <% if (it.auth !== 'none') { %>router.use(auth);<% } %> -/** - * @openapi - * /api/v1/todos: - * get: - * tags: - * - Todos - * responses: - * 200: - * description: Success - * post: - * tags: - * - Todos - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: [title] - * properties: - * title: - * type: string - * description: - * type: string - * responses: - * 201: - * description: Created - */ router.get('/', getTodos); router.get('/:id', getTodoById); router.post('/', validate(createTodoSchema), createTodo); diff --git a/packages/create-express-forge/templates/structure/mvc/schemas/auth.schema.ts.eta b/packages/create-express-forge/templates/structure/mvc/schemas/auth.schema.ts.eta index 2a7b752..0a68a8e 100644 --- a/packages/create-express-forge/templates/structure/mvc/schemas/auth.schema.ts.eta +++ b/packages/create-express-forge/templates/structure/mvc/schemas/auth.schema.ts.eta @@ -1,4 +1,43 @@ import { z } from 'zod'; +<% if (it.openapi) { %> +import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi'; +import { registry } from '../docs/registry.js'; + +extendZodWithOpenApi(z); + +export const AuthSchema = registry.register('Auth', z.object({ + token: z.string(), +})); + +registry.registerPath({ + method: 'post', + path: '/auth/login', + tags: ['Auth'], + summary: 'Login to get a JWT token', + request: { + body: { + content: { + 'application/json': { + schema: z.object({ + email: z.string().email(), + password: z.string().min(8), + }), + }, + }, + }, + }, + responses: { + 200: { + description: 'Login successful', + content: { + 'application/json': { + schema: AuthSchema, + }, + }, + }, + }, +}); +<% } %> export const loginSchema = z.object({ body: z.object({ diff --git a/packages/create-express-forge/templates/structure/mvc/schemas/health.schema.ts.eta b/packages/create-express-forge/templates/structure/mvc/schemas/health.schema.ts.eta new file mode 100644 index 0000000..444ebca --- /dev/null +++ b/packages/create-express-forge/templates/structure/mvc/schemas/health.schema.ts.eta @@ -0,0 +1,35 @@ +import { z } from 'zod'; +<% if (it.openapi) { %> +import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi'; +import { registry } from '../docs/registry.js'; + +extendZodWithOpenApi(z); + +registry.registerPath({ + method: 'get', + path: '/health', + tags: ['Health'], + summary: 'Health check endpoint', + description: 'Responds if the app is up and running', + responses: { + 200: { + description: 'App is up and running', + content: { + 'application/json': { + schema: z.object({ + success: z.boolean(), + message: z.string(), + timestamp: z.string(), + }), + }, + }, + }, + }, +}); +<% } %> + +export const healthResponseSchema = z.object({ + success: z.boolean(), + message: z.string(), + timestamp: z.string(), +}); diff --git a/packages/create-express-forge/templates/structure/mvc/schemas/todo.schema.ts.eta b/packages/create-express-forge/templates/structure/mvc/schemas/todo.schema.ts.eta index ce29481..f73af1e 100644 --- a/packages/create-express-forge/templates/structure/mvc/schemas/todo.schema.ts.eta +++ b/packages/create-express-forge/templates/structure/mvc/schemas/todo.schema.ts.eta @@ -1,4 +1,65 @@ import { z } from 'zod'; +<% if (it.openapi) { %> +import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi'; +import { registry } from '../docs/registry.js'; + +extendZodWithOpenApi(z); + +export const TodoSchema = registry.register('Todo', z.object({ + id: z.string().uuid(), + title: z.string().min(1).max(100), + description: z.string().max(500).optional(), + completed: z.boolean(), + createdAt: z.date(), + updatedAt: z.date(), +})); + +registry.registerPath({ + method: 'get', + path: '/todos', + tags: ['Todos'], + summary: 'Get all todos', + responses: { + 200: { + description: 'List of todos', + content: { + 'application/json': { + schema: z.array(TodoSchema), + }, + }, + }, + }, +}); + +registry.registerPath({ + method: 'post', + path: '/todos', + tags: ['Todos'], + summary: 'Create a new todo', + request: { + body: { + content: { + 'application/json': { + schema: z.object({ + title: z.string().min(1).max(100), + description: z.string().max(500).optional(), + }), + }, + }, + }, + }, + responses: { + 201: { + description: 'Todo created', + content: { + 'application/json': { + schema: TodoSchema, + }, + }, + }, + }, +}); +<% } %> export const createTodoSchema = z.object({ body: z.object({ From 1db5e369e0fbb875af812b1355b88d13dc029a54 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 27 Apr 2026 18:58:02 +0000 Subject: [PATCH 08/16] chore(release): version packages [skip ci] --- .changeset/stale-clouds-tease.md | 12 ------------ packages/create-express-forge/CHANGELOG.md | 13 +++++++++++++ packages/create-express-forge/package.json | 2 +- 3 files changed, 14 insertions(+), 13 deletions(-) delete mode 100644 .changeset/stale-clouds-tease.md diff --git a/.changeset/stale-clouds-tease.md b/.changeset/stale-clouds-tease.md deleted file mode 100644 index ebde1f3..0000000 --- a/.changeset/stale-clouds-tease.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -"create-express-forge": patch ---- - -- **Zod-to-OpenAPI Integration**: Implemented automated, DRY documentation using `@asteasolutions/zod-to-openapi`. Removed all clunky `@openapi` JSDoc comments from route files in favor of pure TypeScript path registration. -- **Unified API Paths**: Standardized all routes and health checks under `/api/v1/` prefix. -- **Improved DX**: Added automatic logging of the API Documentation URL on server startup. -- **Biome 2.4 Integration**: Migrated the monorepo and generated templates to Biome for 20x faster linting and formatting. Templates now pin `@biomejs/biome@2.4.13` for consistent DX across environments. -- **"Pro" Fail-Fast Logic**: Implemented strict connection checks for DB/Redis in production with warnings in development. -- **Template Hardening**: Fixed multiple linting errors in generated templates (auth controllers, ApiResponse, and middleware). -- **Docker Fix**: Resolved `depends_on` formatting and removed obsolete version tags in `docker-compose.yml`. -- **Smoke Test Hardening**: Added mandatory linting checks to the generator test suite. diff --git a/packages/create-express-forge/CHANGELOG.md b/packages/create-express-forge/CHANGELOG.md index 406a279..cf998fd 100644 --- a/packages/create-express-forge/CHANGELOG.md +++ b/packages/create-express-forge/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 4.1.2 + +### Patch Changes + +- 4ffc258: - **Zod-to-OpenAPI Integration**: Implemented automated, DRY documentation using `@asteasolutions/zod-to-openapi`. Removed all clunky `@openapi` JSDoc comments from route files in favor of pure TypeScript path registration. + - **Unified API Paths**: Standardized all routes and health checks under `/api/v1/` prefix. + - **Improved DX**: Added automatic logging of the API Documentation URL on server startup. + - **Biome 2.4 Integration**: Migrated the monorepo and generated templates to Biome for 20x faster linting and formatting. Templates now pin `@biomejs/biome@2.4.13` for consistent DX across environments. + - **"Pro" Fail-Fast Logic**: Implemented strict connection checks for DB/Redis in production with warnings in development. + - **Template Hardening**: Fixed multiple linting errors in generated templates (auth controllers, ApiResponse, and middleware). + - **Docker Fix**: Resolved `depends_on` formatting and removed obsolete version tags in `docker-compose.yml`. + - **Smoke Test Hardening**: Added mandatory linting checks to the generator test suite. + ## 4.1.1 ### Patch Changes diff --git a/packages/create-express-forge/package.json b/packages/create-express-forge/package.json index a13fed6..200204d 100644 --- a/packages/create-express-forge/package.json +++ b/packages/create-express-forge/package.json @@ -1,6 +1,6 @@ { "name": "create-express-forge", - "version": "4.1.1", + "version": "4.1.2", "description": "โšก Scaffold production-ready Express.js TypeScript backends in seconds", "license": "MIT", "type": "module", From 28c3e1b825c2270fe7785331e017ed1cdfe1452b Mon Sep 17 00:00:00 2001 From: code-y02 Date: Tue, 28 Apr 2026 19:40:24 +0530 Subject: [PATCH 09/16] feat: support project generation in current directory and add comprehensive V3 documentation --- .changeset/v4-cli-and-docs-improvements.md | 16 ++ CONTRIBUTING.md | 10 +- README.md | 22 ++- docs/.vitepress/config.ts | 146 +++++++++++++----- docs/guide/architecture.md | 19 ++- docs/guide/auth.md | 4 +- docs/guide/caching.md | 4 +- docs/guide/deployment.md | 2 +- docs/guide/features.md | 43 ++++-- docs/guide/getting-started.md | 12 +- docs/guide/openapi.md | 101 +++++++----- docs/guide/structure.md | 11 +- docs/guide/testing.md | 6 +- docs/guide/troubleshooting.md | 6 +- docs/index.md | 15 +- docs/public/og-image.png | Bin 0 -> 449043 bytes docs/reference/cli-options.md | 2 +- docs/reference/config.md | 2 +- docs/v3/guide/architecture.md | 49 ++++++ docs/v3/guide/auth.md | 46 ++++++ docs/v3/guide/caching.md | 50 ++++++ docs/v3/guide/deployment.md | 42 +++++ docs/v3/guide/features.md | 82 ++++++++++ docs/v3/guide/getting-started.md | 47 ++++++ docs/v3/guide/openapi.md | 48 ++++++ docs/v3/guide/structure.md | 52 +++++++ docs/v3/guide/testing.md | 60 +++++++ docs/v3/guide/troubleshooting.md | 46 ++++++ docs/v3/index.md | 69 +++++++++ docs/v3/reference/cli-options.md | 32 ++++ docs/v3/reference/config.md | 36 +++++ packages/create-express-forge/README.md | 16 +- packages/create-express-forge/src/prompts.ts | 10 +- .../src/utils/template-manager.ts | 22 ++- .../features/testing/smoke.test.ts.eta | 2 +- 35 files changed, 998 insertions(+), 132 deletions(-) create mode 100644 .changeset/v4-cli-and-docs-improvements.md create mode 100644 docs/public/og-image.png create mode 100644 docs/v3/guide/architecture.md create mode 100644 docs/v3/guide/auth.md create mode 100644 docs/v3/guide/caching.md create mode 100644 docs/v3/guide/deployment.md create mode 100644 docs/v3/guide/features.md create mode 100644 docs/v3/guide/getting-started.md create mode 100644 docs/v3/guide/openapi.md create mode 100644 docs/v3/guide/structure.md create mode 100644 docs/v3/guide/testing.md create mode 100644 docs/v3/guide/troubleshooting.md create mode 100644 docs/v3/index.md create mode 100644 docs/v3/reference/cli-options.md create mode 100644 docs/v3/reference/config.md diff --git a/.changeset/v4-cli-and-docs-improvements.md b/.changeset/v4-cli-and-docs-improvements.md new file mode 100644 index 0000000..6adfc2e --- /dev/null +++ b/.changeset/v4-cli-and-docs-improvements.md @@ -0,0 +1,16 @@ +--- +"create-express-forge": patch +--- + +- Added support for scaffolding in the current directory (`.`) +- Implemented automated import alias resolution (`@/`) for scaffolded projects +- Fixed relative import bug in smoke test template +- Synchronized documentation with latest v4 features (Zod-to-OpenAPI, Biome, Pro Fail-Fast) +- Added documentation versioning support with v3 LTS dropdown +- Enhanced documentation SEO, OpenGraph metadata, and social sharing tags +- Standardized product naming to "Create Express Forge" across docs and CLI +- Highlighted **Ultra-Fast scaffolding** in the v3 vs v4 comparison (powered by internal refactoring) +- Updated root and package READMEs with new features and Biome integration +- Removed deprecated `cef` alias from documentation +- Fixed file paths in CONTRIBUTING.md +- Updated v3 documentation and root README to target the correct stable legacy version (`3.3.2`) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4549af8..3f59478 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,11 +35,11 @@ pnpm run test ## Adding a New Generator Feature 1. Add your option to `packages/create-express-forge/src/types.ts` -2. Add the prompt in `src/prompts.ts` -3. Create a generator in `src/generator/features/your-feature.ts` -4. Wire it into `src/generator/index.ts` -5. Update `src/utils/package-builder.ts` if it adds dependencies -6. Add a test in `tests/` +2. Add the prompt in `packages/create-express-forge/src/prompts.ts` +3. Add templates in `packages/create-express-forge/templates/` +4. Wire logic into `packages/create-express-forge/src/generator/index.ts` +5. Update `packages/create-express-forge/src/utils/package-builder.ts` if it adds dependencies +6. Add a test in `packages/create-express-forge/tests/` ## Releasing diff --git a/README.md b/README.md index 37f3318..24a3c20 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,8 @@ ```bash npx create-express-forge my-api -# or -npm create express-forge my-api -# or (short alias) -npx cef my-api +# or scaffold in current directory +npx create-express-forge . ``` ## What You Get @@ -31,7 +29,8 @@ Interactive prompts let you choose: ## Generated Project Includes -- โœ… **TypeScript** + `tsx` hot-reload dev server +- โœ… **TypeScript** + `tsx` hot-reload dev server + **Path Aliases (`@/`)** +- โœ… **Biome** โ€” 20x faster linting and formatting (replaces ESLint/Prettier) - โœ… **Zod** env validation on startup โ€” fails fast on bad config - โœ… **Global centralized error handler** โ€” `ApiError`, `ZodError`, unknown errors all handled - โœ… **Request validation middleware** via `validate(schema)` @@ -42,6 +41,7 @@ Interactive prompts let you choose: - โœ… **Graceful shutdown** (SIGTERM / SIGINT) - โœ… **Multi-stage Dockerfile** with healthcheck - โœ… **docker-compose** with correct DB service +- โœ… **Automated OpenAPI (Swagger)** โ€” Zero-JSDoc documentation via Zod schemas ## Repository Structure @@ -50,12 +50,22 @@ create-express-forge/ โ”œโ”€โ”€ packages/ โ”‚ โ”œโ”€โ”€ create-express-forge/ โ† The published CLI โ”‚ โ”œโ”€โ”€ typescript-config/ โ† Shared internal TS config -โ”‚ โ””โ”€โ”€ eslint-config/ โ† Shared internal ESLint config +โ”‚ โ””โ”€โ”€ lint-config/ โ† Shared internal Biome/Lint config โ”œโ”€โ”€ examples/ โ”‚ โ””โ”€โ”€ modular-postgres-prisma/ โ† Pre-generated example โ””โ”€โ”€ .github/workflows/ โ† CI + Release ``` +## Legacy Support (v3.x) + +Documentation for the legacy v3.x (LTS) version is available at [https://code-y02.github.io/express-cli/v3/](https://code-y02.github.io/express-cli/v3/). + +To scaffold a project using v3.x, run: + +```bash +npx create-express-forge@3.3.2 [project-name] +``` + ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md). diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 1633c34..1bfc759 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -5,9 +5,14 @@ const rawBase = process.env.VITEPRESS_BASE || "/express-cli/"; const base = rawBase.endsWith("/") ? rawBase : `${rawBase}/`; export default defineConfig({ - title: "Express Forge", + title: "Create Express Forge", description: "โšก Production-ready Express backends in seconds", base: base, + cleanUrls: true, + lastUpdated: true, + sitemap: { + hostname: "https://code-y02.github.io/express-cli", + }, head: [ ["link", { rel: "icon", href: `${base}logo.svg` }], @@ -25,7 +30,7 @@ export default defineConfig({ "meta", { property: "og:title", - content: "Express Forge | The Ultimate Express + TypeScript Generator", + content: "create-express-forge | The Ultimate Express + TypeScript Generator", }, ], [ @@ -36,6 +41,39 @@ export default defineConfig({ "Scaffold production-ready Express.js TypeScript backends in seconds with built-in Auth, ORM, and OpenAPI support.", }, ], + ["meta", { property: "og:image", content: `${base}og-image.png` }], + ["meta", { name: "twitter:card", content: "summary_large_image" }], + ["meta", { name: "twitter:site", content: "@code_y02" }], + ["meta", { name: "twitter:title", content: "Create Express Forge" }], + [ + "meta", + { + name: "twitter:description", + content: "โšก Production-ready Express backends in seconds", + }, + ], + ["meta", { name: "twitter:image", content: `${base}og-image.png` }], + [ + "script", + { type: "application/ld+json" }, + JSON.stringify({ + "@context": "https://schema.org", + "@type": "SoftwareApplication", + "name": "Create Express Forge", + "operatingSystem": "Node.js", + "applicationCategory": "DeveloperApplication", + "description": "The ultimate CLI for scaffolding production-ready Express.js TypeScript backends.", + "offers": { + "@type": "Offer", + "price": "0", + "priceCurrency": "USD", + }, + "author": { + "@type": "Person", + "name": "Yatharth Lakhate", + }, + }), + ], ], themeConfig: { @@ -50,47 +88,83 @@ export default defineConfig({ { text: "Features", link: "/guide/features" }, { text: "Reference", link: "/reference/cli-options" }, { - text: "โญ Star on GitHub", - link: "https://github.com/CODE-Y02/express-cli", - }, - ], - - sidebar: [ - { - text: "Introduction", + text: "Versions", items: [ - { text: "What is Express Forge?", link: "/" }, - { text: "Getting Started", link: "/guide/getting-started" }, + { text: "v4.x (Latest)", link: "/" }, + { text: "v3.x (LTS)", link: "/v3/" }, ], }, { - text: "Core Concepts", - items: [ - { 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" }, - ], - }, - { - text: "Advanced", - items: [ - { text: "Deployment", link: "/guide/deployment" }, - { text: "Testing Strategy", link: "/guide/testing" }, - { text: "Troubleshooting", link: "/guide/troubleshooting" }, - ], - }, - { - text: "Reference", - items: [ - { text: "CLI Options", link: "/reference/cli-options" }, - { text: "Configuration", link: "/reference/config" }, - ], + text: "โญ Star on GitHub", + link: "https://github.com/CODE-Y02/express-cli", }, ], + sidebar: { + "/": [ + { + text: "Introduction", + items: [ + { text: "What is create-express-forge?", link: "/" }, + { text: "Getting Started", link: "/guide/getting-started" }, + ], + }, + { + text: "Core Concepts", + items: [ + { 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" }, + ], + }, + { + text: "Advanced", + items: [ + { text: "Deployment", link: "/guide/deployment" }, + { text: "Testing Strategy", link: "/guide/testing" }, + { text: "Troubleshooting", link: "/guide/troubleshooting" }, + ], + }, + { + text: "Reference", + items: [ + { text: "CLI Options", link: "/reference/cli-options" }, + { text: "Configuration", link: "/reference/config" }, + ], + }, + ], + "/v3/": [ + { + text: "v3 Docs", + items: [ + { text: "Introduction", link: "/v3/" }, + { text: "Getting Started", link: "/v3/guide/getting-started" }, + ], + }, + { + text: "Core Concepts", + items: [ + { text: "Architecture Patterns", link: "/v3/guide/architecture" }, + { text: "Project Structure", link: "/v3/guide/structure" }, + { text: "Core Features", link: "/v3/guide/features" }, + { text: "Authentication", link: "/v3/guide/auth" }, + { text: "Caching", link: "/v3/guide/caching" }, + { text: "API Documentation", link: "/v3/guide/openapi" }, + ], + }, + { + text: "Reference", + items: [ + { text: "CLI Options", link: "/v3/reference/cli-options" }, + { text: "Configuration", link: "/v3/reference/config" }, + ], + }, + ], + }, + socialLinks: [ { icon: "github", link: "https://github.com/CODE-Y02/express-cli" }, ], diff --git a/docs/guide/architecture.md b/docs/guide/architecture.md index 355cdc3..ecb8298 100644 --- a/docs/guide/architecture.md +++ b/docs/guide/architecture.md @@ -1,6 +1,6 @@ # Architecture Patterns -Express Forge supports two main architecture patterns to fit your project's needs. Choosing the right one is crucial for long-term maintainability. +Create Express Forge supports two main architecture patterns to fit your project's needs. Choosing the right one is crucial for long-term maintainability. ## ๐Ÿ“ฆ Modular Architecture (Recommended) @@ -43,7 +43,22 @@ src/ services/ # Business logic ``` +## ๐Ÿท๏ธ Path Aliases (@/) + +Regardless of the architecture you choose, Create Express Forge pre-configures **Path Aliases**. This means you can use absolute imports from the `src` directory instead of messy relative paths. + +**Instead of this:** +```typescript +import { User } from '../../../models/user.js'; +``` + +**You do this:** +```typescript +import { User } from '@/models/user.js'; +``` + +This ensures that moving files around won't break your imports. + ## Which one should I use? If you are building a production API that you expect to grow over time, **Modular Architecture** is almost always the better choice. It prevents the "Fat Controller" and "Fat Model" syndromes by keeping related logic close together. - diff --git a/docs/guide/auth.md b/docs/guide/auth.md index 379a829..3dc9bc8 100644 --- a/docs/guide/auth.md +++ b/docs/guide/auth.md @@ -1,6 +1,6 @@ # Authentication -Express Forge provides two battle-tested authentication strategies: **JWT** (JSON Web Tokens) and **Sessions**. +Create Express Forge provides two battle-tested authentication strategies: **JWT** (JSON Web Tokens) and **Sessions**. ## Strategies @@ -43,4 +43,4 @@ export const getProfile = (req: Request, res: Response) => { ## 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. +3. **HTTP-Only Cookies**: If using sessions, Create Express Forge pre-configures cookies to be `httpOnly` to prevent XSS attacks. diff --git a/docs/guide/caching.md b/docs/guide/caching.md index 098518e..4a0467b 100644 --- a/docs/guide/caching.md +++ b/docs/guide/caching.md @@ -1,6 +1,6 @@ # 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. +Create 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 @@ -47,4 +47,4 @@ 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. +3. **Pro Fail-Fast**: In production, Create Express Forge strictly validates Redis connectivity on startup. If Redis is down, the app fails early to prevent inconsistent states. In development, it provides a clear warning and continues. diff --git a/docs/guide/deployment.md b/docs/guide/deployment.md index af9d0fc..32bef43 100644 --- a/docs/guide/deployment.md +++ b/docs/guide/deployment.md @@ -1,6 +1,6 @@ # Deployment -Express Forge provides production-ready configurations to help you ship your API with confidence. +Create Express Forge provides production-ready configurations to help you ship your API with confidence. ## ๐Ÿณ Docker Deployment (Recommended) diff --git a/docs/guide/features.md b/docs/guide/features.md index e6eaad6..1f9307f 100644 --- a/docs/guide/features.md +++ b/docs/guide/features.md @@ -1,9 +1,14 @@ +--- +title: Core Features | Create Express Forge +description: Explore the production-ready features of Create Express Forge, including JWT Auth, Prisma integration, and automated OpenAPI documentation. +--- + # Core Features -Express Forge comes packed with everything you need to build robust APIs. +Create Express Forge comes packed with everything you need to build robust APIs. ## ๐Ÿ›ก๏ธ TypeScript First -Type safety is at the core of Express Forge. Every scaffolded project includes: +Type safety is at the core of Create Express Forge. Every scaffolded project includes: - Strict TypeScript configuration. - Path aliases (e.g., `@/config/env`). - Type-safe environment variables via **Zod**. @@ -22,7 +27,7 @@ Choose your favorite ORM and get started instantly: - **Migrations**: Pre-configured scripts to handle database schema changes. ## ๐Ÿงช Testing Suite -Don't ship broken code. Express Forge sets up a complete testing environment: +Don't ship broken code. Create Express Forge sets up a complete testing environment: - **Vitest/Jest**: Choose your favorite test runner. - **Supertest**: For high-level API integration tests. - **Example Tests**: Every scaffolded project includes example unit and integration tests. @@ -41,23 +46,29 @@ Stay secure by default with pre-configured industry standards: ## ๐Ÿ“ Logging & Monitoring - **Pino/Winston**: High-performance, structured logging. Pino is used by default for its extreme speed and JSON output, which is perfect for log aggregators like ELK or Datadog. -- **Health Checks**: A standard `/health` endpoint is included, providing uptime, memory usage, and database connectivity status. +- **Pro Fail-Fast**: In production, the app strictly validates database and Redis connections on startup. If a dependency is down, the app fails early to prevent inconsistent states. In development, it provides clear warnings. +- **Health Checks**: A standard `/api/v1/health` endpoint is included, providing uptime, memory usage, and database connectivity status. ## ๐Ÿ“œ OpenAPI Documentation Never let your documentation get out of sync: -- **Swagger UI**: Integrated UI to explore and test your API endpoints directly from the browser. -- **Auto-generated Spec**: The CLI generates a `docs.json` endpoint that is always up-to-date with your code's JSDoc annotations. -- **Security Schemas**: Pre-configured security definitions for your chosen auth strategy (Cookie or Bearer). +- **Swagger UI / Scalar**: Beautiful, integrated UI to explore and test your API endpoints directly from the browser. +- **Zero-JSDoc Spec**: Documentation is generated directly from your **Zod schemas** and a centralized registry. No more clunky JSDoc comments in your controllers! +- **Type-Safe Documentation**: Your runtime validation and your API documentation are always 100% in sync. + +## โšก Modern Tooling +- **Biome**: Replaces ESLint and Prettier for 20x faster linting and formatting. +- **Import Aliases**: Pre-configured `@/` paths for clean, absolute imports. +- **ESM Native**: Built from the ground up for modern Node.js and ECMAScript Modules. ## ๐Ÿงฑ Graceful Shutdown -Every Express Forge project handles `SIGTERM` and `SIGINT` signals correctly. This ensures that: +Every Create Express Forge project handles `SIGTERM` and `SIGINT` signals correctly. This ensures that: 1. No new requests are accepted. 2. Existing requests are finished. 3. Database connections are closed cleanly. 4. The process exits without data corruption. ## ๐Ÿ› ๏ธ Error Handling & Responses -Express Forge enforces a consistent communication pattern between your API and clients. +Create Express Forge enforces a consistent communication pattern between your API and clients. ### Centralized Error Handling A global error middleware is the "safety net" for your application. It catches all errors and transforms them into structured JSON responses, handling `Zod` validation errors and custom `ApiError` instances automatically. @@ -80,3 +91,17 @@ Ensure your frontend team always knows what to expect. Every success response fo ### Async Error Wrapper The provided `asyncHandler` utility eliminates the need for `try-catch` blocks in your controllers, automatically forwarding any promise rejections to the global error handler. + +## ๐Ÿ†š Comparison: v4 vs v3 + +| Feature | v3.x (Legacy) | v4.x (Latest) | +| :--- | :--- | :--- | +| **OpenAPI Docs** | JSDoc-based (`swagger-jsdoc`) | **Zero-JSDoc** (via Zod schemas) | +| **Linting & Formatting** | ESLint + Prettier | **Biome** (20x faster) | +| **Path Aliases** | Not supported by default | **Native Support** (`@/` aliases) | +| **CLI Flexibility** | Only new directories | **Scaffold in `.`** supported | +| **Scaffolding Speed** | Standard | **Ultra-Fast** (Refactored logic) | +| **Reliability** | Standard startup | **Pro Fail-Fast** (DB/Redis checks) | +| **Imports** | Relative only (`../../`) | **Automated Alias resolution** | +| **Architecture** | Basic Modular/MVC | **Hardened Structures** | +| **Deployment** | Basic Dockerfiles | **Optimized Multi-stage builds** | diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index 93fca69..551aa3d 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -1,6 +1,11 @@ +--- +title: Getting Started | Create Express Forge +description: Learn how to scaffold a new Express.js project in seconds using Create Express Forge. +--- + # Getting Started -Express Forge is designed to get you up and running with a production-grade Express backend in seconds. +Create Express Forge is designed to get you up and running with a production-grade Express backend in seconds. ## Quick Start @@ -18,6 +23,11 @@ If you want to skip the prompts and use the recommended defaults, use the `--yes npx create-express-forge@latest my-api --yes ``` +> [!TIP] +> You can scaffold a project in your **current directory** by using a period (`.`): +> `npx create-express-forge@latest .` + + ## Step-by-Step Guide ### 1. Initialize Project diff --git a/docs/guide/openapi.md b/docs/guide/openapi.md index 4ce3631..1714b2c 100644 --- a/docs/guide/openapi.md +++ b/docs/guide/openapi.md @@ -1,48 +1,69 @@ -# 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. +--- +title: API Documentation | Create Express Forge +description: Learn how to generate automated, type-safe OpenAPI (Swagger) documentation using Zod schemas with Create Express Forge. +--- +# API Documentation (OpenAPI) + +Create Express Forge uses `@asteasolutions/zod-to-openapi` to provide interactive, type-safe documentation for your API. This ensures that your runtime validation and your API documentation are always in sync. + ## Getting Started - + If you enabled OpenAPI during scaffolding, your documentation is available at: - -- **Swagger UI**: `http://localhost:3000/docs` (Interactive) -- **OpenAPI Spec**: `http://localhost:3000/docs.json` (Raw JSON) - + +- **Interactive UI**: `http://localhost:3000/api-docs` +- **OpenAPI Spec**: `http://localhost:3000/api-docs.json` (Raw JSON) + > [!TIP] -> The `/docs.json` endpoint is always available for external tools (like Postman), even if you chose to disable the Swagger UI during scaffolding. - -## 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 - +> On server startup, the CLI automatically logs the documentation URL for easy access. + +## Zero-JSDoc Documentation + +Unlike traditional Express apps that rely on clunky JSDoc comments (`@openapi`), Create Express Forge uses your **Zod schemas** to generate the OpenAPI specification. This makes your documentation: +1. **DRY (Don't Repeat Yourself)**: Define your schema once, use it for validation and documentation. +2. **Type-Safe**: Any changes to your Zod schemas are automatically reflected in the Swagger UI. +3. **Clean**: Your controller files stay free of giant comment blocks. + +## How it Works + +### 1. The Registry +A centralized registry is located at `src/docs/registry.ts`. This registry collects all your path and component definitions. + +### 2. Documenting a Path +Paths are registered directly in your `schema.ts` files. This keeps the documentation close to the validation logic. + ```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); +import { z } from 'zod'; +import { registry } from '@/docs/registry.js'; + +// Define your schema +export const UserSchema = registry.register('User', z.object({ + id: z.string().uuid(), + name: z.string(), +})); + +// Register the path +registry.registerPath({ + method: 'get', + path: '/users', + summary: 'Get all users', + responses: { + 200: { + description: 'List of users', + content: { + 'application/json': { + schema: z.array(UserSchema), + }, + }, + }, + }, +}); ``` - + +### 3. Setting Up Swagger +The `setupSwagger` function in `src/docs/swagger.ts` generates the final OpenAPI document from the registry and attaches the Swagger UI to your Express app. + ## 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.). +- **Auto-Sync**: Your documentation is built from your code, ensuring it never gets stale. +- **Standardized**: Uses the OpenAPI 3.0 specification, compatible with Postman, Insomnia, and code generators. diff --git a/docs/guide/structure.md b/docs/guide/structure.md index cfa0e14..d5643fb 100644 --- a/docs/guide/structure.md +++ b/docs/guide/structure.md @@ -1,6 +1,6 @@ # Project Structure -Express Forge scaffolds a clean, professional directory structure. Depending on your chosen architecture, the structure will vary slightly. +Create Express Forge scaffolds a clean, professional directory structure. Depending on your chosen architecture, the structure will vary slightly. ## ๐Ÿ“ฆ Modular Architecture @@ -15,14 +15,13 @@ This structure is organized by **features**. Each module is self-contained. โ”‚ โ”‚ โ”œโ”€โ”€ users.service.ts โ”‚ โ”‚ โ”œโ”€โ”€ users.routes.ts โ”‚ โ”‚ โ””โ”€โ”€ users.schema.ts -โ”‚ โ”œโ”€โ”€ shared/ # Code shared across modules -โ”‚ โ”‚ โ”œโ”€โ”€ middleware/ -โ”‚ โ”‚ โ”œโ”€โ”€ utils/ -โ”‚ โ”‚ โ””โ”€โ”€ constants/ +โ”‚ โ”œโ”€โ”€ middleware/ # Global middleware +โ”‚ โ”œโ”€โ”€ utils/ # Shared utilities (ApiResponse, etc.) +โ”‚ โ”œโ”€โ”€ docs/ # OpenAPI registry and swagger setup โ”‚ โ”œโ”€โ”€ config/ # App configuration โ”‚ โ””โ”€โ”€ app.ts # App initialization โ”œโ”€โ”€ prisma/ # Database schema (if Prisma chosen) -โ”œโ”€โ”€ tests/ # Integration and unit tests +โ”œโ”€โ”€ src/__tests__/ # Integration and unit tests โ”œโ”€โ”€ .env # Environment variables โ””โ”€โ”€ package.json ``` diff --git a/docs/guide/testing.md b/docs/guide/testing.md index 773ee11..cecedbe 100644 --- a/docs/guide/testing.md +++ b/docs/guide/testing.md @@ -1,6 +1,6 @@ # Testing Strategy -Express Forge encourages a test-driven approach to development. We provide a pre-configured testing environment using either **Vitest** (recommended for speed) or **Jest**. +Create Express Forge encourages a test-driven approach to development. We provide a pre-configured testing environment using either **Vitest** (recommended for speed) or **Jest**. ## ๐Ÿงช Types of Tests @@ -52,9 +52,9 @@ describe('GET /health', () => { ## ๐Ÿ›  Contributing: CLI Smoke Testing -If you are contributing to the `create-express-forge` CLI itself, you should run the automated smoke test to verify your changes. This test scaffolds a project, runs a full TypeScript type-check, and builds the resulting app. +If you are contributing to the `Create Express Forge` CLI itself, you should run the automated smoke test to verify your changes. This test scaffolds a project, runs a full TypeScript type-check, and builds the resulting app. ```bash -cd packages/create-express-forge +cd packages/Create Express Forge pnpm run test:smoke ``` diff --git a/docs/guide/troubleshooting.md b/docs/guide/troubleshooting.md index fab7dce..def622c 100644 --- a/docs/guide/troubleshooting.md +++ b/docs/guide/troubleshooting.md @@ -1,6 +1,6 @@ # Troubleshooting -Common issues and how to resolve them when using Express Forge. +Common issues and how to resolve them when using Create Express Forge. ## ๐Ÿ’พ Database Issues @@ -29,7 +29,7 @@ Common issues and how to resolve them when using Express Forge. ### Environment variables are missing **Issue**: Zod validation error on startup. -**Solution**: Express Forge validates your `.env` on startup. Ensure all required variables listed in `src/config/index.ts` are present in your `.env` file. +**Solution**: Create Express Forge validates your `.env` on startup. Ensure all required variables listed in `src/config/index.ts` are present in your `.env` file. ### Modules not found (Path Aliases) **Issue**: TypeScript can't find modules starting with `@`. @@ -39,7 +39,7 @@ Common issues and how to resolve them when using Express Forge. ### Tests hanging **Issue**: Tests don't exit after completion. -**Solution**: Ensure you are closing your database connections and server in an `afterAll` hook. Express Forge handles this by default in the generated boilerplate. +**Solution**: Ensure you are closing your database connections and server in an `afterAll` hook. Create Express Forge handles this by default in the generated boilerplate. --- diff --git a/docs/index.md b/docs/index.md index 5a7c27d..1f17b65 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,17 +1,22 @@ --- layout: home +title: Create Express Forge | Modern Express.js CLI +description: The ultimate CLI for scaffolding production-ready, enterprise-grade Express.js backends with TypeScript, Auth, ORM, and OpenAPI. hero: - name: Express Forge + name: Create Express Forge text: Build Better Express APIs, Faster. tagline: The definitive CLI for scaffolding production-ready, enterprise-grade Express.js backends. image: src: /logo.svg - alt: Express Forge Logo + alt: Create Express Forge Logo actions: - theme: brand text: Get Started link: /guide/getting-started + - theme: alt + text: ๐Ÿ†š v4 vs v3 + link: /guide/features#comparison-v4-vs-v3 - theme: alt text: โญ Star on GitHub link: https://github.com/CODE-Y02/express-cli @@ -34,7 +39,7 @@ features: 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. + details: Zero-JSDoc documentation generated directly from your Zod schemas and registry. - icon: ๐Ÿ”ด title: Redis Caching details: Built-in support for Redis and Node-Cache to boost your API performance. @@ -55,10 +60,10 @@ features: ### ๐Ÿ›  Built With -TypeScript ยท Express.js ยท Prisma ยท Docker ยท Vitest ยท Zod ยท Pino +TypeScript ยท Express.js ยท Prisma ยท Docker ยท Vitest ยท Zod ยท Biome ยท Pino --- ### โค๏ธ Support the Project -If Express Forge has saved you time, please consider giving us a star on [GitHub](https://github.com/CODE-Y02/express-cli)! It helps us reach more developers and continue improving the project. +If Create Express Forge has saved you time, please consider giving us a star on [GitHub](https://github.com/CODE-Y02/express-cli)! It helps us reach more developers and continue improving the project. diff --git a/docs/public/og-image.png b/docs/public/og-image.png new file mode 100644 index 0000000000000000000000000000000000000000..c374851d1427b1206690dfa171aa25c21de1d9a7 GIT binary patch literal 449043 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

!}`j zrIox|ru?z##c#Za?dC1Rep-rKyZ$LDJj)qSa=EA{t`3rh58bfcKZ@rcBSzW4cF4R6Q1 zcyG69RO4eQBaarRd2xnTli$=)w?wCPZ63SCNtV$p6+3W^k*NFTa@nfxt|=h5D3^j5 z1P7{DXMg_75^tdK9PY&K+Kt`qH{raQ`Bj$F)>HIF<@$~Abp>TBL4fp{WB;ua-%U7c zzHm*>Zr8?FCD&b>=}S#)OO2O=@LC)A$CYucykQ48?fLFNl@|nKP!}FUsYR+G$wPy!UQyz~9h6Pm*+8 z>D@>9?Rmndf~m1~YEuquwjt3l{ zhOM4zmNQ0(=TG6HS}sg^zsV4}Ghv!psrnn9Y++EG@W&^sO^2H&owR&tJUYT z<=>-aVUZQgoN>+9i3y3}*M_H$bYuJJaqa>|v!--t;gC2xzJ6D(!3M-LG~`veceulR z=S(gN0ylFa;ueLNV+wydkSF*1xhI12T zN46zX&YfKi1~sK{{89cZ_B_Ur)BW(Zy@MkI3F&qtLon zY5Fr%?w8?k;A-2$HGz#SAru&;wX;LisP_4YbtBu)J_;VIER%2z19N#P?E%_0ZnUUv z1<5HM7p-E7o2z0`^!OHWBT$wi*}{D9PQP|*$a@lv^}iyf$WtD0%i^bZARq55yC{y5 zX}L!d)cjF5J=h6^kC{F-TY=AR@J3a)r&@0S=ZRtYe2a?Bzk`z05@tnp2tsLphGzgr z!Qfc)tQFcv&nTxk*>Y}GU1NKLtwW_Ei~`OFfV|=QsiCE8;*^h>Xm(O?x@xhYKjrp= zFZhIw0i!iG9*EA0N~Qg8?r0O6$fN{$o8;J6hPOpefrE`8uf?$e-F&n_Evr&7D#2+H z(C?HrD_XMRAEo!~KuQT9ho$o8hl9A4o zXL6WPfyJ#0K{er{BgpYm`8?LSGH_NRowqs{_w94?{%Em}e z6f^3EMEaPtbo>Ux~!PhYt1UN z^^}Btvr}x+08#XZ5_$=dF^X)Tx6!_gs8 zG@`xb+_9aTZ{T;dCrKk)Gv(iQ44P<_#npU|jKxJ;Wa#3bt_px@4iZ4QVU}#wo_KCp zzG}#zUliA-=iDM%aF})b>2Lz0StG)l&C6Dwz{^eJk(|ko(i>vkjmC&(< zPQ5pM&Y$EyDK@A{l%=#TO6CW&{TpOQ+PEt2u>fjn_J@{E zwR)*kykO6wlgvAjEH?L_534udN1(ARS@>N7#g9N=h&NfbrpQm5QWqMovlGqDxA`d* z;jen4Zf3MfsS2u+>1l%<1hTeoK7CqolcO%B1;dDiQ0(C%Mh>Reu1gq< zC1S8o%7@nG8m7G^?R%VaPp~_N=W^BkDIP3+r^@9RqJqr3R>u!B>9N7<8gohP1Ao<%DeqZ{`J}A{ z6eE{)a|zx?CN>v&%N3^WQp;i55CnKTmTVw?NP8$S(35@DuApD9vRJ`A?^jS{0c;0x z^F5F=PKxiagOlP>Iw2+Bg3bFA+uVykIOj~V;a-)aKm~!w2c@^lFgx^b)Enn&C`*-3 zitQ&6N7o$@r}-&CqQ%kp2XnN~o5JfdXqP%#r*HHeVT`0SSoZNhG-g|ph+sU0dNW}( z2+^XH{ucxZf}Eu{XH;`*@%$+68-gAlU%PeFyoxgqF~{Q~=1a?u;5#XcI-Q%e34i_9 z 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 literal 0 HcmV?d00001 diff --git a/docs/reference/cli-options.md b/docs/reference/cli-options.md index c07d95c..e4d9408 100644 --- a/docs/reference/cli-options.md +++ b/docs/reference/cli-options.md @@ -1,6 +1,6 @@ # CLI Reference -The `create-express-forge` command can be used with various flags to bypass the interactive prompts and speed up your workflow. +The `Create Express Forge` command can be used with various flags to bypass the interactive prompts and speed up your workflow. ## Usage diff --git a/docs/reference/config.md b/docs/reference/config.md index 83efca7..afdaaa3 100644 --- a/docs/reference/config.md +++ b/docs/reference/config.md @@ -1,6 +1,6 @@ # Configuration Reference -Every project created with Express Forge uses a centralized configuration system powered by environment variables. +Every project created with Create Express Forge uses a centralized configuration system powered by environment variables. ## ๐ŸŒ Environment Variables diff --git a/docs/v3/guide/architecture.md b/docs/v3/guide/architecture.md new file mode 100644 index 0000000..84deeb9 --- /dev/null +++ b/docs/v3/guide/architecture.md @@ -0,0 +1,49 @@ +# Architecture Patterns + +Create Express Forge supports two main architecture patterns to fit your project's needs. Choosing the right one is crucial for long-term maintainability. + +## ๐Ÿ“ฆ Modular Architecture (Recommended) + +This is the default and recommended pattern for medium to large applications. It organizes code by **features** (modules) rather than technical roles (controllers, models). + +### Why choose Modular? +- **Scalability**: Each module is self-contained, making it easier to manage as the app grows. +- **Isolation**: Changes to one feature are less likely to break another. +- **Team-Friendly**: Different developers can work on different modules without merge conflicts. + +### Structure: +```text +src/ + modules/ + users/ + users.controller.ts # Request handling + users.service.ts # Business logic + users.routes.ts # Route definitions + users.schema.ts # Validation schemas + products/ + ... +``` + +--- + +## ๐Ÿ› MVC Architecture + +A classic Model-View-Controller pattern. Best for smaller projects or those who prefer a traditional separation of concerns based on technical layers. + +### Why choose MVC? +- **Familiarity**: Most developers are familiar with this pattern from frameworks like Rails or Django. +- **Simplicity**: For very small apps, it might be quicker to navigate. + +### Structure: +```text +src/ + controllers/ # All controllers in one place + models/ # Database models + routes/ # All route definitions + services/ # Business logic +``` + +## Which one should I use? + +If you are building a production API that you expect to grow over time, **Modular Architecture** is almost always the better choice. It prevents the "Fat Controller" and "Fat Model" syndromes by keeping related logic close together. + diff --git a/docs/v3/guide/auth.md b/docs/v3/guide/auth.md new file mode 100644 index 0000000..3dc9bc8 --- /dev/null +++ b/docs/v3/guide/auth.md @@ -0,0 +1,46 @@ +# Authentication + +Create 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/middleware/auth.ts` +- **Storage Options**: + - **๐Ÿช Cookie**: More secure against XSS. Uses `httpOnly` and `secure` flags. + - **๐Ÿ“จ Header**: Standard `Authorization: Bearer `. Recommended for mobile apps. +- **Config**: Set `JWT_SECRET` and `JWT_EXPIRES_IN` 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/middleware/auth.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 '../middleware/auth.js'; +import { todosController } from '../modules/todos/todos.controller.js'; + +// Protected route +router.get('/', auth, todosController.getTodos); +``` + +## 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, Create Express Forge pre-configures cookies to be `httpOnly` to prevent XSS attacks. diff --git a/docs/v3/guide/caching.md b/docs/v3/guide/caching.md new file mode 100644 index 0000000..a4c2525 --- /dev/null +++ b/docs/v3/guide/caching.md @@ -0,0 +1,50 @@ +# Caching + +Create 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**: Create 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/v3/guide/deployment.md b/docs/v3/guide/deployment.md new file mode 100644 index 0000000..32bef43 --- /dev/null +++ b/docs/v3/guide/deployment.md @@ -0,0 +1,42 @@ +# Deployment + +Create Express Forge provides production-ready configurations to help you ship your API with confidence. + +## ๐Ÿณ Docker Deployment (Recommended) + +The easiest way to deploy is using the provided multi-stage `Dockerfile`. + +### Build Image +```bash +docker build -t my-express-api . +``` + +### Run Container +```bash +docker run -p 3000:3000 --env-file .env my-express-api +``` + +### Why Multi-stage? +Our Dockerfile uses multi-stage builds to: +1. **Reduce Image Size**: The final image only contains the compiled JavaScript and production dependencies. +2. **Security**: Source code and build tools are not included in the final production image. + +## โ˜๏ธ Cloud Platforms + +### Railway / Render / Fly.io +Most modern PaaS platforms will automatically detect the `Dockerfile` or the `start` script in `package.json`. + +1. Connect your GitHub repository. +2. Configure your environment variables (copy from `.env`). +3. Set the build command to `npm run build` (if not using Docker). + > **Note for Prisma users**: The generated `package.json` includes a `postinstall: "prisma generate"` script, which ensures your Prisma client is generated automatically before the build step on most PaaS platforms. +4. Set the start command to `npm start`. + +## ๐Ÿ›ก๏ธ Production Checklist + +Before going live, ensure: +- [ ] **Environment Variables**: `NODE_ENV` is set to `production`. +- [ ] **Database**: Migrations have been run on the production database. +- [ ] **Logging**: Log level is set appropriately (e.g., `info` or `error`). +- [ ] **Security**: CORS is restricted to your frontend domain. +- [ ] **Rate Limiting**: Configured for your production traffic. diff --git a/docs/v3/guide/features.md b/docs/v3/guide/features.md new file mode 100644 index 0000000..9f30de0 --- /dev/null +++ b/docs/v3/guide/features.md @@ -0,0 +1,82 @@ +# Core Features + +Create Express Forge comes packed with everything you need to build robust APIs. + +## ๐Ÿ›ก๏ธ TypeScript First +Type safety is at the core of Create Express Forge. Every scaffolded project includes: +- Strict TypeScript configuration. +- Path aliases (e.g., `@/config/env`). +- Type-safe environment variables via **Zod**. +- Automated scaffolding with your choice of **npm**, **pnpm**, **yarn**, or **bun**. + +## ๐Ÿ” Flexible Authentication +Scaffold a complete authentication system with a single choice: +- **JWT Authentication**: Choose between **HttpOnly Cookies** (recommended for web) or **Bearer Headers** (recommended for mobile/API clients). +- **Session Auth**: Battle-tested session management for stateful applications. +- **Protected Routes**: Every boilerplate includes a protected resource showing you exactly how to use the auth middleware. + +## ๐Ÿ’พ Database Integration +Choose your favorite ORM and get started instantly: +- **Prisma**: Modern ORM with auto-generated client and type-safe queries. +- **Sequelize**: The most popular traditional ORM for Node.js. +- **Migrations**: Pre-configured scripts to handle database schema changes. + +## ๐Ÿงช Testing Suite +Don't ship broken code. Create Express Forge sets up a complete testing environment: +- **Vitest/Jest**: Choose your favorite test runner. +- **Supertest**: For high-level API integration tests. +- **Example Tests**: Every scaffolded project includes example unit and integration tests. + +## ๐Ÿณ Docker Support +Ship to production with confidence: +- **Multi-stage Build**: Optimized Dockerfiles for smaller production images. +- **Docker Compose**: Includes a `docker-compose.yml` with a database setup for local development. + +## ๐Ÿ” Security Best Practices +Stay secure by default with pre-configured industry standards: +- **Helmet**: Automatically sets security-related HTTP headers to protect against common vulnerabilities. +- **CORS**: Flexible Cross-Origin Resource Sharing configuration. +- **Rate Limiting**: Integrated `express-rate-limit` to prevent brute-force attacks and DDoS. +- **Dotenv & Zod**: Every environment variable is validated on startup. If a variable is missing or malformed, the app fails fast with a clear error message. + +## ๐Ÿ“ Logging & Monitoring +- **Pino/Winston**: High-performance, structured logging. Pino is used by default for its extreme speed and JSON output, which is perfect for log aggregators like ELK or Datadog. +- **Health Checks**: A standard `/health` endpoint is included, providing uptime, memory usage, and database connectivity status. + +## ๐Ÿ“œ OpenAPI Documentation +Never let your documentation get out of sync: +- **Swagger UI**: Integrated UI to explore and test your API endpoints directly from the browser. +- **Auto-generated Spec**: The CLI generates a `docs.json` endpoint that is always up-to-date with your code's JSDoc annotations. +- **Security Schemas**: Pre-configured security definitions for your chosen auth strategy (Cookie or Bearer). + +## ๐Ÿงฑ Graceful Shutdown +Every Create Express Forge project handles `SIGTERM` and `SIGINT` signals correctly. This ensures that: +1. No new requests are accepted. +2. Existing requests are finished. +3. Database connections are closed cleanly. +4. The process exits without data corruption. + +## ๐Ÿ› ๏ธ Error Handling & Responses +Create Express Forge enforces a consistent communication pattern between your API and clients. + +### Centralized Error Handling +A global error middleware is the "safety net" for your application. It catches all errors and transforms them into structured JSON responses, handling `Zod` validation errors and custom `ApiError` instances automatically. + +### Custom `ApiError` Class +Stop throwing generic strings. Use the built-in `ApiError` class to provide context, status codes, and operational flags: +- `ApiError.notFound('User not found')` +- `ApiError.unauthorized()` +- `ApiError.badRequest('Invalid input', validationErrors)` + +### Standardized `ApiResponse` +Ensure your frontend team always knows what to expect. Every success response follows a predictable schema: +```json +{ + "success": true, + "message": "Operation successful", + "data": { ... } +} +``` + +### Async Error Wrapper +The provided `asyncHandler` utility eliminates the need for `try-catch` blocks in your controllers, automatically forwarding any promise rejections to the global error handler. diff --git a/docs/v3/guide/getting-started.md b/docs/v3/guide/getting-started.md new file mode 100644 index 0000000..a5f0504 --- /dev/null +++ b/docs/v3/guide/getting-started.md @@ -0,0 +1,47 @@ +# Getting Started + +Create Express Forge is designed to get you up and running with a production-grade Express backend in seconds. + +## Quick Start + +The fastest way to create a new project is using `npx`. You don't even need to install the CLI globally! + +```bash +npx create-express-forge@3.3.2 [project-name] +``` + +### Instant Scaffolding + +If you want to skip the prompts and use the recommended defaults, use the `--yes` flag: + +```bash +npx create-express-forge@3.3.2 my-api --yes +``` + +## Step-by-Step Guide + +### 1. Initialize Project +Run the command and follow the interactive prompts. You'll be asked to: +- Give your project a **name**. +- Select your preferred **package manager** (npm, pnpm, yarn). +- Choose an **architecture** (Modular or MVC). +- Select an **ORM** (Prisma, Sequelize, or none). +- Choose a **Testing Framework** (Vitest or Jest). + +### 2. Enter Directory +```bash +cd my-awesome-api +``` + +### 3. Start Development +```bash +npm run dev +``` + +Your API will now be running at `http://localhost:3000` with hot-reloading enabled. + +## Next Steps + +- Explore the [Architecture Patterns](./architecture) to understand how your code is organized. +- Check out the [Core Features](./features) to see what's included out of the box. +- Configure your environment variables in the `.env` file. diff --git a/docs/v3/guide/openapi.md b/docs/v3/guide/openapi.md new file mode 100644 index 0000000..99f4fbf --- /dev/null +++ b/docs/v3/guide/openapi.md @@ -0,0 +1,48 @@ +# API Documentation (OpenAPI) + +Create 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: + +- **Swagger UI**: `http://localhost:3000/docs` (Interactive) +- **OpenAPI Spec**: `http://localhost:3000/docs.json` (Raw JSON) + +> [!TIP] +> The `/docs.json` endpoint is always available for external tools (like Postman), even if you chose to disable the Swagger UI during scaffolding. + +## 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/docs/v3/guide/structure.md b/docs/v3/guide/structure.md new file mode 100644 index 0000000..b2c9a92 --- /dev/null +++ b/docs/v3/guide/structure.md @@ -0,0 +1,52 @@ +# Project Structure + +Create Express Forge scaffolds a clean, professional directory structure. Depending on your chosen architecture, the structure will vary slightly. + +## ๐Ÿ“ฆ Modular Architecture + +This structure is organized by **features**. Each module is self-contained. + +```text +. +โ”œโ”€โ”€ src/ +โ”‚ โ”œโ”€โ”€ modules/ # Feature modules +โ”‚ โ”‚ โ””โ”€โ”€ users/ # Example module +โ”‚ โ”‚ โ”œโ”€โ”€ users.controller.ts +โ”‚ โ”‚ โ”œโ”€โ”€ users.service.ts +โ”‚ โ”‚ โ”œโ”€โ”€ users.routes.ts +โ”‚ โ”‚ โ””โ”€โ”€ users.schema.ts +โ”‚ โ”œโ”€โ”€ shared/ # Code shared across modules +โ”‚ โ”‚ โ”œโ”€โ”€ middleware/ +โ”‚ โ”‚ โ”œโ”€โ”€ utils/ +โ”‚ โ”‚ โ””โ”€โ”€ constants/ +โ”‚ โ”œโ”€โ”€ config/ # App configuration +โ”‚ โ””โ”€โ”€ app.ts # App initialization +โ”œโ”€โ”€ prisma/ # Database schema (if Prisma chosen) +โ”œโ”€โ”€ tests/ # Integration and unit tests +โ”œโ”€โ”€ .env # Environment variables +โ””โ”€โ”€ package.json +``` + +## ๐Ÿ› MVC Architecture + +A traditional technical-layer separation. + +```text +. +โ”œโ”€โ”€ src/ +โ”‚ โ”œโ”€โ”€ controllers/ # Route handlers +โ”‚ โ”œโ”€โ”€ models/ # Data models +โ”‚ โ”œโ”€โ”€ services/ # Business logic +โ”‚ โ”œโ”€โ”€ routes/ # Route definitions +โ”‚ โ”œโ”€โ”€ middleware/ # Global middleware +โ”‚ โ”œโ”€โ”€ config/ +โ”‚ โ””โ”€โ”€ app.ts +โ”œโ”€โ”€ ... +``` + +## Key Files + +- **`src/app.ts`**: The entry point where Express is initialized, middleware is registered, and routes are attached. +- **`src/config/`**: Contains configuration for the database, logger, and environment variables. +- **`prisma/schema.prisma`**: (Optional) Defines your database models and relationships. +- **`docker-compose.yml`**: Defines the local development environment (e.g., PostgreSQL/MySQL containers). diff --git a/docs/v3/guide/testing.md b/docs/v3/guide/testing.md new file mode 100644 index 0000000..cecedbe --- /dev/null +++ b/docs/v3/guide/testing.md @@ -0,0 +1,60 @@ +# Testing Strategy + +Create Express Forge encourages a test-driven approach to development. We provide a pre-configured testing environment using either **Vitest** (recommended for speed) or **Jest**. + +## ๐Ÿงช Types of Tests + +### Unit Tests +Focused on testing individual functions or services in isolation. +- **Location**: `src/modules/**/__tests__/*.unit.spec.ts` +- **Focus**: Business logic, utility functions, validation. + +### Integration Tests +Testing the full API flow, including database interactions. +- **Location**: `tests/*.int.spec.ts` +- **Focus**: HTTP status codes, API responses, database persistence. + +## ๐Ÿš€ Running Tests + +```bash +# Run all tests +npm test + +# Run tests in watch mode +npm run test:watch + +# Generate coverage report +npm run test:coverage +``` + +## ๐Ÿ›  Testing Tools + +- **Supertest**: Used for making HTTP requests to your app without starting a real server. +- **Prisma Mocking**: (If using Prisma) We provide patterns for mocking the Prisma client for unit tests. +- **Test Database**: Integration tests automatically use a separate test database defined in `.env.test`. + +## Example Integration Test + +```typescript +import request from 'supertest'; +import { app } from '../src/app'; + +describe('GET /health', () => { + it('should return 200 OK', async () => { + const response = await request(app).get('/health'); + expect(response.status).toBe(200); + expect(response.body.status).toBe('ok'); + }); +}); +``` + +--- + +## ๐Ÿ›  Contributing: CLI Smoke Testing + +If you are contributing to the `Create Express Forge` CLI itself, you should run the automated smoke test to verify your changes. This test scaffolds a project, runs a full TypeScript type-check, and builds the resulting app. + +```bash +cd packages/Create Express Forge +pnpm run test:smoke +``` diff --git a/docs/v3/guide/troubleshooting.md b/docs/v3/guide/troubleshooting.md new file mode 100644 index 0000000..def622c --- /dev/null +++ b/docs/v3/guide/troubleshooting.md @@ -0,0 +1,46 @@ +# Troubleshooting + +Common issues and how to resolve them when using Create Express Forge. + +## ๐Ÿ’พ Database Issues + +### Prisma migration failed +**Issue**: `prisma migrate dev` fails with a connection error. +**Solution**: +1. Ensure your database container is running: `docker compose up -d`. +2. Check your `DATABASE_URL` in the `.env` file. If running locally (not in Docker), use `localhost` instead of the service name. +3. Ensure the database user has sufficient permissions. + +### Sequelize connection error +**Issue**: `Unable to connect to the database`. +**Solution**: Check the `dialect` and `port` in your `.env` file. Ensure the database service is reachable from your host machine. + +## ๐Ÿณ Docker Issues + +### Permission Denied +**Issue**: Error when running Docker commands. +**Solution**: Run the commands with `sudo` or add your user to the `docker` group. + +### Port already in use +**Issue**: `Bind for 0.0.0.0:3000 failed: port is already allocated`. +**Solution**: Another process is using port 3000. You can change the port in your `.env` file or kill the existing process. + +## ๐Ÿš€ Runtime Issues + +### Environment variables are missing +**Issue**: Zod validation error on startup. +**Solution**: Create Express Forge validates your `.env` on startup. Ensure all required variables listed in `src/config/index.ts` are present in your `.env` file. + +### Modules not found (Path Aliases) +**Issue**: TypeScript can't find modules starting with `@`. +**Solution**: This is usually handled by `tsconfig-paths`. Ensure you are starting the app with `npm run dev`. If you've added new modules, you might need to restart the dev server. + +## ๐Ÿงช Testing Issues + +### Tests hanging +**Issue**: Tests don't exit after completion. +**Solution**: Ensure you are closing your database connections and server in an `afterAll` hook. Create Express Forge handles this by default in the generated boilerplate. + +--- + +Still having trouble? [Open an issue on GitHub](https://github.com/CODE-Y02/express-cli/issues) diff --git a/docs/v3/index.md b/docs/v3/index.md new file mode 100644 index 0000000..d6fc466 --- /dev/null +++ b/docs/v3/index.md @@ -0,0 +1,69 @@ +--- +title: Create Express Forge v3.x | Modern Express.js CLI +layout: home + +hero: + name: Create Express Forge + text: Build Better Express APIs, Faster. + tagline: The definitive CLI for scaffolding production-ready, enterprise-grade Express.js backends. + image: + src: /logo.svg + alt: Create Express Forge Logo + actions: + - theme: brand + text: Get Started + link: /v3/guide/getting-started + - theme: alt + text: โญ Star on GitHub + link: https://github.com/CODE-Y02/express-cli + +features: + - icon: โšก + title: Instant Scaffolding + details: Go from zero to a fully structured API in under 30 seconds with our interactive CLI. + - icon: ๐Ÿ›ก๏ธ + title: TypeScript Native + details: Deeply integrated TypeScript support with strict typing and modern ESM configuration. + - icon: ๐Ÿ“ฆ + title: Modern Architecture + details: Choose between Feature-driven Modular or traditional MVC patterns to suit your scale. + - icon: ๐Ÿ’พ + title: Type-safe ORM + details: First-class support for Prisma and Sequelize with pre-configured migrations. + - 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: Docker Ready + details: Multi-stage Dockerfiles and docker-compose setups included for seamless deployment. +--- + +> [!WARNING] +> You are viewing the documentation for **Create Express Forge v3.x (LTS)**. +> For the latest version (v4.x), please switch using the version dropdown above. + +### ๐Ÿš€ Key Benefits + +- **Zero Configuration**: Sensible defaults that work out of the box. +- **Enterprise Patterns**: Modular architecture that grows with your team. +- **Developer Experience**: Auto-reloading, linting, and formatting pre-configured. +- **Security First**: Best practices for CORS, rate limiting, and environment management. + +### ๐Ÿ›  Built With + +TypeScript ยท Express.js ยท Prisma ยท Docker ยท Vitest ยท Zod ยท Pino + +--- + +### โค๏ธ Support the Project + +If Create Express Forge has saved you time, please consider giving us a star on [GitHub](https://github.com/CODE-Y02/express-cli)! It helps us reach more developers and continue improving the project. diff --git a/docs/v3/reference/cli-options.md b/docs/v3/reference/cli-options.md new file mode 100644 index 0000000..e4d9408 --- /dev/null +++ b/docs/v3/reference/cli-options.md @@ -0,0 +1,32 @@ +# CLI Reference + +The `Create Express Forge` command can be used with various flags to bypass the interactive prompts and speed up your workflow. + +## Usage + +```bash +npx create-express-forge [project-name] [options] +``` + +## Options + +| Flag | Description | Values | +|------|-------------|--------| +| `--help` | Show help information | - | +| `--version` | Show current version | - | +| `--pattern` | Architecture pattern | `modular`, `mvc` | +| `--orm` | ORM to use | `prisma`, `sequelize`, `none` | +| `--db` | Database type | `postgres`, `mysql`, `sqlite` | +| `--logger` | Logging library | `winston`, `pino` | +| `--test` | Testing framework | `vitest`, `jest` | +| `--docker` | Include Docker setup | `true`, `false` | +| `--install` | Auto-install dependencies | `true`, `false` | + +## Example + +Scaffold a modular project with Prisma and Vitest: + +```bash +npx create-express-forge my-api --pattern modular --orm prisma --test vitest --install true +``` + diff --git a/docs/v3/reference/config.md b/docs/v3/reference/config.md new file mode 100644 index 0000000..afdaaa3 --- /dev/null +++ b/docs/v3/reference/config.md @@ -0,0 +1,36 @@ +# Configuration Reference + +Every project created with Create Express Forge uses a centralized configuration system powered by environment variables. + +## ๐ŸŒ Environment Variables + +Create a `.env` file in the root of your project. + +| Variable | Description | Default | Required | +|----------|-------------|---------|----------| +| `PORT` | The port the server listens on | `3000` | No | +| `NODE_ENV` | `development`, `production`, `test` | `development` | No | +| `DATABASE_URL` | Connection string for your DB | - | Yes* | +| `LOG_LEVEL` | `fatal`, `error`, `warn`, `info`, `debug`, `trace` | `debug` | No | +| `CORS_ORIGIN` | Allowed origins (comma separated) | `*` | No | +| `RATE_LIMIT_MAX` | Max requests per window | `100` | No | +| `RATE_LIMIT_WINDOW` | Window size in minutes | `15` | No | + +*\*Required if an ORM is selected.* + +## โš™๏ธ App Configuration + +Configuration is managed in `src/config/index.ts`. This file: +1. Validates environment variables using **Zod**. +2. Exports a typed configuration object. +3. Provides default values for optional variables. + +## ๐Ÿ’พ Database Config + +If you chose Prisma, your configuration is primarily in `prisma/schema.prisma`. + +For Sequelize, configuration is found in `src/config/database.ts`, which handles the connection pooling and dialect-specific settings. + +## ๐Ÿ“ Logger Config + +Logging configuration is found in `src/config/logger.ts`. You can toggle between `pretty-print` (for development) and `JSON` (for production) logging here. diff --git a/packages/create-express-forge/README.md b/packages/create-express-forge/README.md index 7a1619b..cc9e8d5 100644 --- a/packages/create-express-forge/README.md +++ b/packages/create-express-forge/README.md @@ -10,8 +10,8 @@ --- -## ๐ŸŽฏ Why use Express Forge? -Searching for an **Express TypeScript starter**? Most boilerplates are either too bloated or too empty. Express Forge gives you a modular, configurable, and type-safe foundation using the tools you already love. +## ๐ŸŽฏ Why use create-express-forge? +Searching for an **Express TypeScript starter**? Most boilerplates are either too bloated or too empty. create-express-forge gives you a modular, configurable, and type-safe foundation using the tools you already love. ## ๐Ÿš€ Quick Start Create your new project instantly: @@ -19,17 +19,23 @@ Create your new project instantly: npx create-express-forge@latest my-api ``` +Or scaffold in your **current directory**: +```bash +npx create-express-forge@latest . +``` + --- ## ๐Ÿ”ฅ Key Features - **๐Ÿ—๏ธ Smart Architectures**: Choose between **Modular (feature-based)** or **MVC** patterns. -- **๐Ÿ›ก๏ธ Type-Safe by Default**: Fully configured **TypeScript** with **Zod** for environment and request validation. +- **๐Ÿ›ก๏ธ Type-Safe by Default**: Fully configured **TypeScript** with **Path Aliases (`@/`)** and **Zod** for validation. +- **โšก Modern Tooling**: **Biome** is used for 20x faster linting and formatting (replaces ESLint/Prettier). - **๐Ÿ” Professional Auth**: Ready-to-use **JWT Authentication** with **HttpOnly Cookies** or **Bearer Headers**. - **๐Ÿ—„๏ธ ORM Flexibility**: Support for **Prisma**, **Sequelize**, or raw drivers. -- **๐Ÿ“œ Automated Documentation**: Built-in **OpenAPI / Swagger** support with a live UI. +- **๐Ÿ“œ Automated Documentation**: **Zero-JSDoc** OpenAPI support โ€” documentation generated directly from Zod schemas. - **๐Ÿณ DevOps Ready**: One-click **Docker** and **docker-compose** setup. - **๐Ÿชต Observability**: High-performance logging with **Pino** or **Winston**. -- **โšก Performance**: Optimized for fast startup and low overhead. +- **๐Ÿš€ Performance**: Optimized for fast startup, low overhead, and developer velocity. --- diff --git a/packages/create-express-forge/src/prompts.ts b/packages/create-express-forge/src/prompts.ts index e14f649..3130757 100644 --- a/packages/create-express-forge/src/prompts.ts +++ b/packages/create-express-forge/src/prompts.ts @@ -47,6 +47,9 @@ export async function runCLI( installDeps: true, }; const targetDir = path.resolve(process.cwd(), projectName); + if (projectName === ".") { + options.projectName = path.basename(process.cwd()).toLowerCase().replace(/[^a-z0-9-_]/g, "-"); + } await generateProject(options, targetDir); return; } @@ -57,9 +60,9 @@ export async function runCLI( message: "Project name:", default: "my-express-app", validate: (v) => - /^[a-z0-9-_]+$/.test(v) + /^[a-z0-9-_.]+$/.test(v) ? true - : "Use lowercase letters, numbers, hyphens, or underscores", + : "Use lowercase letters, numbers, hyphens, underscores, or a period", })); const packageManager = await select({ @@ -243,5 +246,8 @@ export async function runCLI( }; const targetDir = path.resolve(process.cwd(), projectName); + if (projectName === ".") { + options.projectName = path.basename(process.cwd()).toLowerCase().replace(/[^a-z0-9-_]/g, "-"); + } await generateProject(options, targetDir); } diff --git a/packages/create-express-forge/src/utils/template-manager.ts b/packages/create-express-forge/src/utils/template-manager.ts index ab055b1..892eeee 100644 --- a/packages/create-express-forge/src/utils/template-manager.ts +++ b/packages/create-express-forge/src/utils/template-manager.ts @@ -33,7 +33,27 @@ export class TemplateManager { templatePath: string, destPath: string, ): Promise { - const rendered = await this.eta.renderAsync(templatePath, this.opts); + let rendered = await this.eta.renderAsync(templatePath, this.opts); + + if (this.opts.importAlias && destPath.includes(`${path.sep}src${path.sep}`)) { + const srcIndex = destPath.lastIndexOf(`${path.sep}src${path.sep}`); + const srcDir = destPath.substring(0, srcIndex + 4); // path/to/src + + const importRegex = /(from\s+|import\(\s*)(['"])(\.\/|\.\.\/)([^'"]+)\2/g; + + rendered = rendered.replace(importRegex, (match, prefix, quote, dotPrefix, relPath) => { + const currentDir = path.dirname(destPath); + const absoluteImportPath = path.resolve(currentDir, dotPrefix + relPath); + + if (absoluteImportPath.startsWith(srcDir)) { + let aliasPath = path.relative(srcDir, absoluteImportPath); + aliasPath = aliasPath.split(path.sep).join('/'); // Ensure forward slashes + return `${prefix}${quote}@/${aliasPath}${quote}`; + } + return match; + }); + } + await fs.ensureDir(path.dirname(destPath)); await fs.writeFile(destPath, rendered, "utf-8"); } diff --git a/packages/create-express-forge/templates/features/testing/smoke.test.ts.eta b/packages/create-express-forge/templates/features/testing/smoke.test.ts.eta index 7ee02d7..f167313 100644 --- a/packages/create-express-forge/templates/features/testing/smoke.test.ts.eta +++ b/packages/create-express-forge/templates/features/testing/smoke.test.ts.eta @@ -1,5 +1,5 @@ import request from 'supertest'; -import { app } from '../src/app.js'; +import { app } from '../app.js'; describe('Smoke Test', () => { it('should return 404 for unknown route', async () => { From 056e78ec01923dddc997f92b53999609089aaeb6 Mon Sep 17 00:00:00 2001 From: code-y02 Date: Tue, 28 Apr 2026 19:51:24 +0530 Subject: [PATCH 10/16] feat: implement MCP server and LLM-friendly documentation exports for enhanced AI integration --- .changeset/v4-cli-and-docs-improvements.md | 2 + README.md | 26 + docs/package.json | 5 +- docs/public/ai.json | 48 ++ docs/public/llms-full.txt | 714 +++++++++++++++++++++ docs/public/llms.txt | 16 + docs/public/v3/llms-full.txt | 662 +++++++++++++++++++ docs/scripts/generate-llms-docs.ts | 97 +++ package.json | 2 + packages/mcp/package.json | 27 + packages/mcp/src/index.ts | 168 +++++ packages/mcp/tsconfig.json | 9 + pnpm-lock.yaml | 710 ++++++++++++++++++++ 13 files changed, 2485 insertions(+), 1 deletion(-) create mode 100644 docs/public/ai.json create mode 100644 docs/public/llms-full.txt create mode 100644 docs/public/llms.txt create mode 100644 docs/public/v3/llms-full.txt create mode 100644 docs/scripts/generate-llms-docs.ts create mode 100644 packages/mcp/package.json create mode 100644 packages/mcp/src/index.ts create mode 100644 packages/mcp/tsconfig.json diff --git a/.changeset/v4-cli-and-docs-improvements.md b/.changeset/v4-cli-and-docs-improvements.md index 6adfc2e..7b9422d 100644 --- a/.changeset/v4-cli-and-docs-improvements.md +++ b/.changeset/v4-cli-and-docs-improvements.md @@ -9,6 +9,8 @@ - Added documentation versioning support with v3 LTS dropdown - Enhanced documentation SEO, OpenGraph metadata, and social sharing tags - Standardized product naming to "Create Express Forge" across docs and CLI +- Added a dedicated **MCP Server** (`@create-express-forge/mcp`) for AI-assisted scaffolding and documentation access +- Automated generation of LLM-friendly documentation files (`llms.txt`, `llms-full.txt`, `ai.json`) - Highlighted **Ultra-Fast scaffolding** in the v3 vs v4 comparison (powered by internal refactoring) - Updated root and package READMEs with new features and Biome integration - Removed deprecated `cef` alias from documentation diff --git a/README.md b/README.md index 24a3c20..6e74f86 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,32 @@ create-express-forge/ โ””โ”€โ”€ .github/workflows/ โ† CI + Release ``` +## ๐Ÿค– AI & MCP Integration + +Create Express Forge is designed to be AI-friendly. We provide a built-in **MCP (Model Context Protocol)** server that lets you chat with your AI assistant about the project, fetch documentation, and generate scaffolding commands. + +### Using the MCP Server + +1. **Build the project**: `pnpm build` +2. **Add to your MCP Client**: Add the following configuration to your client (e.g., Claude Desktop). + +```json +{ + "mcpServers": { + "create-express-forge": { + "command": "node", + "args": ["/absolute/path/to/express-cli/packages/mcp/dist/index.js"] + } + } +} +``` + +### LLM Documentation +We also provide machine-readable documentation files for LLMs: +- **`llms.txt`**: [Project summary](https://code-y02.github.io/express-cli/llms.txt) +- **`llms-full.txt`**: [Full documentation context](https://code-y02.github.io/express-cli/llms-full.txt) +- **`ai.json`**: [Capability manifest and CLI flags](https://code-y02.github.io/express-cli/ai.json) + ## Legacy Support (v3.x) Documentation for the legacy v3.x (LTS) version is available at [https://code-y02.github.io/express-cli/v3/](https://code-y02.github.io/express-cli/v3/). diff --git a/docs/package.json b/docs/package.json index 040abff..558a762 100644 --- a/docs/package.json +++ b/docs/package.json @@ -5,11 +5,14 @@ "type": "module", "scripts": { "dev": "vitepress dev", - "build": "vitepress build", + "build": "tsx scripts/generate-llms-docs.ts && vitepress build", "preview": "vitepress preview", "lint": "echo 'no lint'" }, "devDependencies": { + "@types/fs-extra": "^11.0.4", + "fs-extra": "^11.2.0", + "tsx": "^4.15.0", "vitepress": "^1.3.1", "vue": "^3.4.31" } diff --git a/docs/public/ai.json b/docs/public/ai.json new file mode 100644 index 0000000..d35b7b8 --- /dev/null +++ b/docs/public/ai.json @@ -0,0 +1,48 @@ +{ + "name": "Create Express Forge", + "description": "Production-ready Express.js TypeScript backend generator", + "latestVersion": "4.1.2", + "ltsVersion": "3.3.2", + "repository": "https://github.com/CODE-Y02/express-cli", + "homepage": "https://code-y02.github.io/express-cli", + "versions": { + "v4": { + "status": "latest", + "fullDocs": "https://code-y02.github.io/express-cli/llms-full.txt", + "slugs": [ + "index", + "guide/architecture", + "guide/auth", + "guide/caching", + "guide/deployment", + "guide/features", + "guide/getting-started", + "guide/openapi", + "guide/structure", + "guide/testing", + "guide/troubleshooting", + "reference/cli-options", + "reference/config" + ] + }, + "v3": { + "status": "lts", + "fullDocs": "https://code-y02.github.io/express-cli/v3/llms-full.txt", + "slugs": [ + "index", + "guide/architecture", + "guide/auth", + "guide/caching", + "guide/deployment", + "guide/features", + "guide/getting-started", + "guide/openapi", + "guide/structure", + "guide/testing", + "guide/troubleshooting", + "reference/cli-options", + "reference/config" + ] + } + } +} diff --git a/docs/public/llms-full.txt b/docs/public/llms-full.txt new file mode 100644 index 0000000..6bd9c73 --- /dev/null +++ b/docs/public/llms-full.txt @@ -0,0 +1,714 @@ +# Create Express Forge v4.x (Latest) - Full Documentation + + + +--- FILE: index.md --- + +### ๐Ÿš€ Key Benefits + +- **Zero Configuration**: Sensible defaults that work out of the box. +- **Enterprise Patterns**: Modular architecture that grows with your team. +- **Developer Experience**: Auto-reloading, linting, and formatting pre-configured. +- **Security First**: Best practices for CORS, rate limiting, and environment management. + +### ๐Ÿ›  Built With + +TypeScript ยท Express.js ยท Prisma ยท Docker ยท Vitest ยท Zod ยท Biome ยท Pino + +--- + +### โค๏ธ Support the Project + +If Create Express Forge has saved you time, please consider giving us a star on [GitHub](https://github.com/CODE-Y02/express-cli)! It helps us reach more developers and continue improving the project. + + +--- FILE: guide/architecture.md --- + +# Architecture Patterns + +Create Express Forge supports two main architecture patterns to fit your project's needs. Choosing the right one is crucial for long-term maintainability. + +## ๐Ÿ“ฆ Modular Architecture (Recommended) + +This is the default and recommended pattern for medium to large applications. It organizes code by **features** (modules) rather than technical roles (controllers, models). + +### Why choose Modular? +- **Scalability**: Each module is self-contained, making it easier to manage as the app grows. +- **Isolation**: Changes to one feature are less likely to break another. +- **Team-Friendly**: Different developers can work on different modules without merge conflicts. + +### Structure: +```text +src/ + modules/ + users/ + users.controller.ts # Request handling + users.service.ts # Business logic + users.routes.ts # Route definitions + users.schema.ts # Validation schemas + products/ + ... +``` + +--- + +## ๐Ÿ› MVC Architecture + +A classic Model-View-Controller pattern. Best for smaller projects or those who prefer a traditional separation of concerns based on technical layers. + +### Why choose MVC? +- **Familiarity**: Most developers are familiar with this pattern from frameworks like Rails or Django. +- **Simplicity**: For very small apps, it might be quicker to navigate. + +### Structure: +```text +src/ + controllers/ # All controllers in one place + models/ # Database models + routes/ # All route definitions + services/ # Business logic +``` + +## ๐Ÿท๏ธ Path Aliases (@/) + +Regardless of the architecture you choose, Create Express Forge pre-configures **Path Aliases**. This means you can use absolute imports from the `src` directory instead of messy relative paths. + +**Instead of this:** +```typescript +import { User } from '../../../models/user.js'; +``` + +**You do this:** +```typescript +import { User } from '@/models/user.js'; +``` + +This ensures that moving files around won't break your imports. + +## Which one should I use? + +If you are building a production API that you expect to grow over time, **Modular Architecture** is almost always the better choice. It prevents the "Fat Controller" and "Fat Model" syndromes by keeping related logic close together. + + +--- FILE: guide/auth.md --- + +# Authentication + +Create 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/middleware/auth.ts` +- **Storage Options**: + - **๐Ÿช Cookie**: More secure against XSS. Uses `httpOnly` and `secure` flags. + - **๐Ÿ“จ Header**: Standard `Authorization: Bearer `. Recommended for mobile apps. +- **Config**: Set `JWT_SECRET` and `JWT_EXPIRES_IN` 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/middleware/auth.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 '../middleware/auth.js'; +import { todosController } from '../modules/todos/todos.controller.js'; + +// Protected route +router.get('/', auth, todosController.getTodos); +``` + +## 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, Create Express Forge pre-configures cookies to be `httpOnly` to prevent XSS attacks. + + +--- FILE: guide/caching.md --- + +# Caching + +Create 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. **Pro Fail-Fast**: In production, Create Express Forge strictly validates Redis connectivity on startup. If Redis is down, the app fails early to prevent inconsistent states. In development, it provides a clear warning and continues. + + +--- FILE: guide/deployment.md --- + +# Deployment + +Create Express Forge provides production-ready configurations to help you ship your API with confidence. + +## ๐Ÿณ Docker Deployment (Recommended) + +The easiest way to deploy is using the provided multi-stage `Dockerfile`. + +### Build Image +```bash +docker build -t my-express-api . +``` + +### Run Container +```bash +docker run -p 3000:3000 --env-file .env my-express-api +``` + +### Why Multi-stage? +Our Dockerfile uses multi-stage builds to: +1. **Reduce Image Size**: The final image only contains the compiled JavaScript and production dependencies. +2. **Security**: Source code and build tools are not included in the final production image. + +## โ˜๏ธ Cloud Platforms + +### Railway / Render / Fly.io +Most modern PaaS platforms will automatically detect the `Dockerfile` or the `start` script in `package.json`. + +1. Connect your GitHub repository. +2. Configure your environment variables (copy from `.env`). +3. Set the build command to `npm run build` (if not using Docker). + > **Note for Prisma users**: The generated `package.json` includes a `postinstall: "prisma generate"` script, which ensures your Prisma client is generated automatically before the build step on most PaaS platforms. +4. Set the start command to `npm start`. + +## ๐Ÿ›ก๏ธ Production Checklist + +Before going live, ensure: +- [ ] **Environment Variables**: `NODE_ENV` is set to `production`. +- [ ] **Database**: Migrations have been run on the production database. +- [ ] **Logging**: Log level is set appropriately (e.g., `info` or `error`). +- [ ] **Security**: CORS is restricted to your frontend domain. +- [ ] **Rate Limiting**: Configured for your production traffic. + + +--- FILE: guide/features.md --- + +# Core Features + +Create Express Forge comes packed with everything you need to build robust APIs. + +## ๐Ÿ›ก๏ธ TypeScript First +Type safety is at the core of Create Express Forge. Every scaffolded project includes: +- Strict TypeScript configuration. +- Path aliases (e.g., `@/config/env`). +- Type-safe environment variables via **Zod**. +- Automated scaffolding with your choice of **npm**, **pnpm**, **yarn**, or **bun**. + +## ๐Ÿ” Flexible Authentication +Scaffold a complete authentication system with a single choice: +- **JWT Authentication**: Choose between **HttpOnly Cookies** (recommended for web) or **Bearer Headers** (recommended for mobile/API clients). +- **Session Auth**: Battle-tested session management for stateful applications. +- **Protected Routes**: Every boilerplate includes a protected resource showing you exactly how to use the auth middleware. + +## ๐Ÿ’พ Database Integration +Choose your favorite ORM and get started instantly: +- **Prisma**: Modern ORM with auto-generated client and type-safe queries. +- **Sequelize**: The most popular traditional ORM for Node.js. +- **Migrations**: Pre-configured scripts to handle database schema changes. + +## ๐Ÿงช Testing Suite +Don't ship broken code. Create Express Forge sets up a complete testing environment: +- **Vitest/Jest**: Choose your favorite test runner. +- **Supertest**: For high-level API integration tests. +- **Example Tests**: Every scaffolded project includes example unit and integration tests. + +## ๐Ÿณ Docker Support +Ship to production with confidence: +- **Multi-stage Build**: Optimized Dockerfiles for smaller production images. +- **Docker Compose**: Includes a `docker-compose.yml` with a database setup for local development. + +## ๐Ÿ” Security Best Practices +Stay secure by default with pre-configured industry standards: +- **Helmet**: Automatically sets security-related HTTP headers to protect against common vulnerabilities. +- **CORS**: Flexible Cross-Origin Resource Sharing configuration. +- **Rate Limiting**: Integrated `express-rate-limit` to prevent brute-force attacks and DDoS. +- **Dotenv & Zod**: Every environment variable is validated on startup. If a variable is missing or malformed, the app fails fast with a clear error message. + +## ๐Ÿ“ Logging & Monitoring +- **Pino/Winston**: High-performance, structured logging. Pino is used by default for its extreme speed and JSON output, which is perfect for log aggregators like ELK or Datadog. +- **Pro Fail-Fast**: In production, the app strictly validates database and Redis connections on startup. If a dependency is down, the app fails early to prevent inconsistent states. In development, it provides clear warnings. +- **Health Checks**: A standard `/api/v1/health` endpoint is included, providing uptime, memory usage, and database connectivity status. + +## ๐Ÿ“œ OpenAPI Documentation +Never let your documentation get out of sync: +- **Swagger UI / Scalar**: Beautiful, integrated UI to explore and test your API endpoints directly from the browser. +- **Zero-JSDoc Spec**: Documentation is generated directly from your **Zod schemas** and a centralized registry. No more clunky JSDoc comments in your controllers! +- **Type-Safe Documentation**: Your runtime validation and your API documentation are always 100% in sync. + +## โšก Modern Tooling +- **Biome**: Replaces ESLint and Prettier for 20x faster linting and formatting. +- **Import Aliases**: Pre-configured `@/` paths for clean, absolute imports. +- **ESM Native**: Built from the ground up for modern Node.js and ECMAScript Modules. + +## ๐Ÿงฑ Graceful Shutdown +Every Create Express Forge project handles `SIGTERM` and `SIGINT` signals correctly. This ensures that: +1. No new requests are accepted. +2. Existing requests are finished. +3. Database connections are closed cleanly. +4. The process exits without data corruption. + +## ๐Ÿ› ๏ธ Error Handling & Responses +Create Express Forge enforces a consistent communication pattern between your API and clients. + +### Centralized Error Handling +A global error middleware is the "safety net" for your application. It catches all errors and transforms them into structured JSON responses, handling `Zod` validation errors and custom `ApiError` instances automatically. + +### Custom `ApiError` Class +Stop throwing generic strings. Use the built-in `ApiError` class to provide context, status codes, and operational flags: +- `ApiError.notFound('User not found')` +- `ApiError.unauthorized()` +- `ApiError.badRequest('Invalid input', validationErrors)` + +### Standardized `ApiResponse` +Ensure your frontend team always knows what to expect. Every success response follows a predictable schema: +```json +{ + "success": true, + "message": "Operation successful", + "data": { ... } +} +``` + +### Async Error Wrapper +The provided `asyncHandler` utility eliminates the need for `try-catch` blocks in your controllers, automatically forwarding any promise rejections to the global error handler. + +## ๐Ÿ†š Comparison: v4 vs v3 + +| Feature | v3.x (Legacy) | v4.x (Latest) | +| :--- | :--- | :--- | +| **OpenAPI Docs** | JSDoc-based (`swagger-jsdoc`) | **Zero-JSDoc** (via Zod schemas) | +| **Linting & Formatting** | ESLint + Prettier | **Biome** (20x faster) | +| **Path Aliases** | Not supported by default | **Native Support** (`@/` aliases) | +| **CLI Flexibility** | Only new directories | **Scaffold in `.`** supported | +| **Scaffolding Speed** | Standard | **Ultra-Fast** (Refactored logic) | +| **Reliability** | Standard startup | **Pro Fail-Fast** (DB/Redis checks) | +| **Imports** | Relative only (`../../`) | **Automated Alias resolution** | +| **Architecture** | Basic Modular/MVC | **Hardened Structures** | +| **Deployment** | Basic Dockerfiles | **Optimized Multi-stage builds** | + + +--- FILE: guide/getting-started.md --- + +# Getting Started + +Create Express Forge is designed to get you up and running with a production-grade Express backend in seconds. + +## Quick Start + +The fastest way to create a new project is using `npx`. You don't even need to install the CLI globally! + +```bash +npx create-express-forge@latest [project-name] +``` + +### Instant Scaffolding + +If you want to skip the prompts and use the recommended defaults, use the `--yes` flag: + +```bash +npx create-express-forge@latest my-api --yes +``` + +> [!TIP] +> You can scaffold a project in your **current directory** by using a period (`.`): +> `npx create-express-forge@latest .` + + +## Step-by-Step Guide + +### 1. Initialize Project +Run the command and follow the interactive prompts. You'll be asked to: +- Give your project a **name**. +- Select your preferred **package manager** (npm, pnpm, yarn). +- Choose an **architecture** (Modular or MVC). +- Select an **ORM** (Prisma, Sequelize, or none). +- Choose a **Testing Framework** (Vitest or Jest). + +### 2. Enter Directory +```bash +cd my-awesome-api +``` + +### 3. Start Development +```bash +npm run dev +``` + +Your API will now be running at `http://localhost:3000` with hot-reloading enabled. + +## Next Steps + +- Explore the [Architecture Patterns](./architecture) to understand how your code is organized. +- Check out the [Core Features](./features) to see what's included out of the box. +- Configure your environment variables in the `.env` file. + + +--- FILE: guide/openapi.md --- + +# API Documentation (OpenAPI) + +Create Express Forge uses `@asteasolutions/zod-to-openapi` to provide interactive, type-safe documentation for your API. This ensures that your runtime validation and your API documentation are always in sync. + +## Getting Started + +If you enabled OpenAPI during scaffolding, your documentation is available at: + +- **Interactive UI**: `http://localhost:3000/api-docs` +- **OpenAPI Spec**: `http://localhost:3000/api-docs.json` (Raw JSON) + +> [!TIP] +> On server startup, the CLI automatically logs the documentation URL for easy access. + +## Zero-JSDoc Documentation + +Unlike traditional Express apps that rely on clunky JSDoc comments (`@openapi`), Create Express Forge uses your **Zod schemas** to generate the OpenAPI specification. This makes your documentation: +1. **DRY (Don't Repeat Yourself)**: Define your schema once, use it for validation and documentation. +2. **Type-Safe**: Any changes to your Zod schemas are automatically reflected in the Swagger UI. +3. **Clean**: Your controller files stay free of giant comment blocks. + +## How it Works + +### 1. The Registry +A centralized registry is located at `src/docs/registry.ts`. This registry collects all your path and component definitions. + +### 2. Documenting a Path +Paths are registered directly in your `schema.ts` files. This keeps the documentation close to the validation logic. + +```typescript +import { z } from 'zod'; +import { registry } from '@/docs/registry.js'; + +// Define your schema +export const UserSchema = registry.register('User', z.object({ + id: z.string().uuid(), + name: z.string(), +})); + +// Register the path +registry.registerPath({ + method: 'get', + path: '/users', + summary: 'Get all users', + responses: { + 200: { + description: 'List of users', + content: { + 'application/json': { + schema: z.array(UserSchema), + }, + }, + }, + }, +}); +``` + +### 3. Setting Up Swagger +The `setupSwagger` function in `src/docs/swagger.ts` generates the final OpenAPI document from the registry and attaches the Swagger UI to your Express app. + +## Benefits +- **Live Testing**: Use the "Try it out" button to make real requests to your development server. +- **Auto-Sync**: Your documentation is built from your code, ensuring it never gets stale. +- **Standardized**: Uses the OpenAPI 3.0 specification, compatible with Postman, Insomnia, and code generators. + + +--- FILE: guide/structure.md --- + +# Project Structure + +Create Express Forge scaffolds a clean, professional directory structure. Depending on your chosen architecture, the structure will vary slightly. + +## ๐Ÿ“ฆ Modular Architecture + +This structure is organized by **features**. Each module is self-contained. + +```text +. +โ”œโ”€โ”€ src/ +โ”‚ โ”œโ”€โ”€ modules/ # Feature modules +โ”‚ โ”‚ โ””โ”€โ”€ users/ # Example module +โ”‚ โ”‚ โ”œโ”€โ”€ users.controller.ts +โ”‚ โ”‚ โ”œโ”€โ”€ users.service.ts +โ”‚ โ”‚ โ”œโ”€โ”€ users.routes.ts +โ”‚ โ”‚ โ””โ”€โ”€ users.schema.ts +โ”‚ โ”œโ”€โ”€ middleware/ # Global middleware +โ”‚ โ”œโ”€โ”€ utils/ # Shared utilities (ApiResponse, etc.) +โ”‚ โ”œโ”€โ”€ docs/ # OpenAPI registry and swagger setup +โ”‚ โ”œโ”€โ”€ config/ # App configuration +โ”‚ โ””โ”€โ”€ app.ts # App initialization +โ”œโ”€โ”€ prisma/ # Database schema (if Prisma chosen) +โ”œโ”€โ”€ src/__tests__/ # Integration and unit tests +โ”œโ”€โ”€ .env # Environment variables +โ””โ”€โ”€ package.json +``` + +## ๐Ÿ› MVC Architecture + +A traditional technical-layer separation. + +```text +. +โ”œโ”€โ”€ src/ +โ”‚ โ”œโ”€โ”€ controllers/ # Route handlers +โ”‚ โ”œโ”€โ”€ models/ # Data models +โ”‚ โ”œโ”€โ”€ services/ # Business logic +โ”‚ โ”œโ”€โ”€ routes/ # Route definitions +โ”‚ โ”œโ”€โ”€ middleware/ # Global middleware +โ”‚ โ”œโ”€โ”€ config/ +โ”‚ โ””โ”€โ”€ app.ts +โ”œโ”€โ”€ ... +``` + +## Key Files + +- **`src/app.ts`**: The entry point where Express is initialized, middleware is registered, and routes are attached. +- **`src/config/`**: Contains configuration for the database, logger, and environment variables. +- **`prisma/schema.prisma`**: (Optional) Defines your database models and relationships. +- **`docker-compose.yml`**: Defines the local development environment (e.g., PostgreSQL/MySQL containers). + + +--- FILE: guide/testing.md --- + +# Testing Strategy + +Create Express Forge encourages a test-driven approach to development. We provide a pre-configured testing environment using either **Vitest** (recommended for speed) or **Jest**. + +## ๐Ÿงช Types of Tests + +### Unit Tests +Focused on testing individual functions or services in isolation. +- **Location**: `src/modules/**/__tests__/*.unit.spec.ts` +- **Focus**: Business logic, utility functions, validation. + +### Integration Tests +Testing the full API flow, including database interactions. +- **Location**: `tests/*.int.spec.ts` +- **Focus**: HTTP status codes, API responses, database persistence. + +## ๐Ÿš€ Running Tests + +```bash +# Run all tests +npm test + +# Run tests in watch mode +npm run test:watch + +# Generate coverage report +npm run test:coverage +``` + +## ๐Ÿ›  Testing Tools + +- **Supertest**: Used for making HTTP requests to your app without starting a real server. +- **Prisma Mocking**: (If using Prisma) We provide patterns for mocking the Prisma client for unit tests. +- **Test Database**: Integration tests automatically use a separate test database defined in `.env.test`. + +## Example Integration Test + +```typescript +import request from 'supertest'; +import { app } from '../src/app'; + +describe('GET /health', () => { + it('should return 200 OK', async () => { + const response = await request(app).get('/health'); + expect(response.status).toBe(200); + expect(response.body.status).toBe('ok'); + }); +}); +``` + +--- + +## ๐Ÿ›  Contributing: CLI Smoke Testing + +If you are contributing to the `Create Express Forge` CLI itself, you should run the automated smoke test to verify your changes. This test scaffolds a project, runs a full TypeScript type-check, and builds the resulting app. + +```bash +cd packages/Create Express Forge +pnpm run test:smoke +``` + + +--- FILE: guide/troubleshooting.md --- + +# Troubleshooting + +Common issues and how to resolve them when using Create Express Forge. + +## ๐Ÿ’พ Database Issues + +### Prisma migration failed +**Issue**: `prisma migrate dev` fails with a connection error. +**Solution**: +1. Ensure your database container is running: `docker compose up -d`. +2. Check your `DATABASE_URL` in the `.env` file. If running locally (not in Docker), use `localhost` instead of the service name. +3. Ensure the database user has sufficient permissions. + +### Sequelize connection error +**Issue**: `Unable to connect to the database`. +**Solution**: Check the `dialect` and `port` in your `.env` file. Ensure the database service is reachable from your host machine. + +## ๐Ÿณ Docker Issues + +### Permission Denied +**Issue**: Error when running Docker commands. +**Solution**: Run the commands with `sudo` or add your user to the `docker` group. + +### Port already in use +**Issue**: `Bind for 0.0.0.0:3000 failed: port is already allocated`. +**Solution**: Another process is using port 3000. You can change the port in your `.env` file or kill the existing process. + +## ๐Ÿš€ Runtime Issues + +### Environment variables are missing +**Issue**: Zod validation error on startup. +**Solution**: Create Express Forge validates your `.env` on startup. Ensure all required variables listed in `src/config/index.ts` are present in your `.env` file. + +### Modules not found (Path Aliases) +**Issue**: TypeScript can't find modules starting with `@`. +**Solution**: This is usually handled by `tsconfig-paths`. Ensure you are starting the app with `npm run dev`. If you've added new modules, you might need to restart the dev server. + +## ๐Ÿงช Testing Issues + +### Tests hanging +**Issue**: Tests don't exit after completion. +**Solution**: Ensure you are closing your database connections and server in an `afterAll` hook. Create Express Forge handles this by default in the generated boilerplate. + +--- + +Still having trouble? [Open an issue on GitHub](https://github.com/CODE-Y02/express-cli/issues) + + +--- FILE: reference/cli-options.md --- + +# CLI Reference + +The `Create Express Forge` command can be used with various flags to bypass the interactive prompts and speed up your workflow. + +## Usage + +```bash +npx create-express-forge [project-name] [options] +``` + +## Options + +| Flag | Description | Values | +|------|-------------|--------| +| `--help` | Show help information | - | +| `--version` | Show current version | - | +| `--pattern` | Architecture pattern | `modular`, `mvc` | +| `--orm` | ORM to use | `prisma`, `sequelize`, `none` | +| `--db` | Database type | `postgres`, `mysql`, `sqlite` | +| `--logger` | Logging library | `winston`, `pino` | +| `--test` | Testing framework | `vitest`, `jest` | +| `--docker` | Include Docker setup | `true`, `false` | +| `--install` | Auto-install dependencies | `true`, `false` | + +## Example + +Scaffold a modular project with Prisma and Vitest: + +```bash +npx create-express-forge my-api --pattern modular --orm prisma --test vitest --install true +``` + + +--- FILE: reference/config.md --- + +# Configuration Reference + +Every project created with Create Express Forge uses a centralized configuration system powered by environment variables. + +## ๐ŸŒ Environment Variables + +Create a `.env` file in the root of your project. + +| Variable | Description | Default | Required | +|----------|-------------|---------|----------| +| `PORT` | The port the server listens on | `3000` | No | +| `NODE_ENV` | `development`, `production`, `test` | `development` | No | +| `DATABASE_URL` | Connection string for your DB | - | Yes* | +| `LOG_LEVEL` | `fatal`, `error`, `warn`, `info`, `debug`, `trace` | `debug` | No | +| `CORS_ORIGIN` | Allowed origins (comma separated) | `*` | No | +| `RATE_LIMIT_MAX` | Max requests per window | `100` | No | +| `RATE_LIMIT_WINDOW` | Window size in minutes | `15` | No | + +*\*Required if an ORM is selected.* + +## โš™๏ธ App Configuration + +Configuration is managed in `src/config/index.ts`. This file: +1. Validates environment variables using **Zod**. +2. Exports a typed configuration object. +3. Provides default values for optional variables. + +## ๐Ÿ’พ Database Config + +If you chose Prisma, your configuration is primarily in `prisma/schema.prisma`. + +For Sequelize, configuration is found in `src/config/database.ts`, which handles the connection pooling and dialect-specific settings. + +## ๐Ÿ“ Logger Config + +Logging configuration is found in `src/config/logger.ts`. You can toggle between `pretty-print` (for development) and `JSON` (for production) logging here. diff --git a/docs/public/llms.txt b/docs/public/llms.txt new file mode 100644 index 0000000..caee4c2 --- /dev/null +++ b/docs/public/llms.txt @@ -0,0 +1,16 @@ +# Create Express Forge +The ultimate CLI for scaffolding production-ready Express.js TypeScript backends. + +## Versions +- v4.x (Latest): Modern, Biome, Zod-OpenAPI, Path Aliases. +- v3.x (LTS): Stable, ESLint/Prettier, swagger-jsdoc. + +## Quick Start (v4) +npx create-express-forge@latest my-api + +## Quick Start (v3) +npx create-express-forge@3.3.2 my-api + +## Documentation +- v4 Full Docs: https://code-y02.github.io/express-cli/llms-full.txt +- v3 Full Docs: https://code-y02.github.io/express-cli/v3/llms-full.txt \ No newline at end of file diff --git a/docs/public/v3/llms-full.txt b/docs/public/v3/llms-full.txt new file mode 100644 index 0000000..4058f11 --- /dev/null +++ b/docs/public/v3/llms-full.txt @@ -0,0 +1,662 @@ +# Create Express Forge v3.x (LTS) - Full Documentation + + + +--- FILE: index.md --- + +> [!WARNING] +> You are viewing the documentation for **Create Express Forge v3.x (LTS)**. +> For the latest version (v4.x), please switch using the version dropdown above. + +### ๐Ÿš€ Key Benefits + +- **Zero Configuration**: Sensible defaults that work out of the box. +- **Enterprise Patterns**: Modular architecture that grows with your team. +- **Developer Experience**: Auto-reloading, linting, and formatting pre-configured. +- **Security First**: Best practices for CORS, rate limiting, and environment management. + +### ๐Ÿ›  Built With + +TypeScript ยท Express.js ยท Prisma ยท Docker ยท Vitest ยท Zod ยท Pino + +--- + +### โค๏ธ Support the Project + +If Create Express Forge has saved you time, please consider giving us a star on [GitHub](https://github.com/CODE-Y02/express-cli)! It helps us reach more developers and continue improving the project. + + +--- FILE: guide/architecture.md --- + +# Architecture Patterns + +Create Express Forge supports two main architecture patterns to fit your project's needs. Choosing the right one is crucial for long-term maintainability. + +## ๐Ÿ“ฆ Modular Architecture (Recommended) + +This is the default and recommended pattern for medium to large applications. It organizes code by **features** (modules) rather than technical roles (controllers, models). + +### Why choose Modular? +- **Scalability**: Each module is self-contained, making it easier to manage as the app grows. +- **Isolation**: Changes to one feature are less likely to break another. +- **Team-Friendly**: Different developers can work on different modules without merge conflicts. + +### Structure: +```text +src/ + modules/ + users/ + users.controller.ts # Request handling + users.service.ts # Business logic + users.routes.ts # Route definitions + users.schema.ts # Validation schemas + products/ + ... +``` + +--- + +## ๐Ÿ› MVC Architecture + +A classic Model-View-Controller pattern. Best for smaller projects or those who prefer a traditional separation of concerns based on technical layers. + +### Why choose MVC? +- **Familiarity**: Most developers are familiar with this pattern from frameworks like Rails or Django. +- **Simplicity**: For very small apps, it might be quicker to navigate. + +### Structure: +```text +src/ + controllers/ # All controllers in one place + models/ # Database models + routes/ # All route definitions + services/ # Business logic +``` + +## Which one should I use? + +If you are building a production API that you expect to grow over time, **Modular Architecture** is almost always the better choice. It prevents the "Fat Controller" and "Fat Model" syndromes by keeping related logic close together. + + +--- FILE: guide/auth.md --- + +# Authentication + +Create 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/middleware/auth.ts` +- **Storage Options**: + - **๐Ÿช Cookie**: More secure against XSS. Uses `httpOnly` and `secure` flags. + - **๐Ÿ“จ Header**: Standard `Authorization: Bearer `. Recommended for mobile apps. +- **Config**: Set `JWT_SECRET` and `JWT_EXPIRES_IN` 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/middleware/auth.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 '../middleware/auth.js'; +import { todosController } from '../modules/todos/todos.controller.js'; + +// Protected route +router.get('/', auth, todosController.getTodos); +``` + +## 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, Create Express Forge pre-configures cookies to be `httpOnly` to prevent XSS attacks. + + +--- FILE: guide/caching.md --- + +# Caching + +Create 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**: Create Express Forge handles Redis connection errors gracefully via the logger, preventing your entire app from crashing if the cache is down. + + +--- FILE: guide/deployment.md --- + +# Deployment + +Create Express Forge provides production-ready configurations to help you ship your API with confidence. + +## ๐Ÿณ Docker Deployment (Recommended) + +The easiest way to deploy is using the provided multi-stage `Dockerfile`. + +### Build Image +```bash +docker build -t my-express-api . +``` + +### Run Container +```bash +docker run -p 3000:3000 --env-file .env my-express-api +``` + +### Why Multi-stage? +Our Dockerfile uses multi-stage builds to: +1. **Reduce Image Size**: The final image only contains the compiled JavaScript and production dependencies. +2. **Security**: Source code and build tools are not included in the final production image. + +## โ˜๏ธ Cloud Platforms + +### Railway / Render / Fly.io +Most modern PaaS platforms will automatically detect the `Dockerfile` or the `start` script in `package.json`. + +1. Connect your GitHub repository. +2. Configure your environment variables (copy from `.env`). +3. Set the build command to `npm run build` (if not using Docker). + > **Note for Prisma users**: The generated `package.json` includes a `postinstall: "prisma generate"` script, which ensures your Prisma client is generated automatically before the build step on most PaaS platforms. +4. Set the start command to `npm start`. + +## ๐Ÿ›ก๏ธ Production Checklist + +Before going live, ensure: +- [ ] **Environment Variables**: `NODE_ENV` is set to `production`. +- [ ] **Database**: Migrations have been run on the production database. +- [ ] **Logging**: Log level is set appropriately (e.g., `info` or `error`). +- [ ] **Security**: CORS is restricted to your frontend domain. +- [ ] **Rate Limiting**: Configured for your production traffic. + + +--- FILE: guide/features.md --- + +# Core Features + +Create Express Forge comes packed with everything you need to build robust APIs. + +## ๐Ÿ›ก๏ธ TypeScript First +Type safety is at the core of Create Express Forge. Every scaffolded project includes: +- Strict TypeScript configuration. +- Path aliases (e.g., `@/config/env`). +- Type-safe environment variables via **Zod**. +- Automated scaffolding with your choice of **npm**, **pnpm**, **yarn**, or **bun**. + +## ๐Ÿ” Flexible Authentication +Scaffold a complete authentication system with a single choice: +- **JWT Authentication**: Choose between **HttpOnly Cookies** (recommended for web) or **Bearer Headers** (recommended for mobile/API clients). +- **Session Auth**: Battle-tested session management for stateful applications. +- **Protected Routes**: Every boilerplate includes a protected resource showing you exactly how to use the auth middleware. + +## ๐Ÿ’พ Database Integration +Choose your favorite ORM and get started instantly: +- **Prisma**: Modern ORM with auto-generated client and type-safe queries. +- **Sequelize**: The most popular traditional ORM for Node.js. +- **Migrations**: Pre-configured scripts to handle database schema changes. + +## ๐Ÿงช Testing Suite +Don't ship broken code. Create Express Forge sets up a complete testing environment: +- **Vitest/Jest**: Choose your favorite test runner. +- **Supertest**: For high-level API integration tests. +- **Example Tests**: Every scaffolded project includes example unit and integration tests. + +## ๐Ÿณ Docker Support +Ship to production with confidence: +- **Multi-stage Build**: Optimized Dockerfiles for smaller production images. +- **Docker Compose**: Includes a `docker-compose.yml` with a database setup for local development. + +## ๐Ÿ” Security Best Practices +Stay secure by default with pre-configured industry standards: +- **Helmet**: Automatically sets security-related HTTP headers to protect against common vulnerabilities. +- **CORS**: Flexible Cross-Origin Resource Sharing configuration. +- **Rate Limiting**: Integrated `express-rate-limit` to prevent brute-force attacks and DDoS. +- **Dotenv & Zod**: Every environment variable is validated on startup. If a variable is missing or malformed, the app fails fast with a clear error message. + +## ๐Ÿ“ Logging & Monitoring +- **Pino/Winston**: High-performance, structured logging. Pino is used by default for its extreme speed and JSON output, which is perfect for log aggregators like ELK or Datadog. +- **Health Checks**: A standard `/health` endpoint is included, providing uptime, memory usage, and database connectivity status. + +## ๐Ÿ“œ OpenAPI Documentation +Never let your documentation get out of sync: +- **Swagger UI**: Integrated UI to explore and test your API endpoints directly from the browser. +- **Auto-generated Spec**: The CLI generates a `docs.json` endpoint that is always up-to-date with your code's JSDoc annotations. +- **Security Schemas**: Pre-configured security definitions for your chosen auth strategy (Cookie or Bearer). + +## ๐Ÿงฑ Graceful Shutdown +Every Create Express Forge project handles `SIGTERM` and `SIGINT` signals correctly. This ensures that: +1. No new requests are accepted. +2. Existing requests are finished. +3. Database connections are closed cleanly. +4. The process exits without data corruption. + +## ๐Ÿ› ๏ธ Error Handling & Responses +Create Express Forge enforces a consistent communication pattern between your API and clients. + +### Centralized Error Handling +A global error middleware is the "safety net" for your application. It catches all errors and transforms them into structured JSON responses, handling `Zod` validation errors and custom `ApiError` instances automatically. + +### Custom `ApiError` Class +Stop throwing generic strings. Use the built-in `ApiError` class to provide context, status codes, and operational flags: +- `ApiError.notFound('User not found')` +- `ApiError.unauthorized()` +- `ApiError.badRequest('Invalid input', validationErrors)` + +### Standardized `ApiResponse` +Ensure your frontend team always knows what to expect. Every success response follows a predictable schema: +```json +{ + "success": true, + "message": "Operation successful", + "data": { ... } +} +``` + +### Async Error Wrapper +The provided `asyncHandler` utility eliminates the need for `try-catch` blocks in your controllers, automatically forwarding any promise rejections to the global error handler. + + +--- FILE: guide/getting-started.md --- + +# Getting Started + +Create Express Forge is designed to get you up and running with a production-grade Express backend in seconds. + +## Quick Start + +The fastest way to create a new project is using `npx`. You don't even need to install the CLI globally! + +```bash +npx create-express-forge@3.3.2 [project-name] +``` + +### Instant Scaffolding + +If you want to skip the prompts and use the recommended defaults, use the `--yes` flag: + +```bash +npx create-express-forge@3.3.2 my-api --yes +``` + +## Step-by-Step Guide + +### 1. Initialize Project +Run the command and follow the interactive prompts. You'll be asked to: +- Give your project a **name**. +- Select your preferred **package manager** (npm, pnpm, yarn). +- Choose an **architecture** (Modular or MVC). +- Select an **ORM** (Prisma, Sequelize, or none). +- Choose a **Testing Framework** (Vitest or Jest). + +### 2. Enter Directory +```bash +cd my-awesome-api +``` + +### 3. Start Development +```bash +npm run dev +``` + +Your API will now be running at `http://localhost:3000` with hot-reloading enabled. + +## Next Steps + +- Explore the [Architecture Patterns](./architecture) to understand how your code is organized. +- Check out the [Core Features](./features) to see what's included out of the box. +- Configure your environment variables in the `.env` file. + + +--- FILE: guide/openapi.md --- + +# API Documentation (OpenAPI) + +Create 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: + +- **Swagger UI**: `http://localhost:3000/docs` (Interactive) +- **OpenAPI Spec**: `http://localhost:3000/docs.json` (Raw JSON) + +> [!TIP] +> The `/docs.json` endpoint is always available for external tools (like Postman), even if you chose to disable the Swagger UI during scaffolding. + +## 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.). + + +--- FILE: guide/structure.md --- + +# Project Structure + +Create Express Forge scaffolds a clean, professional directory structure. Depending on your chosen architecture, the structure will vary slightly. + +## ๐Ÿ“ฆ Modular Architecture + +This structure is organized by **features**. Each module is self-contained. + +```text +. +โ”œโ”€โ”€ src/ +โ”‚ โ”œโ”€โ”€ modules/ # Feature modules +โ”‚ โ”‚ โ””โ”€โ”€ users/ # Example module +โ”‚ โ”‚ โ”œโ”€โ”€ users.controller.ts +โ”‚ โ”‚ โ”œโ”€โ”€ users.service.ts +โ”‚ โ”‚ โ”œโ”€โ”€ users.routes.ts +โ”‚ โ”‚ โ””โ”€โ”€ users.schema.ts +โ”‚ โ”œโ”€โ”€ shared/ # Code shared across modules +โ”‚ โ”‚ โ”œโ”€โ”€ middleware/ +โ”‚ โ”‚ โ”œโ”€โ”€ utils/ +โ”‚ โ”‚ โ””โ”€โ”€ constants/ +โ”‚ โ”œโ”€โ”€ config/ # App configuration +โ”‚ โ””โ”€โ”€ app.ts # App initialization +โ”œโ”€โ”€ prisma/ # Database schema (if Prisma chosen) +โ”œโ”€โ”€ tests/ # Integration and unit tests +โ”œโ”€โ”€ .env # Environment variables +โ””โ”€โ”€ package.json +``` + +## ๐Ÿ› MVC Architecture + +A traditional technical-layer separation. + +```text +. +โ”œโ”€โ”€ src/ +โ”‚ โ”œโ”€โ”€ controllers/ # Route handlers +โ”‚ โ”œโ”€โ”€ models/ # Data models +โ”‚ โ”œโ”€โ”€ services/ # Business logic +โ”‚ โ”œโ”€โ”€ routes/ # Route definitions +โ”‚ โ”œโ”€โ”€ middleware/ # Global middleware +โ”‚ โ”œโ”€โ”€ config/ +โ”‚ โ””โ”€โ”€ app.ts +โ”œโ”€โ”€ ... +``` + +## Key Files + +- **`src/app.ts`**: The entry point where Express is initialized, middleware is registered, and routes are attached. +- **`src/config/`**: Contains configuration for the database, logger, and environment variables. +- **`prisma/schema.prisma`**: (Optional) Defines your database models and relationships. +- **`docker-compose.yml`**: Defines the local development environment (e.g., PostgreSQL/MySQL containers). + + +--- FILE: guide/testing.md --- + +# Testing Strategy + +Create Express Forge encourages a test-driven approach to development. We provide a pre-configured testing environment using either **Vitest** (recommended for speed) or **Jest**. + +## ๐Ÿงช Types of Tests + +### Unit Tests +Focused on testing individual functions or services in isolation. +- **Location**: `src/modules/**/__tests__/*.unit.spec.ts` +- **Focus**: Business logic, utility functions, validation. + +### Integration Tests +Testing the full API flow, including database interactions. +- **Location**: `tests/*.int.spec.ts` +- **Focus**: HTTP status codes, API responses, database persistence. + +## ๐Ÿš€ Running Tests + +```bash +# Run all tests +npm test + +# Run tests in watch mode +npm run test:watch + +# Generate coverage report +npm run test:coverage +``` + +## ๐Ÿ›  Testing Tools + +- **Supertest**: Used for making HTTP requests to your app without starting a real server. +- **Prisma Mocking**: (If using Prisma) We provide patterns for mocking the Prisma client for unit tests. +- **Test Database**: Integration tests automatically use a separate test database defined in `.env.test`. + +## Example Integration Test + +```typescript +import request from 'supertest'; +import { app } from '../src/app'; + +describe('GET /health', () => { + it('should return 200 OK', async () => { + const response = await request(app).get('/health'); + expect(response.status).toBe(200); + expect(response.body.status).toBe('ok'); + }); +}); +``` + +--- + +## ๐Ÿ›  Contributing: CLI Smoke Testing + +If you are contributing to the `Create Express Forge` CLI itself, you should run the automated smoke test to verify your changes. This test scaffolds a project, runs a full TypeScript type-check, and builds the resulting app. + +```bash +cd packages/Create Express Forge +pnpm run test:smoke +``` + + +--- FILE: guide/troubleshooting.md --- + +# Troubleshooting + +Common issues and how to resolve them when using Create Express Forge. + +## ๐Ÿ’พ Database Issues + +### Prisma migration failed +**Issue**: `prisma migrate dev` fails with a connection error. +**Solution**: +1. Ensure your database container is running: `docker compose up -d`. +2. Check your `DATABASE_URL` in the `.env` file. If running locally (not in Docker), use `localhost` instead of the service name. +3. Ensure the database user has sufficient permissions. + +### Sequelize connection error +**Issue**: `Unable to connect to the database`. +**Solution**: Check the `dialect` and `port` in your `.env` file. Ensure the database service is reachable from your host machine. + +## ๐Ÿณ Docker Issues + +### Permission Denied +**Issue**: Error when running Docker commands. +**Solution**: Run the commands with `sudo` or add your user to the `docker` group. + +### Port already in use +**Issue**: `Bind for 0.0.0.0:3000 failed: port is already allocated`. +**Solution**: Another process is using port 3000. You can change the port in your `.env` file or kill the existing process. + +## ๐Ÿš€ Runtime Issues + +### Environment variables are missing +**Issue**: Zod validation error on startup. +**Solution**: Create Express Forge validates your `.env` on startup. Ensure all required variables listed in `src/config/index.ts` are present in your `.env` file. + +### Modules not found (Path Aliases) +**Issue**: TypeScript can't find modules starting with `@`. +**Solution**: This is usually handled by `tsconfig-paths`. Ensure you are starting the app with `npm run dev`. If you've added new modules, you might need to restart the dev server. + +## ๐Ÿงช Testing Issues + +### Tests hanging +**Issue**: Tests don't exit after completion. +**Solution**: Ensure you are closing your database connections and server in an `afterAll` hook. Create Express Forge handles this by default in the generated boilerplate. + +--- + +Still having trouble? [Open an issue on GitHub](https://github.com/CODE-Y02/express-cli/issues) + + +--- FILE: reference/cli-options.md --- + +# CLI Reference + +The `Create Express Forge` command can be used with various flags to bypass the interactive prompts and speed up your workflow. + +## Usage + +```bash +npx create-express-forge [project-name] [options] +``` + +## Options + +| Flag | Description | Values | +|------|-------------|--------| +| `--help` | Show help information | - | +| `--version` | Show current version | - | +| `--pattern` | Architecture pattern | `modular`, `mvc` | +| `--orm` | ORM to use | `prisma`, `sequelize`, `none` | +| `--db` | Database type | `postgres`, `mysql`, `sqlite` | +| `--logger` | Logging library | `winston`, `pino` | +| `--test` | Testing framework | `vitest`, `jest` | +| `--docker` | Include Docker setup | `true`, `false` | +| `--install` | Auto-install dependencies | `true`, `false` | + +## Example + +Scaffold a modular project with Prisma and Vitest: + +```bash +npx create-express-forge my-api --pattern modular --orm prisma --test vitest --install true +``` + + +--- FILE: reference/config.md --- + +# Configuration Reference + +Every project created with Create Express Forge uses a centralized configuration system powered by environment variables. + +## ๐ŸŒ Environment Variables + +Create a `.env` file in the root of your project. + +| Variable | Description | Default | Required | +|----------|-------------|---------|----------| +| `PORT` | The port the server listens on | `3000` | No | +| `NODE_ENV` | `development`, `production`, `test` | `development` | No | +| `DATABASE_URL` | Connection string for your DB | - | Yes* | +| `LOG_LEVEL` | `fatal`, `error`, `warn`, `info`, `debug`, `trace` | `debug` | No | +| `CORS_ORIGIN` | Allowed origins (comma separated) | `*` | No | +| `RATE_LIMIT_MAX` | Max requests per window | `100` | No | +| `RATE_LIMIT_WINDOW` | Window size in minutes | `15` | No | + +*\*Required if an ORM is selected.* + +## โš™๏ธ App Configuration + +Configuration is managed in `src/config/index.ts`. This file: +1. Validates environment variables using **Zod**. +2. Exports a typed configuration object. +3. Provides default values for optional variables. + +## ๐Ÿ’พ Database Config + +If you chose Prisma, your configuration is primarily in `prisma/schema.prisma`. + +For Sequelize, configuration is found in `src/config/database.ts`, which handles the connection pooling and dialect-specific settings. + +## ๐Ÿ“ Logger Config + +Logging configuration is found in `src/config/logger.ts`. You can toggle between `pretty-print` (for development) and `JSON` (for production) logging here. diff --git a/docs/scripts/generate-llms-docs.ts b/docs/scripts/generate-llms-docs.ts new file mode 100644 index 0000000..dd47d84 --- /dev/null +++ b/docs/scripts/generate-llms-docs.ts @@ -0,0 +1,97 @@ +import fs from 'fs-extra'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const DOCS_DIR = path.join(__dirname, '..'); +const PUBLIC_DIR = path.join(DOCS_DIR, 'public'); + +async function generate() { + console.log('๐Ÿš€ Generating LLM documentation files...'); + + // --- Helper to generate for a specific directory --- + async function generateForVersion(versionDir: string, publicSubDir: string, versionName: string) { + const baseDir = path.join(DOCS_DIR, versionDir); + const targetPublicDir = path.join(PUBLIC_DIR, publicSubDir); + await fs.ensureDir(targetPublicDir); + + const guideFiles = (await fs.readdir(path.join(baseDir, 'guide'))) + .filter(f => f.endsWith('.md')) + .map(f => `guide/${f}`); + + const referenceFiles = (await fs.readdir(path.join(baseDir, 'reference'))) + .filter(f => f.endsWith('.md')) + .map(f => `reference/${f}`); + + const allFiles = ['index.md', ...guideFiles, ...referenceFiles]; + + // 1. Generate llms-full.txt + let fullDocs = `# Create Express Forge ${versionName} - Full Documentation\n\n`; + for (const file of allFiles) { + const filePath = path.join(baseDir, file); + if (!(await fs.pathExists(filePath))) continue; + const content = await fs.readFile(filePath, 'utf-8'); + const cleanContent = content.replace(/^---[\s\S]*?---/, '').trim(); + fullDocs += `\n\n--- FILE: ${file} ---\n\n${cleanContent}\n`; + } + await fs.writeFile(path.join(targetPublicDir, 'llms-full.txt'), fullDocs); + return allFiles.map(f => f.replace('.md', '')); + } + + // Generate Latest (v4) + const v4Slugs = await generateForVersion('.', '.', 'v4.x (Latest)'); + + // Generate v3 (if exists) + let v3Slugs: string[] = []; + if (await fs.pathExists(path.join(DOCS_DIR, 'v3'))) { + v3Slugs = await generateForVersion('v3', 'v3', 'v3.x (LTS)'); + } + + // 2. Generate llms.txt (Main Summary) + const summary = ` +# Create Express Forge +The ultimate CLI for scaffolding production-ready Express.js TypeScript backends. + +## Versions +- v4.x (Latest): Modern, Biome, Zod-OpenAPI, Path Aliases. +- v3.x (LTS): Stable, ESLint/Prettier, swagger-jsdoc. + +## Quick Start (v4) +npx create-express-forge@latest my-api + +## Quick Start (v3) +npx create-express-forge@3.3.2 my-api + +## Documentation +- v4 Full Docs: https://code-y02.github.io/express-cli/llms-full.txt +- v3 Full Docs: https://code-y02.github.io/express-cli/v3/llms-full.txt +`.trim(); + await fs.writeFile(path.join(PUBLIC_DIR, 'llms.txt'), summary); + + // 3. Generate ai.json (Manifest) + const manifest = { + name: "Create Express Forge", + description: "Production-ready Express.js TypeScript backend generator", + latestVersion: "4.1.2", + ltsVersion: "3.3.2", + repository: "https://github.com/CODE-Y02/express-cli", + homepage: "https://code-y02.github.io/express-cli", + versions: { + v4: { + status: "latest", + fullDocs: "https://code-y02.github.io/express-cli/llms-full.txt", + slugs: v4Slugs + }, + v3: { + status: "lts", + fullDocs: "https://code-y02.github.io/express-cli/v3/llms-full.txt", + slugs: v3Slugs + } + } + }; + await fs.writeJson(path.join(PUBLIC_DIR, 'ai.json'), manifest, { spaces: 2 }); + + console.log('โœ… LLM documentation files generated in docs/public/'); +} + +generate().catch(console.error); diff --git a/package.json b/package.json index 40c05b3..eb53e41 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "docs:dev": "cd docs && pnpm run dev", "docs:build": "cd docs && pnpm run build", "docs:preview": "cd docs && pnpm run preview", + "mcp:dev": "pnpm --filter @create-express-forge/mcp dev", + "mcp:start": "pnpm --filter @create-express-forge/mcp start", "prepare": "husky" }, "devDependencies": { diff --git a/packages/mcp/package.json b/packages/mcp/package.json new file mode 100644 index 0000000..4c2c869 --- /dev/null +++ b/packages/mcp/package.json @@ -0,0 +1,27 @@ +{ + "name": "@create-express-forge/mcp", + "version": "1.0.0", + "description": "MCP Server for Create Express Forge documentation and tools", + "type": "module", + "bin": { + "create-express-forge-mcp": "./dist/index.js" + }, + "scripts": { + "dev": "tsx src/index.ts", + "build": "tsup src/index.ts --format esm --clean", + "start": "node dist/index.js", + "check-types": "tsc --noEmit", + "lint": "biome lint ." + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.0.1", + "zod": "^3.23.8" + }, + "devDependencies": { + "@repo/typescript-config": "workspace:*", + "@types/node": "^20.14.0", + "tsup": "^8.1.0", + "tsx": "^4.15.0", + "typescript": "5.5.3" + } +} diff --git a/packages/mcp/src/index.ts b/packages/mcp/src/index.ts new file mode 100644 index 0000000..f938d77 --- /dev/null +++ b/packages/mcp/src/index.ts @@ -0,0 +1,168 @@ +#!/usr/bin/env node +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { z } from "zod"; + +const DOCS_BASE = "https://code-y02.github.io/express-cli"; + +const server = new McpServer({ + name: "create-express-forge", + version: "4.1.2", +}); + +// โ”€โ”€โ”€ Resources โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +// Discovery Manifest (JSON index) +server.registerResource( + "manifest", + `${DOCS_BASE}/ai.json`, + { + description: "Machine-readable index of capabilities, CLI flags, common patterns, and documentation slugs.", + }, + async (uri) => { + try { + const res = await fetch(uri.href); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const json = await res.text(); + return { contents: [{ uri: uri.href, mimeType: "application/json", text: json }] }; + } catch (_e) { + // Fallback to llms.txt if ai.json is missing + const fallbackUrl = `${DOCS_BASE}/llms.txt`; + const res = await fetch(fallbackUrl); + if (!res.ok) throw new Error(`Manifest and Fallback failed: ${res.status}`); + const text = await res.text(); + return { contents: [{ uri: fallbackUrl, mimeType: "text/plain", text }] }; + } + }, +); + +// Full documentation (primary resource) +server.registerResource( + "docs-full", + `${DOCS_BASE}/llms-full.txt`, + { + description: "Full Create Express Forge API documentation, architecture guides, and CLI reference.", + }, + async (uri) => { + try { + const res = await fetch(uri.href); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const text = await res.text(); + return { contents: [{ uri: uri.href, mimeType: "text/plain", text }] }; + } catch (_e) { + const fallbackUrl = `${DOCS_BASE}/llms.txt`; + const res = await fetch(fallbackUrl); + if (!res.ok) throw new Error(`Full Docs and Fallback failed: ${res.status}`); + const text = await res.text(); + return { contents: [{ uri: fallbackUrl, mimeType: "text/plain", text }] }; + } + }, +); + +// โ”€โ”€โ”€ Tools โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +/** + * get-docs โ€” fetch the full documentation on demand. + */ +server.registerTool( + "get-docs", + { + description: "Fetch the full documentation for a specific version of Create Express Forge.", + inputSchema: z.object({ + version: z.enum(["v4", "v3"]).default("v4").describe("Documentation version (v4 = latest, v3 = lts)"), + }), + }, + async ({ version }) => { + const subPath = version === "v3" ? "/v3" : ""; + const res = await fetch(`${DOCS_BASE}${subPath}/llms-full.txt`); + if (!res.ok) { + return { + content: [{ type: "text", text: `Failed to fetch ${version} documentation (HTTP ${res.status}).` }], + isError: true, + }; + } + const text = await res.text(); + return { + content: [{ type: "text", text: `# Create Express Forge ${version} Documentation\nSource: ${DOCS_BASE}${subPath}/llms-full.txt\n\n${text}` }], + }; + } +); + +/** + * get-page โ€” fetch a specific documentation page by slug and version. + */ +server.registerTool( + "get-page", + { + description: "Fetch a specific Create Express Forge documentation page for a specific version.", + inputSchema: z.object({ + slug: z.string().describe("Page slug, e.g. 'guide/getting-started' or 'reference/cli-options'"), + version: z.enum(["v4", "v3"]).default("v4").describe("Documentation version"), + }), + }, + async ({ slug, version }) => { + const cleanSlug = slug.replace(/^\/+|\/+$/g, ""); + const subPath = version === "v3" ? "/v3" : ""; + const url = `${DOCS_BASE}${subPath}/${cleanSlug}.md`; + const res = await fetch(url); + if (!res.ok) { + return { + content: [{ type: "text", text: `Page not found for version ${version}: ${url}. Check the 'manifest' resource for available slugs.` }], + isError: true, + }; + } + const text = await res.text(); + return { + content: [{ type: "text", text: `# Documentation (${version}): ${cleanSlug}\nSource: ${url}\n\n${text}` }], + }; + } +); + +/** + * get-scaffold-command โ€” helper to generate the correct npx command based on requirements. + */ +server.registerTool( + "get-scaffold-command", + { + description: "Generate the correct npx create-express-forge command with appropriate flags based on project requirements.", + inputSchema: z.object({ + version: z.enum(["v4", "v3"]).default("v4").describe("Version of the CLI to use (v4 = latest, v3 = 3.3.2)"), + projectName: z.string().default("my-api").describe("Name of the project"), + pattern: z.enum(["modular", "mvc"]).optional().describe("Architecture pattern"), + orm: z.enum(["prisma", "sequelize", "none"]).optional().describe("ORM to use"), + db: z.enum(["postgresql", "mysql", "sqlite", "none"]).optional().describe("Database type"), + install: z.boolean().optional().describe("Whether to auto-install dependencies"), + }), + }, + async ({ version, projectName, pattern, orm, db, install }) => { + const versionTag = version === "v3" ? "3.3.2" : "latest"; + let command = `npx create-express-forge@${versionTag} ${projectName}`; + if (pattern) command += ` --pattern ${pattern}`; + if (orm) command += ` --orm ${orm}`; + if (db) command += ` --db ${db}`; + if (install !== undefined) command += ` --install ${install}`; + + return { + content: [ + { + type: "text", + text: `Use the following command to scaffold your project:\n\n\`\`\`bash\n${command}\n\`\`\`` + } + ], + }; + } +); + +// โ”€โ”€โ”€ Start Server โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +async function main() { + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error("Create Express Forge MCP server running on stdio"); +} + +main().catch((error) => { + console.error("Fatal error in main():", error); + process.exit(1); +}); + diff --git a/packages/mcp/tsconfig.json b/packages/mcp/tsconfig.json new file mode 100644 index 0000000..e3459f4 --- /dev/null +++ b/packages/mcp/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@repo/typescript-config/base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f5bf5c4..f6abd8f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,15 @@ importers: docs: devDependencies: + '@types/fs-extra': + specifier: ^11.0.4 + version: 11.0.4 + fs-extra: + specifier: ^11.2.0 + version: 11.3.4 + tsx: + specifier: ^4.15.0 + version: 4.21.0 vitepress: specifier: ^1.3.1 version: 1.6.4(@algolia/client-search@5.50.2)(@types/node@20.19.39)(postcss@8.5.10)(search-insights@2.17.3)(typescript@5.5.3) @@ -87,6 +96,31 @@ importers: packages/lint-config: {} + packages/mcp: + dependencies: + '@modelcontextprotocol/sdk': + specifier: ^1.0.1 + version: 1.29.0(zod@3.25.76) + zod: + specifier: ^3.23.8 + version: 3.25.76 + devDependencies: + '@repo/typescript-config': + specifier: workspace:* + version: link:../typescript-config + '@types/node': + specifier: ^20.14.0 + version: 20.19.39 + tsup: + specifier: ^8.1.0 + version: 8.5.1(postcss@8.5.10)(tsx@4.21.0)(typescript@5.5.3) + tsx: + specifier: ^4.15.0 + version: 4.21.0 + typescript: + specifier: 5.5.3 + version: 5.5.3 + packages/typescript-config: {} packages: @@ -613,6 +647,12 @@ packages: cpu: [x64] os: [win32] + '@hono/node-server@1.19.14': + resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + '@iconify-json/simple-icons@1.2.79': resolution: {integrity: sha512-aNyO7Fd1qej9oQfIyohYFRv0lhQLaZ+6UkK1c1qwax0MDPUOZOdq65MlU500kow97pD/W+b2u1And3e25eE24Q==} @@ -776,6 +816,16 @@ packages: '@manypkg/get-packages@1.1.3': resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + '@modelcontextprotocol/sdk@1.29.0': + resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1126,6 +1176,10 @@ packages: '@vueuse/shared@12.8.2': resolution: {integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + acorn-walk@8.3.5: resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} engines: {node: '>=0.4.0'} @@ -1135,6 +1189,17 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + algoliasearch@5.50.2: resolution: {integrity: sha512-Tfp26yoNWurUjfgK4GOrVJQhSNXu9tJtHfFFNosgT2YClG+vPyUjX/gbC8rG39qLncnZg8Fj34iarQWpMkqefw==} engines: {node: '>= 14.0.0'} @@ -1182,6 +1247,10 @@ packages: birpc@2.9.0: resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + engines: {node: '>=18'} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -1192,10 +1261,22 @@ packages: peerDependencies: esbuild: '>=0.18' + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -1260,10 +1341,30 @@ packages: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + copy-anything@4.0.5: resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} engines: {node: '>=18'} + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1284,6 +1385,10 @@ packages: resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} engines: {node: '>=6'} + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -1303,6 +1408,13 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + emoji-regex-xs@1.0.0: resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} @@ -1312,6 +1424,10 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + enquirer@2.4.1: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} @@ -1320,6 +1436,18 @@ packages: resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} engines: {node: '>=0.12'} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} @@ -1330,6 +1458,9 @@ packages: engines: {node: '>=18'} hasBin: true + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} @@ -1345,6 +1476,18 @@ packages: resolution: {integrity: sha512-e3x3FBvGzeCIHhF+zhK8FZA2vC5uFn6b4HJjegUbIWrDb4mJ7JjTGMJY9VGIbRVpmSwHopNiaJibhjIr+HfLug==} engines: {node: '>=6.0.0'} + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventsource-parser@3.0.8: + resolution: {integrity: sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + execa@8.0.1: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} @@ -1353,13 +1496,29 @@ packages: resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} engines: {node: ^18.19.0 || >=20.5.0} + express-rate-limit@8.4.1: + resolution: {integrity: sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + extendable-error@0.1.7: resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} @@ -1380,6 +1539,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -1390,6 +1553,14 @@ packages: focus-trap@7.8.0: resolution: {integrity: sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + fs-extra@11.3.4: resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==} engines: {node: '>=14.14'} @@ -1407,6 +1578,9 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + get-east-asian-width@1.5.0: resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} engines: {node: '>=18'} @@ -1414,6 +1588,14 @@ packages: get-func-name@2.0.2: resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} @@ -1433,21 +1615,41 @@ packages: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.3: + resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} + engines: {node: '>= 0.4'} + hast-util-to-html@9.0.5: resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + hono@4.12.15: + resolution: {integrity: sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==} + engines: {node: '>=16.9.0'} + hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + human-id@4.1.3: resolution: {integrity: sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==} hasBin: true @@ -1473,6 +1675,17 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ip-address@10.1.0: + resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -1497,6 +1710,9 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-stream@3.0.0: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1528,6 +1744,9 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + jose@6.2.3: + resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} @@ -1543,6 +1762,12 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} @@ -1584,9 +1809,21 @@ packages: mark.js@8.11.1: resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + mdast-util-to-hast@13.2.1: resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -1613,6 +1850,14 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + mimic-fn@4.0.0: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} @@ -1649,6 +1894,10 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + npm-run-path@5.3.0: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1661,6 +1910,17 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + onetime@6.0.0: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} @@ -1710,6 +1970,10 @@ packages: resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} engines: {node: '>=18'} + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -1722,6 +1986,9 @@ packages: resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} engines: {node: '>=12'} + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -1757,6 +2024,10 @@ packages: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} @@ -1801,12 +2072,28 @@ packages: property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + qs@6.15.1: + resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} + engines: {node: '>=0.6'} + quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} @@ -1827,6 +2114,10 @@ packages: regex@6.1.0: resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} @@ -1850,6 +2141,10 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -1864,6 +2159,17 @@ packages: engines: {node: '>=10'} hasBin: true + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1875,6 +2181,22 @@ packages: shiki@2.5.0: resolution: {integrity: sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==} + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -1910,6 +2232,10 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} @@ -1996,6 +2322,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -2038,6 +2368,10 @@ packages: resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} engines: {node: '>=4'} + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + typescript@5.5.3: resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==} engines: {node: '>=14.17'} @@ -2076,6 +2410,14 @@ packages: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + vfile-message@4.0.3: resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} @@ -2177,6 +2519,9 @@ packages: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + yocto-queue@1.2.2: resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} engines: {node: '>=12.20'} @@ -2189,6 +2534,14 @@ packages: resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} engines: {node: '>=18'} + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -2670,6 +3023,10 @@ snapshots: '@esbuild/win32-x64@0.27.7': optional: true + '@hono/node-server@1.19.14(hono@4.12.15)': + dependencies: + hono: 4.12.15 + '@iconify-json/simple-icons@1.2.79': dependencies: '@iconify/types': 2.0.0 @@ -2835,6 +3192,28 @@ snapshots: globby: 11.1.0 read-yaml-file: 1.1.0 + '@modelcontextprotocol/sdk@1.29.0(zod@3.25.76)': + dependencies: + '@hono/node-server': 1.19.14(hono@4.12.15) + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.0.8 + express: 5.2.1 + express-rate-limit: 8.4.1(express@5.2.1) + hono: 4.12.15 + jose: 6.2.3 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 3.25.76 + zod-to-json-schema: 3.25.2(zod@3.25.76) + transitivePeerDependencies: + - supports-color + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3159,12 +3538,28 @@ snapshots: transitivePeerDependencies: - typescript + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + acorn-walk@8.3.5: dependencies: acorn: 8.16.0 acorn@8.16.0: {} + ajv-formats@3.0.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + algoliasearch@5.50.2: dependencies: '@algolia/abtesting': 1.16.2 @@ -3212,6 +3607,20 @@ snapshots: birpc@2.9.0: {} + body-parser@2.2.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.15.1 + raw-body: 3.0.2 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -3221,8 +3630,20 @@ snapshots: esbuild: 0.27.7 load-tsconfig: 0.2.5 + bytes@3.1.2: {} + cac@6.7.14: {} + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + ccount@2.0.1: {} chai@4.5.0: @@ -3275,10 +3696,23 @@ snapshots: consola@3.4.2: {} + content-disposition@1.1.0: {} + + content-type@1.0.5: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + copy-anything@4.0.5: dependencies: is-what: 5.5.0 + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -3295,6 +3729,8 @@ snapshots: dependencies: type-detect: 4.1.0 + depd@2.0.0: {} + dequal@2.0.3: {} detect-indent@6.1.0: {} @@ -3309,12 +3745,22 @@ snapshots: dependencies: path-type: 4.0.0 + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + emoji-regex-xs@1.0.0: {} emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} + encodeurl@2.0.0: {} + enquirer@2.4.1: dependencies: ansi-colors: 4.1.3 @@ -3322,6 +3768,14 @@ snapshots: entities@7.0.1: {} + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + esbuild@0.21.5: optionalDependencies: '@esbuild/aix-ppc64': 0.21.5 @@ -3377,6 +3831,8 @@ snapshots: '@esbuild/win32-ia32': 0.27.7 '@esbuild/win32-x64': 0.27.7 + escape-html@1.0.3: {} + esprima@4.0.1: {} estree-walker@2.0.2: {} @@ -3387,6 +3843,14 @@ snapshots: eta@3.5.0: {} + etag@1.8.1: {} + + eventsource-parser@3.0.8: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.0.8 + execa@8.0.1: dependencies: cross-spawn: 7.0.6 @@ -3414,8 +3878,48 @@ snapshots: strip-final-newline: 4.0.0 yoctocolors: 2.1.2 + express-rate-limit@8.4.1(express@5.2.1): + dependencies: + express: 5.2.1 + ip-address: 10.1.0 + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.2 + content-disposition: 1.1.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.1 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + extendable-error@0.1.7: {} + fast-deep-equal@3.1.3: {} + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3424,6 +3928,8 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 + fast-uri@3.1.0: {} + fastq@1.20.1: dependencies: reusify: 1.1.0 @@ -3440,6 +3946,17 @@ snapshots: dependencies: to-regex-range: 5.0.1 + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + find-up@4.1.0: dependencies: locate-path: 5.0.0 @@ -3455,6 +3972,10 @@ snapshots: dependencies: tabbable: 6.4.0 + forwarded@0.2.0: {} + + fresh@2.0.0: {} + fs-extra@11.3.4: dependencies: graceful-fs: 4.2.11 @@ -3476,10 +3997,30 @@ snapshots: fsevents@2.3.3: optional: true + function-bind@1.1.2: {} + get-east-asian-width@1.5.0: {} get-func-name@2.0.2: {} + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.3 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + get-stream@8.0.1: {} get-stream@9.0.1: @@ -3504,8 +4045,16 @@ snapshots: merge2: 1.4.1 slash: 3.0.0 + gopd@1.2.0: {} + graceful-fs@4.2.11: {} + has-symbols@1.1.0: {} + + hasown@2.0.3: + dependencies: + function-bind: 1.1.2 + hast-util-to-html@9.0.5: dependencies: '@types/hast': 3.0.4 @@ -3524,10 +4073,20 @@ snapshots: dependencies: '@types/hast': 3.0.4 + hono@4.12.15: {} + hookable@5.5.3: {} html-void-elements@3.0.0: {} + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + human-id@4.1.3: {} human-signals@5.0.0: {} @@ -3542,6 +4101,12 @@ snapshots: ignore@5.3.2: {} + inherits@2.0.4: {} + + ip-address@10.1.0: {} + + ipaddr.js@1.9.1: {} + is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -3556,6 +4121,8 @@ snapshots: is-plain-obj@4.1.0: {} + is-promise@4.0.0: {} + is-stream@3.0.0: {} is-stream@4.0.1: {} @@ -3574,6 +4141,8 @@ snapshots: isexe@2.0.0: {} + jose@6.2.3: {} + joycon@3.1.1: {} js-tokens@9.0.1: {} @@ -3587,6 +4156,10 @@ snapshots: dependencies: argparse: 2.0.1 + json-schema-traverse@1.0.0: {} + + json-schema-typed@8.0.2: {} + jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 @@ -3629,6 +4202,8 @@ snapshots: mark.js@8.11.1: {} + math-intrinsics@1.1.0: {} + mdast-util-to-hast@13.2.1: dependencies: '@types/hast': 3.0.4 @@ -3641,6 +4216,10 @@ snapshots: unist-util-visit: 5.1.0 vfile: 6.0.3 + media-typer@1.1.0: {} + + merge-descriptors@2.0.0: {} + merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -3667,6 +4246,12 @@ snapshots: braces: 3.0.3 picomatch: 2.3.2 + mime-db@1.54.0: {} + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + mimic-fn@4.0.0: {} mimic-function@5.0.1: {} @@ -3696,6 +4281,8 @@ snapshots: nanoid@3.3.11: {} + negotiator@1.0.0: {} + npm-run-path@5.3.0: dependencies: path-key: 4.0.0 @@ -3707,6 +4294,16 @@ snapshots: object-assign@4.1.1: {} + object-inspect@1.13.4: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + onetime@6.0.0: dependencies: mimic-fn: 4.0.0 @@ -3761,12 +4358,16 @@ snapshots: parse-ms@4.0.0: {} + parseurl@1.3.3: {} + path-exists@4.0.0: {} path-key@3.1.1: {} path-key@4.0.0: {} + path-to-regexp@8.4.2: {} + path-type@4.0.0: {} pathe@1.1.2: {} @@ -3787,6 +4388,8 @@ snapshots: pirates@4.0.7: {} + pkce-challenge@5.0.1: {} + pkg-types@1.3.1: dependencies: confbox: 0.1.8 @@ -3822,10 +4425,28 @@ snapshots: property-information@7.1.0: {} + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + qs@6.15.1: + dependencies: + side-channel: 1.1.0 + quansync@0.2.11: {} queue-microtask@1.2.3: {} + range-parser@1.2.1: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + react-is@18.3.1: {} read-yaml-file@1.1.0: @@ -3847,6 +4468,8 @@ snapshots: dependencies: regex-utilities: 2.3.0 + require-from-string@2.0.2: {} + resolve-from@5.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -3891,6 +4514,16 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.60.2 fsevents: 2.3.3 + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -3901,6 +4534,33 @@ snapshots: semver@7.7.4: {} + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -3918,6 +4578,34 @@ snapshots: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} signal-exit@4.1.0: {} @@ -3941,6 +4629,8 @@ snapshots: stackback@0.0.2: {} + statuses@2.0.2: {} + std-env@3.10.0: {} stdin-discarder@0.2.2: {} @@ -4023,6 +4713,8 @@ snapshots: dependencies: is-number: 7.0.0 + toidentifier@1.0.1: {} + tree-kill@1.2.2: {} trim-lines@3.0.1: {} @@ -4075,6 +4767,12 @@ snapshots: type-detect@4.1.0: {} + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.2 + typescript@5.5.3: {} ufo@1.6.3: {} @@ -4110,6 +4808,10 @@ snapshots: universalify@2.0.1: {} + unpipe@1.0.0: {} + + vary@1.1.2: {} + vfile-message@4.0.3: dependencies: '@types/unist': 3.0.3 @@ -4255,10 +4957,18 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 + wrappy@1.0.2: {} + yocto-queue@1.2.2: {} yoctocolors-cjs@2.1.3: {} yoctocolors@2.1.2: {} + zod-to-json-schema@3.25.2(zod@3.25.76): + dependencies: + zod: 3.25.76 + + zod@3.25.76: {} + zwitch@2.0.4: {} From 3e96fae5723aa6dce3aa6bf94bc8a38c0087ee88 Mon Sep 17 00:00:00 2001 From: code-y02 Date: Tue, 28 Apr 2026 19:56:16 +0530 Subject: [PATCH 11/16] feat: add high-fidelity personalized welcome banner using figlet and gradient-string --- .changeset/v4-cli-and-docs-improvements.md | 1 + packages/create-express-forge/package.json | 6 + packages/create-express-forge/src/prompts.ts | 2 +- .../create-express-forge/src/utils/display.ts | 28 +- pnpm-lock.yaml | 455 ++++++++++++++++++ 5 files changed, 490 insertions(+), 2 deletions(-) diff --git a/.changeset/v4-cli-and-docs-improvements.md b/.changeset/v4-cli-and-docs-improvements.md index 7b9422d..2d98767 100644 --- a/.changeset/v4-cli-and-docs-improvements.md +++ b/.changeset/v4-cli-and-docs-improvements.md @@ -6,6 +6,7 @@ - Implemented automated import alias resolution (`@/`) for scaffolded projects - Fixed relative import bug in smoke test template - Synchronized documentation with latest v4 features (Zod-to-OpenAPI, Biome, Pro Fail-Fast) +- Added a **High-Fidelity Personalized Welcome** message using a large gradient-styled font (powered by `figlet` and `gradient-string`) to greet users by name - Added documentation versioning support with v3 LTS dropdown - Enhanced documentation SEO, OpenGraph metadata, and social sharing tags - Standardized product naming to "Create Express Forge" across docs and CLI diff --git a/packages/create-express-forge/package.json b/packages/create-express-forge/package.json index 200204d..6918082 100644 --- a/packages/create-express-forge/package.json +++ b/packages/create-express-forge/package.json @@ -55,17 +55,23 @@ "dependencies": { "@inquirer/prompts": "^7.1.0", "chalk": "^5.3.0", + "chalk-animation": "^2.0.3", "commander": "^12.1.0", "eta": "^3.5.0", "execa": "^9.3.0", + "figlet": "^1.11.0", "fs-extra": "^11.2.0", + "gradient-string": "^3.0.0", "ora": "^8.1.1", "pkg-types": "^1.2.0" }, "devDependencies": { "@repo/lint-config": "workspace:*", "@repo/typescript-config": "workspace:*", + "@types/chalk-animation": "^1.6.3", + "@types/figlet": "^1.7.0", "@types/fs-extra": "^11.0.4", + "@types/gradient-string": "^1.1.6", "@types/node": "^20.14.0", "tsup": "^8.1.0", "tsx": "^4.15.0", diff --git a/packages/create-express-forge/src/prompts.ts b/packages/create-express-forge/src/prompts.ts index 3130757..bef87c6 100644 --- a/packages/create-express-forge/src/prompts.ts +++ b/packages/create-express-forge/src/prompts.ts @@ -19,7 +19,7 @@ export async function runCLI( initialProjectName?: string, cmdOptions: Record = {}, ) { - displayBanner(); + await displayBanner(); if (cmdOptions.yes) { const projectName = initialProjectName ?? "my-express-app"; diff --git a/packages/create-express-forge/src/utils/display.ts b/packages/create-express-forge/src/utils/display.ts index bf9f981..d83cbbe 100644 --- a/packages/create-express-forge/src/utils/display.ts +++ b/packages/create-express-forge/src/utils/display.ts @@ -1,6 +1,32 @@ +import os from "node:os"; import chalk from "chalk"; +import figlet from "figlet"; +import gradient from "gradient-string"; +import chalkAnimation from "chalk-animation"; -export function displayBanner(): void { +export async function displayBanner(): Promise { + const username = os.userInfo().username; + const capitalizedUser = + username.charAt(0).toUpperCase() + username.slice(1).toLowerCase(); + + const welcomeText = figlet.textSync(`WELCOME, ${capitalizedUser}!`, { + font: "Slant", + horizontalLayout: "default", + }); + + console.log(); + const rainbow = chalkAnimation.rainbow(welcomeText); + + // Let the rainbow shine for a bit + await new Promise((resolve) => setTimeout(resolve, 1500)); + rainbow.stop(); + + console.log(); + console.log( + chalk.bold.hex("#7C3AED")( + " โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€", + ), + ); console.log(); console.log( chalk.bold.hex("#7C3AED")(" โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—"), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6abd8f..a689b60 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,6 +50,9 @@ importers: chalk: specifier: ^5.3.0 version: 5.6.2 + chalk-animation: + specifier: ^2.0.3 + version: 2.0.3 commander: specifier: ^12.1.0 version: 12.1.0 @@ -59,9 +62,15 @@ importers: execa: specifier: ^9.3.0 version: 9.6.1 + figlet: + specifier: ^1.11.0 + version: 1.11.0 fs-extra: specifier: ^11.2.0 version: 11.3.4 + gradient-string: + specifier: ^3.0.0 + version: 3.0.0 ora: specifier: ^8.1.1 version: 8.2.0 @@ -75,9 +84,18 @@ importers: '@repo/typescript-config': specifier: workspace:* version: link:../typescript-config + '@types/chalk-animation': + specifier: ^1.6.3 + version: 1.6.3 + '@types/figlet': + specifier: ^1.7.0 + version: 1.7.0 '@types/fs-extra': specifier: ^11.0.4 version: 11.0.4 + '@types/gradient-string': + specifier: ^1.1.6 + version: 1.1.6 '@types/node': specifier: ^20.14.0 version: 20.19.39 @@ -201,6 +219,10 @@ packages: resolution: {integrity: sha512-Mu9BFtgzGqDUy5Bcs2nMyoILIFSN13GKQaklKAFIsd0K3/9CpNyfeBc+/+Qs6mFZLlxG9qzullO7h+bjcTBuGQ==} engines: {node: '>= 14.0.0'} + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} @@ -1027,12 +1049,21 @@ packages: cpu: [arm64] os: [win32] + '@types/chalk-animation@1.6.3': + resolution: {integrity: sha512-UdaYgzPvgOAJY+5cn3y1rQK5EpJDJOBivF5ftFxe5lNruU1/zTPkmh11iVvRd8tyzVwx92QQLaAWi1fpBAj7aw==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/figlet@1.7.0': + resolution: {integrity: sha512-KwrT7p/8Eo3Op/HBSIwGXOsTZKYiM9NpWRBJ5sVjWP/SmlS+oxxRvJht/FNAtliJvja44N3ul1yATgohnVBV0Q==} + '@types/fs-extra@11.0.4': resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} + '@types/gradient-string@1.1.6': + resolution: {integrity: sha512-LkaYxluY4G5wR1M4AKQUal2q61Di1yVVCw42ImFTuaIoQVgmV0WP1xUaLB8zwb47mp82vWTpePI9JmrjEnJ7nQ==} + '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} @@ -1051,12 +1082,21 @@ packages: '@types/mdurl@2.0.0': resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + '@types/minimist@1.2.5': + resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} + '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} '@types/node@20.19.39': resolution: {integrity: sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==} + '@types/normalize-package-data@2.4.4': + resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + + '@types/tinycolor2@1.4.6': + resolution: {integrity: sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==} + '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} @@ -1237,6 +1277,10 @@ packages: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} + arrify@1.0.1: + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} + assertion-error@1.1.0: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} @@ -1277,6 +1321,14 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} + camelcase-keys@7.0.2: + resolution: {integrity: sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==} + engines: {node: '>=12'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -1284,6 +1336,15 @@ packages: resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==} engines: {node: '>=4'} + chalk-animation@2.0.3: + resolution: {integrity: sha512-Q/GJj6eqcI3HUSz72hUIZtjTlzIHXet8GOteO79Fzx8FMlSywCNP8tpX8uiOOdeMSXFXTXCajZ/Y2FvcU3MOLA==} + engines: {node: '>=12'} + hasBin: true + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + chalk@5.6.2: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} @@ -1330,6 +1391,10 @@ packages: resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} engines: {node: '>=18'} + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -1381,6 +1446,18 @@ packages: supports-color: optional: true + decamelize-keys@1.1.1: + resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} + engines: {node: '>=0.10.0'} + + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + + decamelize@5.0.1: + resolution: {integrity: sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==} + engines: {node: '>=10'} + deep-eql@4.1.4: resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} engines: {node: '>=6'} @@ -1436,6 +1513,9 @@ packages: resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} engines: {node: '>=0.12'} + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -1531,6 +1611,11 @@ packages: picomatch: optional: true + figlet@1.11.0: + resolution: {integrity: sha512-EEx3OS/l2bFqcUNN2NM9FPJp8vAMrgbCxsbl2hbcJNNxOEwVe3mEzrhan7TbJQViZa8mMqhihlbCaqD+LyYKTQ==} + engines: {node: '>= 17.0.0'} + hasBin: true + figures@6.1.0: resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} engines: {node: '>=18'} @@ -1547,6 +1632,10 @@ packages: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + fix-dts-default-cjs-exports@1.0.1: resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} @@ -1622,6 +1711,22 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + gradient-string@2.0.2: + resolution: {integrity: sha512-rEDCuqUQ4tbD78TpzsMtt5OIf0cBCSDWSJtUDaF6JsAh+k0v9r++NzxNEG87oDZx9ZwGhD8DaezR2L/yrw0Jdw==} + engines: {node: '>=10'} + + gradient-string@3.0.0: + resolution: {integrity: sha512-frdKI4Qi8Ihp4C6wZNB565de/THpIaw3DjP5ku87M+N9rNSGmPTjfkq61SdRXB7eCaL8O1hkKDvf6CDMtOzIAg==} + engines: {node: '>=14'} + + hard-rejection@2.1.0: + resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} + engines: {node: '>=6'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} @@ -1643,6 +1748,10 @@ packages: hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + hosted-git-info@4.1.0: + resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} + engines: {node: '>=10'} + html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} @@ -1675,6 +1784,10 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + indent-string@5.0.0: + resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} + engines: {node: '>=12'} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -1686,6 +1799,13 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -1706,6 +1826,10 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-plain-obj@1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} + is-plain-obj@4.1.0: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} @@ -1751,6 +1875,9 @@ packages: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} @@ -1762,6 +1889,9 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} @@ -1774,6 +1904,10 @@ packages: jsonfile@6.2.1: resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -1793,6 +1927,10 @@ packages: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + lodash.startcase@4.4.0: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} @@ -1803,9 +1941,21 @@ packages: loupe@2.3.7: resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + map-obj@1.0.1: + resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} + engines: {node: '>=0.10.0'} + + map-obj@4.3.0: + resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} + engines: {node: '>=8'} + mark.js@8.11.1: resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==} @@ -1820,6 +1970,10 @@ packages: resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} engines: {node: '>= 0.8'} + meow@10.1.5: + resolution: {integrity: sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + merge-descriptors@2.0.0: resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} engines: {node: '>=18'} @@ -1866,6 +2020,10 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} + minimist-options@4.1.0: + resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} + engines: {node: '>= 6'} + minisearch@7.2.0: resolution: {integrity: sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==} @@ -1898,6 +2056,10 @@ packages: resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} + normalize-package-data@3.0.3: + resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} + engines: {node: '>=10'} + npm-run-path@5.3.0: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1947,6 +2109,10 @@ packages: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + p-limit@5.0.0: resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} engines: {node: '>=18'} @@ -1955,6 +2121,10 @@ packages: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + p-map@2.1.0: resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} engines: {node: '>=6'} @@ -1966,6 +2136,10 @@ packages: package-manager-detector@0.2.11: resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + parse-ms@4.0.0: resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} engines: {node: '>=18'} @@ -2086,6 +2260,10 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + quick-lru@5.1.1: + resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} + engines: {node: '>=10'} + range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -2097,6 +2275,14 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + read-pkg-up@8.0.0: + resolution: {integrity: sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==} + engines: {node: '>=12'} + + read-pkg@6.0.0: + resolution: {integrity: sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==} + engines: {node: '>=12'} + read-yaml-file@1.1.0: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} @@ -2105,6 +2291,10 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + redent@4.0.0: + resolution: {integrity: sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==} + engines: {node: '>=12'} + regex-recursion@6.0.2: resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} @@ -2222,6 +2412,18 @@ packages: spawndamnit@3.0.1: resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==} + spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + + spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + + spdx-license-ids@3.0.23: + resolution: {integrity: sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==} + speakingurl@14.0.1: resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} engines: {node: '>=0.10.0'} @@ -2274,6 +2476,10 @@ packages: resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} engines: {node: '>=18'} + strip-indent@4.1.1: + resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==} + engines: {node: '>=12'} + strip-literal@2.1.1: resolution: {integrity: sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==} @@ -2286,6 +2492,10 @@ packages: resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==} engines: {node: '>=16'} + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + tabbable@6.4.0: resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==} @@ -2303,6 +2513,9 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinycolor2@1.6.0: + resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} + tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} @@ -2310,6 +2523,9 @@ packages: resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} + tinygradient@1.1.5: + resolution: {integrity: sha512-8nIfc2vgQ4TeLnk2lFj4tRLvvJwEfQuabdsmvDdQPT0xlk9TaNtpGd6nNRxXoK6vQhN6RSzj+Cnp5tTQmpxmbw==} + tinypool@0.8.4: resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} engines: {node: '>=14.0.0'} @@ -2333,6 +2549,10 @@ packages: trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + trim-newlines@4.1.1: + resolution: {integrity: sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==} + engines: {node: '>=12'} + ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -2368,6 +2588,10 @@ packages: resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} engines: {node: '>=4'} + type-fest@1.4.0: + resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} + engines: {node: '>=10'} + type-is@2.0.1: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} @@ -2414,6 +2638,9 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} + validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -2522,6 +2749,17 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + yocto-queue@1.2.2: resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} engines: {node: '>=12.20'} @@ -2659,6 +2897,12 @@ snapshots: dependencies: '@algolia/client-common': 5.50.2 + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + '@babel/helper-string-parser@7.27.1': {} '@babel/helper-validator-identifier@7.28.5': {} @@ -3365,13 +3609,21 @@ snapshots: '@turbo/windows-arm64@2.9.6': optional: true + '@types/chalk-animation@1.6.3': {} + '@types/estree@1.0.8': {} + '@types/figlet@1.7.0': {} + '@types/fs-extra@11.0.4': dependencies: '@types/jsonfile': 6.1.4 '@types/node': 20.19.39 + '@types/gradient-string@1.1.6': + dependencies: + '@types/tinycolor2': 1.4.6 + '@types/hast@3.0.4': dependencies: '@types/unist': 3.0.3 @@ -3393,12 +3645,18 @@ snapshots: '@types/mdurl@2.0.0': {} + '@types/minimist@1.2.5': {} + '@types/node@12.20.55': {} '@types/node@20.19.39': dependencies: undici-types: 6.21.0 + '@types/normalize-package-data@2.4.4': {} + + '@types/tinycolor2@1.4.6': {} + '@types/unist@3.0.3': {} '@types/web-bluetooth@0.0.21': {} @@ -3599,6 +3857,8 @@ snapshots: array-union@2.1.0: {} + arrify@1.0.1: {} + assertion-error@1.1.0: {} better-path-resolve@1.0.0: @@ -3644,6 +3904,15 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 + camelcase-keys@7.0.2: + dependencies: + camelcase: 6.3.0 + map-obj: 4.3.0 + quick-lru: 5.1.1 + type-fest: 1.4.0 + + camelcase@6.3.0: {} + ccount@2.0.1: {} chai@4.5.0: @@ -3656,6 +3925,17 @@ snapshots: pathval: 1.1.1 type-detect: 4.1.0 + chalk-animation@2.0.3: + dependencies: + chalk: 4.1.2 + gradient-string: 2.0.2 + meow: 10.1.5 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + chalk@5.6.2: {} character-entities-html4@2.1.0: {} @@ -3690,6 +3970,8 @@ snapshots: commander@12.1.0: {} + commander@14.0.3: {} + commander@4.1.1: {} confbox@0.1.8: {} @@ -3725,6 +4007,15 @@ snapshots: dependencies: ms: 2.1.3 + decamelize-keys@1.1.1: + dependencies: + decamelize: 1.2.0 + map-obj: 1.0.1 + + decamelize@1.2.0: {} + + decamelize@5.0.1: {} + deep-eql@4.1.4: dependencies: type-detect: 4.1.0 @@ -3768,6 +4059,10 @@ snapshots: entities@7.0.1: {} + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -3938,6 +4233,10 @@ snapshots: optionalDependencies: picomatch: 4.0.4 + figlet@1.11.0: + dependencies: + commander: 14.0.3 + figures@6.1.0: dependencies: is-unicode-supported: 2.1.0 @@ -3962,6 +4261,11 @@ snapshots: locate-path: 5.0.0 path-exists: 4.0.0 + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + fix-dts-default-cjs-exports@1.0.1: dependencies: magic-string: 0.30.21 @@ -4049,6 +4353,20 @@ snapshots: graceful-fs@4.2.11: {} + gradient-string@2.0.2: + dependencies: + chalk: 4.1.2 + tinygradient: 1.1.5 + + gradient-string@3.0.0: + dependencies: + chalk: 5.6.2 + tinygradient: 1.1.5 + + hard-rejection@2.1.0: {} + + has-flag@4.0.0: {} + has-symbols@1.1.0: {} hasown@2.0.3: @@ -4077,6 +4395,10 @@ snapshots: hookable@5.5.3: {} + hosted-git-info@4.1.0: + dependencies: + lru-cache: 6.0.0 + html-void-elements@3.0.0: {} http-errors@2.0.1: @@ -4101,12 +4423,20 @@ snapshots: ignore@5.3.2: {} + indent-string@5.0.0: {} + inherits@2.0.4: {} ip-address@10.1.0: {} ipaddr.js@1.9.1: {} + is-arrayish@0.2.1: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.3 + is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -4119,6 +4449,8 @@ snapshots: is-number@7.0.0: {} + is-plain-obj@1.1.0: {} + is-plain-obj@4.1.0: {} is-promise@4.0.0: {} @@ -4145,6 +4477,8 @@ snapshots: joycon@3.1.1: {} + js-tokens@4.0.0: {} + js-tokens@9.0.1: {} js-yaml@3.14.2: @@ -4156,6 +4490,8 @@ snapshots: dependencies: argparse: 2.0.1 + json-parse-even-better-errors@2.3.1: {} + json-schema-traverse@1.0.0: {} json-schema-typed@8.0.2: {} @@ -4170,6 +4506,8 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + kind-of@6.0.3: {} + lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} @@ -4185,6 +4523,10 @@ snapshots: dependencies: p-locate: 4.1.0 + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + lodash.startcase@4.4.0: {} log-symbols@6.0.0: @@ -4196,10 +4538,18 @@ snapshots: dependencies: get-func-name: 2.0.2 + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + map-obj@1.0.1: {} + + map-obj@4.3.0: {} + mark.js@8.11.1: {} math-intrinsics@1.1.0: {} @@ -4218,6 +4568,21 @@ snapshots: media-typer@1.1.0: {} + meow@10.1.5: + dependencies: + '@types/minimist': 1.2.5 + camelcase-keys: 7.0.2 + decamelize: 5.0.1 + decamelize-keys: 1.1.1 + hard-rejection: 2.1.0 + minimist-options: 4.1.0 + normalize-package-data: 3.0.3 + read-pkg-up: 8.0.0 + redent: 4.0.0 + trim-newlines: 4.1.1 + type-fest: 1.4.0 + yargs-parser: 20.2.9 + merge-descriptors@2.0.0: {} merge-stream@2.0.0: {} @@ -4256,6 +4621,12 @@ snapshots: mimic-function@5.0.1: {} + minimist-options@4.1.0: + dependencies: + arrify: 1.0.1 + is-plain-obj: 1.1.0 + kind-of: 6.0.3 + minisearch@7.2.0: {} mitt@3.0.1: {} @@ -4283,6 +4654,13 @@ snapshots: negotiator@1.0.0: {} + normalize-package-data@3.0.3: + dependencies: + hosted-git-info: 4.1.0 + is-core-module: 2.16.1 + semver: 7.7.4 + validate-npm-package-license: 3.0.4 + npm-run-path@5.3.0: dependencies: path-key: 4.0.0 @@ -4340,6 +4718,10 @@ snapshots: dependencies: p-try: 2.2.0 + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + p-limit@5.0.0: dependencies: yocto-queue: 1.2.2 @@ -4348,6 +4730,10 @@ snapshots: dependencies: p-limit: 2.3.0 + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + p-map@2.1.0: {} p-try@2.2.0: {} @@ -4356,6 +4742,13 @@ snapshots: dependencies: quansync: 0.2.11 + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.0 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + parse-ms@4.0.0: {} parseurl@1.3.3: {} @@ -4438,6 +4831,8 @@ snapshots: queue-microtask@1.2.3: {} + quick-lru@5.1.1: {} + range-parser@1.2.1: {} raw-body@3.0.2: @@ -4449,6 +4844,19 @@ snapshots: react-is@18.3.1: {} + read-pkg-up@8.0.0: + dependencies: + find-up: 5.0.0 + read-pkg: 6.0.0 + type-fest: 1.4.0 + + read-pkg@6.0.0: + dependencies: + '@types/normalize-package-data': 2.4.4 + normalize-package-data: 3.0.3 + parse-json: 5.2.0 + type-fest: 1.4.0 + read-yaml-file@1.1.0: dependencies: graceful-fs: 4.2.11 @@ -4458,6 +4866,11 @@ snapshots: readdirp@4.1.2: {} + redent@4.0.0: + dependencies: + indent-string: 5.0.0 + strip-indent: 4.1.1 + regex-recursion@6.0.2: dependencies: regex-utilities: 2.3.0 @@ -4623,6 +5036,20 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + spdx-correct@3.2.0: + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.23 + + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@3.0.1: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.23 + + spdx-license-ids@3.0.23: {} + speakingurl@14.0.1: {} sprintf-js@1.0.3: {} @@ -4666,6 +5093,8 @@ snapshots: strip-final-newline@4.0.0: {} + strip-indent@4.1.1: {} + strip-literal@2.1.1: dependencies: js-tokens: 9.0.1 @@ -4684,6 +5113,10 @@ snapshots: dependencies: copy-anything: 4.0.5 + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + tabbable@6.4.0: {} term-size@2.2.1: {} @@ -4698,6 +5131,8 @@ snapshots: tinybench@2.9.0: {} + tinycolor2@1.6.0: {} + tinyexec@0.3.2: {} tinyglobby@0.2.16: @@ -4705,6 +5140,11 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinygradient@1.1.5: + dependencies: + '@types/tinycolor2': 1.4.6 + tinycolor2: 1.6.0 + tinypool@0.8.4: {} tinyspy@2.2.1: {} @@ -4719,6 +5159,8 @@ snapshots: trim-lines@3.0.1: {} + trim-newlines@4.1.1: {} + ts-interface-checker@0.1.13: {} tsup@8.5.1(postcss@8.5.10)(tsx@4.21.0)(typescript@5.5.3): @@ -4767,6 +5209,8 @@ snapshots: type-detect@4.1.0: {} + type-fest@1.4.0: {} + type-is@2.0.1: dependencies: content-type: 1.0.5 @@ -4810,6 +5254,11 @@ snapshots: unpipe@1.0.0: {} + validate-npm-package-license@3.0.4: + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + vary@1.1.2: {} vfile-message@4.0.3: @@ -4959,6 +5408,12 @@ snapshots: wrappy@1.0.2: {} + yallist@4.0.0: {} + + yargs-parser@20.2.9: {} + + yocto-queue@0.1.0: {} + yocto-queue@1.2.2: {} yoctocolors-cjs@2.1.3: {} From 006855ddbd406a7db04ad2e8cf67a4a96ce3616b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 28 Apr 2026 14:27:49 +0000 Subject: [PATCH 12/16] chore(release): version packages [skip ci] --- .changeset/v4-cli-and-docs-improvements.md | 19 ------------------- packages/create-express-forge/CHANGELOG.md | 20 ++++++++++++++++++++ packages/create-express-forge/package.json | 2 +- 3 files changed, 21 insertions(+), 20 deletions(-) delete mode 100644 .changeset/v4-cli-and-docs-improvements.md diff --git a/.changeset/v4-cli-and-docs-improvements.md b/.changeset/v4-cli-and-docs-improvements.md deleted file mode 100644 index 2d98767..0000000 --- a/.changeset/v4-cli-and-docs-improvements.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -"create-express-forge": patch ---- - -- Added support for scaffolding in the current directory (`.`) -- Implemented automated import alias resolution (`@/`) for scaffolded projects -- Fixed relative import bug in smoke test template -- Synchronized documentation with latest v4 features (Zod-to-OpenAPI, Biome, Pro Fail-Fast) -- Added a **High-Fidelity Personalized Welcome** message using a large gradient-styled font (powered by `figlet` and `gradient-string`) to greet users by name -- Added documentation versioning support with v3 LTS dropdown -- Enhanced documentation SEO, OpenGraph metadata, and social sharing tags -- Standardized product naming to "Create Express Forge" across docs and CLI -- Added a dedicated **MCP Server** (`@create-express-forge/mcp`) for AI-assisted scaffolding and documentation access -- Automated generation of LLM-friendly documentation files (`llms.txt`, `llms-full.txt`, `ai.json`) -- Highlighted **Ultra-Fast scaffolding** in the v3 vs v4 comparison (powered by internal refactoring) -- Updated root and package READMEs with new features and Biome integration -- Removed deprecated `cef` alias from documentation -- Fixed file paths in CONTRIBUTING.md -- Updated v3 documentation and root README to target the correct stable legacy version (`3.3.2`) diff --git a/packages/create-express-forge/CHANGELOG.md b/packages/create-express-forge/CHANGELOG.md index cf998fd..01af209 100644 --- a/packages/create-express-forge/CHANGELOG.md +++ b/packages/create-express-forge/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## 4.1.3 + +### Patch Changes + +- 28c3e1b: - Added support for scaffolding in the current directory (`.`) + - Implemented automated import alias resolution (`@/`) for scaffolded projects + - Fixed relative import bug in smoke test template + - Synchronized documentation with latest v4 features (Zod-to-OpenAPI, Biome, Pro Fail-Fast) + - Added a **High-Fidelity Personalized Welcome** message using a large gradient-styled font (powered by `figlet` and `gradient-string`) to greet users by name + - Added documentation versioning support with v3 LTS dropdown + - Enhanced documentation SEO, OpenGraph metadata, and social sharing tags + - Standardized product naming to "Create Express Forge" across docs and CLI + - Added a dedicated **MCP Server** (`@create-express-forge/mcp`) for AI-assisted scaffolding and documentation access + - Automated generation of LLM-friendly documentation files (`llms.txt`, `llms-full.txt`, `ai.json`) + - Highlighted **Ultra-Fast scaffolding** in the v3 vs v4 comparison (powered by internal refactoring) + - Updated root and package READMEs with new features and Biome integration + - Removed deprecated `cef` alias from documentation + - Fixed file paths in CONTRIBUTING.md + - Updated v3 documentation and root README to target the correct stable legacy version (`3.3.2`) + ## 4.1.2 ### Patch Changes diff --git a/packages/create-express-forge/package.json b/packages/create-express-forge/package.json index 6918082..1d2b7ef 100644 --- a/packages/create-express-forge/package.json +++ b/packages/create-express-forge/package.json @@ -1,6 +1,6 @@ { "name": "create-express-forge", - "version": "4.1.2", + "version": "4.1.3", "description": "โšก Scaffold production-ready Express.js TypeScript backends in seconds", "license": "MIT", "type": "module", From e7113baf6c633cd74e72847bc9582cdd52c9fb99 Mon Sep 17 00:00:00 2001 From: code-y02 Date: Tue, 28 Apr 2026 20:10:46 +0530 Subject: [PATCH 13/16] feat: update MCP server package configuration and improve README documentation --- .changeset/v4-cli-and-docs-improvements.md | 20 +++++++++ README.md | 47 +++++++++++++++------- packages/mcp/src/index.ts | 4 +- 3 files changed, 55 insertions(+), 16 deletions(-) create mode 100644 .changeset/v4-cli-and-docs-improvements.md diff --git a/.changeset/v4-cli-and-docs-improvements.md b/.changeset/v4-cli-and-docs-improvements.md new file mode 100644 index 0000000..d150b44 --- /dev/null +++ b/.changeset/v4-cli-and-docs-improvements.md @@ -0,0 +1,20 @@ +--- +"create-express-forge": patch +"@create-express-forge/mcp": patch +--- + +- Added support for scaffolding in the current directory (`.`) +- Implemented automated import alias resolution (`@/`) for scaffolded projects +- Fixed relative import bug in smoke test template +- Synchronized documentation with latest v4 features (Zod-to-OpenAPI, Biome, Pro Fail-Fast) +- Added a **High-Fidelity Personalized Welcome** message using a large gradient-styled font (powered by `figlet` and `gradient-string`) to greet users by name +- Added documentation versioning support with v3 LTS dropdown +- Enhanced documentation SEO, OpenGraph metadata, and social sharing tags +- Standardized product naming to "Create Express Forge" across docs and CLI +- Added a dedicated **MCP Server** (`@create-express-forge/mcp`) for AI-assisted scaffolding and documentation access +- Automated generation of LLM-friendly documentation files (`llms.txt`, `llms-full.txt`, `ai.json`) +- Highlighted **Ultra-Fast scaffolding** in the v3 vs v4 comparison (powered by internal refactoring) +- Updated root and package READMEs with new features and Biome integration +- Removed deprecated `cef` alias from documentation +- Fixed file paths in CONTRIBUTING.md +- Updated v3 documentation and root README to target the correct stable legacy version (`3.3.2`) diff --git a/README.md b/README.md index 6e74f86..c81ebe5 100644 --- a/README.md +++ b/README.md @@ -18,14 +18,14 @@ npx create-express-forge . Interactive prompts let you choose: -| Option | Choices | -|--------|---------| -| **Architecture** | Modular (feature-based) ยท MVC | -| **ORM** | Prisma ยท Sequelize ยท None | -| **Database** | PostgreSQL ยท MySQL ยท SQLite ยท None | -| **Logger** | Winston ยท Pino ยท None | -| **Testing** | Vitest ยท Jest ยท None | -| **Docker** | Dockerfile + docker-compose | +| Option | Choices | +| ---------------- | ---------------------------------- | +| **Architecture** | Modular (feature-based) ยท MVC | +| **ORM** | Prisma ยท Sequelize ยท None | +| **Database** | PostgreSQL ยท MySQL ยท SQLite ยท None | +| **Logger** | Winston ยท Pino ยท None | +| **Testing** | Vitest ยท Jest ยท None | +| **Docker** | Dockerfile + docker-compose | ## Generated Project Includes @@ -49,6 +49,7 @@ Interactive prompts let you choose: create-express-forge/ โ”œโ”€โ”€ packages/ โ”‚ โ”œโ”€โ”€ create-express-forge/ โ† The published CLI +โ”‚ โ”œโ”€โ”€ mcp/ โ† @create-express-forge/mcp Server โ”‚ โ”œโ”€โ”€ typescript-config/ โ† Shared internal TS config โ”‚ โ””โ”€โ”€ lint-config/ โ† Shared internal Biome/Lint config โ”œโ”€โ”€ examples/ @@ -62,9 +63,25 @@ Create Express Forge is designed to be AI-friendly. We provide a built-in **MCP ### Using the MCP Server -1. **Build the project**: `pnpm build` -2. **Add to your MCP Client**: Add the following configuration to your client (e.g., Claude Desktop). +You can run the MCP server directly via `npx` (recommended) or by building the source. + +**Option 1: Using `npx` (Recommended)** +Add this to your Claude Desktop config or other MCP client: + +```json +{ + "mcpServers": { + "create-express-forge": { + "command": "npx", + "args": ["-y", "@create-express-forge/mcp"] + } + } +} +``` +**Option 2: From Source** +1. **Build the project**: `pnpm build` +2. **Add to your MCP Client**: ```json { "mcpServers": { @@ -77,7 +94,9 @@ Create Express Forge is designed to be AI-friendly. We provide a built-in **MCP ``` ### LLM Documentation + We also provide machine-readable documentation files for LLMs: + - **`llms.txt`**: [Project summary](https://code-y02.github.io/express-cli/llms.txt) - **`llms-full.txt`**: [Full documentation context](https://code-y02.github.io/express-cli/llms-full.txt) - **`ai.json`**: [Capability manifest and CLI flags](https://code-y02.github.io/express-cli/ai.json) @@ -98,10 +117,10 @@ See [CONTRIBUTING.md](CONTRIBUTING.md). ## Versioning & Branches -| Branch | npm tag | Description | -|--------|---------|-------------| -| `main` | `latest` | Stable releases | -| `next` | `next` | Pre-releases / beta | +| Branch | npm tag | Description | +| ------ | -------- | ------------------- | +| `main` | `latest` | Stable releases | +| `next` | `next` | Pre-releases / beta | ## License diff --git a/packages/mcp/src/index.ts b/packages/mcp/src/index.ts index f938d77..147caf2 100644 --- a/packages/mcp/src/index.ts +++ b/packages/mcp/src/index.ts @@ -6,8 +6,8 @@ import { z } from "zod"; const DOCS_BASE = "https://code-y02.github.io/express-cli"; const server = new McpServer({ - name: "create-express-forge", - version: "4.1.2", + name: "@create-express-forge/mcp", + version: "1.0.0", }); // โ”€โ”€โ”€ Resources โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ From 70339110d148976000c4851928b1ff7123708a5b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 28 Apr 2026 14:43:27 +0000 Subject: [PATCH 14/16] chore(release): version packages [skip ci] --- .changeset/v4-cli-and-docs-improvements.md | 20 -------------------- packages/create-express-forge/CHANGELOG.md | 20 ++++++++++++++++++++ packages/create-express-forge/package.json | 2 +- packages/mcp/CHANGELOG.md | 21 +++++++++++++++++++++ packages/mcp/package.json | 2 +- 5 files changed, 43 insertions(+), 22 deletions(-) delete mode 100644 .changeset/v4-cli-and-docs-improvements.md create mode 100644 packages/mcp/CHANGELOG.md diff --git a/.changeset/v4-cli-and-docs-improvements.md b/.changeset/v4-cli-and-docs-improvements.md deleted file mode 100644 index d150b44..0000000 --- a/.changeset/v4-cli-and-docs-improvements.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -"create-express-forge": patch -"@create-express-forge/mcp": patch ---- - -- Added support for scaffolding in the current directory (`.`) -- Implemented automated import alias resolution (`@/`) for scaffolded projects -- Fixed relative import bug in smoke test template -- Synchronized documentation with latest v4 features (Zod-to-OpenAPI, Biome, Pro Fail-Fast) -- Added a **High-Fidelity Personalized Welcome** message using a large gradient-styled font (powered by `figlet` and `gradient-string`) to greet users by name -- Added documentation versioning support with v3 LTS dropdown -- Enhanced documentation SEO, OpenGraph metadata, and social sharing tags -- Standardized product naming to "Create Express Forge" across docs and CLI -- Added a dedicated **MCP Server** (`@create-express-forge/mcp`) for AI-assisted scaffolding and documentation access -- Automated generation of LLM-friendly documentation files (`llms.txt`, `llms-full.txt`, `ai.json`) -- Highlighted **Ultra-Fast scaffolding** in the v3 vs v4 comparison (powered by internal refactoring) -- Updated root and package READMEs with new features and Biome integration -- Removed deprecated `cef` alias from documentation -- Fixed file paths in CONTRIBUTING.md -- Updated v3 documentation and root README to target the correct stable legacy version (`3.3.2`) diff --git a/packages/create-express-forge/CHANGELOG.md b/packages/create-express-forge/CHANGELOG.md index 01af209..7a5be4e 100644 --- a/packages/create-express-forge/CHANGELOG.md +++ b/packages/create-express-forge/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## 4.1.4 + +### Patch Changes + +- e7113ba: - Added support for scaffolding in the current directory (`.`) + - Implemented automated import alias resolution (`@/`) for scaffolded projects + - Fixed relative import bug in smoke test template + - Synchronized documentation with latest v4 features (Zod-to-OpenAPI, Biome, Pro Fail-Fast) + - Added a **High-Fidelity Personalized Welcome** message using a large gradient-styled font (powered by `figlet` and `gradient-string`) to greet users by name + - Added documentation versioning support with v3 LTS dropdown + - Enhanced documentation SEO, OpenGraph metadata, and social sharing tags + - Standardized product naming to "Create Express Forge" across docs and CLI + - Added a dedicated **MCP Server** (`@create-express-forge/mcp`) for AI-assisted scaffolding and documentation access + - Automated generation of LLM-friendly documentation files (`llms.txt`, `llms-full.txt`, `ai.json`) + - Highlighted **Ultra-Fast scaffolding** in the v3 vs v4 comparison (powered by internal refactoring) + - Updated root and package READMEs with new features and Biome integration + - Removed deprecated `cef` alias from documentation + - Fixed file paths in CONTRIBUTING.md + - Updated v3 documentation and root README to target the correct stable legacy version (`3.3.2`) + ## 4.1.3 ### Patch Changes diff --git a/packages/create-express-forge/package.json b/packages/create-express-forge/package.json index 1d2b7ef..945106b 100644 --- a/packages/create-express-forge/package.json +++ b/packages/create-express-forge/package.json @@ -1,6 +1,6 @@ { "name": "create-express-forge", - "version": "4.1.3", + "version": "4.1.4", "description": "โšก Scaffold production-ready Express.js TypeScript backends in seconds", "license": "MIT", "type": "module", diff --git a/packages/mcp/CHANGELOG.md b/packages/mcp/CHANGELOG.md new file mode 100644 index 0000000..f5553bf --- /dev/null +++ b/packages/mcp/CHANGELOG.md @@ -0,0 +1,21 @@ +# @create-express-forge/mcp + +## 1.0.1 + +### Patch Changes + +- e7113ba: - Added support for scaffolding in the current directory (`.`) + - Implemented automated import alias resolution (`@/`) for scaffolded projects + - Fixed relative import bug in smoke test template + - Synchronized documentation with latest v4 features (Zod-to-OpenAPI, Biome, Pro Fail-Fast) + - Added a **High-Fidelity Personalized Welcome** message using a large gradient-styled font (powered by `figlet` and `gradient-string`) to greet users by name + - Added documentation versioning support with v3 LTS dropdown + - Enhanced documentation SEO, OpenGraph metadata, and social sharing tags + - Standardized product naming to "Create Express Forge" across docs and CLI + - Added a dedicated **MCP Server** (`@create-express-forge/mcp`) for AI-assisted scaffolding and documentation access + - Automated generation of LLM-friendly documentation files (`llms.txt`, `llms-full.txt`, `ai.json`) + - Highlighted **Ultra-Fast scaffolding** in the v3 vs v4 comparison (powered by internal refactoring) + - Updated root and package READMEs with new features and Biome integration + - Removed deprecated `cef` alias from documentation + - Fixed file paths in CONTRIBUTING.md + - Updated v3 documentation and root README to target the correct stable legacy version (`3.3.2`) diff --git a/packages/mcp/package.json b/packages/mcp/package.json index 4c2c869..2c88e16 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -1,6 +1,6 @@ { "name": "@create-express-forge/mcp", - "version": "1.0.0", + "version": "1.0.1", "description": "MCP Server for Create Express Forge documentation and tools", "type": "module", "bin": { From a337e5c5ef6b30067915c6e39d2a7079eabdcc56 Mon Sep 17 00:00:00 2001 From: code-y02 Date: Sat, 25 Apr 2026 19:16:42 +0530 Subject: [PATCH 15/16] style: improve typography and alignment for hero section headers --- docs/.vitepress/theme/custom.css | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css index f2f2249..aeee9d6 100644 --- a/docs/.vitepress/theme/custom.css +++ b/docs/.vitepress/theme/custom.css @@ -63,8 +63,10 @@ } .name { - font-weight: 900; - letter-spacing: -0.02em; + font-weight: 900 !important; + letter-spacing: -0.02em !important; + padding-bottom: 0.1em; + line-height: 1.2; } /* Feature Cards */ @@ -113,6 +115,8 @@ h6 { letter-spacing: -0.02em; font-weight: 800; color: var(--vp-c-text-1); + line-height: 1.3; + padding-bottom: 0.1em; } .VPHero .tagline { @@ -285,6 +289,11 @@ h6 { background: var(--vp-home-hero-name-background); -webkit-background-clip: text; -webkit-text-fill-color: transparent; + padding: 0.2em 0; + line-height: 1.4; + display: table; + margin-left: auto; + margin-right: auto; } .VPHome .vp-doc ul { From 91baac3ae14b1bd304b664de8ec90da8a603749c Mon Sep 17 00:00:00 2001 From: code-y02 Date: Wed, 29 Apr 2026 19:00:44 +0530 Subject: [PATCH 16/16] style: resolve git merge conflict and reformat font family declaration in custom CSS --- docs/.vitepress/theme/custom.css | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css index aeee9d6..e6ecd12 100644 --- a/docs/.vitepress/theme/custom.css +++ b/docs/.vitepress/theme/custom.css @@ -101,9 +101,8 @@ /* Typography & Content Visibility */ :root { - --vp-font-family-base: - "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, - Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; + --vp-font-family-base: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", + Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; } h1,

J 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