forgedev 1.3.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 (46) hide show
  1. package/bin/chainproof.js +1 -1
  2. package/bin/devforge.js +1 -1
  3. package/package.json +1 -1
  4. package/src/chainproof-bridge.js +9 -2
  5. package/src/ci-mode.js +3 -4
  6. package/src/claude-configurator.js +114 -58
  7. package/src/composer.js +17 -128
  8. package/src/doctor-checks-chainproof.js +14 -11
  9. package/src/doctor-checks.js +3 -19
  10. package/src/index.js +6 -34
  11. package/src/init-mode.js +14 -3
  12. package/src/recommender.js +319 -310
  13. package/src/uat-generator.js +105 -93
  14. package/src/update.js +56 -12
  15. package/src/utils.js +245 -116
  16. package/templates/auth/jwt-custom/backend/app/api/auth.py.template +39 -45
  17. package/templates/auth/jwt-custom/backend/app/core/security.py.template +44 -37
  18. package/templates/backend/express/package.json.template +35 -33
  19. package/templates/backend/express/src/lib/prisma.ts.template +32 -0
  20. package/templates/backend/express/src/routes/health.ts.template +35 -27
  21. package/templates/backend/fastapi/backend/app/main.py.template +67 -60
  22. package/templates/backend/fastapi/backend/requirements.txt.template +18 -16
  23. package/templates/backend/hono/package.json.template +33 -31
  24. package/templates/backend/hono/src/lib/prisma.ts.template +32 -0
  25. package/templates/backend/hono/src/routes/health.ts.template +38 -27
  26. package/templates/base/.gitignore.template +32 -29
  27. package/templates/claude-code/commands/workflows.md +52 -52
  28. package/templates/database/prisma-postgres/prisma/schema.prisma.template +19 -18
  29. package/templates/database/sqlalchemy-postgres/backend/alembic/env.py.template +39 -40
  30. package/templates/database/sqlalchemy-postgres/backend/alembic.ini.template +38 -36
  31. package/templates/database/sqlalchemy-postgres/backend/app/db/session.py.template +50 -48
  32. package/templates/frontend/nextjs/package.json.template +45 -43
  33. package/templates/frontend/remix/package.json.template +41 -39
  34. package/templates/infra/docker/.dockerignore.template +16 -0
  35. package/templates/infra/docker-compose/docker-compose.yml.template +22 -19
  36. package/templates/infra/github-actions/.github/workflows/ci.yml.template +61 -52
  37. package/templates/infra/k8s/k8s/deployment.yml.template +70 -0
  38. package/templates/infra/k8s/k8s/hpa.yml.template +24 -0
  39. package/templates/infra/k8s/k8s/ingress.yml.template +26 -0
  40. package/templates/infra/k8s/k8s/kustomization.yml.template +13 -0
  41. package/templates/infra/k8s/k8s/namespace.yml.template +4 -0
  42. package/templates/infra/k8s/k8s/networkpolicy.yml.template +41 -0
  43. package/templates/infra/k8s/k8s/secrets.yml.template +10 -0
  44. package/templates/infra/k8s/k8s/service.yml.template +15 -0
  45. package/templates/testing/load/k6/README.md.template +48 -0
  46. package/templates/testing/load/k6/load-test.js.template +57 -0
