create-projx 1.7.4 → 1.7.6

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
@@ -86,16 +86,16 @@ If this saves you even one hour, it's already paid for itself. (It's free.)
86
86
 
87
87
  ## What you get
88
88
 
89
- | Component | Stack | What it gives you |
90
- | ------------- | ------------------------------------------------------------- | -------------------------------------------------------------------------- |
91
- | `fastapi` | Python, SQLAlchemy, Alembic | Auto-entity CRUD, JWT auth, migrations, OpenAPI docs |
92
- | `fastify` | Node.js, Prisma / Drizzle / Sequelize / TypeORM, TypeBox | Auto-entity CRUD, JWT auth, typed schemas, OpenAPI docs |
93
- | `express` | Express 5, TypeScript, Prisma / Drizzle / Sequelize / TypeORM | Auto-entity CRUD, JWT auth, validation, security middleware, health checks |
94
- | `frontend` | React 19, TypeScript, Vite | Auth, theming, design tokens, light/dark mode |
95
- | `mobile` | Flutter, Riverpod, GoRouter | Auth, biometric, theming, GoRouter shell |
96
- | `e2e` | Playwright | Page object model, auth fixtures, accessibility scans |
97
- | `infra` | Terraform, AWS | EKS, RDS, VPC, ALB, CodePipeline, multi-environment |
98
- | `admin-panel` | Directus (Docker), Postgres | Instant admin UI + REST/GraphQL over your DB, internal-only behind nginx |
89
+ | Component | Stack | What it gives you |
90
+ | ------------- | ------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
91
+ | `fastapi` | Python, SQLAlchemy, Alembic | Auto-entity CRUD, JWT auth, migrations, OpenAPI docs |
92
+ | `fastify` | Node.js, Prisma / Drizzle / Sequelize / TypeORM, TypeBox | Auto-entity CRUD, JWT auth, typed schemas, OpenAPI docs |
93
+ | `express` | Express 5, TypeScript, Prisma / Drizzle / Sequelize / TypeORM | Auto-entity CRUD, JWT auth, validation, security middleware, health checks |
94
+ | `frontend` | React 19, TypeScript, Vite | Auth, theming, design tokens, light/dark mode |
95
+ | `mobile` | Flutter, Riverpod, GoRouter | Auth, biometric, theming, GoRouter shell |
96
+ | `e2e` | Playwright | Page object model, auth fixtures, accessibility scans |
97
+ | `infra` | Terraform, AWS | EKS, RDS, VPC, ALB, CodePipeline, multi-environment |
98
+ | `admin-panel` | Go, HTMX (Docker), Postgres | Auth-gated table browser over any Postgres, read-only by default, internal-only behind nginx |
99
99
 
100
100
  Plus, in every project: Docker Compose for dev + prod, GitHub Actions CI per component (path-filtered), pre-commit hooks, secret detection, VS Code settings, and 80% test coverage enforced.
101
101
 
