create-projx 1.4.0 → 1.4.2
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/README.md +42 -0
- package/dist/index.js +76 -33
- package/package.json +1 -1
- package/src/templates/docker-compose.dev.yml.ejs +28 -18
- package/src/templates/docker-compose.yml.ejs +18 -12
package/README.md
CHANGED
|
@@ -131,6 +131,8 @@ npx create-projx pin <patterns...>
|
|
|
131
131
|
npx create-projx unpin <patterns...>
|
|
132
132
|
npx create-projx pin --list
|
|
133
133
|
npx create-projx doctor [--fix]
|
|
134
|
+
npx create-projx gen entity <name>
|
|
135
|
+
npx create-projx sync [--url <url>]
|
|
134
136
|
|
|
135
137
|
--components <list> Comma-separated: fastapi,fastify,frontend,mobile,e2e,infra
|
|
136
138
|
--no-git Skip git init
|
|
@@ -174,6 +176,46 @@ npx create-projx doctor --fix # auto-fix what's possible
|
|
|
174
176
|
|
|
175
177
|
Checks: config validity, component markers, baseline ref, stale worktrees, skip pattern coverage.
|
|
176
178
|
|
|
179
|
+
### Generate Entities
|
|
180
|
+
|
|
181
|
+
Scaffold a new entity across all components in your project:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
npx create-projx gen entity invoice # interactive
|
|
185
|
+
npx create-projx gen entity invoice --fields "name:string,amount:number" # non-interactive
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Generates based on what's in your `.projx`:
|
|
189
|
+
|
|
190
|
+
| Component | Generated |
|
|
191
|
+
| --------- | --------- |
|
|
192
|
+
| `fastapi` | `src/entities/<name>/_model.py` — auto-discovered by registry |
|
|
193
|
+
| `fastify` | `src/modules/<name>/schemas.ts` + `index.ts` + Prisma model + app.ts import |
|
|
194
|
+
| `frontend` | `src/types/<name>.ts` — TypeScript interface + Create/Update variants |
|
|
195
|
+
| `mobile` | `lib/entities/<name>/model.dart` — Dart class with fromJson/toJson/copyWith |
|
|
196
|
+
|
|
197
|
+
No migrations — run `alembic revision --autogenerate` or `npx prisma migrate dev` when ready.
|
|
198
|
+
|
|
199
|
+
### Sync Types
|
|
200
|
+
|
|
201
|
+
Regenerate all frontend/mobile types from a running backend:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
npx create-projx sync # auto-detects URL
|
|
205
|
+
npx create-projx sync --url http://localhost:8000/api/v1/_meta # explicit URL
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Fetches `/_meta` from your backend, generates typed interfaces for every entity. Run after any backend change — new field, renamed column, new entity.
|
|
209
|
+
|
|
210
|
+
The generic `api.ts` client accepts type parameters:
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
import type { Invoice } from '../types/invoice';
|
|
214
|
+
|
|
215
|
+
const { data } = await api.list<Invoice>('/invoices'); // data: Invoice[]
|
|
216
|
+
const item = await api.get<Invoice>('/invoices', id); // item: Invoice
|
|
217
|
+
```
|
|
218
|
+
|
|
177
219
|
## Rename Component Directories
|
|
178
220
|
|
|
179
221
|
Rename `fastapi/` to `backend/`? Just rename the folder — the `.projx-component` marker file moves with it. The `update` command auto-discovers where each component lives by scanning for these markers. No config changes needed.
|
package/dist/index.js
CHANGED
|
@@ -251,6 +251,35 @@ async function discoverComponentPaths(cwd, components) {
|
|
|
251
251
|
}
|
|
252
252
|
return paths;
|
|
253
253
|
}
|
|
254
|
+
async function discoverComponentsFromMarkers(cwd) {
|
|
255
|
+
const components = [];
|
|
256
|
+
const paths = {};
|
|
257
|
+
const entries = await readdir(cwd, { withFileTypes: true });
|
|
258
|
+
for (const entry of entries) {
|
|
259
|
+
if (!entry.isDirectory()) continue;
|
|
260
|
+
if (EXCLUDE.has(entry.name)) continue;
|
|
261
|
+
if (entry.name.startsWith(".")) continue;
|
|
262
|
+
const full = join(cwd, entry.name);
|
|
263
|
+
const marker = join(full, COMPONENT_MARKER);
|
|
264
|
+
if (existsSync(marker)) {
|
|
265
|
+
try {
|
|
266
|
+
const data = JSON.parse(await readFile(marker, "utf-8"));
|
|
267
|
+
const markerComponents = data.components ?? (data.component ? [data.component] : []);
|
|
268
|
+
for (const mc of markerComponents) {
|
|
269
|
+
if (COMPONENTS.includes(mc) && !components.includes(mc)) {
|
|
270
|
+
components.push(mc);
|
|
271
|
+
paths[mc] = entry.name;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
} catch {
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
for (const c of components) {
|
|
279
|
+
if (!paths[c]) paths[c] = c;
|
|
280
|
+
}
|
|
281
|
+
return { components, paths };
|
|
282
|
+
}
|
|
254
283
|
function render(template, vars) {
|
|
255
284
|
const components = vars.components;
|
|
256
285
|
const projectName = vars.projectName;
|
|
@@ -261,12 +290,29 @@ function render(template, vars) {
|
|
|
261
290
|
const ifMatch = line.match(/^<%\s*if\s*\((.+?)\)\s*\{?\s*%>$/);
|
|
262
291
|
if (ifMatch) {
|
|
263
292
|
const fn = new Function("components", "projectName", `return ${ifMatch[1]}`);
|
|
264
|
-
|
|
293
|
+
const result = fn(components, projectName);
|
|
294
|
+
stack.push({ active: result, matched: result });
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
const elseIfMatch = line.match(/^<%\s*\}\s*else\s+if\s*\((.+?)\)\s*\{?\s*%>$/);
|
|
298
|
+
if (elseIfMatch) {
|
|
299
|
+
if (stack.length > 0) {
|
|
300
|
+
const top = stack[stack.length - 1];
|
|
301
|
+
if (top.matched) {
|
|
302
|
+
top.active = false;
|
|
303
|
+
} else {
|
|
304
|
+
const fn = new Function("components", "projectName", `return ${elseIfMatch[1]}`);
|
|
305
|
+
const result = fn(components, projectName);
|
|
306
|
+
top.active = result;
|
|
307
|
+
if (result) top.matched = true;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
265
310
|
continue;
|
|
266
311
|
}
|
|
267
312
|
if (/^<%\s*\}\s*else\s*\{?\s*%>$/.test(line)) {
|
|
268
313
|
if (stack.length > 0) {
|
|
269
|
-
|
|
314
|
+
const top = stack[stack.length - 1];
|
|
315
|
+
top.active = !top.matched;
|
|
270
316
|
}
|
|
271
317
|
continue;
|
|
272
318
|
}
|
|
@@ -274,7 +320,7 @@ function render(template, vars) {
|
|
|
274
320
|
stack.pop();
|
|
275
321
|
continue;
|
|
276
322
|
}
|
|
277
|
-
if (stack.length > 0 && stack.some((v) => !v)) continue;
|
|
323
|
+
if (stack.length > 0 && stack.some((v) => !v.active)) continue;
|
|
278
324
|
const replaced = line.replace(
|
|
279
325
|
/<%=\s*([\w.]+)\s*%>/g,
|
|
280
326
|
(_, expr) => {
|
|
@@ -908,17 +954,19 @@ async function update(cwd, localRepo) {
|
|
|
908
954
|
const configPath = join5(cwd, ".projx");
|
|
909
955
|
let config;
|
|
910
956
|
if (existsSync4(configPath)) {
|
|
911
|
-
|
|
957
|
+
const raw = JSON.parse(await readFile5(configPath, "utf-8"));
|
|
958
|
+
const { components: discovered } = await discoverComponentsFromMarkers(cwd);
|
|
959
|
+
config = { ...raw, components: discovered.length > 0 ? discovered : raw.components };
|
|
912
960
|
p3.log.info(`Found .projx (v${config.version}, components: ${config.components.join(", ")})`);
|
|
913
961
|
} else {
|
|
914
962
|
p3.log.warn("No .projx file found. Detecting components from directories.");
|
|
915
|
-
const
|
|
916
|
-
if (
|
|
963
|
+
const { components: discovered } = await discoverComponentsFromMarkers(cwd);
|
|
964
|
+
if (discovered.length === 0) {
|
|
917
965
|
p3.log.error("No projx components found. Run 'projx init' first.");
|
|
918
966
|
process.exit(1);
|
|
919
967
|
}
|
|
920
|
-
config = { version: "0.0.0", components:
|
|
921
|
-
p3.log.info(`Detected: ${
|
|
968
|
+
config = { version: "0.0.0", components: discovered, createdAt: "unknown" };
|
|
969
|
+
p3.log.info(`Detected: ${discovered.join(", ")}`);
|
|
922
970
|
}
|
|
923
971
|
const componentPaths = await discoverComponentPaths(cwd, config.components);
|
|
924
972
|
for (const c of config.components) {
|
|
@@ -1476,7 +1524,7 @@ async function pin(cwd, patterns) {
|
|
|
1476
1524
|
process.exit(1);
|
|
1477
1525
|
}
|
|
1478
1526
|
const config = JSON.parse(await readFile8(configPath, "utf-8"));
|
|
1479
|
-
const componentPaths = await
|
|
1527
|
+
const componentPaths = (await discoverComponentsFromMarkers(cwd)).paths;
|
|
1480
1528
|
const rootAdds = [];
|
|
1481
1529
|
const componentAdds = {};
|
|
1482
1530
|
for (const pattern of patterns) {
|
|
@@ -1533,7 +1581,7 @@ async function unpin(cwd, patterns) {
|
|
|
1533
1581
|
process.exit(1);
|
|
1534
1582
|
}
|
|
1535
1583
|
const config = JSON.parse(await readFile8(configPath, "utf-8"));
|
|
1536
|
-
const componentPaths = await
|
|
1584
|
+
const componentPaths = (await discoverComponentsFromMarkers(cwd)).paths;
|
|
1537
1585
|
const rootRemoves = [];
|
|
1538
1586
|
const componentRemoves = {};
|
|
1539
1587
|
for (const pattern of patterns) {
|
|
@@ -1594,7 +1642,7 @@ async function listPins(cwd) {
|
|
|
1594
1642
|
process.exit(1);
|
|
1595
1643
|
}
|
|
1596
1644
|
const config = JSON.parse(await readFile8(configPath, "utf-8"));
|
|
1597
|
-
const componentPaths = await
|
|
1645
|
+
const { components: discovered, paths: componentPaths } = await discoverComponentsFromMarkers(cwd);
|
|
1598
1646
|
let hasAny = false;
|
|
1599
1647
|
if (config.skip && config.skip.length > 0) {
|
|
1600
1648
|
hasAny = true;
|
|
@@ -1603,7 +1651,7 @@ async function listPins(cwd) {
|
|
|
1603
1651
|
p6.log.info(` ${s}`);
|
|
1604
1652
|
}
|
|
1605
1653
|
}
|
|
1606
|
-
for (const component of
|
|
1654
|
+
for (const component of discovered) {
|
|
1607
1655
|
const dir = componentPaths[component];
|
|
1608
1656
|
const marker = await readComponentMarker(join9(cwd, dir));
|
|
1609
1657
|
if (marker?.skip && marker.skip.length > 0) {
|
|
@@ -1860,10 +1908,11 @@ async function doctor(cwd, fix = false) {
|
|
|
1860
1908
|
printReport(allResults);
|
|
1861
1909
|
process.exit(1);
|
|
1862
1910
|
}
|
|
1863
|
-
const componentPaths = await
|
|
1864
|
-
|
|
1911
|
+
const { components: discovered, paths: componentPaths } = await discoverComponentsFromMarkers(cwd);
|
|
1912
|
+
const resolvedConfig = { ...config, components: discovered.length > 0 ? discovered : config.components };
|
|
1913
|
+
allResults.push(...await checkComponents(cwd, resolvedConfig, componentPaths));
|
|
1865
1914
|
allResults.push(...checkGit(cwd, fix));
|
|
1866
|
-
allResults.push(...await checkSkipPatterns(cwd,
|
|
1915
|
+
allResults.push(...await checkSkipPatterns(cwd, resolvedConfig, componentPaths));
|
|
1867
1916
|
printReport(allResults);
|
|
1868
1917
|
const passed = allResults.filter((r) => r.status === "pass").length;
|
|
1869
1918
|
const warns = allResults.filter((r) => r.status === "warn").length;
|
|
@@ -1918,8 +1967,9 @@ async function diff(cwd, localRepo) {
|
|
|
1918
1967
|
p8.log.error("No .projx file found. Run 'npx create-projx init' first.");
|
|
1919
1968
|
process.exit(1);
|
|
1920
1969
|
}
|
|
1921
|
-
const
|
|
1922
|
-
const componentPaths = await
|
|
1970
|
+
const raw = JSON.parse(await readFile10(configPath, "utf-8"));
|
|
1971
|
+
const { components: discovered, paths: componentPaths } = await discoverComponentsFromMarkers(cwd);
|
|
1972
|
+
const config = { ...raw, components: discovered.length > 0 ? discovered : raw.components };
|
|
1923
1973
|
const componentSkips = {};
|
|
1924
1974
|
for (const component of config.components) {
|
|
1925
1975
|
const dir = componentPaths[component];
|
|
@@ -2579,12 +2629,11 @@ async function gen(cwd, entityName, fieldsFlag) {
|
|
|
2579
2629
|
p9.log.error("No .projx file found. Run 'npx create-projx init' first.");
|
|
2580
2630
|
process.exit(1);
|
|
2581
2631
|
}
|
|
2582
|
-
const
|
|
2583
|
-
const
|
|
2584
|
-
const
|
|
2585
|
-
const
|
|
2586
|
-
const
|
|
2587
|
-
const hasMobile = projxConfig.components.includes("mobile");
|
|
2632
|
+
const { components: discovered, paths: componentPaths } = await discoverComponentsFromMarkers(cwd);
|
|
2633
|
+
const hasFastapi = discovered.includes("fastapi");
|
|
2634
|
+
const hasFastify = discovered.includes("fastify");
|
|
2635
|
+
const hasFrontend = discovered.includes("frontend");
|
|
2636
|
+
const hasMobile = discovered.includes("mobile");
|
|
2588
2637
|
if (!hasFastapi && !hasFastify) {
|
|
2589
2638
|
p9.log.error("No backend component found. Need fastapi or fastify.");
|
|
2590
2639
|
process.exit(1);
|
|
@@ -2732,7 +2781,7 @@ async function gen(cwd, entityName, fieldsFlag) {
|
|
|
2732
2781
|
|
|
2733
2782
|
// src/sync.ts
|
|
2734
2783
|
import { existsSync as existsSync12, readFileSync as readFileSync2 } from "fs";
|
|
2735
|
-
import {
|
|
2784
|
+
import { writeFile as writeFile6, mkdir as mkdir6 } from "fs/promises";
|
|
2736
2785
|
import { join as join13 } from "path";
|
|
2737
2786
|
import * as p10 from "@clack/prompts";
|
|
2738
2787
|
function toPascal2(s) {
|
|
@@ -2909,15 +2958,9 @@ async function sync(cwd, url) {
|
|
|
2909
2958
|
p10.log.error("No .projx file found. Run 'npx create-projx init' first.");
|
|
2910
2959
|
process.exit(1);
|
|
2911
2960
|
}
|
|
2912
|
-
const
|
|
2913
|
-
|
|
2914
|
-
);
|
|
2915
|
-
const componentPaths = await discoverComponentPaths(
|
|
2916
|
-
cwd,
|
|
2917
|
-
projxConfig.components
|
|
2918
|
-
);
|
|
2919
|
-
const hasFrontend = projxConfig.components.includes("frontend");
|
|
2920
|
-
const hasMobile = projxConfig.components.includes("mobile");
|
|
2961
|
+
const { components: discovered, paths: componentPaths } = await discoverComponentsFromMarkers(cwd);
|
|
2962
|
+
const hasFrontend = discovered.includes("frontend");
|
|
2963
|
+
const hasMobile = discovered.includes("mobile");
|
|
2921
2964
|
if (!hasFrontend && !hasMobile) {
|
|
2922
2965
|
p10.log.error("No frontend or mobile component found. Nothing to sync.");
|
|
2923
2966
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-projx",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.2",
|
|
4
4
|
"description": "Scaffold production-grade fullstack projects in seconds. FastAPI, Fastify, React, Flutter, Terraform — with auth, database, CI/CD, E2E tests, and Docker. One command, ready to deploy.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -24,7 +24,7 @@ services:
|
|
|
24
24
|
- app-network
|
|
25
25
|
<% } %>
|
|
26
26
|
<% if (components.includes('fastapi')) { %>
|
|
27
|
-
migrate:
|
|
27
|
+
<%= paths.fastapi %>-migrate:
|
|
28
28
|
build: ./<%= paths.fastapi %>
|
|
29
29
|
command: ['uv', 'run', 'migrate.py']
|
|
30
30
|
environment:
|
|
@@ -39,8 +39,7 @@ services:
|
|
|
39
39
|
cpus: '0.5'
|
|
40
40
|
networks:
|
|
41
41
|
- app-network
|
|
42
|
-
|
|
43
|
-
backend:
|
|
42
|
+
<%= paths.fastapi %>:
|
|
44
43
|
build: ./<%= paths.fastapi %>
|
|
45
44
|
command:
|
|
46
45
|
[
|
|
@@ -61,7 +60,7 @@ services:
|
|
|
61
60
|
- ./<%= paths.fastapi %>/alembic.ini:/app/alembic.ini
|
|
62
61
|
- ./<%= paths.fastapi %>/migrate.py:/app/migrate.py
|
|
63
62
|
depends_on:
|
|
64
|
-
migrate:
|
|
63
|
+
<%= paths.fastapi %>-migrate:
|
|
65
64
|
condition: service_completed_successfully
|
|
66
65
|
restart: unless-stopped
|
|
67
66
|
healthcheck:
|
|
@@ -83,7 +82,22 @@ services:
|
|
|
83
82
|
- app-network
|
|
84
83
|
<% } %>
|
|
85
84
|
<% if (components.includes('fastify')) { %>
|
|
86
|
-
|
|
85
|
+
<%= paths.fastify %>-migrate:
|
|
86
|
+
build: ./<%= paths.fastify %>
|
|
87
|
+
command: ['pnpm', 'prisma', 'migrate', 'deploy']
|
|
88
|
+
environment:
|
|
89
|
+
- DATABASE_URL=postgresql://dev:dev@db:5432/app
|
|
90
|
+
depends_on:
|
|
91
|
+
db:
|
|
92
|
+
condition: service_healthy
|
|
93
|
+
deploy:
|
|
94
|
+
resources:
|
|
95
|
+
limits:
|
|
96
|
+
memory: 256M
|
|
97
|
+
cpus: '0.5'
|
|
98
|
+
networks:
|
|
99
|
+
- app-network
|
|
100
|
+
<%= paths.fastify %>:
|
|
87
101
|
build: ./<%= paths.fastify %>
|
|
88
102
|
command: ['pnpm', 'dev']
|
|
89
103
|
ports:
|
|
@@ -96,8 +110,8 @@ services:
|
|
|
96
110
|
volumes:
|
|
97
111
|
- ./<%= paths.fastify %>/src:/app/src
|
|
98
112
|
depends_on:
|
|
99
|
-
|
|
100
|
-
condition:
|
|
113
|
+
<%= paths.fastify %>-migrate:
|
|
114
|
+
condition: service_completed_successfully
|
|
101
115
|
restart: unless-stopped
|
|
102
116
|
healthcheck:
|
|
103
117
|
test: ['CMD', 'wget', '--spider', '-q', 'http://localhost:3000/api/health']
|
|
@@ -121,23 +135,21 @@ services:
|
|
|
121
135
|
ports:
|
|
122
136
|
- '5173:5173'
|
|
123
137
|
environment:
|
|
124
|
-
<% if (components.includes('
|
|
125
|
-
- VITE_API_URL=http://localhost:7860
|
|
126
|
-
<% } %>
|
|
127
|
-
<% if (components.includes('fastify') && !components.includes('fastapi')) { %>
|
|
138
|
+
<% if (components.includes('fastify')) { %>
|
|
128
139
|
- VITE_API_URL=http://localhost:3000
|
|
140
|
+
<% } else if (components.includes('fastapi')) { %>
|
|
141
|
+
- VITE_API_URL=http://localhost:7860
|
|
129
142
|
<% } %>
|
|
130
143
|
volumes:
|
|
131
144
|
- ./<%= paths.frontend %>:/app
|
|
132
145
|
- frontend_node_modules:/app/node_modules
|
|
133
|
-
<% if (components.includes('
|
|
146
|
+
<% if (components.includes('fastify')) { %>
|
|
134
147
|
depends_on:
|
|
135
|
-
|
|
148
|
+
<%= paths.fastify %>:
|
|
136
149
|
condition: service_healthy
|
|
137
|
-
<% } %>
|
|
138
|
-
<% if (components.includes('fastify') && !components.includes('fastapi')) { %>
|
|
150
|
+
<% } else if (components.includes('fastapi')) { %>
|
|
139
151
|
depends_on:
|
|
140
|
-
|
|
152
|
+
<%= paths.fastapi %>:
|
|
141
153
|
condition: service_healthy
|
|
142
154
|
<% } %>
|
|
143
155
|
healthcheck:
|
|
@@ -154,7 +166,6 @@ services:
|
|
|
154
166
|
networks:
|
|
155
167
|
- app-network
|
|
156
168
|
<% } %>
|
|
157
|
-
|
|
158
169
|
volumes:
|
|
159
170
|
<% if (components.includes('fastapi') || components.includes('fastify')) { %>
|
|
160
171
|
pgdata:
|
|
@@ -162,7 +173,6 @@ volumes:
|
|
|
162
173
|
<% if (components.includes('frontend')) { %>
|
|
163
174
|
frontend_node_modules:
|
|
164
175
|
<% } %>
|
|
165
|
-
|
|
166
176
|
networks:
|
|
167
177
|
app-network:
|
|
168
178
|
driver: bridge
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
services:
|
|
2
2
|
<% if (components.includes('fastapi')) { %>
|
|
3
|
-
migrate:
|
|
3
|
+
<%= paths.fastapi %>-migrate:
|
|
4
4
|
build: ./<%= paths.fastapi %>
|
|
5
5
|
command: ["uv", "run", "migrate.py"]
|
|
6
6
|
env_file:
|
|
7
7
|
- ./<%= paths.fastapi %>/.env
|
|
8
8
|
networks:
|
|
9
9
|
- app-network
|
|
10
|
-
|
|
11
|
-
backend:
|
|
10
|
+
<%= paths.fastapi %>:
|
|
12
11
|
build: ./<%= paths.fastapi %>
|
|
13
12
|
expose:
|
|
14
13
|
- "7860"
|
|
@@ -16,7 +15,7 @@ services:
|
|
|
16
15
|
- ./<%= paths.fastapi %>/.env
|
|
17
16
|
restart: unless-stopped
|
|
18
17
|
depends_on:
|
|
19
|
-
migrate:
|
|
18
|
+
<%= paths.fastapi %>-migrate:
|
|
20
19
|
condition: service_completed_successfully
|
|
21
20
|
healthcheck:
|
|
22
21
|
test:
|
|
@@ -34,13 +33,23 @@ services:
|
|
|
34
33
|
- app-network
|
|
35
34
|
<% } %>
|
|
36
35
|
<% if (components.includes('fastify')) { %>
|
|
37
|
-
|
|
36
|
+
<%= paths.fastify %>-migrate:
|
|
37
|
+
build: ./<%= paths.fastify %>
|
|
38
|
+
command: ["pnpm", "prisma", "migrate", "deploy"]
|
|
39
|
+
env_file:
|
|
40
|
+
- ./<%= paths.fastify %>/.env
|
|
41
|
+
networks:
|
|
42
|
+
- app-network
|
|
43
|
+
<%= paths.fastify %>:
|
|
38
44
|
build: ./<%= paths.fastify %>
|
|
39
45
|
expose:
|
|
40
46
|
- "3000"
|
|
41
47
|
env_file:
|
|
42
48
|
- ./<%= paths.fastify %>/.env
|
|
43
49
|
restart: unless-stopped
|
|
50
|
+
depends_on:
|
|
51
|
+
<%= paths.fastify %>-migrate:
|
|
52
|
+
condition: service_completed_successfully
|
|
44
53
|
healthcheck:
|
|
45
54
|
test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/api/health"]
|
|
46
55
|
interval: 30s
|
|
@@ -62,14 +71,13 @@ services:
|
|
|
62
71
|
volumes:
|
|
63
72
|
- letsencrypt:/etc/letsencrypt
|
|
64
73
|
- certbot-www:/var/www/certbot
|
|
65
|
-
<% if (components.includes('
|
|
74
|
+
<% if (components.includes('fastify')) { %>
|
|
66
75
|
depends_on:
|
|
67
|
-
|
|
76
|
+
<%= paths.fastify %>:
|
|
68
77
|
condition: service_healthy
|
|
69
|
-
<% } %>
|
|
70
|
-
<% if (components.includes('fastify') && !components.includes('fastapi')) { %>
|
|
78
|
+
<% } else if (components.includes('fastapi')) { %>
|
|
71
79
|
depends_on:
|
|
72
|
-
|
|
80
|
+
<%= paths.fastapi %>:
|
|
73
81
|
condition: service_healthy
|
|
74
82
|
<% } %>
|
|
75
83
|
restart: unless-stopped
|
|
@@ -81,7 +89,6 @@ services:
|
|
|
81
89
|
start_period: 10s
|
|
82
90
|
networks:
|
|
83
91
|
- app-network
|
|
84
|
-
|
|
85
92
|
certbot:
|
|
86
93
|
image: certbot/certbot:latest
|
|
87
94
|
volumes:
|
|
@@ -102,7 +109,6 @@ volumes:
|
|
|
102
109
|
letsencrypt:
|
|
103
110
|
certbot-www:
|
|
104
111
|
<% } %>
|
|
105
|
-
|
|
106
112
|
networks:
|
|
107
113
|
app-network:
|
|
108
114
|
driver: bridge
|