create-projx 0.1.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/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "create-projx",
3
+ "version": "0.1.1",
4
+ "description": "Scaffold production-grade projects. Pick your stack (FastAPI, Fastify, React, Flutter), get a fully wired template with auth, database, CI/CD, and E2E tests.",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-projx": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "src/templates"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsup src/index.ts --format esm --target node18 --clean",
15
+ "dev": "tsup src/index.ts --format esm --target node18 --watch",
16
+ "prepublishOnly": "npm run build"
17
+ },
18
+ "keywords": [
19
+ "projx",
20
+ "scaffold",
21
+ "template",
22
+ "fastapi",
23
+ "fastify",
24
+ "react",
25
+ "flutter",
26
+ "fullstack",
27
+ "create"
28
+ ],
29
+ "license": "MIT",
30
+ "engines": {
31
+ "node": ">=18"
32
+ },
33
+ "dependencies": {
34
+ "@clack/prompts": "^0.9"
35
+ },
36
+ "devDependencies": {
37
+ "@eslint/js": "^10.0.1",
38
+ "@types/node": "^25.5.2",
39
+ "eslint": "^10.2.0",
40
+ "tsup": "^8",
41
+ "typescript": "^5",
42
+ "typescript-eslint": "^8.58.0"
43
+ }
44
+ }
@@ -0,0 +1,286 @@
1
+ .PHONY: setup install format lint fix check typecheck test test-coverage test-e2e \
2
+ migrate migrate-down seed run run-dev stop logs clean
3
+
4
+ # ─── Setup ───────────────────────────────────────────────────────────
5
+
6
+ setup:
7
+ git config core.hooksPath .githooks
8
+ @echo "Git hooks configured."
9
+ $(MAKE) install
10
+
11
+ install:
12
+ <% if (components.includes('fastapi')) { %>
13
+ cd fastapi && uv sync --all-extras
14
+ <% } %>
15
+ <% if (components.includes('fastify')) { %>
16
+ cd fastify && pnpm install --frozen-lockfile
17
+ <% } %>
18
+ <% if (components.includes('frontend')) { %>
19
+ cd frontend && npm ci
20
+ <% } %>
21
+ <% if (components.includes('e2e')) { %>
22
+ cd e2e && npm ci
23
+ <% } %>
24
+ <% if (components.includes('mobile')) { %>
25
+ cd mobile && flutter pub get 2>/dev/null || echo "Flutter SDK not installed — skipping mobile"
26
+ <% } %>
27
+ @echo "All dependencies installed."
28
+
29
+ # ─── Format ──────────────────────────────────────────────────────────
30
+
31
+ format:
32
+ <% if (components.includes('fastapi')) { %>
33
+ cd fastapi && uv run ruff format src tests
34
+ <% } %>
35
+ <% if (components.includes('fastify')) { %>
36
+ cd fastify && npx prettier --write --ignore-unknown .
37
+ <% } %>
38
+ <% if (components.includes('frontend')) { %>
39
+ cd frontend && npx prettier --write --ignore-unknown .
40
+ <% } %>
41
+ <% if (components.includes('e2e')) { %>
42
+ cd e2e && npx prettier --write --ignore-unknown .
43
+ <% } %>
44
+ <% if (components.includes('mobile')) { %>
45
+ cd mobile && dart format . 2>/dev/null || true
46
+ <% } %>
47
+ <% if (components.includes('infra')) { %>
48
+ cd infra/stack && terraform fmt -recursive 2>/dev/null || true
49
+ <% } %>
50
+
51
+ # ─── Lint ────────────────────────────────────────────────────────────
52
+
53
+ lint:
54
+ <% if (components.includes('fastapi')) { %>
55
+ cd fastapi && uv run ruff check src tests
56
+ <% } %>
57
+ <% if (components.includes('fastify')) { %>
58
+ cd fastify && npx eslint 'src/**/*.ts' 'tests/**/*.ts'
59
+ <% } %>
60
+ <% if (components.includes('frontend')) { %>
61
+ cd frontend && npx eslint 'src/**/*.{ts,tsx}'
62
+ <% } %>
63
+ <% if (components.includes('e2e')) { %>
64
+ cd e2e && npx eslint '**/*.ts'
65
+ <% } %>
66
+ <% if (components.includes('mobile')) { %>
67
+ cd mobile && dart analyze --fatal-infos 2>/dev/null || true
68
+ <% } %>
69
+ <% if (components.includes('infra')) { %>
70
+ cd infra/stack && terraform validate 2>/dev/null || true
71
+ <% } %>
72
+
73
+ # ─── Fix (format + lint with auto-fix) ──────────────────────────────
74
+
75
+ fix:
76
+ <% if (components.includes('fastapi')) { %>
77
+ cd fastapi && uv run ruff format src tests && uv run ruff check --fix src tests
78
+ <% } %>
79
+ <% if (components.includes('fastify')) { %>
80
+ cd fastify && npx prettier --write --ignore-unknown . && npx eslint --fix 'src/**/*.ts' 'tests/**/*.ts'
81
+ <% } %>
82
+ <% if (components.includes('frontend')) { %>
83
+ cd frontend && npx prettier --write --ignore-unknown . && npx eslint --fix 'src/**/*.{ts,tsx}'
84
+ <% } %>
85
+ <% if (components.includes('e2e')) { %>
86
+ cd e2e && npx prettier --write --ignore-unknown . && npx eslint --fix '**/*.ts'
87
+ <% } %>
88
+ <% if (components.includes('mobile')) { %>
89
+ cd mobile && dart format . 2>/dev/null && dart analyze --fatal-infos 2>/dev/null || true
90
+ <% } %>
91
+ <% if (components.includes('infra')) { %>
92
+ cd infra/stack && terraform fmt -recursive 2>/dev/null || true
93
+ <% } %>
94
+
95
+ # ─── Type Check ──────────────────────────────────────────────────────
96
+
97
+ typecheck:
98
+ <% if (components.includes('fastify')) { %>
99
+ cd fastify && npx tsc --noEmit
100
+ <% } %>
101
+ <% if (components.includes('frontend')) { %>
102
+ cd frontend && npx tsc --noEmit
103
+ <% } %>
104
+ <% if (components.includes('e2e')) { %>
105
+ cd e2e && npx tsc --noEmit
106
+ <% } %>
107
+
108
+ # ─── Check (CI-equivalent) ──────────────────────────────────────────
109
+
110
+ check:
111
+ <% if (components.includes('fastapi')) { %>
112
+ cd fastapi && uv run ruff format --check src tests && uv run ruff check src tests
113
+ <% } %>
114
+ <% if (components.includes('fastify')) { %>
115
+ cd fastify && npx prettier --check . && npx eslint . && npx tsc --noEmit
116
+ <% } %>
117
+ <% if (components.includes('frontend')) { %>
118
+ cd frontend && npx prettier --check . && npx eslint 'src/**/*.{ts,tsx}' && npx tsc --noEmit
119
+ <% } %>
120
+ <% if (components.includes('e2e')) { %>
121
+ cd e2e && npx prettier --check . && npx eslint '**/*.ts' && npx tsc --noEmit
122
+ <% } %>
123
+ <% if (components.includes('mobile')) { %>
124
+ cd mobile && dart format --set-exit-if-changed . 2>/dev/null && dart analyze --fatal-infos 2>/dev/null || true
125
+ <% } %>
126
+ <% if (components.includes('infra')) { %>
127
+ cd infra/stack && terraform fmt -check -recursive 2>/dev/null && terraform validate 2>/dev/null || true
128
+ <% } %>
129
+
130
+ # ─── Test ────────────────────────────────────────────────────────────
131
+
132
+ test:
133
+ <% if (components.includes('fastapi')) { %>
134
+ cd fastapi && uv run pytest --tb=short -q
135
+ <% } %>
136
+ <% if (components.includes('fastify')) { %>
137
+ cd fastify && pnpm test
138
+ <% } %>
139
+ <% if (components.includes('frontend')) { %>
140
+ cd frontend && npx vitest run
141
+ <% } %>
142
+ <% if (components.includes('mobile')) { %>
143
+ cd mobile && flutter test 2>/dev/null || true
144
+ <% } %>
145
+
146
+ test-coverage:
147
+ <% if (components.includes('fastapi')) { %>
148
+ cd fastapi && uv run pytest --cov=src --cov-report=term-missing --cov-fail-under=80
149
+ <% } %>
150
+ <% if (components.includes('fastify')) { %>
151
+ cd fastify && pnpm test -- --coverage
152
+ <% } %>
153
+ <% if (components.includes('frontend')) { %>
154
+ cd frontend && npx vitest run --coverage
155
+ <% } %>
156
+ <% if (components.includes('mobile')) { %>
157
+ cd mobile && flutter test --coverage 2>/dev/null || true
158
+ <% } %>
159
+ <% if (components.includes('e2e')) { %>
160
+
161
+ test-e2e:
162
+ cd e2e && npx playwright test
163
+ <% } %>
164
+ <% if (components.includes('mobile')) { %>
165
+
166
+ test-e2e-mobile:
167
+ cd mobile && flutter test integration_test/
168
+
169
+ mobile-build-apk:
170
+ cd mobile && flutter build apk --release
171
+
172
+ mobile-build-ios:
173
+ cd mobile && flutter build ios --release --no-codesign
174
+
175
+ mobile-codegen:
176
+ cd mobile && dart run build_runner build --delete-conflicting-outputs
177
+ <% } %>
178
+ <% if (components.includes('fastapi')) { %>
179
+
180
+ # ─── Database ────────────────────────────────────────────────────────
181
+
182
+ migrate:
183
+ cd fastapi && uv run migrate.py
184
+ <% if (components.includes('fastify')) { %>
185
+ cd fastify && pnpm prisma:migrate:dev
186
+ <% } %>
187
+
188
+ migrate-deploy:
189
+ cd fastapi && uv run migrate.py
190
+ <% if (components.includes('fastify')) { %>
191
+ cd fastify && pnpm prisma:migrate:deploy
192
+ <% } %>
193
+
194
+ migrate-down:
195
+ cd fastapi && uv run migrate.py --downgrade -1
196
+
197
+ seed:
198
+ cd fastapi && uv run seed.py
199
+ <% } %>
200
+ <% if (components.includes('fastify') && !components.includes('fastapi')) { %>
201
+
202
+ migrate:
203
+ cd fastify && pnpm prisma:migrate:dev
204
+
205
+ migrate-deploy:
206
+ cd fastify && pnpm prisma:migrate:deploy
207
+ <% } %>
208
+
209
+ # ─── Run (local) ─────────────────────────────────────────────────────
210
+
211
+ run-local:
212
+ <% if (components.includes('fastapi')) { %>
213
+ cd fastapi && uv run main.py &
214
+ <% } %>
215
+ <% if (components.includes('fastify')) { %>
216
+ cd fastify && pnpm dev &
217
+ <% } %>
218
+ <% if (components.includes('frontend')) { %>
219
+ cd frontend && npm run dev &
220
+ <% } %>
221
+ @echo "All services starting. Run 'make stop-local' to stop."
222
+ @wait
223
+
224
+ stop-local:
225
+ @kill $$(lsof -ti:7860,3000,5173 2>/dev/null) 2>/dev/null || true
226
+ @echo "All local services stopped."
227
+ <% if (components.includes('fastapi')) { %>
228
+
229
+ run-fastapi:
230
+ cd fastapi && uv run main.py
231
+ <% } %>
232
+ <% if (components.includes('fastify')) { %>
233
+
234
+ run-fastify:
235
+ cd fastify && pnpm dev
236
+ <% } %>
237
+ <% if (components.includes('frontend')) { %>
238
+
239
+ run-frontend:
240
+ cd frontend && npm run dev
241
+ <% } %>
242
+ <% if (components.includes('mobile')) { %>
243
+
244
+ run-mobile:
245
+ cd mobile && flutter run
246
+ <% } %>
247
+
248
+ # ─── Run (Docker Compose) ───────────────────────────────────────────
249
+
250
+ run:
251
+ docker compose up -d --build
252
+
253
+ run-dev:
254
+ docker compose -f docker-compose.dev.yml up -d --build
255
+
256
+ stop:
257
+ docker compose down
258
+ docker compose -f docker-compose.dev.yml down 2>/dev/null || true
259
+
260
+ logs:
261
+ docker compose logs -f
262
+ <% if (components.includes('fastapi')) { %>
263
+
264
+ entity:
265
+ @test -n "$(name)" || (echo "Usage: make entity name=my_entity" && exit 1)
266
+ cd fastapi && uv run scaffold.py $(name)
267
+
268
+ entity-custom:
269
+ @test -n "$(name)" || (echo "Usage: make entity-custom name=my_entity" && exit 1)
270
+ cd fastapi && uv run scaffold.py $(name) --controller
271
+ <% } %>
272
+
273
+ # ─── Clean ───────────────────────────────────────────────────────────
274
+
275
+ clean:
276
+ docker compose down -v
277
+ docker compose -f docker-compose.dev.yml down -v 2>/dev/null || true
278
+ <% if (components.includes('fastapi')) { %>
279
+ cd fastapi && rm -rf .pytest_cache __pycache__ .coverage htmlcov
280
+ <% } %>
281
+ <% if (components.includes('fastify')) { %>
282
+ cd fastify && rm -rf dist coverage node_modules/.cache
283
+ <% } %>
284
+ <% if (components.includes('frontend')) { %>
285
+ cd frontend && rm -rf dist coverage node_modules/.cache playwright-report test-results
286
+ <% } %>
@@ -0,0 +1,87 @@
1
+ # <%= projectName %>
2
+
3
+ Scaffolded with [Projx](https://github.com/ukanhaupa/projx).
4
+
5
+ ## Stack
6
+
7
+ | Layer | Technology |
8
+ | ----- | ---------- |
9
+ <% if (components.includes('fastapi')) { %>
10
+ | **Backend (Python)** | FastAPI, SQLAlchemy, Alembic, uvicorn |
11
+ <% } %>
12
+ <% if (components.includes('fastify')) { %>
13
+ | **Backend (Node)** | Fastify, Prisma, TypeBox, TypeScript |
14
+ <% } %>
15
+ <% if (components.includes('frontend')) { %>
16
+ | **Frontend** | React 19, TypeScript, Vite, React Router |
17
+ <% } %>
18
+ <% if (components.includes('mobile')) { %>
19
+ | **Mobile** | Flutter, Dart, Riverpod, GoRouter, Isar |
20
+ <% } %>
21
+ <% if (components.includes('e2e')) { %>
22
+ | **E2E Tests** | Playwright |
23
+ <% } %>
24
+ <% if (components.includes('infra')) { %>
25
+ | **Infrastructure** | Terraform, AWS (EKS, RDS, VPC, CodePipeline) |
26
+ <% } %>
27
+ | **Identity** | Keycloak (OIDC / JWT) |
28
+ | **Containers** | Docker, Docker Compose |
29
+
30
+ ## Getting Started
31
+
32
+ ```bash
33
+ make setup # Install all dependencies
34
+ make run-dev # Start with Docker (dev mode)
35
+ ```
36
+ <% if (components.includes('fastapi')) { %>
37
+
38
+ ### FastAPI
39
+
40
+ ```bash
41
+ cd fastapi && cp .env.example .env && uv sync && uv run main.py
42
+ ```
43
+
44
+ API docs at `http://localhost:7860/docs`.
45
+ <% } %>
46
+ <% if (components.includes('fastify')) { %>
47
+
48
+ ### Fastify
49
+
50
+ ```bash
51
+ cd fastify && cp .env.example .env && pnpm install && npx prisma migrate dev && pnpm dev
52
+ ```
53
+
54
+ API docs at `http://localhost:3000/docs`.
55
+ <% } %>
56
+ <% if (components.includes('frontend')) { %>
57
+
58
+ ### Frontend
59
+
60
+ ```bash
61
+ cd frontend && cp .env.example .env && npm install && npm run dev
62
+ ```
63
+ <% } %>
64
+ <% if (components.includes('mobile')) { %>
65
+
66
+ ### Mobile
67
+
68
+ ```bash
69
+ cd mobile && cp .env.example .env && flutter pub get && flutter run
70
+ ```
71
+ <% } %>
72
+
73
+ ## Testing
74
+
75
+ ```bash
76
+ make test # Run all tests
77
+ make test-coverage # Run with coverage
78
+ <% if (components.includes('e2e')) { %>
79
+ make test-e2e # Playwright E2E tests
80
+ <% } %>
81
+ ```
82
+
83
+ ## Update
84
+
85
+ ```bash
86
+ npx create-projx@latest update
87
+ ```
@@ -0,0 +1,117 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ branches: [main]
6
+ push:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ <% if (components.includes('fastapi')) { %>
11
+ fastapi:
12
+ name: FastAPI (format + lint)
13
+ runs-on: ubuntu-latest
14
+ defaults:
15
+ run:
16
+ working-directory: fastapi
17
+ steps:
18
+ - uses: actions/checkout@v5
19
+ - uses: astral-sh/setup-uv@v4
20
+ - run: uv sync --group dev
21
+ - run: uv run ruff format --check src tests
22
+ - run: uv run ruff check src tests
23
+ <% } %>
24
+ <% if (components.includes('fastify')) { %>
25
+ fastify:
26
+ name: Fastify (format + lint + typecheck)
27
+ runs-on: ubuntu-latest
28
+ defaults:
29
+ run:
30
+ working-directory: fastify
31
+ steps:
32
+ - uses: actions/checkout@v5
33
+ - uses: pnpm/action-setup@v4
34
+ with:
35
+ version: 9
36
+ - uses: actions/setup-node@v5
37
+ with:
38
+ node-version: 20
39
+ cache: pnpm
40
+ cache-dependency-path: fastify/pnpm-lock.yaml
41
+ - run: pnpm install --frozen-lockfile
42
+ - run: npx prisma generate
43
+ - run: npx prettier --check .
44
+ - run: npx eslint .
45
+ - run: npx tsc --noEmit
46
+ <% } %>
47
+ <% if (components.includes('frontend')) { %>
48
+ frontend:
49
+ name: Frontend (format + lint + typecheck)
50
+ runs-on: ubuntu-latest
51
+ defaults:
52
+ run:
53
+ working-directory: frontend
54
+ steps:
55
+ - uses: actions/checkout@v5
56
+ - uses: actions/setup-node@v5
57
+ with:
58
+ node-version: 22
59
+ cache: npm
60
+ cache-dependency-path: frontend/package-lock.json
61
+ - run: npm ci
62
+ - run: npx prettier --check .
63
+ - run: npx eslint 'src/**/*.{ts,tsx}'
64
+ - run: npx tsc --noEmit
65
+ <% } %>
66
+ <% if (components.includes('mobile')) { %>
67
+ mobile:
68
+ name: Flutter (format + analyze)
69
+ runs-on: ubuntu-latest
70
+ defaults:
71
+ run:
72
+ working-directory: mobile
73
+ steps:
74
+ - uses: actions/checkout@v5
75
+ - uses: subosito/flutter-action@v2
76
+ with:
77
+ channel: stable
78
+ - run: flutter pub get
79
+ - run: dart run build_runner build --delete-conflicting-outputs
80
+ - run: dart format --set-exit-if-changed .
81
+ - run: dart analyze --fatal-infos
82
+ <% } %>
83
+ <% if (components.includes('e2e')) { %>
84
+ e2e:
85
+ name: E2E (format + lint + typecheck)
86
+ runs-on: ubuntu-latest
87
+ defaults:
88
+ run:
89
+ working-directory: e2e
90
+ steps:
91
+ - uses: actions/checkout@v5
92
+ - uses: actions/setup-node@v5
93
+ with:
94
+ node-version: 22
95
+ cache: npm
96
+ cache-dependency-path: e2e/package-lock.json
97
+ - run: npm ci
98
+ - run: npx prettier --check .
99
+ - run: npx eslint '**/*.ts'
100
+ - run: npx tsc --noEmit
101
+ <% } %>
102
+ <% if (components.includes('infra')) { %>
103
+ infra:
104
+ name: Terraform (fmt + validate)
105
+ runs-on: ubuntu-latest
106
+ defaults:
107
+ run:
108
+ working-directory: infra/stack
109
+ steps:
110
+ - uses: actions/checkout@v5
111
+ - uses: hashicorp/setup-terraform@v3
112
+ with:
113
+ terraform_version: '1.11'
114
+ - run: terraform fmt -check -recursive
115
+ - run: terraform init -backend=false
116
+ - run: terraform validate
117
+ <% } %>
@@ -0,0 +1,168 @@
1
+ services:
2
+ <% if (components.includes('fastapi') || components.includes('fastify')) { %>
3
+ db:
4
+ image: postgres:16-alpine
5
+ environment:
6
+ POSTGRES_USER: dev
7
+ POSTGRES_PASSWORD: dev
8
+ POSTGRES_DB: app
9
+ ports:
10
+ - '5432:5432'
11
+ volumes:
12
+ - pgdata:/var/lib/postgresql/data
13
+ healthcheck:
14
+ test: ['CMD-SHELL', 'pg_isready -U dev -d app']
15
+ interval: 5s
16
+ timeout: 3s
17
+ retries: 5
18
+ deploy:
19
+ resources:
20
+ limits:
21
+ memory: 256M
22
+ cpus: '0.5'
23
+ networks:
24
+ - app-network
25
+ <% } %>
26
+ <% if (components.includes('fastapi')) { %>
27
+ migrate:
28
+ build: ./fastapi
29
+ command: ['uv', 'run', 'migrate.py']
30
+ environment:
31
+ - SQLALCHEMY_DATABASE_URI=postgresql+asyncpg://dev:dev@db:5432/app
32
+ depends_on:
33
+ db:
34
+ condition: service_healthy
35
+ deploy:
36
+ resources:
37
+ limits:
38
+ memory: 256M
39
+ cpus: '0.5'
40
+ networks:
41
+ - app-network
42
+
43
+ backend:
44
+ build: ./fastapi
45
+ command:
46
+ [
47
+ 'uv', 'run', 'uvicorn', 'src.app:app',
48
+ '--host', '0.0.0.0', '--port', '7860',
49
+ '--workers', '2', '--timeout-keep-alive', '120', '--reload',
50
+ ]
51
+ ports:
52
+ - '7860:7860'
53
+ environment:
54
+ - SQLALCHEMY_DATABASE_URI=postgresql+asyncpg://dev:dev@db:5432/app
55
+ - CORS_ALLOW_ORIGINS=http://localhost:3000,http://localhost
56
+ - JWT_PROVIDER=shared_secret
57
+ - JWT_SECRET=dev-secret-that-is-at-least-32-bytes-long
58
+ - JWT_ALGORITHMS=HS256
59
+ volumes:
60
+ - ./fastapi/src:/app/src
61
+ - ./fastapi/alembic.ini:/app/alembic.ini
62
+ - ./fastapi/migrate.py:/app/migrate.py
63
+ depends_on:
64
+ migrate:
65
+ condition: service_completed_successfully
66
+ restart: unless-stopped
67
+ healthcheck:
68
+ test:
69
+ [
70
+ 'CMD', 'python', '-c',
71
+ "import urllib.request; urllib.request.urlopen('http://localhost:7860/api/health')",
72
+ ]
73
+ interval: 15s
74
+ timeout: 5s
75
+ retries: 3
76
+ start_period: 10s
77
+ deploy:
78
+ resources:
79
+ limits:
80
+ memory: 512M
81
+ cpus: '1.0'
82
+ networks:
83
+ - app-network
84
+ <% } %>
85
+ <% if (components.includes('fastify')) { %>
86
+ backend-fastify:
87
+ build: ./fastify
88
+ command: ['pnpm', 'dev']
89
+ ports:
90
+ - '3000:3000'
91
+ environment:
92
+ - DATABASE_URL=postgresql://dev:dev@db:5432/app
93
+ - CORS_ALLOW_ORIGINS=http://localhost:5173,http://localhost
94
+ - JWT_PROVIDER=shared_secret
95
+ - JWT_SECRET=dev-secret-that-is-at-least-32-bytes-long
96
+ volumes:
97
+ - ./fastify/src:/app/src
98
+ depends_on:
99
+ db:
100
+ condition: service_healthy
101
+ restart: unless-stopped
102
+ healthcheck:
103
+ test: ['CMD', 'wget', '--spider', '-q', 'http://localhost:3000/api/health']
104
+ interval: 15s
105
+ timeout: 5s
106
+ retries: 3
107
+ start_period: 10s
108
+ deploy:
109
+ resources:
110
+ limits:
111
+ memory: 512M
112
+ cpus: '1.0'
113
+ networks:
114
+ - app-network
115
+ <% } %>
116
+ <% if (components.includes('frontend')) { %>
117
+ frontend:
118
+ image: node:20-alpine
119
+ working_dir: /app
120
+ command: sh -c "npm install && npm run dev -- --host 0.0.0.0"
121
+ ports:
122
+ - '5173:5173'
123
+ environment:
124
+ <% if (components.includes('fastapi')) { %>
125
+ - VITE_API_URL=http://localhost:7860
126
+ <% } %>
127
+ <% if (components.includes('fastify') && !components.includes('fastapi')) { %>
128
+ - VITE_API_URL=http://localhost:3000
129
+ <% } %>
130
+ volumes:
131
+ - ./frontend:/app
132
+ - frontend_node_modules:/app/node_modules
133
+ <% if (components.includes('fastapi')) { %>
134
+ depends_on:
135
+ backend:
136
+ condition: service_healthy
137
+ <% } %>
138
+ <% if (components.includes('fastify') && !components.includes('fastapi')) { %>
139
+ depends_on:
140
+ backend-fastify:
141
+ condition: service_healthy
142
+ <% } %>
143
+ healthcheck:
144
+ test: ['CMD', 'wget', '--spider', '-q', 'http://localhost:5173/']
145
+ interval: 15s
146
+ timeout: 5s
147
+ retries: 3
148
+ start_period: 30s
149
+ deploy:
150
+ resources:
151
+ limits:
152
+ memory: 512M
153
+ cpus: '1.0'
154
+ networks:
155
+ - app-network
156
+ <% } %>
157
+
158
+ volumes:
159
+ <% if (components.includes('fastapi') || components.includes('fastify')) { %>
160
+ pgdata:
161
+ <% } %>
162
+ <% if (components.includes('frontend')) { %>
163
+ frontend_node_modules:
164
+ <% } %>
165
+
166
+ networks:
167
+ app-network:
168
+ driver: bridge