create-projx 1.7.1 → 1.7.3

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.
@@ -8,8 +8,8 @@ import {
8
8
  matchesSkip,
9
9
  saveBaselineRef,
10
10
  writeTemplateToDir
11
- } from "./chunk-N4WD4VN3.js";
12
- import "./chunk-OLPF7FAN.js";
11
+ } from "./chunk-RA6FWWUM.js";
12
+ import "./chunk-3NL6OTAP.js";
13
13
  export {
14
14
  BASELINE_REF,
15
15
  applyTemplate,
@@ -223,6 +223,8 @@ async function copyStaticFiles(repoDir, dest) {
223
223
  }
224
224
  const staticScripts = [
225
225
  "ci-local.sh",
226
+ "ci-runner-gc.sh",
227
+ "check-bundle-size.sh",
226
228
  "setup-docker.sh",
227
229
  "setup-ssl.sh",
228
230
  "style-check.py"
@@ -343,6 +345,8 @@ var DEFAULT_ROOT_SKIP_PATTERNS = [
343
345
  ".githooks/pre-commit",
344
346
  ".github/workflows/ci.yml",
345
347
  "scripts/ci-local.sh",
348
+ "scripts/ci-runner-gc.sh",
349
+ "scripts/check-bundle-size.sh",
346
350
  "scripts/setup.sh",
347
351
  "scripts/setup-docker.sh",
348
352
  "scripts/setup-ssl.sh"
@@ -394,10 +398,15 @@ function render(template, vars) {
394
398
  function evalExpr(expr, vars) {
395
399
  const components = vars.components;
396
400
  const projectName = vars.projectName;
397
- const pmName = vars.pm?.name ?? "npm";
401
+ if (vars.pm !== void 0 && typeof vars.pm !== "object") {
402
+ throw new Error(
403
+ `render: vars.pm must be a PmCommands object (got ${typeof vars.pm}). Templates use pm.exec, pm.name etc.; passing the package-manager name as a bare string silently breaks those references.`
404
+ );
405
+ }
406
+ const pm = typeof vars.pm === "object" && vars.pm !== null ? vars.pm : { name: "npm" };
398
407
  const orm = vars.orm ?? "prisma";
399
408
  const argNames = ["components", "projectName", "pm", "orm"];
400
- const argValues = [components, projectName, pmName, orm];
409
+ const argValues = [components, projectName, pm, orm];
401
410
  for (const [k, v] of Object.entries(vars)) {
402
411
  if (["components", "projectName", "pm", "orm"].includes(k)) continue;
403
412
  if (!/^[a-zA-Z_$][\w$]*$/.test(k)) continue;
@@ -13,7 +13,7 @@ import {
13
13
  toSnake,
14
14
  upsertComponentMarker,
15
15
  writeProjxConfig
16
- } from "./chunk-OLPF7FAN.js";
16
+ } from "./chunk-3NL6OTAP.js";
17
17
 
18
18
  // src/baseline.ts
19
19
  import { existsSync, writeFileSync, unlinkSync } from "fs";
@@ -126,7 +126,7 @@ function generateVscodeSettings(vars) {
126
126
  // src/baseline.ts
127
127
  var BASELINE_REF = "refs/projx/baseline";
128
128
  async function migrateComponentMarkers(cwd, components, componentPaths, applyDefaults) {
129
- const { readComponentMarker: readComponentMarker2, writeComponentMarker } = await import("./utils-4G3HNHES.js");
129
+ const { readComponentMarker: readComponentMarker2, writeComponentMarker } = await import("./utils-W4CWICA7.js");
130
130
  for (const component of components) {
131
131
  const dir = componentPaths[component];
132
132
  const markerDir = join2(cwd, dir);
@@ -658,25 +658,35 @@ async function substituteNamesForInstance(inst, dest, name, nameSnake, overrides
658
658
  const isCanonical = path === type;
659
659
  if (type === "fastapi") {
660
660
  const target = isCanonical ? overrides?.fastapi ?? `${name}-fastapi` : `${name}-${path}`;
661
- await replaceInFile(
662
- join2(dest, `${path}/pyproject.toml`),
663
- "projx-fastapi",
664
- target
665
- );
661
+ for (const file of [
662
+ "pyproject.toml",
663
+ "src/configs/_database.py",
664
+ "tests/test_app.py"
665
+ ]) {
666
+ await replaceInFile(
667
+ join2(dest, `${path}/${file}`),
668
+ "projx-fastapi",
669
+ target
670
+ );
671
+ }
666
672
  } else if (type === "fastify") {
667
673
  const target = isCanonical ? overrides?.fastify ?? `${name}-fastify` : `${name}-${path}`;
668
- await replaceInFile(
669
- join2(dest, `${path}/package.json`),
670
- "projx-fastify",
671
- target
672
- );
674
+ for (const file of ["package.json", ".env.example", ".env.test"]) {
675
+ await replaceInFile(
676
+ join2(dest, `${path}/${file}`),
677
+ "projx-fastify",
678
+ target
679
+ );
680
+ }
673
681
  } else if (type === "express") {
674
682
  const target = isCanonical ? overrides?.express ?? `${name}-express` : `${name}-${path}`;
675
- await replaceInFile(
676
- join2(dest, `${path}/package.json`),
677
- "projx-express",
678
- target
679
- );
683
+ for (const file of ["package.json", ".env.example", ".env.test"]) {
684
+ await replaceInFile(
685
+ join2(dest, `${path}/${file}`),
686
+ "projx-express",
687
+ target
688
+ );
689
+ }
680
690
  } else if (type === "frontend") {
681
691
  const target = isCanonical ? overrides?.frontend ?? `${name}-frontend` : `${name}-${path}`;
682
692
  await replaceInFile(
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  matchesSkip,
10
10
  saveBaselineRef,
11
11
  writeTemplateToDir
12
- } from "./chunk-N4WD4VN3.js";
12
+ } from "./chunk-RA6FWWUM.js";
13
13
  import {
14
14
  COMPONENTS,
15
15
  COMPONENT_MARKER,
@@ -36,7 +36,7 @@ import {
36
36
  toSnake,
37
37
  writeComponentMarker,
38
38
  writeProjxConfig
39
- } from "./chunk-OLPF7FAN.js";
39
+ } from "./chunk-3NL6OTAP.js";
40
40
 
41
41
  // src/index.ts
42
42
  import { existsSync as existsSync11 } from "fs";
@@ -851,7 +851,7 @@ function hasUncommittedChanges(cwd) {
851
851
  async function findPinnedFilesWithUpdates(cwd, repoDir, components, componentPaths, vars, version, componentSkips, rootSkip) {
852
852
  const { mkdtemp: mkdtemp2, rm: rm2, readFile: readFile8 } = await import("fs/promises");
853
853
  const { tmpdir: tmpdir2 } = await import("os");
854
- const { writeTemplateToDir: writeTemplateToDir2 } = await import("./baseline-FKCXQFRD.js");
854
+ const { writeTemplateToDir: writeTemplateToDir2 } = await import("./baseline-HNSDAHQ4.js");
855
855
  const config = await readProjxConfig(cwd);
856
856
  const rootPinned = Array.isArray(config.skip) ? config.skip : [];
857
857
  const componentPinned = [];
@@ -35,7 +35,7 @@ import {
35
35
  upsertComponentMarker,
36
36
  writeComponentMarker,
37
37
  writeProjxConfig
38
- } from "./chunk-OLPF7FAN.js";
38
+ } from "./chunk-3NL6OTAP.js";
39
39
  export {
40
40
  COMPONENTS,
41
41
  COMPONENT_MARKER,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-projx",
3
- "version": "1.7.1",
3
+ "version": "1.7.3",
4
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": {
@@ -11,6 +11,9 @@ permissions:
11
11
  contents: read
12
12
  pull-requests: read
13
13
 
14
+ env:
15
+ WS: run-${{ github.run_id }}-${{ github.run_attempt }}
16
+
14
17
  jobs:
15
18
  changes:
16
19
  name: Detect changes
@@ -39,9 +42,12 @@ jobs:
39
42
  <% } %>
40
43
  steps:
41
44
  - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
45
+ with:
46
+ path: ${{ env.WS }}
42
47
  - uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3
43
48
  id: filter
44
49
  with:
50
+ working-directory: ${{ env.WS }}
45
51
  filters: |
46
52
  <% for (const inst of fastapiInstances) { %>
47
53
  <%= inst.path %>:
@@ -79,10 +85,15 @@ jobs:
79
85
  - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
80
86
  with:
81
87
  fetch-depth: 0
82
- - uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2
83
- env:
84
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
85
- - run: python3 scripts/style-check.py frontend/src
88
+ path: ${{ env.WS }}
89
+ - name: Install gitleaks
90
+ run: |
91
+ curl -sSL -o /tmp/gitleaks.tar.gz https://github.com/gitleaks/gitleaks/releases/download/v8.21.2/gitleaks_8.21.2_linux_x64.tar.gz
92
+ tar -xzf /tmp/gitleaks.tar.gz -C /tmp gitleaks
93
+ sudo mv /tmp/gitleaks /usr/local/bin/gitleaks
94
+ - name: Run gitleaks
95
+ run: gitleaks detect --source ${{ env.WS }} --no-git --no-banner --redact --exit-code 1
96
+ - run: python3 ${{ env.WS }}/scripts/style-check.py ${{ env.WS }}/frontend/src
86
97
  <% for (const inst of fastapiInstances) { %>
87
98
 
88
99
  <%= inst.path %>:
@@ -106,7 +117,7 @@ jobs:
106
117
  --health-retries 5
107
118
  defaults:
108
119
  run:
109
- working-directory: <%= inst.path %>
120
+ working-directory: ${{ env.WS }}/<%= inst.path %>
110
121
  env:
111
122
  SQLALCHEMY_DATABASE_URI: postgresql+asyncpg://postgres:postgres@localhost:5432/ci_test
112
123
  JWT_PROVIDER: shared_secret
@@ -114,6 +125,8 @@ jobs:
114
125
  JWT_ALGORITHMS: HS256
115
126
  steps:
116
127
  - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
128
+ with:
129
+ path: ${{ env.WS }}
117
130
  - uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
118
131
  - run: uv sync --group dev
119
132
  - run: uv run ruff format --check src tests
@@ -123,7 +136,7 @@ jobs:
123
136
  run: |
124
137
  run_pip_audit() {
125
138
  for attempt in 1 2 3; do
126
- if uv run pip-audit --ignore-vuln CVE-2026-3219; then
139
+ if bash audit.sh; then
127
140
  return 0
128
141
  fi
129
142
  if [ "$attempt" -eq 3 ]; then
@@ -157,26 +170,28 @@ jobs:
157
170
  --health-retries 5
158
171
  defaults:
159
172
  run:
160
- working-directory: <%= inst.path %>
173
+ working-directory: ${{ env.WS }}/<%= inst.path %>
161
174
  env:
162
175
  DATABASE_URL: postgresql://postgres:postgres@localhost:5432/ci_test
163
176
  JWT_SECRET: ci-test-secret # gitleaks:allow
164
177
  steps:
165
178
  - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
166
- <% if (pm === 'pnpm') { %>
179
+ with:
180
+ path: ${{ env.WS }}
181
+ <% if (pm.name === 'pnpm') { %>
167
182
  - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
168
183
  with:
169
184
  version: 10
170
185
  <% } %>
171
- <% if (pm === 'bun') { %>
186
+ <% if (pm.name === 'bun') { %>
172
187
  - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
173
188
  <% } %>
174
- <% if (pm !== 'bun') { %>
189
+ <% if (pm.name !== 'bun') { %>
175
190
  - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
176
191
  with:
177
192
  node-version: 22
178
193
  cache: <%= pm.name %>
179
- cache-dependency-path: <%= inst.path %>/<%= pm.lockfile %>
194
+ cache-dependency-path: ${{ env.WS }}/<%= inst.path %>/<%= pm.lockfile %>
180
195
  <% } %>
181
196
  - run: <%= pm.ci %>
182
197
  <% if (orm === 'drizzle') { %>
@@ -196,7 +211,7 @@ jobs:
196
211
  <% for (const inst of expressInstances) { %>
197
212
 
198
213
  <%= inst.path %>:
199
- name: <%= inst.display %> (format + lint + typecheck + build + test + audit)
214
+ name: <%= inst.display %> (format + lint + typecheck + build + audit)
200
215
  needs: changes
201
216
  if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.<%= inst.path %> == 'true'
202
217
  runs-on: ubuntu-latest
@@ -216,25 +231,27 @@ jobs:
216
231
  --health-retries 5
217
232
  defaults:
218
233
  run:
219
- working-directory: <%= inst.path %>
234
+ working-directory: ${{ env.WS }}/<%= inst.path %>
220
235
  env:
221
236
  DATABASE_URL: postgresql://postgres:postgres@localhost:5432/ci_test
222
237
  steps:
223
238
  - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
224
- <% if (pm === 'pnpm') { %>
239
+ with:
240
+ path: ${{ env.WS }}
241
+ <% if (pm.name === 'pnpm') { %>
225
242
  - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
226
243
  with:
227
244
  version: 10
228
245
  <% } %>
229
- <% if (pm === 'bun') { %>
246
+ <% if (pm.name === 'bun') { %>
230
247
  - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
231
248
  <% } %>
232
- <% if (pm !== 'bun') { %>
249
+ <% if (pm.name !== 'bun') { %>
233
250
  - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
234
251
  with:
235
252
  node-version: 22
236
253
  cache: <%= pm.name %>
237
- cache-dependency-path: <%= inst.path %>/<%= pm.lockfile %>
254
+ cache-dependency-path: ${{ env.WS }}/<%= inst.path %>/<%= pm.lockfile %>
238
255
  <% } %>
239
256
  - run: <%= pm.ci %>
240
257
  <% if (orm === 'drizzle') { %>
@@ -249,7 +266,6 @@ jobs:
249
266
  - run: <%= pm.exec %> eslint .
250
267
  - run: <%= pm.exec %> tsc --noEmit
251
268
  - run: <%= pm.run %> build
252
- - run: <%= pm.exec %> vitest run --coverage.enabled=false
253
269
  - run: <%= pm.audit %>
254
270
  <% } %>
255
271
  <% for (const inst of frontendInstances) { %>
@@ -261,23 +277,25 @@ jobs:
261
277
  runs-on: ubuntu-latest
262
278
  defaults:
263
279
  run:
264
- working-directory: <%= inst.path %>
280
+ working-directory: ${{ env.WS }}/<%= inst.path %>
265
281
  steps:
266
282
  - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
267
- <% if (pm === 'pnpm') { %>
283
+ with:
284
+ path: ${{ env.WS }}
285
+ <% if (pm.name === 'pnpm') { %>
268
286
  - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
269
287
  with:
270
288
  version: 10
271
289
  <% } %>
272
- <% if (pm === 'bun') { %>
290
+ <% if (pm.name === 'bun') { %>
273
291
  - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
274
292
  <% } %>
275
- <% if (pm !== 'bun') { %>
293
+ <% if (pm.name !== 'bun') { %>
276
294
  - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
277
295
  with:
278
296
  node-version: 22
279
297
  cache: <%= pm.name %>
280
- cache-dependency-path: <%= inst.path %>/<%= pm.lockfile %>
298
+ cache-dependency-path: ${{ env.WS }}/<%= inst.path %>/<%= pm.lockfile %>
281
299
  <% } %>
282
300
  - run: <%= pm.ci %>
283
301
  - run: <%= pm.exec %> prettier --check .
@@ -296,9 +314,11 @@ jobs:
296
314
  runs-on: ubuntu-latest
297
315
  defaults:
298
316
  run:
299
- working-directory: <%= inst.path %>
317
+ working-directory: ${{ env.WS }}/<%= inst.path %>
300
318
  steps:
301
319
  - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
320
+ with:
321
+ path: ${{ env.WS }}
302
322
  - uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2
303
323
  with:
304
324
  channel: stable
@@ -318,23 +338,25 @@ jobs:
318
338
  runs-on: ubuntu-latest
319
339
  defaults:
320
340
  run:
321
- working-directory: <%= inst.path %>
341
+ working-directory: ${{ env.WS }}/<%= inst.path %>
322
342
  steps:
323
343
  - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
324
- <% if (pm === 'pnpm') { %>
344
+ with:
345
+ path: ${{ env.WS }}
346
+ <% if (pm.name === 'pnpm') { %>
325
347
  - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
326
348
  with:
327
349
  version: 10
328
350
  <% } %>
329
- <% if (pm === 'bun') { %>
351
+ <% if (pm.name === 'bun') { %>
330
352
  - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
331
353
  <% } %>
332
- <% if (pm !== 'bun') { %>
354
+ <% if (pm.name !== 'bun') { %>
333
355
  - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
334
356
  with:
335
357
  node-version: 22
336
358
  cache: <%= pm.name %>
337
- cache-dependency-path: <%= inst.path %>/<%= pm.lockfile %>
359
+ cache-dependency-path: ${{ env.WS }}/<%= inst.path %>/<%= pm.lockfile %>
338
360
  <% } %>
339
361
  - run: <%= pm.ci %>
340
362
  - run: <%= pm.exec %> prettier --check .
@@ -351,9 +373,11 @@ jobs:
351
373
  runs-on: ubuntu-latest
352
374
  defaults:
353
375
  run:
354
- working-directory: <%= inst.path %>/stack
376
+ working-directory: ${{ env.WS }}/<%= inst.path %>/stack
355
377
  steps:
356
378
  - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
379
+ with:
380
+ path: ${{ env.WS }}
357
381
  - uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3
358
382
  with:
359
383
  terraform_version: '1.11'
@@ -39,8 +39,8 @@ if [ -n "$<%= inst.upper %>_PY" ]; then
39
39
  echo "$<%= inst.upper %>_PY" | sed 's|^<%= inst.path %>/||' | xargs uv run ruff format
40
40
  echo "$<%= inst.upper %>_PY" | sed 's|^<%= inst.path %>/||' | xargs uv run ruff check --fix
41
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."
42
+ if grep -rEn 'from src\.[a-z_]+(\.[a-z_]+)?\._[a-z_]+ import' src/ | grep -v 'pragma:.*allow-private-import'; then
43
+ echo "ERROR: src/ files cannot import from another module's _-prefixed file. Import from the package, or add '# pragma: allow-private-import' on the line if the cycle makes a package import impossible."
44
44
  exit 1
45
45
  fi
46
46
  uv run lint-imports
@@ -135,9 +135,21 @@ fi
135
135
  <%= inst.upper %>_TF=$(echo "$STAGED_FILES" | grep '^<%= inst.path %>/.*\.tf$' || true)
136
136
  if [ -n "$<%= inst.upper %>_TF" ]; then
137
137
  if command -v terraform &> /dev/null; then
138
- echo "Formatting <%= inst.path %>..."
138
+ echo "Checking <%= inst.path %>..."
139
139
  cd <%= inst.path %>/stack
140
140
  echo "$<%= inst.upper %>_TF" | sed 's|^<%= inst.path %>/stack/||' | xargs terraform fmt
141
+ [ -d .terraform ] || terraform init -backend=false -input=false >/dev/null
142
+ terraform validate
143
+ if command -v tflint &> /dev/null; then
144
+ tflint --recursive
145
+ else
146
+ echo "Skipping tflint (not installed)"
147
+ fi
148
+ if command -v trivy &> /dev/null; then
149
+ trivy config --exit-code 1 --severity HIGH,CRITICAL .
150
+ else
151
+ echo "Skipping trivy config scan (not installed)"
152
+ fi
141
153
  cd ../..
142
154
  echo "$<%= inst.upper %>_TF" | xargs git add
143
155
  else