@@ -8,8 +8,8 @@ import {
8
8
  matchesSkip,
9
9
  saveBaselineRef,
10
10
  writeTemplateToDir
11
- } from "./chunk-RFHLWYJ4.js";
12
- import "./chunk-B7PW6QO7.js";
11
+ } from "./chunk-NPBLDU4H.js";
12
+ import "./chunk-N66CVDEV.js";
13
13
  export {
14
14
  BASELINE_REF,
15
15
  applyTemplate,
@@ -26,6 +26,42 @@ var COMPONENTS = [
26
26
  "infra",
27
27
  "admin-panel"
28
28
  ];
29
+ function editDistance(a, b) {
30
+ const rows = a.length + 1;
31
+ const cols = b.length + 1;
32
+ const dist = Array.from(
33
+ { length: rows },
34
+ () => new Array(cols).fill(0)
35
+ );
36
+ for (let i = 0; i < rows; i++) dist[i][0] = i;
37
+ for (let j = 0; j < cols; j++) dist[0][j] = j;
38
+ for (let i = 1; i < rows; i++) {
39
+ for (let j = 1; j < cols; j++) {
40
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
41
+ dist[i][j] = Math.min(
42
+ dist[i - 1][j] + 1,
43
+ dist[i][j - 1] + 1,
44
+ dist[i - 1][j - 1] + cost
45
+ );
46
+ }
47
+ }
48
+ return dist[a.length][b.length];
49
+ }
50
+ function suggestComponent(input) {
51
+ const needle = input.toLowerCase();
52
+ let best = null;
53
+ let bestDistance = Infinity;
54
+ for (const c of COMPONENTS) {
55
+ const d = editDistance(needle, c);
56
+ if (d < bestDistance) {
57
+ bestDistance = d;
58
+ best = c;
59
+ }
60
+ }
61
+ if (best === null) return null;
62
+ const maxAllowed = Math.min(3, Math.floor(best.length / 2));
63
+ return bestDistance <= maxAllowed ? best : null;
64
+ }
29
65
  var PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
30
66
  var ORM_PROVIDERS = [
31
67
  "prisma",
@@ -564,6 +600,7 @@ export {
564
600
  REPO,
565
601
  REPO_URL,
566
602
  COMPONENTS,
603
+ suggestComponent,
567
604
  PACKAGE_MANAGERS,
568
605
  ORM_PROVIDERS,
569
606
  pmCommands,
@@ -13,7 +13,7 @@ import {
13
13
  toSnake,
14
14
  upsertComponentMarker,
15
15
  writeProjxConfig
16
- } from "./chunk-B7PW6QO7.js";
16
+ } from "./chunk-N66CVDEV.js";
17
17
 
18
18
  // src/baseline.ts
19
19
  import { existsSync, writeFileSync, unlinkSync } from "fs";
@@ -128,7 +128,7 @@ function generateVscodeSettings(vars) {
128
128
  // src/baseline.ts
129
129
  var BASELINE_REF = "refs/projx/baseline";
130
130
  async function migrateComponentMarkers(cwd, components, componentPaths, applyDefaults) {
131
- const { readComponentMarker: readComponentMarker2, writeComponentMarker } = await import("./utils-X2P47QNN.js");
131
+ const { readComponentMarker: readComponentMarker2, writeComponentMarker } = await import("./utils-QQUKUZKQ.js");
132
132
  for (const component of components) {
133
133
  const dir = componentPaths[component];
134
134
  const markerDir = join2(cwd, dir);
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  matchesSkip,
10
10
  saveBaselineRef,
11
11
  writeTemplateToDir
12
- } from "./chunk-RFHLWYJ4.js";
12
+ } from "./chunk-NPBLDU4H.js";
13
13
  import {
14
14
  COMPONENTS,
15
15
  COMPONENT_MARKER,
@@ -32,11 +32,12 @@ import {
32
32
  readFileOrNull,
33
33
  readProjxConfig,
34
34
  render,
35
+ suggestComponent,
35
36
  toKebab,
36
37
  toSnake,
37
38
  writeComponentMarker,
38
39
  writeProjxConfig
39
- } from "./chunk-B7PW6QO7.js";
40
+ } from "./chunk-N66CVDEV.js";
40
41
 
41
42
  // src/index.ts
42
43
  import { existsSync as existsSync11 } from "fs";
@@ -386,7 +387,7 @@ var LABELS = {
386
387
  infra: { label: "Infrastructure", hint: "Terraform + AWS" },
387
388
  "admin-panel": {
388
389
  label: "Admin Panel",
389
- hint: "Directus \u2014 instant admin over Postgres (Docker)"
390
+ hint: "Go + HTMX \u2014 auth-gated table browser over any Postgres"
390
391
  }
391
392
  };
392
393
  var DEFAULTS = ["fastify", "frontend", "e2e"];
@@ -902,7 +903,7 @@ function hasUncommittedChanges(cwd) {
902
903
  async function findPinnedFilesWithUpdates(cwd, repoDir, components, componentPaths, vars, version, componentSkips, rootSkip) {
903
904
  const { mkdtemp: mkdtemp2, rm: rm2, readFile: readFile8 } = await import("fs/promises");
904
905
  const { tmpdir: tmpdir2 } = await import("os");
905
- const { writeTemplateToDir: writeTemplateToDir2 } = await import("./baseline-O25CAKIL.js");
906
+ const { writeTemplateToDir: writeTemplateToDir2 } = await import("./baseline-2PBT5JBT.js");
906
907
  const config = await readProjxConfig(cwd);
907
908
  const rootPinned = Array.isArray(config.skip) ? config.skip : [];
908
909
  const componentPinned = [];
@@ -1484,13 +1485,13 @@ async function scanDirectory(dir, relPath) {
1484
1485
  evidence: "Terraform .tf files found"
1485
1486
  });
1486
1487
  }
1487
- const dockerfile = await readFileOrNull(join5(dir, "Dockerfile"));
1488
- if (dockerfile && /^FROM\s+directus\/directus/m.test(dockerfile)) {
1488
+ const goMod = await readFileOrNull(join5(dir, "go.mod"));
1489
+ if (goMod && /^module\s+adminpanel\b/m.test(goMod)) {
1489
1490
  results.push({
1490
1491
  component: "admin-panel",
1491
1492
  directory: relPath,
1492
1493
  confidence: "high",
1493
- evidence: "Dockerfile builds from directus/directus image"
1494
+ evidence: 'Go module "adminpanel" found'
1494
1495
  });
1495
1496
  }
1496
1497
  return results;
@@ -4228,7 +4229,20 @@ function parseArgs(argv = process.argv.slice(2)) {
4228
4229
  if (arg === "--components") {
4229
4230
  const val = args[++i];
4230
4231
  if (val) {
4231
- options.components = val.split(",").filter((c) => COMPONENTS.includes(c));
4232
+ const requested = val.split(",").map((c) => c.trim());
4233
+ const invalid = requested.filter(
4234
+ (c) => !COMPONENTS.includes(c)
4235
+ );
4236
+ if (invalid.length > 0) {
4237
+ const hints = invalid.map((c) => {
4238
+ const guess = suggestComponent(c);
4239
+ return guess ? `${c} (did you mean ${guess}?)` : c;
4240
+ }).join(", ");
4241
+ throw new Error(
4242
+ `Invalid --components: ${hints}. Available: ${COMPONENTS.join(", ")}`
4243
+ );
4244
+ }
4245
+ options.components = requested;
4232
4246
  }
4233
4247
  continue;
4234
4248
  }
@@ -4361,14 +4375,27 @@ async function main() {
4361
4375
  return;
4362
4376
  }
4363
4377
  if (command === "add") {
4364
- const components = extraArgs.filter(
4378
+ const positionals = extraArgs.filter((a) => !a.startsWith("-"));
4379
+ const components = positionals.filter(
4365
4380
  (c) => COMPONENTS.includes(c)
4366
4381
  );
4382
+ const unknown = positionals.filter(
4383
+ (c) => !COMPONENTS.includes(c)
4384
+ );
4385
+ if (unknown.length > 0) {
4386
+ for (const u of unknown) {
4387
+ const guess = suggestComponent(u);
4388
+ console.error(
4389
+ guess ? `Error: unknown component ${u} \u2014 did you mean ${guess}?` : `Error: unknown component ${u}. Available: ${COMPONENTS.join(", ")}`
4390
+ );
4391
+ }
4392
+ process.exit(2);
4393
+ }
4367
4394
  if (components.length === 0) {
4368
4395
  console.error(
4369
4396
  `Error: specify components to add. Available: ${COMPONENTS.join(", ")}`
4370
4397
  );
4371
- process.exit(1);
4398
+ process.exit(2);
4372
4399
  }
4373
4400
  const customName = extraArgs.find((a) => a.startsWith("--name="))?.slice("--name=".length);
4374
4401
  if (customName && components.length > 1) {
@@ -29,13 +29,14 @@ import {
29
29
  replaceInDir,
30
30
  replaceInFile,
31
31
  sharedTemplateDir,
32
+ suggestComponent,
32
33
  toKebab,
33
34
  toSnake,
34
35
  toTitle,
35
36
  upsertComponentMarker,
36
37
  writeComponentMarker,
37
38
  writeProjxConfig
38
- } from "./chunk-B7PW6QO7.js";
39
+ } from "./chunk-N66CVDEV.js";
39
40
  export {
40
41
  COMPONENTS,
41
42
  COMPONENT_MARKER,
@@ -67,6 +68,7 @@ export {
67
68
  replaceInDir,
68
69
  replaceInFile,
69
70
  sharedTemplateDir,
71
+ suggestComponent,
70
72
  toKebab,
71
73
  toSnake,
72
74
  toTitle,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-projx",
3
- "version": "1.7.4",
3
+ "version": "1.7.6",
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": {
@@ -28,7 +28,7 @@ Scaffolded with [Projx](https://github.com/ukanhaupa/projx).
28
28
  | **<%= paths.infra %>/** | Terraform, AWS (EKS, RDS, VPC, CodePipeline) |
29
29
  <% } %>
30
30
  <% if (components.includes('admin-panel')) { %>
31
- | **<%= paths['admin-panel'] %>/** | Directus admin panel over Postgres |
31
+ | **<%= paths['admin-panel'] %>/** | Go + HTMX admin panel — auth-gated table browser over Postgres |
32
32
  <% } %>
33
33
  | **Identity** | OIDC / JWT |
34
34
  | **Containers** | Docker, Docker Compose |
@@ -91,7 +91,7 @@ cd <%= paths.mobile %> && cp .env.example .env && flutter pub get && flutter run
91
91
  ### <%= paths['admin-panel'] %>/
92
92
 
93
93
  ```bash
94
- cd <%= paths['admin-panel'] %> && cp .env.example .env # set KEY, SECRET, ADMIN_PASSWORD, DB_*
94
+ cd <%= paths['admin-panel'] %> && cp .env.example .env # set DATABASE_URL, SESSION_SECRET, ADMIN_EMAIL, ADMIN_PASSWORD
95
95
  docker compose up --build <%= paths['admin-panel'] %>
96
96
  ```
97
97
  <% if (components.includes('frontend')) { %>
@@ -395,7 +395,7 @@ jobs:
395
395
  <% for (const inst of adminPanelInstances) { %>
396
396
 
397
397
  <%= inst.path %>:
398
- name: <%= inst.display %> (docker build)
398
+ name: <%= inst.display %>
399
399
  needs: changes
400
400
  if: github.event_name == 'workflow_dispatch' || needs.changes.outputs.<%= inst.path %> == 'true'
401
401
  runs-on: ubuntu-latest
@@ -406,5 +406,12 @@ jobs:
406
406
  - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
407
407
  with:
408
408
  path: ${{ env.WS }}
409
+ - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
410
+ with:
411
+ go-version: '1.26'
412
+ cache-dependency-path: ${{ env.WS }}/<%= inst.path %>/go.sum
413
+ - run: test -z "$(gofmt -l .)"
414
+ - run: go vet ./...
415
+ - run: go build ./...
409
416
  - run: docker build -t <%= inst.path %>:ci .
410
417
  <% } %>
@@ -164,22 +164,13 @@ services:
164
164
  - "8055"
165
165
  env_file:
166
166
  - ./<%= inst.path %>/.env
167
- volumes:
168
- - ./<%= inst.path %>/uploads:/directus/uploads
169
- - ./<%= inst.path %>/extensions:/directus/extensions
170
167
  restart: unless-stopped
171
168
  healthcheck:
172
- test:
173
- [
174
- "CMD",
175
- "node",
176
- "-e",
177
- "require('http').get('http://localhost:8055/server/health', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))",
178
- ]
169
+ test: ["CMD", "/admin", "healthcheck"]
179
170
  interval: 30s
180
- timeout: 10s
171
+ timeout: 5s
181
172
  retries: 3
182
- start_period: 20s
173
+ start_period: 10s
183
174
  networks:
184
175
  - app-network
185
176
  <% } %>
@@ -157,3 +157,17 @@ if [ -n "$<%= inst.upper %>_TF" ]; then
157
157
  fi
158
158
  fi
159
159
  <% } %>
160
+ <% for (const inst of adminPanelInstances) { %>
161
+
162
+ <%= inst.upper %>_GO=$(echo "$STAGED_FILES" | grep '^<%= inst.path %>/.*\.go$' || true)
163
+ if [ -n "$<%= inst.upper %>_GO" ]; then
164
+ if command -v go &> /dev/null; then
165
+ echo "Checking <%= inst.path %>..."
166
+ echo "$<%= inst.upper %>_GO" | sed 's|^<%= inst.path %>/||' | (cd <%= inst.path %> && xargs gofmt -w)
167
+ (cd <%= inst.path %> && go vet ./...)
168
+ echo "$<%= inst.upper %>_GO" | xargs git add
169
+ else
170
+ echo "Skipping <%= inst.path %> checks (go not installed)"
171
+ fi
172
+ fi
173
+ <% } %>