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 +10 -10
- package/dist/{baseline-O25CAKIL.js → baseline-2PBT5JBT.js} +2 -2
- package/dist/{chunk-B7PW6QO7.js → chunk-N66CVDEV.js} +37 -0
- package/dist/{chunk-RFHLWYJ4.js → chunk-NPBLDU4H.js} +2 -2
- package/dist/index.js +37 -10
- package/dist/{utils-X2P47QNN.js → utils-QQUKUZKQ.js} +3 -1
- package/package.json +1 -1
- package/src/templates/README.md.ejs +2 -2
- package/src/templates/ci.yml.ejs +8 -1
- package/src/templates/docker-compose.yml.ejs +3 -12
- package/src/templates/pre-commit.ejs +14 -0
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` |
|
|
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
|
|
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
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: "
|
|
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-
|
|
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
|
|
1488
|
-
if (
|
|
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:
|
|
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
|
-
|
|
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
|
|
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(
|
|
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-
|
|
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.
|
|
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'] %>/** |
|
|
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
|
|
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')) { %>
|
package/src/templates/ci.yml.ejs
CHANGED
|
@@ -395,7 +395,7 @@ jobs:
|
|
|
395
395
|
<% for (const inst of adminPanelInstances) { %>
|
|
396
396
|
|
|
397
397
|
<%= inst.path %>:
|
|
398
|
-
name: <%= inst.display %>
|
|
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:
|
|
171
|
+
timeout: 5s
|
|
181
172
|
retries: 3
|
|
182
|
-
start_period:
|
|
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
|
+
<% } %>
|