create-projx 1.6.1 → 1.6.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 CHANGED
@@ -7,6 +7,8 @@
7
7
 
8
8
  **Go from blank folder to production-ready project in 30 seconds.** Backend-only API, AI/ML app, mobile, full-stack, infra setup — pick what you need and get it wired with auth, database, Docker, CI/CD, hooks, and tests. All optional. All yours.
9
9
 
10
+ ![projx demo](.github/demo.gif)
11
+
10
12
  ```bash
11
13
  npx create-projx my-app
12
14
  ```
@@ -289,7 +291,7 @@ Override with `--ai` (fastapi) or `--backend` (fastify).
289
291
  | `frontend` | `src/types/<name>.ts` — TypeScript interface + Create/Update variants |
290
292
  | `mobile` | `lib/entities/<name>/model.dart` — Dart class with fromJson/toJson/copyWith |
291
293
 
292
- **Tests included**: every `gen entity` writes a working integration test file alongside the model — 11 tests for FastAPI (extending `BaseEntityApiTest`), 11 tests for Fastify (via `describeCrudEntity`). Both run against a real database (Postgres for Fastify, SQLite-in-memory for FastAPI today). New entities ship green from day one — no scrambling to bolt on tests at go-live.
294
+ **Tests included**: every `gen entity` writes a working integration test file alongside the model — 11 tests for FastAPI (extending `BaseEntityApiTest`), 11 tests for Fastify (via `describeCrudEntity`). Both run against a real database (Postgres). New entities ship green from day one — no scrambling to bolt on tests at go-live.
293
295
 
294
296
  No migrations — run `alembic revision --autogenerate` or `prisma migrate dev` (via your package manager) when ready.
295
297
 
