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.
- package/bin/chainproof.js +1 -1
- package/bin/devforge.js +1 -1
- package/package.json +1 -1
- package/src/chainproof-bridge.js +9 -2
- package/src/ci-mode.js +3 -4
- package/src/claude-configurator.js +114 -58
- package/src/composer.js +17 -128
- package/src/doctor-checks-chainproof.js +14 -11
- package/src/doctor-checks.js +3 -19
- package/src/index.js +6 -34
- package/src/init-mode.js +14 -3
- package/src/recommender.js +319 -310
- package/src/uat-generator.js +105 -93
- package/src/update.js +56 -12
- package/src/utils.js +245 -116
- package/templates/auth/jwt-custom/backend/app/api/auth.py.template +39 -45
- package/templates/auth/jwt-custom/backend/app/core/security.py.template +44 -37
- package/templates/backend/express/package.json.template +35 -33
- package/templates/backend/express/src/lib/prisma.ts.template +32 -0
- package/templates/backend/express/src/routes/health.ts.template +35 -27
- package/templates/backend/fastapi/backend/app/main.py.template +67 -60
- package/templates/backend/fastapi/backend/requirements.txt.template +18 -16
- package/templates/backend/hono/package.json.template +33 -31
- package/templates/backend/hono/src/lib/prisma.ts.template +32 -0
- package/templates/backend/hono/src/routes/health.ts.template +38 -27
- package/templates/base/.gitignore.template +32 -29
- package/templates/claude-code/commands/workflows.md +52 -52
- package/templates/database/prisma-postgres/prisma/schema.prisma.template +19 -18
- package/templates/database/sqlalchemy-postgres/backend/alembic/env.py.template +39 -40
- package/templates/database/sqlalchemy-postgres/backend/alembic.ini.template +38 -36
- package/templates/database/sqlalchemy-postgres/backend/app/db/session.py.template +50 -48
- package/templates/frontend/nextjs/package.json.template +45 -43
- package/templates/frontend/remix/package.json.template +41 -39
- package/templates/infra/docker/.dockerignore.template +16 -0
- package/templates/infra/docker-compose/docker-compose.yml.template +22 -19
- package/templates/infra/github-actions/.github/workflows/ci.yml.template +61 -52
- package/templates/infra/k8s/k8s/deployment.yml.template +70 -0
- package/templates/infra/k8s/k8s/hpa.yml.template +24 -0
- package/templates/infra/k8s/k8s/ingress.yml.template +26 -0
- package/templates/infra/k8s/k8s/kustomization.yml.template +13 -0
- package/templates/infra/k8s/k8s/namespace.yml.template +4 -0
- package/templates/infra/k8s/k8s/networkpolicy.yml.template +41 -0
- package/templates/infra/k8s/k8s/secrets.yml.template +10 -0
- package/templates/infra/k8s/k8s/service.yml.template +15 -0
- package/templates/testing/load/k6/README.md.template +48 -0
- 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
|
-
|
|
6
|
-
|
|
7
|
-
from app.
|
|
8
|
-
from app.db.
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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:
|
|
16
|
-
"db:
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
"@types/
|
|
33
|
-
"
|
|
34
|
-
"@
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
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:
|
|
16
|
-
"db:
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"@remix-run/
|
|
22
|
-
"react": "^
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"@
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
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
|
+
}
|
|
@@ -1,19 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|