forgedev 1.4.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/package.json +1 -1
  2. package/src/chainproof-bridge.js +9 -2
  3. package/src/ci-mode.js +3 -4
  4. package/src/composer.js +228 -242
  5. package/src/doctor-checks-chainproof.js +14 -11
  6. package/src/doctor-checks.js +3 -19
  7. package/src/index.js +6 -34
  8. package/src/recommender.js +319 -319
  9. package/src/uat-generator.js +105 -93
  10. package/src/utils.js +245 -214
  11. package/templates/auth/jwt-custom/backend/app/api/auth.py.template +39 -45
  12. package/templates/auth/jwt-custom/backend/app/core/security.py.template +44 -37
  13. package/templates/backend/express/package.json.template +35 -33
  14. package/templates/backend/express/src/lib/prisma.ts.template +32 -0
  15. package/templates/backend/express/src/routes/health.ts.template +35 -27
  16. package/templates/backend/fastapi/backend/app/main.py.template +67 -60
  17. package/templates/backend/fastapi/backend/requirements.txt.template +18 -16
  18. package/templates/backend/hono/package.json.template +33 -31
  19. package/templates/backend/hono/src/lib/prisma.ts.template +32 -0
  20. package/templates/backend/hono/src/routes/health.ts.template +38 -27
  21. package/templates/base/.gitignore.template +32 -32
  22. package/templates/claude-code/commands/workflows.md +52 -52
  23. package/templates/database/prisma-postgres/prisma/schema.prisma.template +19 -18
  24. package/templates/database/sqlalchemy-postgres/backend/alembic/env.py.template +39 -40
  25. package/templates/database/sqlalchemy-postgres/backend/alembic.ini.template +38 -36
  26. package/templates/database/sqlalchemy-postgres/backend/app/db/session.py.template +50 -48
  27. package/templates/frontend/nextjs/package.json.template +45 -43
  28. package/templates/frontend/remix/package.json.template +41 -39
  29. package/templates/infra/docker/.dockerignore.template +16 -0
  30. package/templates/infra/docker-compose/docker-compose.yml.template +22 -19
  31. package/templates/infra/github-actions/.github/workflows/ci.yml.template +61 -52
  32. package/templates/infra/k8s/k8s/deployment.yml.template +70 -70
  33. package/templates/infra/k8s/k8s/hpa.yml.template +24 -24
  34. package/templates/infra/k8s/k8s/ingress.yml.template +26 -26
  35. package/templates/infra/k8s/k8s/kustomization.yml.template +13 -13
  36. package/templates/infra/k8s/k8s/namespace.yml.template +4 -4
  37. package/templates/infra/k8s/k8s/networkpolicy.yml.template +41 -41
  38. package/templates/infra/k8s/k8s/secrets.yml.template +10 -10
  39. package/templates/infra/k8s/k8s/service.yml.template +15 -15
  40. package/templates/testing/load/k6/README.md.template +48 -48
  41. package/templates/testing/load/k6/load-test.js.template +57 -57