@@ -371,8 +373,8 @@ The CLI lives in `cli/`. Templates are the root-level component directories (`fa
371
373
 
372
374
  ```bash
373
375
  cd cli
374
- npm test # run tests
375
- npm run build # build CLI
376
+ pnpm test # run tests
377
+ pnpm build # build CLI
376
378
  ```
377
379
 
378
380
  ## Try it now
@@ -10,8 +10,8 @@ import {
10
10
  matchesSkip,
11
11
  saveBaselineRef,
12
12
  writeTemplateToDir
13
- } from "./chunk-TNI4XBVS.js";
14
- import "./chunk-FTHX7ILT.js";
13
+ } from "./chunk-D33FXCNT.js";
14
+ import "./chunk-LTIJPVRZ.js";
15
15
  export {
16
16
  BASELINE_REF,
17
17
  applyTemplate,
@@ -6,13 +6,14 @@ import {
6
6
  readComponentMarker,
7
7
  readProjxConfig,
8
8
  render,
9
+ renderEjsInDir,
9
10
  replaceInDir,
10
11
  replaceInFile,
11
12
  sharedTemplateDir,
12
13
  toSnake,
13
14
  upsertComponentMarker,
14
15
  writeProjxConfig
15
- } from "./chunk-FTHX7ILT.js";
16
+ } from "./chunk-LTIJPVRZ.js";
16
17
 
17
18
  // src/baseline.ts
18
19
  import { existsSync, writeFileSync, unlinkSync } from "fs";
@@ -106,7 +107,7 @@ function buildDisplayNames(paths) {
106
107
  }
107
108
  var BASELINE_REF = "refs/projx/baseline";
108
109
  async function migrateComponentMarkers(cwd, components, componentPaths, applyDefaults) {
109
- const { readComponentMarker: readComponentMarker2, writeComponentMarker } = await import("./utils-OOY5OZDX.js");
110
+ const { readComponentMarker: readComponentMarker2, writeComponentMarker } = await import("./utils-VY5BBJBQ.js");
110
111
  for (const component of components) {
111
112
  const dir = componentPaths[component];
112
113
  const markerDir = join2(cwd, dir);
@@ -329,8 +330,10 @@ async function removeSkippedFiles(dir, skipPatterns, realDir) {
329
330
  const rel = full.slice(base.length + 1);
330
331
  if (entry.isDirectory()) {
331
332
  await walk(full, base);
332
- } else if (entry.name !== ".projx-component" && matchesSkip(rel, skipPatterns)) {
333
- if (realDir && !existsSync(join2(realDir, rel))) continue;
333
+ } else if (entry.name !== ".projx-component") {
334
+ const targetRel = rel.endsWith(".ejs") ? rel.slice(0, -".ejs".length) : rel;
335
+ if (!matchesSkip(targetRel, skipPatterns) && !matchesSkip(rel, skipPatterns)) continue;
336
+ if (realDir && !existsSync(join2(realDir, targetRel))) continue;
334
337
  await unlink(full);
335
338
  }
336
339
  }
@@ -363,6 +366,7 @@ async function writeTemplateToDir(dest, repoDir, components, componentPaths, var
363
366
  await cp(srcDir, outDir, { recursive: true, force: true });
364
367
  }
365
368
  await rm(tmpDir, { recursive: true, force: true });
369
+ await renderEjsInDir(outDir, vars);
366
370
  await upsertComponentMarker(join2(dest, targetDir), component, skipPatterns.length > 0 ? skipPatterns : void 0);
367
371
  }
368
372
  if (!vars.pathsUpper) {
@@ -19,13 +19,13 @@ var PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
19
19
  function pmCommands(pm) {
20
20
  switch (pm) {
21
21
  case "npm":
22
- return { name: "npm", install: "npm install", ci: "npm ci", run: "npm run", exec: "npx", dlx: "npx", lockfile: "package-lock.json", prismaExec: "npx prisma", runDev: "npm run dev" };
22
+ return { name: "npm", install: "npm install", ci: "npm ci", run: "npm run", exec: "npx", dlx: "npx", lockfile: "package-lock.json", prismaExec: "npx prisma", runDev: "npm run dev", audit: "npm audit --omit=dev" };
23
23
  case "pnpm":
24
- return { name: "pnpm", install: "pnpm install", ci: "pnpm install --frozen-lockfile", run: "pnpm", exec: "pnpm exec", dlx: "pnpm dlx", lockfile: "pnpm-lock.yaml", prismaExec: "pnpm prisma", runDev: "pnpm dev" };
24
+ return { name: "pnpm", install: "pnpm install", ci: "pnpm install --frozen-lockfile", run: "pnpm", exec: "pnpm exec", dlx: "pnpm dlx", lockfile: "pnpm-lock.yaml", prismaExec: "pnpm prisma", runDev: "pnpm dev", audit: "pnpm audit --prod" };
25
25
  case "yarn":
26
- return { name: "yarn", install: "yarn", ci: "yarn --frozen-lockfile", run: "yarn", exec: "yarn", dlx: "yarn dlx", lockfile: "yarn.lock", prismaExec: "yarn prisma", runDev: "yarn dev" };
26
+ return { name: "yarn", install: "yarn", ci: "yarn --frozen-lockfile", run: "yarn", exec: "yarn", dlx: "yarn dlx", lockfile: "yarn.lock", prismaExec: "yarn prisma", runDev: "yarn dev", audit: "yarn npm audit --environment production" };
27
27
  case "bun":
28
- return { name: "bun", install: "bun install", ci: "bun install --frozen-lockfile", run: "bun run", exec: "bunx", dlx: "bunx", lockfile: "bun.lockb", prismaExec: "bunx prisma", runDev: "bun run dev" };
28
+ return { name: "bun", install: "bun install", ci: "bun install --frozen-lockfile", run: "bun run", exec: "bunx", dlx: "bunx", lockfile: "bun.lockb", prismaExec: "bunx prisma", runDev: "bun run dev", audit: "bun audit --prod" };
29
29
  }
30
30
  }
31
31
  function detectPackageManager(cwd) {
@@ -369,6 +369,22 @@ function render(template, vars) {
369
369
  }
370
370
  return output.join("\n").replace(/\n{3,}/g, "\n\n");
371
371
  }
372
+ async function renderEjsInDir(dir, vars) {
373
+ if (!existsSync(dir)) return;
374
+ const entries = await readdir(dir, { withFileTypes: true });
375
+ for (const entry of entries) {
376
+ const full = join(dir, entry.name);
377
+ if (entry.isDirectory()) {
378
+ await renderEjsInDir(full, vars);
379
+ } else if (entry.name.endsWith(".ejs")) {
380
+ const content = await readFile(full, "utf-8");
381
+ const rendered = render(content, vars);
382
+ const out = full.slice(0, -".ejs".length);
383
+ await writeFile(out, rendered);
384
+ await rm(full);
385
+ }
386
+ }
387
+ }
372
388
  function detectProjectName(cwd, components, componentPaths) {
373
389
  for (const component of components) {
374
390
  const dir = componentPaths[component] ?? component;
@@ -420,5 +436,6 @@ export {
420
436
  discoverComponentPaths,
421
437
  discoverComponentsFromMarkers,
422
438
  render,
439
+ renderEjsInDir,
423
440
  detectProjectName
424
441
  };
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  matchesSkip,
10
10
  saveBaselineRef,
11
11
  writeTemplateToDir
12
- } from "./chunk-TNI4XBVS.js";
12
+ } from "./chunk-D33FXCNT.js";
13
13
  import {
14
14
  COMPONENTS,
15
15
  COMPONENT_MARKER,
@@ -33,7 +33,7 @@ import {
33
33
  toTitle,
34
34
  writeComponentMarker,
35
35
  writeProjxConfig
36
- } from "./chunk-FTHX7ILT.js";
36
+ } from "./chunk-LTIJPVRZ.js";
37
37
 
38
38
  // src/index.ts
39
39
  import { existsSync as existsSync11 } from "fs";
@@ -371,7 +371,7 @@ function hasUncommittedChanges(cwd) {
371
371
  async function findPinnedFilesWithUpdates(cwd, repoDir, components, componentPaths, vars, version, componentSkips, rootSkip) {
372
372
  const { mkdir: mkdir5, rm: rm2, readFile: readFile7 } = await import("fs/promises");
373
373
  const { tmpdir: tmpdir2 } = await import("os");
374
- const { writeTemplateToDir: writeTemplateToDir2 } = await import("./baseline-5XAJJ457.js");
374
+ const { writeTemplateToDir: writeTemplateToDir2 } = await import("./baseline-KTCFW2FK.js");
375
375
  const config = await readProjxConfig(cwd);
376
376
  const rootPinned = Array.isArray(config.skip) ? config.skip : [];
377
377
  const componentPinned = [];
@@ -23,6 +23,7 @@ import {
23
23
  readFileOrNull,
24
24
  readProjxConfig,
25
25
  render,
26
+ renderEjsInDir,
26
27
  replaceInDir,
27
28
  replaceInFile,
28
29
  sharedTemplateDir,
@@ -32,7 +33,7 @@ import {
32
33
  upsertComponentMarker,
33
34
  writeComponentMarker,
34
35
  writeProjxConfig
35
- } from "./chunk-FTHX7ILT.js";
36
+ } from "./chunk-LTIJPVRZ.js";
36
37
  export {
37
38
  COMPONENTS,
38
39
  COMPONENT_MARKER,
@@ -58,6 +59,7 @@ export {
58
59
  readFileOrNull,
59
60
  readProjxConfig,
60
61
  render,
62
+ renderEjsInDir,
61
63
  replaceInDir,
62
64
  replaceInFile,
63
65
  sharedTemplateDir,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-projx",
3
- "version": "1.6.1",
3
+ "version": "1.6.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": {
@@ -10,13 +10,6 @@
10
10
  "dist",
11
11
  "src/templates"
12
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
- "test": "vitest run",
17
- "test:watch": "vitest",
18
- "prepublishOnly": "npm run build"
19
- },
20
13
  "keywords": [
21
14
  "projx",
22
15
  "scaffold",
@@ -61,5 +54,11 @@
61
54
  "typescript": "^5",
62
55
  "typescript-eslint": "^8.58.0",
63
56
  "vitest": "^4.1.2"
57
+ },
58
+ "scripts": {
59
+ "build": "tsup src/index.ts --format esm --target node18 --clean",
60
+ "dev": "tsup src/index.ts --format esm --target node18 --watch",
61
+ "test": "vitest run",
62
+ "test:watch": "vitest"
64
63
  }
65
- }
64
+ }
@@ -24,7 +24,7 @@ Scaffolded with [Projx](https://github.com/ukanhaupa/projx).
24
24
  <% if (components.includes('infra')) { %>
25
25
  | **<%= paths.infra %>/** | Terraform, AWS (EKS, RDS, VPC, CodePipeline) |
26
26
  <% } %>
27
- | **Identity** | Keycloak (OIDC / JWT) |
27
+ | **Identity** | OIDC / JWT |
28
28
  | **Containers** | Docker, Docker Compose |
29
29
 
30
30
  ## Getting Started
@@ -61,10 +61,21 @@ jobs:
61
61
  <%= paths.infra %>:
62
62
  - '<%= paths.infra %>/**'
63
63
  <% } %>
64
+
65
+ secrets:
66
+ name: Secret scan
67
+ runs-on: ubuntu-latest
68
+ steps:
69
+ - uses: actions/checkout@v5
70
+ with:
71
+ fetch-depth: 0
72
+ - uses: gitleaks/gitleaks-action@v2
73
+ env:
74
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
64
75
  <% if (components.includes('fastapi')) { %>
65
76
 
66
77
  <%= paths.fastapi %>:
67
- name: <%= displayNames.fastapi %> (format + lint)
78
+ name: <%= displayNames.fastapi %> (format + lint + typecheck + test + audit)
68
79
  needs: changes
69
80
  if: needs.changes.outputs.<%= paths.fastapi %> == 'true'
70
81
  runs-on: ubuntu-latest
@@ -77,11 +88,14 @@ jobs:
77
88
  - run: uv sync --group dev
78
89
  - run: uv run ruff format --check src tests
79
90
  - run: uv run ruff check src tests
91
+ - run: uv run mypy
92
+ - run: uv run pytest
93
+ - run: uv run pip-audit
80
94
  <% } %>
81
95
  <% if (components.includes('fastify')) { %>
82
96
 
83
97
  <%= paths.fastify %>:
84
- name: <%= displayNames.fastify %> (format + lint + typecheck)
98
+ name: <%= displayNames.fastify %> (format + lint + typecheck + audit)
85
99
  needs: changes
86
100
  if: needs.changes.outputs.<%= paths.fastify %> == 'true'
87
101
  runs-on: ubuntu-latest
@@ -93,7 +107,7 @@ jobs:
93
107
  <% if (pm === 'pnpm') { %>
94
108
  - uses: pnpm/action-setup@v4
95
109
  with:
96
- version: 9
110
+ version: 10
97
111
  <% } %>
98
112
  <% if (pm === 'bun') { %>
99
113
  - uses: oven-sh/setup-bun@v2
@@ -110,11 +124,12 @@ jobs:
110
124
  - run: <%= pm.exec %> prettier --check .
111
125
  - run: <%= pm.exec %> eslint .
112
126
  - run: <%= pm.exec %> tsc --noEmit
127
+ - run: <%= pm.audit %>
113
128
  <% } %>
114
129
  <% if (components.includes('frontend')) { %>
115
130
 
116
131
  <%= paths.frontend %>:
117
- name: <%= displayNames.frontend %> (format + lint + typecheck)
132
+ name: <%= displayNames.frontend %> (format + lint + typecheck + audit)
118
133
  needs: changes
119
134
  if: needs.changes.outputs.<%= paths.frontend %> == 'true'
120
135
  runs-on: ubuntu-latest
@@ -126,7 +141,7 @@ jobs:
126
141
  <% if (pm === 'pnpm') { %>
127
142
  - uses: pnpm/action-setup@v4
128
143
  with:
129
- version: 9
144
+ version: 10
130
145
  <% } %>
131
146
  <% if (pm === 'bun') { %>
132
147
  - uses: oven-sh/setup-bun@v2
@@ -142,6 +157,7 @@ jobs:
142
157
  - run: <%= pm.exec %> prettier --check .
143
158
  - run: <%= pm.exec %> eslint 'src/**/*.{ts,tsx}'
144
159
  - run: <%= pm.exec %> tsc --noEmit
160
+ - run: <%= pm.audit %>
145
161
  <% } %>
146
162
  <% if (components.includes('mobile')) { %>
147
163
 
@@ -166,7 +182,7 @@ jobs:
166
182
  <% if (components.includes('e2e')) { %>
167
183
 
168
184
  <%= paths.e2e %>:
169
- name: <%= displayNames.e2e %> (format + lint + typecheck)
185
+ name: <%= displayNames.e2e %> (format + lint + typecheck + audit)
170
186
  needs: changes
171
187
  if: needs.changes.outputs.<%= paths.e2e %> == 'true'
172
188
  runs-on: ubuntu-latest
@@ -178,7 +194,7 @@ jobs:
178
194
  <% if (pm === 'pnpm') { %>
179
195
  - uses: pnpm/action-setup@v4
180
196
  with:
181
- version: 9
197
+ version: 10
182
198
  <% } %>
183
199
  <% if (pm === 'bun') { %>
184
200
  - uses: oven-sh/setup-bun@v2
@@ -194,6 +210,7 @@ jobs:
194
210
  - run: <%= pm.exec %> prettier --check .
195
211
  - run: <%= pm.exec %> eslint '**/*.ts'
196
212
  - run: <%= pm.exec %> tsc --noEmit
213
+ - run: <%= pm.audit %>
197
214
  <% } %>
198
215
  <% if (components.includes('infra')) { %>
199
216
 
@@ -31,10 +31,11 @@ STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACMR)
31
31
 
32
32
  <%= pathsUpper.fastapi %>_PY=$(echo "$STAGED_FILES" | grep '^<%= paths.fastapi %>/.*\.py$' || true)
33
33
  if [ -n "$<%= pathsUpper.fastapi %>_PY" ]; then
34
- echo "Formatting & linting <%= paths.fastapi %>..."
34
+ echo "Running quality gates for <%= paths.fastapi %>..."
35
35
  cd <%= paths.fastapi %>
36
36
  echo "$<%= pathsUpper.fastapi %>_PY" | sed 's|^<%= paths.fastapi %>/||' | xargs uv run ruff format
37
37
  echo "$<%= pathsUpper.fastapi %>_PY" | sed 's|^<%= paths.fastapi %>/||' | xargs uv run ruff check --fix
38
+ uv run mypy
38
39
  cd ..
39
40
  echo "$<%= pathsUpper.fastapi %>_PY" | xargs git add
40
41
  fi