create-projx 1.6.5 → 1.7.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/README.md +88 -37
- package/dist/{baseline-PZM4KJJW.js → baseline-FKCXQFRD.js} +2 -2
- package/dist/{chunk-XQ7FE4U3.js → chunk-N4WD4VN3.js} +158 -19
- package/dist/{chunk-6YRBHJ2V.js → chunk-OLPF7FAN.js} +26 -9
- package/dist/index.js +1607 -603
- package/dist/{utils-AVKSTHIF.js → utils-4G3HNHES.js} +5 -1
- package/package.json +11 -6
- package/src/templates/README.md.ejs +21 -4
- package/src/templates/ci.yml.ejs +167 -37
- package/src/templates/docker-compose.yml.ejs +72 -5
- package/src/templates/pre-commit.ejs +28 -4
- package/src/templates/setup.sh.ejs +75 -1
- package/src/templates/docker-compose.dev.yml.ejs +0 -189
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
DEFAULT_COMPONENT_SKIP_PATTERNS,
|
|
5
5
|
DEFAULT_ROOT_SKIP_PATTERNS,
|
|
6
6
|
EXCLUDE,
|
|
7
|
+
KNOWN_FEATURES,
|
|
8
|
+
ORM_PROVIDERS,
|
|
7
9
|
PACKAGE_MANAGERS,
|
|
8
10
|
REPO,
|
|
9
11
|
REPO_URL,
|
|
@@ -33,13 +35,15 @@ import {
|
|
|
33
35
|
upsertComponentMarker,
|
|
34
36
|
writeComponentMarker,
|
|
35
37
|
writeProjxConfig
|
|
36
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-OLPF7FAN.js";
|
|
37
39
|
export {
|
|
38
40
|
COMPONENTS,
|
|
39
41
|
COMPONENT_MARKER,
|
|
40
42
|
DEFAULT_COMPONENT_SKIP_PATTERNS,
|
|
41
43
|
DEFAULT_ROOT_SKIP_PATTERNS,
|
|
42
44
|
EXCLUDE,
|
|
45
|
+
KNOWN_FEATURES,
|
|
46
|
+
ORM_PROVIDERS,
|
|
43
47
|
PACKAGE_MANAGERS,
|
|
44
48
|
REPO,
|
|
45
49
|
REPO_URL,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-projx",
|
|
3
|
-
"version": "1.
|
|
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.",
|
|
3
|
+
"version": "1.7.1",
|
|
4
|
+
"description": "Scaffold production-grade fullstack projects in seconds. FastAPI, Fastify, Express, React, Flutter, Terraform — with auth, database, CI/CD, E2E tests, and Docker. One command, ready to deploy.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"create-projx": "dist/index.js"
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"fullstack",
|
|
21
21
|
"fastapi",
|
|
22
22
|
"fastify",
|
|
23
|
+
"express",
|
|
23
24
|
"react",
|
|
24
25
|
"flutter",
|
|
25
26
|
"terraform",
|
|
@@ -44,21 +45,25 @@
|
|
|
44
45
|
"node": ">=18"
|
|
45
46
|
},
|
|
46
47
|
"dependencies": {
|
|
47
|
-
"@clack/prompts": "^0
|
|
48
|
+
"@clack/prompts": "^1.2.0"
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
50
51
|
"@eslint/js": "^10.0.1",
|
|
51
52
|
"@types/node": "^25.5.2",
|
|
53
|
+
"@vitest/coverage-v8": "^4.1.5",
|
|
52
54
|
"eslint": "^10.2.0",
|
|
55
|
+
"prettier": "^3.8.3",
|
|
53
56
|
"tsup": "^8",
|
|
54
57
|
"typescript": "^5",
|
|
55
|
-
"typescript-eslint": "^8.
|
|
56
|
-
"vitest": "^4.1.
|
|
58
|
+
"typescript-eslint": "^8.59.0",
|
|
59
|
+
"vitest": "^4.1.5"
|
|
57
60
|
},
|
|
58
61
|
"scripts": {
|
|
59
62
|
"build": "tsup src/index.ts --format esm --target node18 --clean",
|
|
60
63
|
"dev": "tsup src/index.ts --format esm --target node18 --watch",
|
|
61
|
-
"
|
|
64
|
+
"format": "prettier --write .",
|
|
65
|
+
"format:check": "prettier --check .",
|
|
66
|
+
"test": "vitest run --coverage",
|
|
62
67
|
"test:watch": "vitest"
|
|
63
68
|
}
|
|
64
69
|
}
|
|
@@ -10,7 +10,10 @@ Scaffolded with [Projx](https://github.com/ukanhaupa/projx).
|
|
|
10
10
|
| **<%= paths.fastapi %>/** | FastAPI, SQLAlchemy, Alembic, uvicorn |
|
|
11
11
|
<% } %>
|
|
12
12
|
<% if (components.includes('fastify')) { %>
|
|
13
|
-
| **<%= paths.fastify %>/** | Fastify, Prisma
|
|
13
|
+
| **<%= paths.fastify %>/** | Fastify, <%= orm === 'drizzle' ? 'Drizzle' : 'Prisma' %>, TypeBox, TypeScript |
|
|
14
|
+
<% } %>
|
|
15
|
+
<% if (components.includes('express')) { %>
|
|
16
|
+
| **<%= paths.express %>/** | Express 5, TypeScript, <%= orm === 'drizzle' ? 'Drizzle' : 'Prisma' %> |
|
|
14
17
|
<% } %>
|
|
15
18
|
<% if (components.includes('frontend')) { %>
|
|
16
19
|
| **<%= paths.frontend %>/** | React 19, TypeScript, Vite, React Router |
|
|
@@ -30,8 +33,9 @@ Scaffolded with [Projx](https://github.com/ukanhaupa/projx).
|
|
|
30
33
|
## Getting Started
|
|
31
34
|
|
|
32
35
|
```bash
|
|
33
|
-
./scripts/setup.sh
|
|
34
|
-
|
|
36
|
+
./scripts/setup.sh # Install all dependencies
|
|
37
|
+
# Ensure PostgreSQL is running locally, then run `<%= pm.runDev %>` in each service.
|
|
38
|
+
# Production stack: docker compose up --build
|
|
35
39
|
```
|
|
36
40
|
<% if (components.includes('fastapi')) { %>
|
|
37
41
|
|
|
@@ -48,11 +52,21 @@ API docs at `http://localhost:7860/docs`.
|
|
|
48
52
|
### <%= paths.fastify %>/
|
|
49
53
|
|
|
50
54
|
```bash
|
|
51
|
-
cd <%= paths.fastify %> && cp .env.example .env && <%= pm.install %> && <%= pm.exec
|
|
55
|
+
cd <%= paths.fastify %> && cp .env.example .env && <%= pm.install %> && <%= orm === 'drizzle' ? `${pm.exec} drizzle-kit push --force` : `${pm.exec} prisma migrate dev` %> && <%= pm.run %> dev
|
|
52
56
|
```
|
|
53
57
|
|
|
54
58
|
API docs at `http://localhost:3000/docs`.
|
|
55
59
|
<% } %>
|
|
60
|
+
<% if (components.includes('express')) { %>
|
|
61
|
+
|
|
62
|
+
### <%= paths.express %>/
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
cd <%= paths.express %> && cp .env.example .env && <%= pm.install %><%= orm === 'drizzle' ? ` && ${pm.exec} drizzle-kit push --force` : '' %> && <%= pm.run %> dev
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Health check at `http://localhost:3000/api/health`.
|
|
69
|
+
<% } %>
|
|
56
70
|
<% if (components.includes('frontend')) { %>
|
|
57
71
|
|
|
58
72
|
### <%= paths.frontend %>/
|
|
@@ -79,6 +93,9 @@ cd <%= paths.fastapi %> && uv run pytest
|
|
|
79
93
|
<% if (components.includes('fastify')) { %>
|
|
80
94
|
cd <%= paths.fastify %> && <%= pm.run %> test
|
|
81
95
|
<% } %>
|
|
96
|
+
<% if (components.includes('express')) { %>
|
|
97
|
+
cd <%= paths.express %> && <%= pm.run %> test
|
|
98
|
+
<% } %>
|
|
82
99
|
<% if (components.includes('frontend')) { %>
|
|
83
100
|
cd <%= paths.frontend %> && <%= pm.exec %> vitest run
|
|
84
101
|
<% } %>
|
package/src/templates/ci.yml.ejs
CHANGED
|
@@ -5,13 +5,16 @@ on:
|
|
|
5
5
|
branches: [main]
|
|
6
6
|
push:
|
|
7
7
|
branches: [main]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
pull-requests: read
|
|
8
13
|
|
|
9
14
|
jobs:
|
|
10
15
|
changes:
|
|
11
16
|
name: Detect changes
|
|
12
17
|
runs-on: ubuntu-latest
|
|
13
|
-
permissions:
|
|
14
|
-
pull-requests: read
|
|
15
18
|
outputs:
|
|
16
19
|
<% for (const inst of fastapiInstances) { %>
|
|
17
20
|
<%= inst.path %>: ${{ steps.filter.outputs.<%= inst.path %> }}
|
|
@@ -19,6 +22,9 @@ jobs:
|
|
|
19
22
|
<% for (const inst of fastifyInstances) { %>
|
|
20
23
|
<%= inst.path %>: ${{ steps.filter.outputs.<%= inst.path %> }}
|
|
21
24
|
<% } %>
|
|
25
|
+
<% for (const inst of expressInstances) { %>
|
|
26
|
+
<%= inst.path %>: ${{ steps.filter.outputs.<%= inst.path %> }}
|
|
27
|
+
<% } %>
|
|
22
28
|
<% for (const inst of frontendInstances) { %>
|
|
23
29
|
<%= inst.path %>: ${{ steps.filter.outputs.<%= inst.path %> }}
|
|
24
30
|
<% } %>
|
|
@@ -32,8 +38,8 @@ jobs:
|
|
|
32
38
|
<%= inst.path %>: ${{ steps.filter.outputs.<%= inst.path %> }}
|
|
33
39
|
<% } %>
|
|
34
40
|
steps:
|
|
35
|
-
- uses: actions/checkout@v5
|
|
36
|
-
- uses: dorny/paths-filter@v3
|
|
41
|
+
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
|
|
42
|
+
- uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3
|
|
37
43
|
id: filter
|
|
38
44
|
with:
|
|
39
45
|
filters: |
|
|
@@ -45,6 +51,10 @@ jobs:
|
|
|
45
51
|
<%= inst.path %>:
|
|
46
52
|
- '<%= inst.path %>/**'
|
|
47
53
|
<% } %>
|
|
54
|
+
<% for (const inst of expressInstances) { %>
|
|
55
|
+
<%= inst.path %>:
|
|
56
|
+
- '<%= inst.path %>/**'
|
|
57
|
+
<% } %>
|
|
48
58
|
<% for (const inst of frontendInstances) { %>
|
|
49
59
|
<%= inst.path %>:
|
|
50
60
|
- '<%= inst.path %>/**'
|
|
@@ -66,88 +76,204 @@ jobs:
|
|
|
66
76
|
name: Secret scan
|
|
67
77
|
runs-on: ubuntu-latest
|
|
68
78
|
steps:
|
|
69
|
-
- uses: actions/checkout@v5
|
|
79
|
+
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
|
|
70
80
|
with:
|
|
71
81
|
fetch-depth: 0
|
|
72
|
-
- uses: gitleaks/gitleaks-action@v2
|
|
82
|
+
- uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2
|
|
73
83
|
env:
|
|
74
84
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
85
|
+
- run: python3 scripts/style-check.py frontend/src
|
|
75
86
|
<% for (const inst of fastapiInstances) { %>
|
|
76
87
|
|
|
77
88
|
<%= inst.path %>:
|
|
78
|
-
name: <%= inst.display %> (format + lint + typecheck +
|
|
89
|
+
name: <%= inst.display %> (format + lint + typecheck + audit)
|
|
79
90
|
needs: changes
|
|
80
|
-
if: needs.changes.outputs.<%= inst.path %> == 'true'
|
|
91
|
+
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.<%= inst.path %> == 'true'
|
|
81
92
|
runs-on: ubuntu-latest
|
|
93
|
+
services:
|
|
94
|
+
postgres:
|
|
95
|
+
image: postgres:16
|
|
96
|
+
env:
|
|
97
|
+
POSTGRES_USER: postgres
|
|
98
|
+
POSTGRES_PASSWORD: postgres
|
|
99
|
+
POSTGRES_DB: ci_test
|
|
100
|
+
ports:
|
|
101
|
+
- 5432:5432
|
|
102
|
+
options: >-
|
|
103
|
+
--health-cmd pg_isready
|
|
104
|
+
--health-interval 5s
|
|
105
|
+
--health-timeout 5s
|
|
106
|
+
--health-retries 5
|
|
82
107
|
defaults:
|
|
83
108
|
run:
|
|
84
109
|
working-directory: <%= inst.path %>
|
|
110
|
+
env:
|
|
111
|
+
SQLALCHEMY_DATABASE_URI: postgresql+asyncpg://postgres:postgres@localhost:5432/ci_test
|
|
112
|
+
JWT_PROVIDER: shared_secret
|
|
113
|
+
JWT_SECRET: ci-test-secret # gitleaks:allow
|
|
114
|
+
JWT_ALGORITHMS: HS256
|
|
85
115
|
steps:
|
|
86
|
-
- uses: actions/checkout@v5
|
|
87
|
-
- uses: astral-sh/setup-uv@v4
|
|
116
|
+
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
|
|
117
|
+
- uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
|
|
88
118
|
- run: uv sync --group dev
|
|
89
119
|
- run: uv run ruff format --check src tests
|
|
90
120
|
- run: uv run ruff check src tests
|
|
91
121
|
- run: uv run mypy
|
|
92
|
-
-
|
|
93
|
-
|
|
122
|
+
- name: Audit Python dependencies
|
|
123
|
+
run: |
|
|
124
|
+
run_pip_audit() {
|
|
125
|
+
for attempt in 1 2 3; do
|
|
126
|
+
if uv run pip-audit --ignore-vuln CVE-2026-3219; then
|
|
127
|
+
return 0
|
|
128
|
+
fi
|
|
129
|
+
if [ "$attempt" -eq 3 ]; then
|
|
130
|
+
return 1
|
|
131
|
+
fi
|
|
132
|
+
sleep $((attempt * 5))
|
|
133
|
+
done
|
|
134
|
+
}
|
|
135
|
+
run_pip_audit
|
|
94
136
|
<% } %>
|
|
95
137
|
<% for (const inst of fastifyInstances) { %>
|
|
96
138
|
|
|
97
139
|
<%= inst.path %>:
|
|
98
|
-
name: <%= inst.display %> (format + lint + typecheck + audit)
|
|
140
|
+
name: <%= inst.display %> (format + lint + typecheck + build + audit)
|
|
141
|
+
needs: changes
|
|
142
|
+
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.<%= inst.path %> == 'true'
|
|
143
|
+
runs-on: ubuntu-latest
|
|
144
|
+
services:
|
|
145
|
+
postgres:
|
|
146
|
+
image: postgres:16
|
|
147
|
+
env:
|
|
148
|
+
POSTGRES_USER: postgres
|
|
149
|
+
POSTGRES_PASSWORD: postgres
|
|
150
|
+
POSTGRES_DB: ci_test
|
|
151
|
+
ports:
|
|
152
|
+
- 5432:5432
|
|
153
|
+
options: >-
|
|
154
|
+
--health-cmd pg_isready
|
|
155
|
+
--health-interval 5s
|
|
156
|
+
--health-timeout 5s
|
|
157
|
+
--health-retries 5
|
|
158
|
+
defaults:
|
|
159
|
+
run:
|
|
160
|
+
working-directory: <%= inst.path %>
|
|
161
|
+
env:
|
|
162
|
+
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/ci_test
|
|
163
|
+
JWT_SECRET: ci-test-secret # gitleaks:allow
|
|
164
|
+
steps:
|
|
165
|
+
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
|
|
166
|
+
<% if (pm === 'pnpm') { %>
|
|
167
|
+
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
|
|
168
|
+
with:
|
|
169
|
+
version: 10
|
|
170
|
+
<% } %>
|
|
171
|
+
<% if (pm === 'bun') { %>
|
|
172
|
+
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
|
|
173
|
+
<% } %>
|
|
174
|
+
<% if (pm !== 'bun') { %>
|
|
175
|
+
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
|
|
176
|
+
with:
|
|
177
|
+
node-version: 22
|
|
178
|
+
cache: <%= pm.name %>
|
|
179
|
+
cache-dependency-path: <%= inst.path %>/<%= pm.lockfile %>
|
|
180
|
+
<% } %>
|
|
181
|
+
- run: <%= pm.ci %>
|
|
182
|
+
<% if (orm === 'drizzle') { %>
|
|
183
|
+
- run: <%= pm.exec %> drizzle-kit push --force
|
|
184
|
+
<% } else if (orm === 'sequelize' || orm === 'typeorm') { %>
|
|
185
|
+
- run: <%= pm.exec %> tsx scripts/db-sync.ts
|
|
186
|
+
<% } else { %>
|
|
187
|
+
- run: <%= pm.prismaExec %> generate
|
|
188
|
+
- run: <%= pm.prismaExec %> migrate deploy
|
|
189
|
+
<% } %>
|
|
190
|
+
- run: <%= pm.exec %> prettier --check .
|
|
191
|
+
- run: <%= pm.exec %> eslint .
|
|
192
|
+
- run: <%= pm.exec %> tsc --noEmit
|
|
193
|
+
- run: <%= pm.run %> build
|
|
194
|
+
- run: <%= pm.audit %>
|
|
195
|
+
<% } %>
|
|
196
|
+
<% for (const inst of expressInstances) { %>
|
|
197
|
+
|
|
198
|
+
<%= inst.path %>:
|
|
199
|
+
name: <%= inst.display %> (format + lint + typecheck + build + test + audit)
|
|
99
200
|
needs: changes
|
|
100
|
-
if: needs.changes.outputs.<%= inst.path %> == 'true'
|
|
201
|
+
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.<%= inst.path %> == 'true'
|
|
101
202
|
runs-on: ubuntu-latest
|
|
203
|
+
services:
|
|
204
|
+
postgres:
|
|
205
|
+
image: postgres:16
|
|
206
|
+
env:
|
|
207
|
+
POSTGRES_USER: postgres
|
|
208
|
+
POSTGRES_PASSWORD: postgres
|
|
209
|
+
POSTGRES_DB: ci_test
|
|
210
|
+
ports:
|
|
211
|
+
- 5432:5432
|
|
212
|
+
options: >-
|
|
213
|
+
--health-cmd pg_isready
|
|
214
|
+
--health-interval 5s
|
|
215
|
+
--health-timeout 5s
|
|
216
|
+
--health-retries 5
|
|
102
217
|
defaults:
|
|
103
218
|
run:
|
|
104
219
|
working-directory: <%= inst.path %>
|
|
220
|
+
env:
|
|
221
|
+
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/ci_test
|
|
105
222
|
steps:
|
|
106
|
-
- uses: actions/checkout@v5
|
|
223
|
+
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
|
|
107
224
|
<% if (pm === 'pnpm') { %>
|
|
108
|
-
- uses: pnpm/action-setup@v4
|
|
225
|
+
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
|
|
109
226
|
with:
|
|
110
227
|
version: 10
|
|
111
228
|
<% } %>
|
|
112
229
|
<% if (pm === 'bun') { %>
|
|
113
|
-
- uses: oven-sh/setup-bun@v2
|
|
230
|
+
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
|
|
114
231
|
<% } %>
|
|
115
232
|
<% if (pm !== 'bun') { %>
|
|
116
|
-
- uses: actions/setup-node@v5
|
|
233
|
+
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
|
|
117
234
|
with:
|
|
118
|
-
node-version:
|
|
235
|
+
node-version: 22
|
|
119
236
|
cache: <%= pm.name %>
|
|
120
237
|
cache-dependency-path: <%= inst.path %>/<%= pm.lockfile %>
|
|
121
238
|
<% } %>
|
|
122
239
|
- run: <%= pm.ci %>
|
|
240
|
+
<% if (orm === 'drizzle') { %>
|
|
241
|
+
- run: <%= pm.exec %> drizzle-kit push --force
|
|
242
|
+
<% } else if (orm === 'sequelize' || orm === 'typeorm') { %>
|
|
243
|
+
- run: <%= pm.exec %> tsx scripts/db-sync.ts
|
|
244
|
+
<% } else { %>
|
|
123
245
|
- run: <%= pm.prismaExec %> generate
|
|
246
|
+
- run: <%= pm.prismaExec %> migrate deploy
|
|
247
|
+
<% } %>
|
|
124
248
|
- run: <%= pm.exec %> prettier --check .
|
|
125
249
|
- run: <%= pm.exec %> eslint .
|
|
126
250
|
- run: <%= pm.exec %> tsc --noEmit
|
|
251
|
+
- run: <%= pm.run %> build
|
|
252
|
+
- run: <%= pm.exec %> vitest run --coverage.enabled=false
|
|
127
253
|
- run: <%= pm.audit %>
|
|
128
254
|
<% } %>
|
|
129
255
|
<% for (const inst of frontendInstances) { %>
|
|
130
256
|
|
|
131
257
|
<%= inst.path %>:
|
|
132
|
-
name: <%= inst.display %> (format + lint + typecheck + audit)
|
|
258
|
+
name: <%= inst.display %> (format + lint + typecheck + build + audit)
|
|
133
259
|
needs: changes
|
|
134
|
-
if: needs.changes.outputs.<%= inst.path %> == 'true'
|
|
260
|
+
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.<%= inst.path %> == 'true'
|
|
135
261
|
runs-on: ubuntu-latest
|
|
136
262
|
defaults:
|
|
137
263
|
run:
|
|
138
264
|
working-directory: <%= inst.path %>
|
|
139
265
|
steps:
|
|
140
|
-
- uses: actions/checkout@v5
|
|
266
|
+
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
|
|
141
267
|
<% if (pm === 'pnpm') { %>
|
|
142
|
-
- uses: pnpm/action-setup@v4
|
|
268
|
+
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
|
|
143
269
|
with:
|
|
144
270
|
version: 10
|
|
145
271
|
<% } %>
|
|
146
272
|
<% if (pm === 'bun') { %>
|
|
147
|
-
- uses: oven-sh/setup-bun@v2
|
|
273
|
+
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
|
|
148
274
|
<% } %>
|
|
149
275
|
<% if (pm !== 'bun') { %>
|
|
150
|
-
- uses: actions/setup-node@v5
|
|
276
|
+
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
|
|
151
277
|
with:
|
|
152
278
|
node-version: 22
|
|
153
279
|
cache: <%= pm.name %>
|
|
@@ -157,50 +283,54 @@ jobs:
|
|
|
157
283
|
- run: <%= pm.exec %> prettier --check .
|
|
158
284
|
- run: <%= pm.exec %> eslint 'src/**/*.{ts,tsx}'
|
|
159
285
|
- run: <%= pm.exec %> tsc --noEmit
|
|
286
|
+
- run: <%= pm.run %> build
|
|
287
|
+
- run: bash scripts/check-bundle-size.sh
|
|
160
288
|
- run: <%= pm.audit %>
|
|
161
289
|
<% } %>
|
|
162
290
|
<% for (const inst of mobileInstances) { %>
|
|
163
291
|
|
|
164
292
|
<%= inst.path %>:
|
|
165
|
-
name: <%= inst.display %> (format + analyze)
|
|
293
|
+
name: <%= inst.display %> (format + analyze + test + coverage)
|
|
166
294
|
needs: changes
|
|
167
|
-
if: needs.changes.outputs.<%= inst.path %> == 'true'
|
|
295
|
+
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.<%= inst.path %> == 'true'
|
|
168
296
|
runs-on: ubuntu-latest
|
|
169
297
|
defaults:
|
|
170
298
|
run:
|
|
171
299
|
working-directory: <%= inst.path %>
|
|
172
300
|
steps:
|
|
173
|
-
- uses: actions/checkout@v5
|
|
174
|
-
- uses: subosito/flutter-action@v2
|
|
301
|
+
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
|
|
302
|
+
- uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2
|
|
175
303
|
with:
|
|
176
304
|
channel: stable
|
|
177
305
|
- run: flutter pub get
|
|
178
306
|
- run: dart run build_runner build --delete-conflicting-outputs
|
|
179
307
|
- run: dart format --set-exit-if-changed .
|
|
180
308
|
- run: dart analyze --fatal-infos
|
|
309
|
+
- run: flutter test --coverage
|
|
310
|
+
- run: bash scripts/check-coverage.sh
|
|
181
311
|
<% } %>
|
|
182
312
|
<% for (const inst of e2eInstances) { %>
|
|
183
313
|
|
|
184
314
|
<%= inst.path %>:
|
|
185
315
|
name: <%= inst.display %> (format + lint + typecheck + audit)
|
|
186
316
|
needs: changes
|
|
187
|
-
if: needs.changes.outputs.<%= inst.path %> == 'true'
|
|
317
|
+
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.<%= inst.path %> == 'true'
|
|
188
318
|
runs-on: ubuntu-latest
|
|
189
319
|
defaults:
|
|
190
320
|
run:
|
|
191
321
|
working-directory: <%= inst.path %>
|
|
192
322
|
steps:
|
|
193
|
-
- uses: actions/checkout@v5
|
|
323
|
+
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
|
|
194
324
|
<% if (pm === 'pnpm') { %>
|
|
195
|
-
- uses: pnpm/action-setup@v4
|
|
325
|
+
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
|
|
196
326
|
with:
|
|
197
327
|
version: 10
|
|
198
328
|
<% } %>
|
|
199
329
|
<% if (pm === 'bun') { %>
|
|
200
|
-
- uses: oven-sh/setup-bun@v2
|
|
330
|
+
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
|
|
201
331
|
<% } %>
|
|
202
332
|
<% if (pm !== 'bun') { %>
|
|
203
|
-
- uses: actions/setup-node@v5
|
|
333
|
+
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
|
|
204
334
|
with:
|
|
205
335
|
node-version: 22
|
|
206
336
|
cache: <%= pm.name %>
|
|
@@ -217,14 +347,14 @@ jobs:
|
|
|
217
347
|
<%= inst.path %>:
|
|
218
348
|
name: <%= inst.display %> (fmt + validate)
|
|
219
349
|
needs: changes
|
|
220
|
-
if: needs.changes.outputs.<%= inst.path %> == 'true'
|
|
350
|
+
if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.<%= inst.path %> == 'true'
|
|
221
351
|
runs-on: ubuntu-latest
|
|
222
352
|
defaults:
|
|
223
353
|
run:
|
|
224
354
|
working-directory: <%= inst.path %>/stack
|
|
225
355
|
steps:
|
|
226
|
-
- uses: actions/checkout@v5
|
|
227
|
-
- uses: hashicorp/setup-terraform@v3
|
|
356
|
+
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
|
|
357
|
+
- uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3
|
|
228
358
|
with:
|
|
229
359
|
terraform_version: '1.11'
|
|
230
360
|
- run: terraform fmt -check -recursive
|
|
@@ -30,12 +30,17 @@ services:
|
|
|
30
30
|
retries: 3
|
|
31
31
|
start_period: 15s
|
|
32
32
|
networks:
|
|
33
|
-
|
|
33
|
+
app-network:
|
|
34
|
+
<% if (fastifyInstances.length === 0 && expressInstances.length === 0 && inst.path === fastapiInstances[0].path) { %>
|
|
35
|
+
aliases:
|
|
36
|
+
- backend
|
|
37
|
+
<% } %>
|
|
34
38
|
<% } %>
|
|
35
39
|
<% for (const inst of fastifyInstances) { %>
|
|
36
40
|
<%= inst.path %>-migrate:
|
|
37
|
-
build:
|
|
38
|
-
|
|
41
|
+
build:
|
|
42
|
+
context: ./<%= inst.path %>
|
|
43
|
+
target: migrate
|
|
39
44
|
env_file:
|
|
40
45
|
- ./<%= inst.path %>/.env
|
|
41
46
|
networks:
|
|
@@ -51,13 +56,61 @@ services:
|
|
|
51
56
|
<%= inst.path %>-migrate:
|
|
52
57
|
condition: service_completed_successfully
|
|
53
58
|
healthcheck:
|
|
54
|
-
test:
|
|
59
|
+
test:
|
|
60
|
+
[
|
|
61
|
+
"CMD",
|
|
62
|
+
"node",
|
|
63
|
+
"-e",
|
|
64
|
+
"require('http').get('http://localhost:3000/api/health', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))",
|
|
65
|
+
]
|
|
55
66
|
interval: 30s
|
|
56
67
|
timeout: 10s
|
|
57
68
|
retries: 3
|
|
58
69
|
start_period: 15s
|
|
70
|
+
networks:
|
|
71
|
+
app-network:
|
|
72
|
+
<% if (inst.path === fastifyInstances[0].path) { %>
|
|
73
|
+
aliases:
|
|
74
|
+
- backend
|
|
75
|
+
<% } %>
|
|
76
|
+
<% } %>
|
|
77
|
+
<% for (const inst of expressInstances) { %>
|
|
78
|
+
<%= inst.path %>-migrate:
|
|
79
|
+
build:
|
|
80
|
+
context: ./<%= inst.path %>
|
|
81
|
+
target: migrate
|
|
82
|
+
env_file:
|
|
83
|
+
- ./<%= inst.path %>/.env
|
|
59
84
|
networks:
|
|
60
85
|
- app-network
|
|
86
|
+
<%= inst.path %>:
|
|
87
|
+
build: ./<%= inst.path %>
|
|
88
|
+
expose:
|
|
89
|
+
- "3000"
|
|
90
|
+
env_file:
|
|
91
|
+
- ./<%= inst.path %>/.env
|
|
92
|
+
restart: unless-stopped
|
|
93
|
+
depends_on:
|
|
94
|
+
<%= inst.path %>-migrate:
|
|
95
|
+
condition: service_completed_successfully
|
|
96
|
+
healthcheck:
|
|
97
|
+
test:
|
|
98
|
+
[
|
|
99
|
+
"CMD",
|
|
100
|
+
"node",
|
|
101
|
+
"-e",
|
|
102
|
+
"require('http').get('http://localhost:3000/api/health', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))",
|
|
103
|
+
]
|
|
104
|
+
interval: 30s
|
|
105
|
+
timeout: 10s
|
|
106
|
+
retries: 3
|
|
107
|
+
start_period: 15s
|
|
108
|
+
networks:
|
|
109
|
+
app-network:
|
|
110
|
+
<% if (fastifyInstances.length === 0 && inst.path === expressInstances[0].path) { %>
|
|
111
|
+
aliases:
|
|
112
|
+
- backend
|
|
113
|
+
<% } %>
|
|
61
114
|
<% } %>
|
|
62
115
|
<% for (const inst of frontendInstances) { %>
|
|
63
116
|
<%= inst.path %>:
|
|
@@ -65,6 +118,8 @@ services:
|
|
|
65
118
|
context: ./<%= inst.path %>
|
|
66
119
|
args:
|
|
67
120
|
VITE_API_URL: ""
|
|
121
|
+
environment:
|
|
122
|
+
DOMAIN: ${DOMAIN:-localhost}
|
|
68
123
|
ports:
|
|
69
124
|
- "80:80"
|
|
70
125
|
- "443:443"
|
|
@@ -75,6 +130,10 @@ services:
|
|
|
75
130
|
depends_on:
|
|
76
131
|
<%= fastifyInstances[0].path %>:
|
|
77
132
|
condition: service_healthy
|
|
133
|
+
<% } else if (expressInstances.length > 0) { %>
|
|
134
|
+
depends_on:
|
|
135
|
+
<%= expressInstances[0].path %>:
|
|
136
|
+
condition: service_healthy
|
|
78
137
|
<% } else if (fastapiInstances.length > 0) { %>
|
|
79
138
|
depends_on:
|
|
80
139
|
<%= fastapiInstances[0].path %>:
|
|
@@ -82,7 +141,15 @@ services:
|
|
|
82
141
|
<% } %>
|
|
83
142
|
restart: unless-stopped
|
|
84
143
|
healthcheck:
|
|
85
|
-
test:
|
|
144
|
+
test:
|
|
145
|
+
[
|
|
146
|
+
"CMD",
|
|
147
|
+
"wget",
|
|
148
|
+
"--spider",
|
|
149
|
+
"-q",
|
|
150
|
+
"--no-check-certificate",
|
|
151
|
+
"https://127.0.0.1/",
|
|
152
|
+
]
|
|
86
153
|
interval: 30s
|
|
87
154
|
timeout: 5s
|
|
88
155
|
retries: 3
|
|
@@ -4,17 +4,20 @@ set -e
|
|
|
4
4
|
# ── Secret detection ──────────────────────────────────────────────
|
|
5
5
|
STAGED_DIFF="$(git diff --cached --diff-filter=ACMR)"
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
AKIA_HITS="$(echo "$STAGED_DIFF" | grep -E '(AKIA|ASIA)[A-Z0-9]{16}' | grep -vE '(pragma: allowlist secret|gitleaks:allow)' || true)"
|
|
8
|
+
if [ -n "$AKIA_HITS" ]; then
|
|
9
|
+
echo "ERROR: Possible AWS access key detected in staged changes:"
|
|
10
|
+
echo "$AKIA_HITS" | head -5
|
|
11
|
+
echo "Add 'pragma: allowlist secret' on the line to whitelist if intentional."
|
|
9
12
|
exit 1
|
|
10
13
|
fi
|
|
11
14
|
|
|
12
15
|
if echo "$STAGED_DIFF" | grep -qE '(password|secret|token)\s*[:=]\s*["\x27][^"'\'']{8,}' 2>/dev/null; then
|
|
13
|
-
REAL_SECRETS="$(echo "$STAGED_DIFF" | grep -E '(password|secret|token)\s*[:=]\s*["\x27][^"'\'']{8,}' | grep -vE '(test-secret|dev-secret|example|placeholder|your-|changeme|CHANGE_ME)' || true)"
|
|
16
|
+
REAL_SECRETS="$(echo "$STAGED_DIFF" | grep -E '(password|secret|token)\s*[:=]\s*["\x27][^"'\'']{8,}' | grep -vE '(test-secret|dev-secret|example|placeholder|your-|changeme|CHANGE_ME|pragma: allowlist secret|gitleaks:allow)' || true)"
|
|
14
17
|
if [ -n "$REAL_SECRETS" ]; then
|
|
15
18
|
echo "WARNING: Possible secret detected in staged changes:"
|
|
16
19
|
echo "$REAL_SECRETS" | head -5
|
|
17
|
-
echo "
|
|
20
|
+
echo "Add 'pragma: allowlist secret' on the line to whitelist, or commit with --no-verify if intentional."
|
|
18
21
|
exit 1
|
|
19
22
|
fi
|
|
20
23
|
fi
|
|
@@ -36,12 +39,33 @@ if [ -n "$<%= inst.upper %>_PY" ]; then
|
|
|
36
39
|
echo "$<%= inst.upper %>_PY" | sed 's|^<%= inst.path %>/||' | xargs uv run ruff format
|
|
37
40
|
echo "$<%= inst.upper %>_PY" | sed 's|^<%= inst.path %>/||' | xargs uv run ruff check --fix
|
|
38
41
|
uv run mypy
|
|
42
|
+
if grep -rEn 'from src\.[a-z_]+(\.[a-z_]+)?\._[a-z_]+ import' src/; then
|
|
43
|
+
echo "ERROR: src/ files cannot import from another module's _-prefixed file. Import from the package."
|
|
44
|
+
exit 1
|
|
45
|
+
fi
|
|
46
|
+
uv run lint-imports
|
|
39
47
|
cd ..
|
|
40
48
|
echo "$<%= inst.upper %>_PY" | xargs git add
|
|
41
49
|
fi
|
|
42
50
|
<% } %>
|
|
43
51
|
<% for (const inst of fastifyInstances) { %>
|
|
44
52
|
|
|
53
|
+
<%= inst.upper %>_TS=$(echo "$STAGED_FILES" | grep '^<%= inst.path %>/.*\.ts$' || true)
|
|
54
|
+
<%= inst.upper %>_ALL=$(echo "$STAGED_FILES" | grep '^<%= inst.path %>/' || true)
|
|
55
|
+
if [ -n "$<%= inst.upper %>_ALL" ]; then
|
|
56
|
+
echo "Formatting <%= inst.path %>..."
|
|
57
|
+
cd <%= inst.path %>
|
|
58
|
+
echo "$<%= inst.upper %>_ALL" | sed 's|^<%= inst.path %>/||' | xargs <%= pm.exec %> prettier --write --ignore-unknown
|
|
59
|
+
if [ -n "$<%= inst.upper %>_TS" ]; then
|
|
60
|
+
echo "$<%= inst.upper %>_TS" | sed 's|^<%= inst.path %>/||' | xargs <%= pm.exec %> eslint --fix
|
|
61
|
+
<%= pm.exec %> tsc --noEmit
|
|
62
|
+
fi
|
|
63
|
+
cd ..
|
|
64
|
+
echo "$<%= inst.upper %>_ALL" | xargs git add
|
|
65
|
+
fi
|
|
66
|
+
<% } %>
|
|
67
|
+
<% for (const inst of expressInstances) { %>
|
|
68
|
+
|
|
45
69
|
<%= inst.upper %>_TS=$(echo "$STAGED_FILES" | grep '^<%= inst.path %>/.*\.ts$' || true)
|
|
46
70
|
<%= inst.upper %>_ALL=$(echo "$STAGED_FILES" | grep '^<%= inst.path %>/' || true)
|
|
47
71
|
if [ -n "$<%= inst.upper %>_ALL" ]; then
|