@@ -1,33 +1,35 @@
1
- {
2
- "name": "{{PROJECT_NAME}}-server",
3
- "version": "0.1.0",
4
- "private": true,
5
- "type": "module",
6
- "scripts": {
7
- "dev": "tsx watch src/index.ts",
8
- "build": "tsc",
9
- "start": "node dist/index.js",
10
- "lint": "eslint .",
11
- "typecheck": "tsc --noEmit",
12
- "test": "vitest run",
13
- "test:watch": "vitest",
14
- "db:push": "prisma db push",
15
- "db:studio": "prisma studio",
16
- "db:generate": "prisma generate"
17
- },
18
- "dependencies": {
19
- "express": "^5.1.0",
20
- "cors": "^2.8.5",
21
- "@prisma/client": "^6.6.0"
22
- },
23
- "devDependencies": {
24
- "typescript": "^5.8.3",
25
- "@types/node": "^22.14.0",
26
- "@types/express": "^5.0.0",
27
- "@types/cors": "^2.8.17",
28
- "tsx": "^4.19.0",
29
- "eslint": "^9.25.0",
30
- "prisma": "^6.6.0",
31
- "vitest": "^3.1.1"
32
- }
33
- }
1
+ {
2
+ "name": "{{PROJECT_NAME}}-server",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "tsx watch src/index.ts",
8
+ "build": "tsc",
9
+ "start": "node dist/index.js",
10
+ "lint": "eslint .",
11
+ "typecheck": "tsc --noEmit",
12
+ "test": "vitest run",
13
+ "test:watch": "vitest",
14
+ "db:push": "prisma db push",
15
+ "db:migrate": "prisma migrate dev",
16
+ "db:migrate:deploy": "prisma migrate deploy",
17
+ "db:studio": "prisma studio",
18
+ "db:generate": "prisma generate"
19
+ },
20
+ "dependencies": {
21
+ "express": "^5.1.0",
22
+ "cors": "^2.8.5",
23
+ "@prisma/client": "^6.6.0"
24
+ },
25
+ "devDependencies": {
26
+ "typescript": "^5.8.3",
27
+ "@types/node": "^22.14.0",
28
+ "@types/express": "^5.0.0",
29
+ "@types/cors": "^2.8.17",
30
+ "tsx": "^4.19.0",
31
+ "eslint": "^9.25.0",
32
+ "prisma": "^6.6.0",
33
+ "vitest": "^3.1.1"
34
+ }
35
+ }
@@ -0,0 +1,32 @@
1
+ import { PrismaClient } from '@prisma/client';
2
+
3
+ const MAX_RETRIES = 3;
4
+ const RETRY_DELAY_MS = 1000;
5
+
6
+ async function connectWithRetry(client: PrismaClient, retries = MAX_RETRIES): Promise<void> {
7
+ for (let attempt = 1; attempt <= retries; attempt++) {
8
+ try {
9
+ await client.$connect();
10
+ return;
11
+ } catch (error) {
12
+ if (attempt === retries) throw error;
13
+ const delay = RETRY_DELAY_MS * Math.pow(2, attempt - 1);
14
+ console.warn(`Database connection attempt ${attempt} failed. Retrying in ${delay}ms...`);
15
+ await new Promise((resolve) => setTimeout(resolve, delay));
16
+ }
17
+ }
18
+ }
19
+
20
+ export const prisma = new PrismaClient({
21
+ log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
22
+ });
23
+
24
+ // Graceful shutdown
25
+ const shutdownHandler = async () => {
26
+ await prisma.$disconnect();
27
+ };
28
+
29
+ process.on('SIGTERM', shutdownHandler);
30
+ process.on('SIGINT', shutdownHandler);
31
+
32
+ export { connectWithRetry };
@@ -1,27 +1,35 @@
1
- import { Router } from 'express';
2
-
3
- export const healthRouter = Router();
4
-
5
- // Liveness probe — always returns ok if the process is running
6
- healthRouter.get('/health', (_req, res) => {
7
- res.json({
8
- status: 'ok',
9
- timestamp: new Date().toISOString(),
10
- uptime: process.uptime(),
11
- service: '{{PROJECT_NAME}}',
12
- });
13
- });
14
-
15
- // Readiness probe — add real dependency checks (database, cache, etc.)
16
- // before using this as a Kubernetes readiness probe
17
- healthRouter.get('/healthz', async (_req, res) => {
18
- // TODO: Replace with actual checks, e.g.:
19
- // const db = await prisma.$queryRaw`SELECT 1`;
20
- const checks: Record<string, string> = {};
21
-
22
- res.json({
23
- status: 'ok',
24
- service: '{{PROJECT_NAME}}',
25
- checks,
26
- });
27
- });
1
+ import { Router } from 'express';
2
+ import { prisma } from '../lib/prisma';
3
+
4
+ export const healthRouter = Router();
5
+
6
+ // Liveness probe always returns ok if the process is running
7
+ healthRouter.get('/health', (_req, res) => {
8
+ res.json({
9
+ status: 'ok',
10
+ timestamp: new Date().toISOString(),
11
+ uptime: process.uptime(),
12
+ service: '{{PROJECT_NAME}}',
13
+ });
14
+ });
15
+
16
+ // Readiness probe verifies database connectivity
17
+ healthRouter.get('/healthz', async (_req, res) => {
18
+ let dbStatus = 'disconnected';
19
+
20
+ try {
21
+ await prisma.$queryRaw`SELECT 1`;
22
+ dbStatus = 'connected';
23
+ } catch {
24
+ dbStatus = 'disconnected';
25
+ }
26
+
27
+ const status = dbStatus === 'connected' ? 'ok' : 'degraded';
28
+ const statusCode = dbStatus === 'connected' ? 200 : 503;
29
+
30
+ res.status(statusCode).json({
31
+ status,
32
+ service: '{{PROJECT_NAME}}',
33
+ checks: { database: dbStatus },
34
+ });
35
+ });
@@ -1,60 +1,67 @@
1
- from contextlib import asynccontextmanager
2
- from fastapi import FastAPI, Request
3
- from fastapi.middleware.cors import CORSMiddleware
4
- from fastapi.responses import JSONResponse
5
-
6
- from app.api.health import router as health_router
7
- from app.portal.router import router as portal_router
8
- from app.core.config import settings
9
- from app.core.errors import AppError
10
-
11
-
12
- @asynccontextmanager
13
- async def lifespan(app: FastAPI):
14
- # Startup: connect to database with retry
15
- from app.db.session import engine, connect_with_retry
16
-
17
- await connect_with_retry(engine)
18
- yield
19
- # Shutdown: close database connections gracefully
20
- await engine.dispose()
21
-
22
-
23
- app = FastAPI(
24
- title="{{PROJECT_NAME_PASCAL}}",
25
- version="0.1.0",
26
- lifespan=lifespan,
27
- )
28
-
29
- # CORS
30
- app.add_middleware(
31
- CORSMiddleware,
32
- allow_origins=settings.cors_origins,
33
- allow_credentials=True,
34
- allow_methods=["*"],
35
- allow_headers=["*"],
36
- )
37
-
38
- # Routes
39
- app.include_router(health_router, tags=["health"])
40
- app.include_router(portal_router)
41
-
42
-
43
- # Global exception handler - never leak stack traces
44
- @app.exception_handler(AppError)
45
- async def app_error_handler(request: Request, exc: AppError):
46
- return JSONResponse(
47
- status_code=exc.status_code,
48
- content={"error": {"code": exc.code, "message": exc.message}},
49
- )
50
-
51
-
52
- @app.exception_handler(Exception)
53
- async def global_exception_handler(request: Request, exc: Exception):
54
- import logging
55
-
56
- logging.exception("Unhandled exception")
57
- return JSONResponse(
58
- status_code=500,
59
- content={"error": {"code": "INTERNAL_ERROR", "message": "An unexpected error occurred"}},
60
- )
1
+ import logging
2
+ import sys
3
+ from contextlib import asynccontextmanager
4
+
5
+ from fastapi import FastAPI, Request
6
+ from fastapi.middleware.cors import CORSMiddleware
7
+ from fastapi.responses import JSONResponse
8
+ from pythonjsonlogger.json import JsonFormatter
9
+
10
+ from app.api.health import router as health_router
11
+ from app.portal.router import router as portal_router
12
+ from app.core.config import settings
13
+ from app.core.errors import AppError
14
+
15
+ # Structured JSON logging
16
+ handler = logging.StreamHandler(sys.stdout)
17
+ handler.setFormatter(JsonFormatter("%(asctime)s %(name)s %(levelname)s %(message)s"))
18
+ logging.basicConfig(level=logging.INFO, handlers=[handler])
19
+
20
+
21
+ @asynccontextmanager
22
+ async def lifespan(app: FastAPI):
23
+ # Startup: connect to database with retry
24
+ from app.db.session import engine, connect_with_retry
25
+
26
+ await connect_with_retry(engine)
27
+ yield
28
+ # Shutdown: close database connections gracefully
29
+ await engine.dispose()
30
+
31
+
32
+ app = FastAPI(
33
+ title="{{PROJECT_NAME_PASCAL}}",
34
+ version="0.1.0",
35
+ lifespan=lifespan,
36
+ )
37
+
38
+ # CORS
39
+ app.add_middleware(
40
+ CORSMiddleware,
41
+ allow_origins=settings.cors_origins,
42
+ allow_credentials=True,
43
+ allow_methods=["*"],
44
+ allow_headers=["*"],
45
+ )
46
+
47
+ # Routes
48
+ app.include_router(health_router, tags=["health"])
49
+ app.include_router(portal_router)
50
+
51
+
52
+ # Global exception handler - never leak stack traces
53
+ @app.exception_handler(AppError)
54
+ async def app_error_handler(request: Request, exc: AppError):
55
+ return JSONResponse(
56
+ status_code=exc.status_code,
57
+ content={"error": {"code": exc.code, "message": exc.message}},
58
+ )
59
+
60
+
61
+ @app.exception_handler(Exception)
62
+ async def global_exception_handler(request: Request, exc: Exception):
63
+ logging.exception("Unhandled exception")
64
+ return JSONResponse(
65
+ status_code=500,
66
+ content={"error": {"code": "INTERNAL_ERROR", "message": "An unexpected error occurred"}},
67
+ )
@@ -1,16 +1,18 @@
1
- fastapi>=0.115.0
2
- uvicorn[standard]>=0.34.0
3
- sqlalchemy[asyncio]>=2.0.38
4
- asyncpg>=0.30.0
5
- pydantic>=2.11.0
6
- pydantic-settings>=2.8.0
7
- alembic>=1.15.0
8
- python-jose[cryptography]>=3.4.0
9
- passlib[bcrypt]>=1.7.4
10
- httpx>=0.28.0
11
- pytest>=8.3.0
12
- pytest-asyncio>=0.25.0
13
- markdown>=3.7
14
- bleach>=6.2.0
15
- ruff>=0.11.0
16
- pyright>=1.1.400
1
+ fastapi>=0.115.0
2
+ uvicorn[standard]>=0.34.0
3
+ sqlalchemy[asyncio]>=2.0.38
4
+ asyncpg>=0.30.0
5
+ pydantic>=2.11.0
6
+ pydantic-settings>=2.8.0
7
+ alembic>=1.15.0
8
+ python-jose[cryptography]>=3.4.0
9
+ passlib[bcrypt]>=1.7.4
10
+ httpx>=0.28.0
11
+ pytest>=8.3.0
12
+ pytest-asyncio>=0.25.0
13
+ markdown>=3.7
14
+ bleach>=6.2.0
15
+ # v3 uses: from pythonjsonlogger.json import JsonFormatter (v2 used pythonjsonlogger.jsonlogger)
16
+ python-json-logger>=3.2.0
17
+ ruff>=0.11.0
18
+ pyright>=1.1.400
@@ -1,31 +1,33 @@
1
- {
2
- "name": "{{PROJECT_NAME}}",
3
- "version": "0.1.0",
4
- "private": true,
5
- "type": "module",
6
- "scripts": {
7
- "dev": "tsx watch src/index.ts",
8
- "build": "tsc",
9
- "start": "node dist/index.js",
10
- "lint": "eslint .",
11
- "typecheck": "tsc --noEmit",
12
- "test": "vitest run",
13
- "test:watch": "vitest",
14
- "db:push": "prisma db push",
15
- "db:studio": "prisma studio",
16
- "db:generate": "prisma generate"
17
- },
18
- "dependencies": {
19
- "hono": "^4.7.0",
20
- "@hono/node-server": "^1.14.0",
21
- "@prisma/client": "^6.6.0"
22
- },
23
- "devDependencies": {
24
- "typescript": "^5.8.3",
25
- "@types/node": "^22.14.0",
26
- "tsx": "^4.19.0",
27
- "eslint": "^9.25.0",
28
- "prisma": "^6.6.0",
29
- "vitest": "^3.1.1"
30
- }
31
- }
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "tsx watch src/index.ts",
8
+ "build": "tsc",
9
+ "start": "node dist/index.js",
10
+ "lint": "eslint .",
11
+ "typecheck": "tsc --noEmit",
12
+ "test": "vitest run",
13
+ "test:watch": "vitest",
14
+ "db:push": "prisma db push",
15
+ "db:migrate": "prisma migrate dev",
16
+ "db:migrate:deploy": "prisma migrate deploy",
17
+ "db:studio": "prisma studio",
18
+ "db:generate": "prisma generate"
19
+ },
20
+ "dependencies": {
21
+ "hono": "^4.7.0",
22
+ "@hono/node-server": "^1.14.0",
23
+ "@prisma/client": "^6.6.0"
24
+ },
25
+ "devDependencies": {
26
+ "typescript": "^5.8.3",
27
+ "@types/node": "^22.14.0",
28
+ "tsx": "^4.19.0",
29
+ "eslint": "^9.25.0",
30
+ "prisma": "^6.6.0",
31
+ "vitest": "^3.1.1"
32
+ }
33
+ }
@@ -0,0 +1,32 @@
1
+ import { PrismaClient } from '@prisma/client';
2
+
3
+ const MAX_RETRIES = 3;
4
+ const RETRY_DELAY_MS = 1000;
5
+
6
+ async function connectWithRetry(client: PrismaClient, retries = MAX_RETRIES): Promise<void> {
7
+ for (let attempt = 1; attempt <= retries; attempt++) {
8
+ try {
9
+ await client.$connect();
10
+ return;
11
+ } catch (error) {
12
+ if (attempt === retries) throw error;
13
+ const delay = RETRY_DELAY_MS * Math.pow(2, attempt - 1);
14
+ console.warn(`Database connection attempt ${attempt} failed. Retrying in ${delay}ms...`);
15
+ await new Promise((resolve) => setTimeout(resolve, delay));
16
+ }
17
+ }
18
+ }
19
+
20
+ export const prisma = new PrismaClient({
21
+ log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
22
+ });
23
+
24
+ // Graceful shutdown
25
+ const shutdownHandler = async () => {
26
+ await prisma.$disconnect();
27
+ };
28
+
29
+ process.on('SIGTERM', shutdownHandler);
30
+ process.on('SIGINT', shutdownHandler);
31
+
32
+ export { connectWithRetry };
@@ -1,27 +1,38 @@
1
- import { Hono } from 'hono';
2
-
3
- export const healthRoutes = new Hono();
4
-
5
- // Liveness probe — always returns ok if the process is running
6
- healthRoutes.get('/health', (c) => {
7
- return c.json({
8
- status: 'ok',
9
- timestamp: new Date().toISOString(),
10
- uptime: process.uptime(),
11
- service: '{{PROJECT_NAME}}',
12
- });
13
- });
14
-
15
- // Readiness probe — add real dependency checks (database, cache, etc.)
16
- // before using this as a Kubernetes readiness probe
17
- healthRoutes.get('/healthz', async (c) => {
18
- // TODO: Replace with actual checks, e.g.:
19
- // const db = await prisma.$queryRaw`SELECT 1`;
20
- const checks: Record<string, string> = {};
21
-
22
- return c.json({
23
- status: 'ok',
24
- service: '{{PROJECT_NAME}}',
25
- checks,
26
- });
27
- });
1
+ import { Hono } from 'hono';
2
+ import { prisma } from '../lib/prisma';
3
+
4
+ export const healthRoutes = new Hono();
5
+
6
+ // Liveness probe — always returns ok if the process is running
7
+ healthRoutes.get('/health', (c) => {
8
+ return c.json({
9
+ status: 'ok',
10
+ timestamp: new Date().toISOString(),
11
+ uptime: process.uptime(),
12
+ service: '{{PROJECT_NAME}}',
13
+ });
14
+ });
15
+
16
+ // Readiness probe verifies database connectivity
17
+ healthRoutes.get('/healthz', async (c) => {
18
+ let dbStatus = 'disconnected';
19
+
20
+ try {
21
+ await prisma.$queryRaw`SELECT 1`;
22
+ dbStatus = 'connected';
23
+ } catch {
24
+ dbStatus = 'disconnected';
25
+ }
26
+
27
+ const status = dbStatus === 'connected' ? 'ok' : 'degraded';
28
+ const statusCode = dbStatus === 'connected' ? 200 : 503;
29
+
30
+ return c.json(
31
+ {
32
+ status,
33
+ service: '{{PROJECT_NAME}}',
34
+ checks: { database: dbStatus },
35
+ },
36
+ statusCode,
37
+ );
38
+ });
@@ -1,32 +1,32 @@
1
- # Dependencies
2
- node_modules/
3
-
4
- # Environment
5
- .env
6
- .env.local
7
- .env.*.local
8
-
9
- # Build output
10
- dist/
11
- .next/
12
- out/
13
- build/
14
-
15
- # IDE
16
- .vscode/
17
- .idea/
18
- *.swp
19
- *.swo
20
-
21
- # OS
22
- .DS_Store
23
- Thumbs.db
24
-
25
- # Testing
26
- coverage/
27
- test-results/
28
- playwright-report/
29
-
30
- # Kubernetes secrets (never commit real credentials)
31
- k8s/secrets.yml
32
- {{EXTRA_IGNORES}}
1
+ # Dependencies
2
+ node_modules/
3
+
4
+ # Environment
5
+ .env
6
+ .env.local
7
+ .env.*.local
8
+
9
+ # Build output
10
+ dist/
11
+ .next/
12
+ out/
13
+ build/
14
+
15
+ # IDE
16
+ .vscode/
17
+ .idea/
18
+ *.swp
19
+ *.swo
20
+
21
+ # OS
22
+ .DS_Store
23
+ Thumbs.db
24
+
25
+ # Testing
26
+ coverage/
27
+ test-results/
28
+ playwright-report/
29
+
30
+ # Kubernetes secrets (never commit real credentials)
31
+ k8s/secrets.yml
32
+ {{EXTRA_IGNORES}}