@@ -1,40 +1,39 @@
1
- import asyncio
2
- from logging.config import fileConfig
3
-
4
- from alembic import context
5
- from sqlalchemy.ext.asyncio import create_async_engine
6
-
7
- from app.core.config import settings
8
- from app.db.base import Base
9
-
10
- config = context.config
11
- if config.config_file_name is not None:
12
- fileConfig(config.config_file_name)
13
-
14
- target_metadata = Base.metadata
15
-
16
-
17
- def run_migrations_offline() -> None:
18
- url = settings.database_url
19
- context.configure(url=url, target_metadata=target_metadata, literal_binds=True)
20
- with context.begin_transaction():
21
- context.run_migrations()
22
-
23
-
24
- def do_run_migrations(connection):
25
- context.configure(connection=connection, target_metadata=target_metadata)
26
- with context.begin_transaction():
27
- context.run_migrations()
28
-
29
-
30
- async def run_migrations_online() -> None:
31
- connectable = create_async_engine(settings.database_url)
32
- async with connectable.connect() as connection:
33
- await connection.run_sync(do_run_migrations)
34
- await connectable.dispose()
35
-
36
-
37
- if context.is_offline_mode():
38
- run_migrations_offline()
39
- else:
40
- asyncio.run(run_migrations_online())
1
+ import asyncio
2
+ from logging.config import fileConfig
3
+
4
+ from alembic import context
5
+
6
+ from app.core.config import settings
7
+ from app.db.base import Base
8
+ from app.db.session import engine
9
+
10
+ config = context.config
11
+ if config.config_file_name is not None:
12
+ fileConfig(config.config_file_name)
13
+
14
+ target_metadata = Base.metadata
15
+
16
+
17
+ def run_migrations_offline() -> None:
18
+ url = settings.database_url
19
+ context.configure(url=url, target_metadata=target_metadata, literal_binds=True)
20
+ with context.begin_transaction():
21
+ context.run_migrations()
22
+
23
+
24
+ def do_run_migrations(connection):
25
+ context.configure(connection=connection, target_metadata=target_metadata)
26
+ with context.begin_transaction():
27
+ context.run_migrations()
28
+
29
+
30
+ async def run_migrations_online() -> None:
31
+ async with engine.connect() as connection:
32
+ await connection.run_sync(do_run_migrations)
33
+ await engine.dispose()
34
+
35
+
36
+ if context.is_offline_mode():
37
+ run_migrations_offline()
38
+ else:
39
+ asyncio.run(run_migrations_online())
@@ -1,36 +1,38 @@
1
- [alembic]
2
- script_location = alembic
3
- sqlalchemy.url = postgresql+asyncpg://postgres:postgres@localhost:5432/{{PROJECT_NAME_SNAKE}}
4
-
5
- [loggers]
6
- keys = root,sqlalchemy,alembic
7
-
8
- [handlers]
9
- keys = console
10
-
11
- [formatters]
12
- keys = generic
13
-
14
- [logger_root]
15
- level = WARN
16
- handlers = console
17
-
18
- [logger_sqlalchemy]
19
- level = WARN
20
- handlers =
21
- qualname = sqlalchemy.engine
22
-
23
- [logger_alembic]
24
- level = INFO
25
- handlers =
26
- qualname = alembic
27
-
28
- [handler_console]
29
- class = StreamHandler
30
- args = (sys.stderr,)
31
- level = NOTSET
32
- formatter = generic
33
-
34
- [formatter_generic]
35
- format = %(levelname)-5.5s [%(name)s] %(message)s
36
- datefmt = %H:%M:%S
1
+ [alembic]
2
+ script_location = alembic
3
+ # Overridden at runtime by env.py see app/db/session.py for connection config
4
+ # Do not put real credentials here; env.py reads from settings.database_url
5
+ sqlalchemy.url = driver://user:pass@localhost/dbname
6
+
7
+ [loggers]
8
+ keys = root,sqlalchemy,alembic
9
+
10
+ [handlers]
11
+ keys = console
12
+
13
+ [formatters]
14
+ keys = generic
15
+
16
+ [logger_root]
17
+ level = WARN
18
+ handlers = console
19
+
20
+ [logger_sqlalchemy]
21
+ level = WARN
22
+ handlers =
23
+ qualname = sqlalchemy.engine
24
+
25
+ [logger_alembic]
26
+ level = INFO
27
+ handlers =
28
+ qualname = alembic
29
+
30
+ [handler_console]
31
+ class = StreamHandler
32
+ args = (sys.stderr,)
33
+ level = NOTSET
34
+ formatter = generic
35
+
36
+ [formatter_generic]
37
+ format = %(levelname)-5.5s [%(name)s] %(message)s
38
+ datefmt = %H:%M:%S
@@ -1,48 +1,50 @@
1
- import asyncio
2
- import logging
3
-
4
- from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
5
-
6
- from app.core.config import settings
7
-
8
- logger = logging.getLogger(__name__)
9
-
10
- engine = create_async_engine(
11
- settings.database_url,
12
- echo=settings.debug,
13
- pool_size=5,
14
- max_overflow=10,
15
- pool_pre_ping=True,
16
- )
17
-
18
- async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
19
-
20
-
21
- async def connect_with_retry(
22
- engine, max_retries: int = 3, base_delay: float = 1.0
23
- ) -> None:
24
- """Attempt database connection with exponential backoff."""
25
- for attempt in range(1, max_retries + 1):
26
- try:
27
- async with engine.connect() as conn:
28
- await conn.execute(
29
- __import__("sqlalchemy").text("SELECT 1")
30
- )
31
- logger.info("Database connected successfully")
32
- return
33
- except Exception as e:
34
- if attempt == max_retries:
35
- logger.error(f"Database connection failed after {max_retries} attempts: {e}")
36
- raise
37
- delay = base_delay * (2 ** (attempt - 1))
38
- logger.warning(
39
- f"Database connection attempt {attempt}/{max_retries} failed. "
40
- f"Retrying in {delay:.1f}s..."
41
- )
42
- await asyncio.sleep(delay)
43
-
44
-
45
- async def get_db():
46
- """Dependency for FastAPI endpoints."""
47
- async with async_session() as session:
48
- yield session
1
+ import asyncio
2
+ import logging
3
+
4
+ from sqlalchemy import text
5
+ from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
6
+
7
+ from app.core.config import settings
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ engine = create_async_engine(
12
+ settings.database_url,
13
+ echo=settings.debug,
14
+ pool_size=5,
15
+ max_overflow=10,
16
+ pool_pre_ping=True,
17
+ pool_recycle=300, # Recycle connections before cloud provider timeout
18
+ )
19
+
20
+ async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
21
+
22
+
23
+ async def connect_with_retry(
24
+ engine, max_retries: int = 3, base_delay: float = 1.0
25
+ ) -> None:
26
+ """Attempt database connection with exponential backoff."""
27
+ for attempt in range(1, max_retries + 1):
28
+ try:
29
+ async with engine.connect() as conn:
30
+ await conn.execute(
31
+ text("SELECT 1")
32
+ )
33
+ logger.info("Database connected successfully")
34
+ return
35
+ except Exception as e:
36
+ if attempt == max_retries:
37
+ logger.error(f"Database connection failed after {max_retries} attempts: {e}")
38
+ raise
39
+ delay = base_delay * (2 ** (attempt - 1))
40
+ logger.warning(
41
+ f"Database connection attempt {attempt}/{max_retries} failed. "
42
+ f"Retrying in {delay:.1f}s..."
43
+ )
44
+ await asyncio.sleep(delay)
45
+
46
+
47
+ async def get_db():
48
+ """Dependency for FastAPI endpoints."""
49
+ async with async_session() as session:
50
+ yield session
@@ -1,43 +1,45 @@
1
- {
2
- "name": "{{PROJECT_NAME}}",
3
- "version": "0.1.0",
4
- "private": true,
5
- "scripts": {
6
- "dev": "next dev",
7
- "build": "next build",
8
- "start": "next start",
9
- "lint": "eslint .",
10
- "typecheck": "tsc --noEmit",
11
- "test": "vitest run",
12
- "test:watch": "vitest",
13
- "test:e2e": "playwright test",
14
- "db:push": "prisma db push",
15
- "db:studio": "prisma studio",
16
- "db:generate": "prisma generate"
17
- },
18
- "dependencies": {
19
- "next": "^15.3.0",
20
- "react": "^19.1.0",
21
- "react-dom": "^19.1.0",
22
- "@prisma/client": "^6.6.0",
23
- "clsx": "^2.1.1",
24
- "tailwind-merge": "^3.2.0",
25
- "react-markdown": "^9.0.3",
26
- "remark-gfm": "^4.0.0"
27
- },
28
- "devDependencies": {
29
- "typescript": "^5.8.3",
30
- "@types/node": "^22.14.0",
31
- "@types/react": "^19.1.0",
32
- "@types/react-dom": "^19.1.0",
33
- "tailwindcss": "^4.1.3",
34
- "@tailwindcss/postcss": "^4.1.3",
35
- "postcss": "^8.5.3",
36
- "eslint": "^9.25.0",
37
- "eslint-config-next": "^15.3.0",
38
- "prisma": "^6.6.0",
39
- "vitest": "^3.1.1",
40
- "@playwright/test": "^1.52.0",
41
- "@testing-library/react": "^16.3.0"
42
- }
43
- }
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "eslint .",
10
+ "typecheck": "tsc --noEmit",
11
+ "test": "vitest run",
12
+ "test:watch": "vitest",
13
+ "test:e2e": "playwright test",
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
+ "next": "^15.3.0",
22
+ "react": "^19.1.0",
23
+ "react-dom": "^19.1.0",
24
+ "@prisma/client": "^6.6.0",
25
+ "clsx": "^2.1.1",
26
+ "tailwind-merge": "^3.2.0",
27
+ "react-markdown": "^9.0.3",
28
+ "remark-gfm": "^4.0.0"
29
+ },
30
+ "devDependencies": {
31
+ "typescript": "^5.8.3",
32
+ "@types/node": "^22.14.0",
33
+ "@types/react": "^19.1.0",
34
+ "@types/react-dom": "^19.1.0",
35
+ "tailwindcss": "^4.1.3",
36
+ "@tailwindcss/postcss": "^4.1.3",
37
+ "postcss": "^8.5.3",
38
+ "eslint": "^9.25.0",
39
+ "eslint-config-next": "^15.3.0",
40
+ "prisma": "^6.6.0",
41
+ "vitest": "^3.1.1",
42
+ "@playwright/test": "^1.52.0",
43
+ "@testing-library/react": "^16.3.0"
44
+ }
45
+ }
@@ -1,39 +1,41 @@
1
- {
2
- "name": "{{PROJECT_NAME}}",
3
- "version": "0.1.0",
4
- "private": true,
5
- "type": "module",
6
- "scripts": {
7
- "dev": "remix vite:dev",
8
- "build": "remix vite:build",
9
- "start": "remix-serve ./build/server/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
- "@remix-run/node": "^2.16.0",
20
- "@remix-run/react": "^2.16.0",
21
- "@remix-run/serve": "^2.16.0",
22
- "react": "^19.1.0",
23
- "react-dom": "^19.1.0",
24
- "isbot": "^5.1.0",
25
- "@prisma/client": "^6.6.0"
26
- },
27
- "devDependencies": {
28
- "@remix-run/dev": "^2.16.0",
29
- "typescript": "^5.8.3",
30
- "@types/react": "^19.1.0",
31
- "@types/react-dom": "^19.1.0",
32
- "vite": "^6.3.0",
33
- "tailwindcss": "^4.1.3",
34
- "@tailwindcss/vite": "^4.1.3",
35
- "eslint": "^9.25.0",
36
- "prisma": "^6.6.0",
37
- "vitest": "^3.1.1"
38
- }
39
- }
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "remix vite:dev",
8
+ "build": "remix vite:build",
9
+ "start": "remix-serve ./build/server/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
+ "@remix-run/node": "^2.16.0",
22
+ "@remix-run/react": "^2.16.0",
23
+ "@remix-run/serve": "^2.16.0",
24
+ "react": "^19.1.0",
25
+ "react-dom": "^19.1.0",
26
+ "isbot": "^5.1.0",
27
+ "@prisma/client": "^6.6.0"
28
+ },
29
+ "devDependencies": {
30
+ "@remix-run/dev": "^2.16.0",
31
+ "typescript": "^5.8.3",
32
+ "@types/react": "^19.1.0",
33
+ "@types/react-dom": "^19.1.0",
34
+ "vite": "^6.3.0",
35
+ "tailwindcss": "^4.1.3",
36
+ "@tailwindcss/vite": "^4.1.3",
37
+ "eslint": "^9.25.0",
38
+ "prisma": "^6.6.0",
39
+ "vitest": "^3.1.1"
40
+ }
41
+ }
@@ -0,0 +1,16 @@
1
+ node_modules
2
+ npm-debug.log*
3
+ .git
4
+ .gitignore
5
+ .env
6
+ .env.*
7
+ *.md
8
+ !README.md
9
+ tests
10
+ coverage
11
+ .nyc_output
12
+ .vscode
13
+ .idea
14
+ .claude
15
+ docs
16
+ dist
@@ -1,19 +1,22 @@
1
- services:
2
- postgres:
3
- image: postgres:17-alpine
4
- environment:
5
- POSTGRES_DB: {{PROJECT_NAME_SNAKE}}
6
- POSTGRES_USER: postgres
7
- POSTGRES_PASSWORD: postgres
8
- ports:
9
- - "5432:5432"
10
- volumes:
11
- - postgres_data:/var/lib/postgresql/data
12
- healthcheck:
13
- test: ["CMD-SHELL", "pg_isready -U postgres"]
14
- interval: 5s
15
- timeout: 5s
16
- retries: 5
17
-
18
- volumes:
19
- postgres_data:
1
+ # WARNING: Default credentials below are for local development only.
2
+ # For production, use secrets management (e.g. Docker secrets, Vault, or env vars).
3
+ services:
4
+ postgres:
5
+ image: postgres:17-alpine
6
+ shm_size: '256mb'
7
+ environment:
8
+ POSTGRES_DB: {{PROJECT_NAME_SNAKE}}
9
+ POSTGRES_USER: ${POSTGRES_USER:-postgres}
10
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
11
+ ports:
12
+ - "5432:5432"
13
+ volumes:
14
+ - postgres_data:/var/lib/postgresql/data
15
+ healthcheck:
16
+ test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"]
17
+ interval: 5s
18
+ timeout: 5s
19
+ retries: 5
20
+
21
+ volumes:
22
+ postgres_data:
@@ -1,52 +1,61 @@
1
- name: CI
2
-
3
- on:
4
- push:
5
- branches: [main]
6
- pull_request:
7
- branches: [main]
8
-
9
- jobs:
10
- lint-and-test:
11
- runs-on: ubuntu-latest
12
-
13
- services:
14
- postgres:
15
- image: postgres:17-alpine
16
- env:
17
- POSTGRES_DB: {{PROJECT_NAME_SNAKE}}
18
- POSTGRES_USER: postgres
19
- POSTGRES_PASSWORD: postgres
20
- ports:
21
- - 5432:5432
22
- options: >-
23
- --health-cmd pg_isready
24
- --health-interval 10s
25
- --health-timeout 5s
26
- --health-retries 5
27
-
28
- steps:
29
- - uses: actions/checkout@v4
30
-
31
- - name: Setup Node.js
32
- uses: actions/setup-node@v4
33
- with:
34
- node-version: '22'
35
- cache: 'npm'
36
-
37
- - name: Install dependencies
38
- run: npm ci
39
-
40
- - name: Lint
41
- run: {{LINT_COMMAND}}
42
-
43
- - name: Type check
44
- run: {{TYPE_CHECK_COMMAND}}
45
-
46
- - name: Build
47
- run: {{BUILD_COMMAND}}
48
-
49
- - name: Test
50
- run: {{TEST_COMMAND}}
51
- env:
52
- DATABASE_URL: postgresql://postgres:postgres@localhost:5432/{{PROJECT_NAME_SNAKE}}
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ lint-and-test:
11
+ runs-on: ubuntu-latest
12
+
13
+ services:
14
+ postgres:
15
+ image: postgres:17-alpine
16
+ env:
17
+ POSTGRES_DB: {{PROJECT_NAME_SNAKE}}
18
+ POSTGRES_USER: postgres
19
+ POSTGRES_PASSWORD: postgres
20
+ ports:
21
+ - 5432:5432
22
+ options: >-
23
+ --health-cmd pg_isready
24
+ --health-interval 10s
25
+ --health-timeout 5s
26
+ --health-retries 5
27
+
28
+ steps:
29
+ - uses: actions/checkout@v4
30
+
31
+ - name: Setup Node.js
32
+ uses: actions/setup-node@v4
33
+ with:
34
+ node-version: '22'
35
+ cache: 'npm'
36
+
37
+ - name: Install dependencies
38
+ run: npm ci
39
+
40
+ - name: Lint
41
+ run: {{LINT_COMMAND}}
42
+
43
+ - name: Type check
44
+ run: {{TYPE_CHECK_COMMAND}}
45
+
46
+ - name: Build
47
+ run: {{BUILD_COMMAND}}
48
+
49
+ - name: Test
50
+ run: {{TEST_COMMAND}}
51
+ env:
52
+ DATABASE_URL: postgresql://postgres:postgres@localhost:5432/{{PROJECT_NAME_SNAKE}}
53
+
54
+ - name: Security audit (npm)
55
+ run: npm audit --audit-level=high
56
+ continue-on-error: true
57
+
58
+ - name: Security audit (pip)
59
+ if: hashFiles('**/requirements.txt') != ''
60
+ run: pip install pip-audit && pip-audit -r requirements.txt
61
+ continue-on-error: true