create-projx 1.7.0 → 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 +13 -35
- package/dist/{baseline-FHOZNS4D.js → baseline-FKCXQFRD.js} +2 -2
- package/dist/{chunk-IMZKHDIL.js → chunk-N4WD4VN3.js} +15 -18
- package/dist/{chunk-HAT7D4G2.js → chunk-OLPF7FAN.js} +1 -1
- package/dist/index.js +166 -385
- package/dist/{utils-BZGSJ7XZ.js → utils-4G3HNHES.js} +1 -1
- package/package.json +2 -3
- package/src/templates/README.md.ejs +1 -1
- package/src/addons/orms/drizzle/express/src/app.ts +0 -81
- package/src/addons/orms/drizzle/express/src/modules/_base/auto-routes.ts +0 -278
- package/src/addons/orms/drizzle/express/src/modules/_base/index.ts +0 -20
- package/src/addons/orms/drizzle/express/src/server.ts +0 -32
- package/src/addons/orms/drizzle/express/tests/app.test.ts +0 -24
- package/src/addons/orms/drizzle/express/vitest.config.ts +0 -20
- package/src/addons/orms/drizzle/fastify/src/app.ts +0 -90
- package/src/addons/orms/drizzle/fastify/src/modules/_base/auto-routes.ts +0 -268
- package/src/addons/orms/drizzle/fastify/src/modules/_base/index.ts +0 -20
- package/src/addons/orms/drizzle/fastify/tests/modules/app.test.ts +0 -20
- package/src/addons/orms/drizzle/fastify/vitest.config.ts +0 -31
- package/src/addons/orms/drizzle/gen-entity/express-router.ts +0 -21
- package/src/addons/orms/drizzle/gen-entity/express-test.ts +0 -61
- package/src/addons/orms/drizzle/gen-entity/fastify-router.ts +0 -19
- package/src/addons/orms/drizzle/gen-entity/fastify-test.ts +0 -87
- package/src/addons/orms/drizzle/manifest.json +0 -52
- package/src/addons/orms/drizzle/shared/drizzle.config.ts +0 -12
- package/src/addons/orms/drizzle/shared/src/db/client.ts +0 -17
- package/src/addons/orms/drizzle/shared/src/db/schema.ts +0 -14
- package/src/addons/orms/drizzle/shared/src/modules/_base/query-engine.ts +0 -115
- package/src/addons/orms/drizzle/shared/src/modules/_base/registry.ts +0 -15
- package/src/addons/orms/sequelize/express/src/app.ts +0 -82
- package/src/addons/orms/sequelize/express/src/modules/_base/auto-routes.ts +0 -226
- package/src/addons/orms/sequelize/express/src/modules/_base/index.ts +0 -20
- package/src/addons/orms/sequelize/express/src/server.ts +0 -32
- package/src/addons/orms/sequelize/express/tests/app.test.ts +0 -24
- package/src/addons/orms/sequelize/express/vitest.config.ts +0 -20
- package/src/addons/orms/sequelize/fastify/src/app.ts +0 -83
- package/src/addons/orms/sequelize/fastify/src/modules/_base/auto-routes.ts +0 -216
- package/src/addons/orms/sequelize/fastify/src/modules/_base/index.ts +0 -20
- package/src/addons/orms/sequelize/fastify/tests/modules/app.test.ts +0 -20
- package/src/addons/orms/sequelize/fastify/vitest.config.ts +0 -31
- package/src/addons/orms/sequelize/gen-entity/express-router.ts +0 -17
- package/src/addons/orms/sequelize/gen-entity/express-test.ts +0 -65
- package/src/addons/orms/sequelize/gen-entity/fastify-router.ts +0 -19
- package/src/addons/orms/sequelize/gen-entity/fastify-test.ts +0 -89
- package/src/addons/orms/sequelize/gen-entity/model.ts +0 -21
- package/src/addons/orms/sequelize/manifest.json +0 -53
- package/src/addons/orms/sequelize/shared/scripts/db-sync.ts +0 -14
- package/src/addons/orms/sequelize/shared/src/db/client.ts +0 -19
- package/src/addons/orms/sequelize/shared/src/models/index.ts +0 -9
- package/src/addons/orms/sequelize/shared/src/modules/_base/query-engine.ts +0 -101
- package/src/addons/orms/sequelize/shared/src/modules/_base/registry.ts +0 -15
- package/src/addons/orms/typeorm/express/src/app.ts +0 -82
- package/src/addons/orms/typeorm/express/src/modules/_base/auto-routes.ts +0 -249
- package/src/addons/orms/typeorm/express/src/modules/_base/index.ts +0 -19
- package/src/addons/orms/typeorm/express/src/server.ts +0 -43
- package/src/addons/orms/typeorm/express/tests/app.test.ts +0 -24
- package/src/addons/orms/typeorm/express/vitest.config.ts +0 -20
- package/src/addons/orms/typeorm/fastify/src/app.ts +0 -86
- package/src/addons/orms/typeorm/fastify/src/modules/_base/auto-routes.ts +0 -239
- package/src/addons/orms/typeorm/fastify/src/modules/_base/index.ts +0 -19
- package/src/addons/orms/typeorm/fastify/tests/modules/app.test.ts +0 -20
- package/src/addons/orms/typeorm/fastify/vitest.config.ts +0 -31
- package/src/addons/orms/typeorm/gen-entity/entity.ts +0 -21
- package/src/addons/orms/typeorm/gen-entity/express-router.ts +0 -17
- package/src/addons/orms/typeorm/gen-entity/express-test.ts +0 -66
- package/src/addons/orms/typeorm/gen-entity/fastify-router.ts +0 -19
- package/src/addons/orms/typeorm/gen-entity/fastify-test.ts +0 -89
- package/src/addons/orms/typeorm/manifest.json +0 -53
- package/src/addons/orms/typeorm/shared/scripts/db-sync.ts +0 -14
- package/src/addons/orms/typeorm/shared/src/db/data-source.ts +0 -21
- package/src/addons/orms/typeorm/shared/src/entities/index.ts +0 -8
- package/src/addons/orms/typeorm/shared/src/modules/_base/query-engine.ts +0 -94
- package/src/addons/orms/typeorm/shared/src/modules/_base/registry.ts +0 -15
- package/src/addons/orms/typeorm/shared/tsconfig.json +0 -16
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-N4WD4VN3.js";
|
|
13
13
|
import {
|
|
14
14
|
COMPONENTS,
|
|
15
15
|
COMPONENT_MARKER,
|
|
@@ -36,10 +36,10 @@ import {
|
|
|
36
36
|
toSnake,
|
|
37
37
|
writeComponentMarker,
|
|
38
38
|
writeProjxConfig
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-OLPF7FAN.js";
|
|
40
40
|
|
|
41
41
|
// src/index.ts
|
|
42
|
-
import { existsSync as
|
|
42
|
+
import { existsSync as existsSync11 } from "fs";
|
|
43
43
|
import { resolve } from "path";
|
|
44
44
|
|
|
45
45
|
// src/features.ts
|
|
@@ -58,8 +58,8 @@ function parseFeatureFlag(input) {
|
|
|
58
58
|
`Invalid feature flag: empty target in "${input}". Use "component[:instance],..." form.`
|
|
59
59
|
);
|
|
60
60
|
}
|
|
61
|
-
const parts = piece.split(":").map((
|
|
62
|
-
if (parts.length > 2 || parts.some((
|
|
61
|
+
const parts = piece.split(":").map((p10) => p10.trim());
|
|
62
|
+
if (parts.length > 2 || parts.some((p10) => !p10)) {
|
|
63
63
|
throw new Error(
|
|
64
64
|
`Invalid feature flag target "${piece}". Expected "component" or "component:instance".`
|
|
65
65
|
);
|
|
@@ -195,13 +195,45 @@ async function applyTarget(args2) {
|
|
|
195
195
|
`Target instance path ${args2.target.path} not found in ${args2.dest}.`
|
|
196
196
|
);
|
|
197
197
|
}
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
198
|
+
const orm = typeof args2.vars.orm === "string" ? args2.vars.orm : void 0;
|
|
199
|
+
const ormDir = orm ? join(stackDir, orm) : void 0;
|
|
200
|
+
const hasOrmDir = ormDir !== void 0 && existsSync(ormDir);
|
|
201
|
+
const ormPatchNames = /* @__PURE__ */ new Set();
|
|
202
|
+
if (hasOrmDir) {
|
|
203
|
+
const ormPatchesDir = join(ormDir, "patches");
|
|
204
|
+
if (existsSync(ormPatchesDir)) {
|
|
205
|
+
for (const f of await readdir(ormPatchesDir)) {
|
|
206
|
+
if (f.endsWith(".json")) ormPatchNames.add(f);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
for (const sub of ["files", join("common", "files")]) {
|
|
211
|
+
const filesDir = join(stackDir, sub);
|
|
212
|
+
if (existsSync(filesDir)) {
|
|
213
|
+
await renderFilesInto(filesDir, targetPath, args2.vars);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
for (const sub of ["patches", join("common", "patches")]) {
|
|
217
|
+
const patchesDir = join(stackDir, sub);
|
|
218
|
+
if (existsSync(patchesDir)) {
|
|
219
|
+
await applyPatches(
|
|
220
|
+
patchesDir,
|
|
221
|
+
targetPath,
|
|
222
|
+
args2.featureName,
|
|
223
|
+
ormPatchNames,
|
|
224
|
+
hasOrmDir
|
|
225
|
+
);
|
|
226
|
+
}
|
|
201
227
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
228
|
+
if (hasOrmDir) {
|
|
229
|
+
const ormFilesDir = join(ormDir, "files");
|
|
230
|
+
if (existsSync(ormFilesDir)) {
|
|
231
|
+
await renderFilesInto(ormFilesDir, targetPath, args2.vars);
|
|
232
|
+
}
|
|
233
|
+
const ormPatchesDir = join(ormDir, "patches");
|
|
234
|
+
if (existsSync(ormPatchesDir)) {
|
|
235
|
+
await applyPatches(ormPatchesDir, targetPath, args2.featureName);
|
|
236
|
+
}
|
|
205
237
|
}
|
|
206
238
|
const envKeys = args2.manifest.env?.[args2.target.component] ?? [];
|
|
207
239
|
if (envKeys.length > 0) {
|
|
@@ -238,15 +270,20 @@ async function collectFiles(root) {
|
|
|
238
270
|
await walk(root);
|
|
239
271
|
return out.sort();
|
|
240
272
|
}
|
|
241
|
-
async function applyPatches(patchesDir, targetPath, featureName) {
|
|
242
|
-
const files = (await readdir(patchesDir)).filter((f) => f.endsWith(".json")).sort();
|
|
273
|
+
async function applyPatches(patchesDir, targetPath, featureName, skipNames = /* @__PURE__ */ new Set(), tolerateMissingTarget = false) {
|
|
274
|
+
const files = (await readdir(patchesDir)).filter((f) => f.endsWith(".json")).filter((f) => !skipNames.has(f)).sort();
|
|
243
275
|
for (const file of files) {
|
|
244
276
|
const raw = await readFile(join(patchesDir, file), "utf-8");
|
|
245
277
|
const patch = JSON.parse(raw);
|
|
246
278
|
if (patch.type === "package-json") {
|
|
247
279
|
await applyPackageJsonPatch(targetPath, patch);
|
|
248
280
|
} else if (patch.type === "text") {
|
|
249
|
-
await applyTextPatch(
|
|
281
|
+
await applyTextPatch(
|
|
282
|
+
targetPath,
|
|
283
|
+
patch,
|
|
284
|
+
featureName,
|
|
285
|
+
tolerateMissingTarget
|
|
286
|
+
);
|
|
250
287
|
} else {
|
|
251
288
|
throw new Error(
|
|
252
289
|
`Unknown patch type in ${file}: ${patch.type}.`
|
|
@@ -268,22 +305,25 @@ async function applyPackageJsonPatch(targetPath, patch) {
|
|
|
268
305
|
}
|
|
269
306
|
await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
270
307
|
}
|
|
271
|
-
async function applyTextPatch(targetPath, patch, featureName) {
|
|
308
|
+
async function applyTextPatch(targetPath, patch, featureName, tolerateMissingTarget = false) {
|
|
272
309
|
const filePath = join(targetPath, patch.file);
|
|
273
310
|
if (!existsSync(filePath)) {
|
|
311
|
+
if (tolerateMissingTarget) return;
|
|
274
312
|
throw new Error(
|
|
275
313
|
`text patch failed: ${patch.file} not found in ${targetPath}.`
|
|
276
314
|
);
|
|
277
315
|
}
|
|
278
316
|
const content = await readFile(filePath, "utf-8");
|
|
279
|
-
const sentinel = sentinelFor(featureName, patch.anchor, patch.insert);
|
|
280
|
-
if (content.includes(sentinel)) return;
|
|
281
317
|
const idx = content.indexOf(patch.anchor);
|
|
282
318
|
if (idx === -1) {
|
|
283
319
|
throw new Error(
|
|
284
320
|
`text patch anchor "${patch.anchor}" not found in ${patch.file}.`
|
|
285
321
|
);
|
|
286
322
|
}
|
|
323
|
+
const lineStart = content.lastIndexOf("\n", idx) + 1;
|
|
324
|
+
const indent = content.slice(lineStart, idx).match(/^\s*/)?.[0] ?? "";
|
|
325
|
+
const sentinel = sentinelFor(featureName, patch.anchor, patch.insert, indent);
|
|
326
|
+
if (content.includes(sentinel)) return;
|
|
287
327
|
const insertWithSentinel = patch.insert + sentinel;
|
|
288
328
|
let next;
|
|
289
329
|
if (patch.position === "before") {
|
|
@@ -296,11 +336,11 @@ async function applyTextPatch(targetPath, patch, featureName) {
|
|
|
296
336
|
}
|
|
297
337
|
await writeFile(filePath, next);
|
|
298
338
|
}
|
|
299
|
-
function sentinelFor(feature, anchor, insert) {
|
|
339
|
+
function sentinelFor(feature, anchor, insert, indent = "") {
|
|
300
340
|
const hash = simpleHash(anchor + "|" + insert);
|
|
301
341
|
const isHash = anchor.includes("#");
|
|
302
342
|
const open = isHash ? "# " : "// ";
|
|
303
|
-
return `${open}projx-feature: ${feature} ${hash}
|
|
343
|
+
return `${indent}${open}projx-feature: ${feature} ${hash}
|
|
304
344
|
`;
|
|
305
345
|
}
|
|
306
346
|
function simpleHash(s) {
|
|
@@ -442,8 +482,8 @@ async function scaffold(opts, dest, localRepo) {
|
|
|
442
482
|
if (opts.git) {
|
|
443
483
|
exec("git init", dest);
|
|
444
484
|
}
|
|
445
|
-
const
|
|
446
|
-
|
|
485
|
+
const spinner6 = p2.spinner();
|
|
486
|
+
spinner6.start("Scaffolding project");
|
|
447
487
|
await applyTemplate(
|
|
448
488
|
dest,
|
|
449
489
|
repoDir,
|
|
@@ -455,7 +495,7 @@ async function scaffold(opts, dest, localRepo) {
|
|
|
455
495
|
void 0,
|
|
456
496
|
true
|
|
457
497
|
);
|
|
458
|
-
|
|
498
|
+
spinner6.stop("Scaffold complete.");
|
|
459
499
|
if (opts.features && Object.keys(opts.features).length > 0) {
|
|
460
500
|
const featSpinner = p2.spinner();
|
|
461
501
|
featSpinner.start("Applying features");
|
|
@@ -498,23 +538,23 @@ async function installDeps(dest, components, pm) {
|
|
|
498
538
|
const cmds = pmCommands(pm);
|
|
499
539
|
const pmBin = pm === "bun" ? "bun" : pm;
|
|
500
540
|
for (const component of components) {
|
|
501
|
-
const
|
|
541
|
+
const spinner6 = p2.spinner();
|
|
502
542
|
try {
|
|
503
543
|
switch (component) {
|
|
504
544
|
case "fastapi":
|
|
505
545
|
if (hasCommand("uv")) {
|
|
506
|
-
|
|
546
|
+
spinner6.start("Installing FastAPI dependencies (uv sync)");
|
|
507
547
|
exec("uv sync --all-extras", join2(dest, "fastapi"));
|
|
508
|
-
|
|
548
|
+
spinner6.stop("FastAPI dependencies installed.");
|
|
509
549
|
} else {
|
|
510
550
|
p2.log.warn("uv not found \u2014 run 'cd fastapi && uv sync' manually.");
|
|
511
551
|
}
|
|
512
552
|
break;
|
|
513
553
|
case "fastify":
|
|
514
554
|
if (hasCommand(pmBin)) {
|
|
515
|
-
|
|
555
|
+
spinner6.start(`Installing Fastify dependencies (${cmds.install})`);
|
|
516
556
|
exec(cmds.install, join2(dest, "fastify"));
|
|
517
|
-
|
|
557
|
+
spinner6.stop("Fastify dependencies installed.");
|
|
518
558
|
} else {
|
|
519
559
|
p2.log.warn(
|
|
520
560
|
`${pm} not found \u2014 run 'cd fastify && ${cmds.install}' manually.`
|
|
@@ -523,9 +563,9 @@ async function installDeps(dest, components, pm) {
|
|
|
523
563
|
break;
|
|
524
564
|
case "express":
|
|
525
565
|
if (hasCommand(pmBin)) {
|
|
526
|
-
|
|
566
|
+
spinner6.start(`Installing Express dependencies (${cmds.install})`);
|
|
527
567
|
exec(cmds.install, join2(dest, "express"));
|
|
528
|
-
|
|
568
|
+
spinner6.stop("Express dependencies installed.");
|
|
529
569
|
} else {
|
|
530
570
|
p2.log.warn(
|
|
531
571
|
`${pm} not found \u2014 run 'cd express && ${cmds.install}' manually.`
|
|
@@ -534,9 +574,9 @@ async function installDeps(dest, components, pm) {
|
|
|
534
574
|
break;
|
|
535
575
|
case "frontend":
|
|
536
576
|
if (hasCommand(pmBin)) {
|
|
537
|
-
|
|
577
|
+
spinner6.start(`Installing Frontend dependencies (${cmds.install})`);
|
|
538
578
|
exec(cmds.install, join2(dest, "frontend"));
|
|
539
|
-
|
|
579
|
+
spinner6.stop("Frontend dependencies installed.");
|
|
540
580
|
} else {
|
|
541
581
|
p2.log.warn(
|
|
542
582
|
`${pm} not found \u2014 run 'cd frontend && ${cmds.install}' manually.`
|
|
@@ -545,9 +585,9 @@ async function installDeps(dest, components, pm) {
|
|
|
545
585
|
break;
|
|
546
586
|
case "e2e":
|
|
547
587
|
if (hasCommand(pmBin)) {
|
|
548
|
-
|
|
588
|
+
spinner6.start(`Installing E2E dependencies (${cmds.install})`);
|
|
549
589
|
exec(cmds.install, join2(dest, "e2e"));
|
|
550
|
-
|
|
590
|
+
spinner6.stop("E2E dependencies installed.");
|
|
551
591
|
} else {
|
|
552
592
|
p2.log.warn(
|
|
553
593
|
`${pm} not found \u2014 run 'cd e2e && ${cmds.install}' manually.`
|
|
@@ -556,9 +596,9 @@ async function installDeps(dest, components, pm) {
|
|
|
556
596
|
break;
|
|
557
597
|
case "mobile":
|
|
558
598
|
if (hasCommand("flutter")) {
|
|
559
|
-
|
|
599
|
+
spinner6.start("Installing Flutter dependencies");
|
|
560
600
|
exec("flutter pub get", join2(dest, "mobile"));
|
|
561
|
-
|
|
601
|
+
spinner6.stop("Flutter dependencies installed.");
|
|
562
602
|
} else {
|
|
563
603
|
p2.log.warn(
|
|
564
604
|
"Flutter not found \u2014 run 'cd mobile && flutter pub get' manually."
|
|
@@ -569,7 +609,7 @@ async function installDeps(dest, components, pm) {
|
|
|
569
609
|
break;
|
|
570
610
|
}
|
|
571
611
|
} catch {
|
|
572
|
-
|
|
612
|
+
spinner6.stop(`Failed to install ${component} dependencies.`);
|
|
573
613
|
}
|
|
574
614
|
}
|
|
575
615
|
}
|
|
@@ -704,8 +744,8 @@ async function update(cwd, localRepo) {
|
|
|
704
744
|
nameOverrides,
|
|
705
745
|
orm: raw.orm ?? "prisma"
|
|
706
746
|
};
|
|
707
|
-
const
|
|
708
|
-
|
|
747
|
+
const spinner6 = p3.spinner();
|
|
748
|
+
spinner6.start("Applying template update");
|
|
709
749
|
const rootSkip = Array.isArray(raw.skip) ? raw.skip : [];
|
|
710
750
|
const isLegacyMigration = !raw.defaultsApplied;
|
|
711
751
|
if (isLegacyMigration) {
|
|
@@ -725,7 +765,7 @@ async function update(cwd, localRepo) {
|
|
|
725
765
|
isLegacyMigration,
|
|
726
766
|
extraInstances
|
|
727
767
|
);
|
|
728
|
-
|
|
768
|
+
spinner6.stop("Template applied.");
|
|
729
769
|
const pinnedUpdates = await findPinnedFilesWithUpdates(
|
|
730
770
|
cwd,
|
|
731
771
|
repoDir,
|
|
@@ -811,7 +851,7 @@ function hasUncommittedChanges(cwd) {
|
|
|
811
851
|
async function findPinnedFilesWithUpdates(cwd, repoDir, components, componentPaths, vars, version, componentSkips, rootSkip) {
|
|
812
852
|
const { mkdtemp: mkdtemp2, rm: rm2, readFile: readFile8 } = await import("fs/promises");
|
|
813
853
|
const { tmpdir: tmpdir2 } = await import("os");
|
|
814
|
-
const { writeTemplateToDir: writeTemplateToDir2 } = await import("./baseline-
|
|
854
|
+
const { writeTemplateToDir: writeTemplateToDir2 } = await import("./baseline-FKCXQFRD.js");
|
|
815
855
|
const config = await readProjxConfig(cwd);
|
|
816
856
|
const rootPinned = Array.isArray(config.skip) ? config.skip : [];
|
|
817
857
|
const componentPinned = [];
|
|
@@ -1072,8 +1112,8 @@ async function add(cwd, newComponents, localRepo, skipInstall = false, customNam
|
|
|
1072
1112
|
await readFile4(join4(repoDir, "cli/package.json"), "utf-8")
|
|
1073
1113
|
);
|
|
1074
1114
|
const version = pkg.version;
|
|
1075
|
-
const
|
|
1076
|
-
|
|
1115
|
+
const spinner6 = p4.spinner();
|
|
1116
|
+
spinner6.start("Adding components");
|
|
1077
1117
|
await writeTemplateToDir(
|
|
1078
1118
|
cwd,
|
|
1079
1119
|
repoDir,
|
|
@@ -1083,7 +1123,7 @@ async function add(cwd, newComponents, localRepo, skipInstall = false, customNam
|
|
|
1083
1123
|
version,
|
|
1084
1124
|
{ realCwd: cwd }
|
|
1085
1125
|
);
|
|
1086
|
-
|
|
1126
|
+
spinner6.stop("Components added.");
|
|
1087
1127
|
if (!skipInstall) {
|
|
1088
1128
|
await installDeps2(
|
|
1089
1129
|
cwd,
|
|
@@ -1148,14 +1188,14 @@ async function addInstance(cwd, type, customName, config, existing, localRepo, s
|
|
|
1148
1188
|
"docker-compose.yml"
|
|
1149
1189
|
]);
|
|
1150
1190
|
const rawSkip = Array.isArray(config.skip) ? config.skip : [];
|
|
1151
|
-
const rootSkip = rawSkip.filter((
|
|
1191
|
+
const rootSkip = rawSkip.filter((p10) => !INSTANCE_AWARE_ROOT.has(p10));
|
|
1152
1192
|
const componentSkips = {};
|
|
1153
1193
|
for (const inst of existingInstances) {
|
|
1154
1194
|
const m = await readComponentMarker(join4(cwd, inst.path));
|
|
1155
1195
|
if (m?.skip && m.skip.length > 0) componentSkips[inst.type] = m.skip;
|
|
1156
1196
|
}
|
|
1157
|
-
const
|
|
1158
|
-
|
|
1197
|
+
const spinner6 = p4.spinner();
|
|
1198
|
+
spinner6.start(`Scaffolding ${customName}/`);
|
|
1159
1199
|
const result = await applyTemplate(
|
|
1160
1200
|
cwd,
|
|
1161
1201
|
repoDir,
|
|
@@ -1169,7 +1209,7 @@ async function addInstance(cwd, type, customName, config, existing, localRepo, s
|
|
|
1169
1209
|
[newInstance],
|
|
1170
1210
|
[newInstance]
|
|
1171
1211
|
);
|
|
1172
|
-
|
|
1212
|
+
spinner6.stop(`Scaffolded ${customName}/.`);
|
|
1173
1213
|
if (result.status === "merged") {
|
|
1174
1214
|
p4.log.success(
|
|
1175
1215
|
`${result.mergedFiles?.length ?? 0} root file(s) merged cleanly.`
|
|
@@ -1205,25 +1245,25 @@ async function installDeps2(dest, instances, pm) {
|
|
|
1205
1245
|
const pmBin = pm === "bun" ? "bun" : pm;
|
|
1206
1246
|
for (const { type, path } of instances) {
|
|
1207
1247
|
const dir = join4(dest, path);
|
|
1208
|
-
const
|
|
1248
|
+
const spinner6 = p4.spinner();
|
|
1209
1249
|
try {
|
|
1210
1250
|
switch (type) {
|
|
1211
1251
|
case "fastapi":
|
|
1212
1252
|
if (hasCommand("uv")) {
|
|
1213
|
-
|
|
1253
|
+
spinner6.start(`Installing FastAPI dependencies (${path}/)`);
|
|
1214
1254
|
exec("uv sync --all-extras", dir);
|
|
1215
|
-
|
|
1255
|
+
spinner6.stop(`FastAPI dependencies installed (${path}/).`);
|
|
1216
1256
|
} else {
|
|
1217
1257
|
p4.log.warn(`uv not found \u2014 run 'cd ${path} && uv sync' manually.`);
|
|
1218
1258
|
}
|
|
1219
1259
|
break;
|
|
1220
1260
|
case "fastify":
|
|
1221
1261
|
if (hasCommand(pmBin)) {
|
|
1222
|
-
|
|
1262
|
+
spinner6.start(
|
|
1223
1263
|
`Installing Fastify dependencies (${path}/, ${cmds.install})`
|
|
1224
1264
|
);
|
|
1225
1265
|
exec(cmds.install, dir);
|
|
1226
|
-
|
|
1266
|
+
spinner6.stop(`Fastify dependencies installed (${path}/).`);
|
|
1227
1267
|
} else {
|
|
1228
1268
|
p4.log.warn(
|
|
1229
1269
|
`${pm} not found \u2014 run 'cd ${path} && ${cmds.install}' manually.`
|
|
@@ -1232,11 +1272,11 @@ async function installDeps2(dest, instances, pm) {
|
|
|
1232
1272
|
break;
|
|
1233
1273
|
case "express":
|
|
1234
1274
|
if (hasCommand(pmBin)) {
|
|
1235
|
-
|
|
1275
|
+
spinner6.start(
|
|
1236
1276
|
`Installing Express dependencies (${path}/, ${cmds.install})`
|
|
1237
1277
|
);
|
|
1238
1278
|
exec(cmds.install, dir);
|
|
1239
|
-
|
|
1279
|
+
spinner6.stop(`Express dependencies installed (${path}/).`);
|
|
1240
1280
|
} else {
|
|
1241
1281
|
p4.log.warn(
|
|
1242
1282
|
`${pm} not found \u2014 run 'cd ${path} && ${cmds.install}' manually.`
|
|
@@ -1245,11 +1285,11 @@ async function installDeps2(dest, instances, pm) {
|
|
|
1245
1285
|
break;
|
|
1246
1286
|
case "frontend":
|
|
1247
1287
|
if (hasCommand(pmBin)) {
|
|
1248
|
-
|
|
1288
|
+
spinner6.start(
|
|
1249
1289
|
`Installing Frontend dependencies (${path}/, ${cmds.install})`
|
|
1250
1290
|
);
|
|
1251
1291
|
exec(cmds.install, dir);
|
|
1252
|
-
|
|
1292
|
+
spinner6.stop(`Frontend dependencies installed (${path}/).`);
|
|
1253
1293
|
} else {
|
|
1254
1294
|
p4.log.warn(
|
|
1255
1295
|
`${pm} not found \u2014 run 'cd ${path} && ${cmds.install}' manually.`
|
|
@@ -1258,11 +1298,11 @@ async function installDeps2(dest, instances, pm) {
|
|
|
1258
1298
|
break;
|
|
1259
1299
|
case "e2e":
|
|
1260
1300
|
if (hasCommand(pmBin)) {
|
|
1261
|
-
|
|
1301
|
+
spinner6.start(
|
|
1262
1302
|
`Installing E2E dependencies (${path}/, ${cmds.install})`
|
|
1263
1303
|
);
|
|
1264
1304
|
exec(cmds.install, dir);
|
|
1265
|
-
|
|
1305
|
+
spinner6.stop(`E2E dependencies installed (${path}/).`);
|
|
1266
1306
|
} else {
|
|
1267
1307
|
p4.log.warn(
|
|
1268
1308
|
`${pm} not found \u2014 run 'cd ${path} && ${cmds.install}' manually.`
|
|
@@ -1271,9 +1311,9 @@ async function installDeps2(dest, instances, pm) {
|
|
|
1271
1311
|
break;
|
|
1272
1312
|
case "mobile":
|
|
1273
1313
|
if (hasCommand("flutter")) {
|
|
1274
|
-
|
|
1314
|
+
spinner6.start(`Installing Flutter dependencies (${path}/)`);
|
|
1275
1315
|
exec("flutter pub get", dir);
|
|
1276
|
-
|
|
1316
|
+
spinner6.stop(`Flutter dependencies installed (${path}/).`);
|
|
1277
1317
|
} else {
|
|
1278
1318
|
p4.log.warn(
|
|
1279
1319
|
`Flutter not found \u2014 run 'cd ${path} && flutter pub get' manually.`
|
|
@@ -1284,7 +1324,7 @@ async function installDeps2(dest, instances, pm) {
|
|
|
1284
1324
|
break;
|
|
1285
1325
|
}
|
|
1286
1326
|
} catch {
|
|
1287
|
-
|
|
1327
|
+
spinner6.stop(`Failed to install ${type} dependencies (${path}/).`);
|
|
1288
1328
|
}
|
|
1289
1329
|
}
|
|
1290
1330
|
}
|
|
@@ -1410,10 +1450,10 @@ async function init(cwd, localRepo) {
|
|
|
1410
1450
|
p5.log.error("You have uncommitted changes. Commit or stash them first.");
|
|
1411
1451
|
process.exit(1);
|
|
1412
1452
|
}
|
|
1413
|
-
const
|
|
1414
|
-
|
|
1453
|
+
const spinner6 = p5.spinner();
|
|
1454
|
+
spinner6.start("Scanning for components");
|
|
1415
1455
|
const detected = await detectComponents(cwd);
|
|
1416
|
-
|
|
1456
|
+
spinner6.stop(
|
|
1417
1457
|
detected.length > 0 ? `Found ${detected.length} component(s).` : "No components detected."
|
|
1418
1458
|
);
|
|
1419
1459
|
if (detected.length === 0) {
|
|
@@ -2097,8 +2137,8 @@ async function diff(cwd, localRepo) {
|
|
|
2097
2137
|
pm: pmCommands(raw.packageManager ?? "npm"),
|
|
2098
2138
|
orm: raw.orm ?? "prisma"
|
|
2099
2139
|
};
|
|
2100
|
-
const
|
|
2101
|
-
|
|
2140
|
+
const spinner6 = p8.spinner();
|
|
2141
|
+
spinner6.start("Analyzing changes");
|
|
2102
2142
|
const tmpTemplate = await mkdtemp(join9(tmpdir(), "projx-diff-"));
|
|
2103
2143
|
await writeTemplateToDir(
|
|
2104
2144
|
tmpTemplate,
|
|
@@ -2157,7 +2197,7 @@ async function diff(cwd, localRepo) {
|
|
|
2157
2197
|
}
|
|
2158
2198
|
}
|
|
2159
2199
|
await rm(tmpTemplate, { recursive: true, force: true });
|
|
2160
|
-
|
|
2200
|
+
spinner6.stop("Analysis complete.");
|
|
2161
2201
|
const groups = {
|
|
2162
2202
|
new: [],
|
|
2163
2203
|
"clean-update": [],
|
|
@@ -2214,7 +2254,6 @@ async function diff(cwd, localRepo) {
|
|
|
2214
2254
|
import { existsSync as existsSync10 } from "fs";
|
|
2215
2255
|
import { readFile as readFile7, writeFile as writeFile2, mkdir as mkdir3 } from "fs/promises";
|
|
2216
2256
|
import { join as join10 } from "path";
|
|
2217
|
-
import { fileURLToPath } from "url";
|
|
2218
2257
|
import * as p9 from "@clack/prompts";
|
|
2219
2258
|
var FIELD_TYPES = [
|
|
2220
2259
|
"string",
|
|
@@ -3206,9 +3245,8 @@ function generateExpressTest(config) {
|
|
|
3206
3245
|
lines.push("");
|
|
3207
3246
|
return lines.join("\n");
|
|
3208
3247
|
}
|
|
3209
|
-
function addonGenEntityPath(orm, fileName) {
|
|
3210
|
-
|
|
3211
|
-
return join10(thisFile, "../../src/addons/orms", orm, "gen-entity", fileName);
|
|
3248
|
+
function addonGenEntityPath(repoDir, orm, fileName) {
|
|
3249
|
+
return join10(repoDir, "addons", "orms", orm, "gen-entity", fileName);
|
|
3212
3250
|
}
|
|
3213
3251
|
function sampleValue(field) {
|
|
3214
3252
|
switch (field.type) {
|
|
@@ -3251,8 +3289,8 @@ function insertAtAnchor(content, anchor, insertion) {
|
|
|
3251
3289
|
}
|
|
3252
3290
|
return content;
|
|
3253
3291
|
}
|
|
3254
|
-
async function fillTemplate(orm, fileName, vars) {
|
|
3255
|
-
const path = addonGenEntityPath(orm, fileName);
|
|
3292
|
+
async function fillTemplate(repoDir, orm, fileName, vars) {
|
|
3293
|
+
const path = addonGenEntityPath(repoDir, orm, fileName);
|
|
3256
3294
|
if (!existsSync10(path)) {
|
|
3257
3295
|
throw new Error(`Addon template not found: ${path}`);
|
|
3258
3296
|
}
|
|
@@ -3393,14 +3431,19 @@ function buildTypeormEntityVars(config) {
|
|
|
3393
3431
|
COLUMN_DECORATORS: typeormColumnDecorators(config.fields)
|
|
3394
3432
|
};
|
|
3395
3433
|
}
|
|
3396
|
-
async function appendTypeormEntity(cwd, dir, framework, config, generated) {
|
|
3434
|
+
async function appendTypeormEntity(repoDir, cwd, dir, framework, config, generated) {
|
|
3397
3435
|
const vars = buildTypeormEntityVars(config);
|
|
3398
3436
|
const kebab = vars.ENTITY_KEBAB;
|
|
3399
3437
|
const entitiesDir = join10(cwd, dir, "src/entities");
|
|
3400
3438
|
await mkdir3(entitiesDir, { recursive: true });
|
|
3401
3439
|
const entityPath = join10(entitiesDir, `${kebab}.ts`);
|
|
3402
3440
|
if (!existsSync10(entityPath)) {
|
|
3403
|
-
const entitySource = await fillTemplate(
|
|
3441
|
+
const entitySource = await fillTemplate(
|
|
3442
|
+
repoDir,
|
|
3443
|
+
"typeorm",
|
|
3444
|
+
"entity.ts",
|
|
3445
|
+
vars
|
|
3446
|
+
);
|
|
3404
3447
|
await writeFile2(entityPath, entitySource);
|
|
3405
3448
|
generated.push(`${dir}/src/entities/${kebab}.ts`);
|
|
3406
3449
|
}
|
|
@@ -3424,6 +3467,7 @@ async function appendTypeormEntity(cwd, dir, framework, config, generated) {
|
|
|
3424
3467
|
if (!existsSync10(moduleDir)) {
|
|
3425
3468
|
await mkdir3(moduleDir, { recursive: true });
|
|
3426
3469
|
const routerSource = await fillTemplate(
|
|
3470
|
+
repoDir,
|
|
3427
3471
|
"typeorm",
|
|
3428
3472
|
framework === "fastify" ? "fastify-router.ts" : "express-router.ts",
|
|
3429
3473
|
vars
|
|
@@ -3452,6 +3496,7 @@ async function appendTypeormEntity(cwd, dir, framework, config, generated) {
|
|
|
3452
3496
|
const testFile = join10(testsDir, `${kebab}.test.ts`);
|
|
3453
3497
|
if (!existsSync10(testFile)) {
|
|
3454
3498
|
const testSource = await fillTemplate(
|
|
3499
|
+
repoDir,
|
|
3455
3500
|
"typeorm",
|
|
3456
3501
|
framework === "fastify" ? "fastify-test.ts" : "express-test.ts",
|
|
3457
3502
|
vars
|
|
@@ -3461,14 +3506,19 @@ async function appendTypeormEntity(cwd, dir, framework, config, generated) {
|
|
|
3461
3506
|
generated.push(`${dir}/${testRel}`);
|
|
3462
3507
|
}
|
|
3463
3508
|
}
|
|
3464
|
-
async function appendSequelizeEntity(cwd, dir, framework, config, generated) {
|
|
3509
|
+
async function appendSequelizeEntity(repoDir, cwd, dir, framework, config, generated) {
|
|
3465
3510
|
const vars = buildSequelizeEntityVars(config);
|
|
3466
3511
|
const kebab = vars.ENTITY_KEBAB;
|
|
3467
3512
|
const modelsDir = join10(cwd, dir, "src/models");
|
|
3468
3513
|
await mkdir3(modelsDir, { recursive: true });
|
|
3469
3514
|
const modelPath = join10(modelsDir, `${kebab}.ts`);
|
|
3470
3515
|
if (!existsSync10(modelPath)) {
|
|
3471
|
-
const modelSource = await fillTemplate(
|
|
3516
|
+
const modelSource = await fillTemplate(
|
|
3517
|
+
repoDir,
|
|
3518
|
+
"sequelize",
|
|
3519
|
+
"model.ts",
|
|
3520
|
+
vars
|
|
3521
|
+
);
|
|
3472
3522
|
await writeFile2(modelPath, modelSource);
|
|
3473
3523
|
generated.push(`${dir}/src/models/${kebab}.ts`);
|
|
3474
3524
|
}
|
|
@@ -3492,6 +3542,7 @@ async function appendSequelizeEntity(cwd, dir, framework, config, generated) {
|
|
|
3492
3542
|
if (!existsSync10(moduleDir)) {
|
|
3493
3543
|
await mkdir3(moduleDir, { recursive: true });
|
|
3494
3544
|
const routerSource = await fillTemplate(
|
|
3545
|
+
repoDir,
|
|
3495
3546
|
"sequelize",
|
|
3496
3547
|
framework === "fastify" ? "fastify-router.ts" : "express-router.ts",
|
|
3497
3548
|
vars
|
|
@@ -3520,6 +3571,7 @@ async function appendSequelizeEntity(cwd, dir, framework, config, generated) {
|
|
|
3520
3571
|
const testFile = join10(testsDir, `${kebab}.test.ts`);
|
|
3521
3572
|
if (!existsSync10(testFile)) {
|
|
3522
3573
|
const testSource = await fillTemplate(
|
|
3574
|
+
repoDir,
|
|
3523
3575
|
"sequelize",
|
|
3524
3576
|
framework === "fastify" ? "fastify-test.ts" : "express-test.ts",
|
|
3525
3577
|
vars
|
|
@@ -3529,7 +3581,7 @@ async function appendSequelizeEntity(cwd, dir, framework, config, generated) {
|
|
|
3529
3581
|
generated.push(`${dir}/${testRel}`);
|
|
3530
3582
|
}
|
|
3531
3583
|
}
|
|
3532
|
-
async function appendDrizzleEntity(cwd, dir, framework, config, generated) {
|
|
3584
|
+
async function appendDrizzleEntity(repoDir, cwd, dir, framework, config, generated) {
|
|
3533
3585
|
const schemaDir = join10(cwd, dir, "src/db");
|
|
3534
3586
|
const schemaPath = join10(schemaDir, "schema.ts");
|
|
3535
3587
|
const tableConst = toCamel(pluralize(toPascal(config.name)));
|
|
@@ -3579,6 +3631,7 @@ ${tableSource}
|
|
|
3579
3631
|
if (!existsSync10(moduleDir)) {
|
|
3580
3632
|
await mkdir3(moduleDir, { recursive: true });
|
|
3581
3633
|
const routerSource = await fillTemplate(
|
|
3634
|
+
repoDir,
|
|
3582
3635
|
"drizzle",
|
|
3583
3636
|
framework === "fastify" ? "fastify-router.ts" : "express-router.ts",
|
|
3584
3637
|
vars
|
|
@@ -3607,6 +3660,7 @@ ${tableSource}
|
|
|
3607
3660
|
const testFile = join10(testsDir, `${kebab}.test.ts`);
|
|
3608
3661
|
if (!existsSync10(testFile)) {
|
|
3609
3662
|
const testSource = await fillTemplate(
|
|
3663
|
+
repoDir,
|
|
3610
3664
|
"drizzle",
|
|
3611
3665
|
framework === "fastify" ? "fastify-test.ts" : "express-test.ts",
|
|
3612
3666
|
vars
|
|
@@ -3645,7 +3699,7 @@ async function resolvePrimaryBackend(cwd, hasFastapi, hasFastify, hasExpress, ba
|
|
|
3645
3699
|
p9.log.success(`Saved primaryBackend: ${choice} to .projx`);
|
|
3646
3700
|
return choice;
|
|
3647
3701
|
}
|
|
3648
|
-
async function gen(cwd, entityName, fieldsFlag, backendFlag) {
|
|
3702
|
+
async function gen(cwd, entityName, fieldsFlag, backendFlag, localRepo) {
|
|
3649
3703
|
p9.intro(`projx gen entity ${entityName}`);
|
|
3650
3704
|
if (!existsSync10(join10(cwd, ".projx"))) {
|
|
3651
3705
|
p9.log.error("No .projx file found. Run 'npx create-projx init' first.");
|
|
@@ -3655,6 +3709,30 @@ async function gen(cwd, entityName, fieldsFlag, backendFlag) {
|
|
|
3655
3709
|
const pmName = projxData.packageManager ?? "npm";
|
|
3656
3710
|
const pm = pmCommands(pmName);
|
|
3657
3711
|
const orm = projxData.orm ?? "prisma";
|
|
3712
|
+
const needsAddon = orm === "drizzle" || orm === "sequelize" || orm === "typeorm";
|
|
3713
|
+
const repoDir = needsAddon ? await downloadRepo(localRepo).catch((err) => {
|
|
3714
|
+
p9.cancel(`Failed to fetch templates: ${err.message}`);
|
|
3715
|
+
process.exit(1);
|
|
3716
|
+
}) : "";
|
|
3717
|
+
const isLocal = Boolean(localRepo);
|
|
3718
|
+
try {
|
|
3719
|
+
return await runGen({
|
|
3720
|
+
cwd,
|
|
3721
|
+
entityName,
|
|
3722
|
+
fieldsFlag,
|
|
3723
|
+
backendFlag,
|
|
3724
|
+
pm,
|
|
3725
|
+
orm,
|
|
3726
|
+
repoDir
|
|
3727
|
+
});
|
|
3728
|
+
} finally {
|
|
3729
|
+
if (needsAddon && repoDir) {
|
|
3730
|
+
await cleanupRepo(repoDir, isLocal);
|
|
3731
|
+
}
|
|
3732
|
+
}
|
|
3733
|
+
}
|
|
3734
|
+
async function runGen(opts) {
|
|
3735
|
+
const { cwd, entityName, fieldsFlag, backendFlag, pm, orm, repoDir } = opts;
|
|
3658
3736
|
const { components: discovered, paths: componentPaths } = await discoverComponentsFromMarkers(cwd);
|
|
3659
3737
|
const hasFastapi = discovered.includes("fastapi");
|
|
3660
3738
|
const hasFastify = discovered.includes("fastify");
|
|
@@ -3788,6 +3866,7 @@ async function gen(cwd, entityName, fieldsFlag, backendFlag) {
|
|
|
3788
3866
|
}
|
|
3789
3867
|
if (genFastify && orm === "drizzle") {
|
|
3790
3868
|
await appendDrizzleEntity(
|
|
3869
|
+
repoDir,
|
|
3791
3870
|
cwd,
|
|
3792
3871
|
componentPaths.fastify,
|
|
3793
3872
|
"fastify",
|
|
@@ -3796,6 +3875,7 @@ async function gen(cwd, entityName, fieldsFlag, backendFlag) {
|
|
|
3796
3875
|
);
|
|
3797
3876
|
} else if (genFastify && orm === "sequelize") {
|
|
3798
3877
|
await appendSequelizeEntity(
|
|
3878
|
+
repoDir,
|
|
3799
3879
|
cwd,
|
|
3800
3880
|
componentPaths.fastify,
|
|
3801
3881
|
"fastify",
|
|
@@ -3804,6 +3884,7 @@ async function gen(cwd, entityName, fieldsFlag, backendFlag) {
|
|
|
3804
3884
|
);
|
|
3805
3885
|
} else if (genFastify && orm === "typeorm") {
|
|
3806
3886
|
await appendTypeormEntity(
|
|
3887
|
+
repoDir,
|
|
3807
3888
|
cwd,
|
|
3808
3889
|
componentPaths.fastify,
|
|
3809
3890
|
"fastify",
|
|
@@ -3872,6 +3953,7 @@ async function gen(cwd, entityName, fieldsFlag, backendFlag) {
|
|
|
3872
3953
|
}
|
|
3873
3954
|
if (genExpress && orm === "drizzle") {
|
|
3874
3955
|
await appendDrizzleEntity(
|
|
3956
|
+
repoDir,
|
|
3875
3957
|
cwd,
|
|
3876
3958
|
componentPaths.express,
|
|
3877
3959
|
"express",
|
|
@@ -3880,6 +3962,7 @@ async function gen(cwd, entityName, fieldsFlag, backendFlag) {
|
|
|
3880
3962
|
);
|
|
3881
3963
|
} else if (genExpress && orm === "sequelize") {
|
|
3882
3964
|
await appendSequelizeEntity(
|
|
3965
|
+
repoDir,
|
|
3883
3966
|
cwd,
|
|
3884
3967
|
componentPaths.express,
|
|
3885
3968
|
"express",
|
|
@@ -3888,6 +3971,7 @@ async function gen(cwd, entityName, fieldsFlag, backendFlag) {
|
|
|
3888
3971
|
);
|
|
3889
3972
|
} else if (genExpress && orm === "typeorm") {
|
|
3890
3973
|
await appendTypeormEntity(
|
|
3974
|
+
repoDir,
|
|
3891
3975
|
cwd,
|
|
3892
3976
|
componentPaths.express,
|
|
3893
3977
|
"express",
|
|
@@ -4001,293 +4085,6 @@ async function gen(cwd, entityName, fieldsFlag, backendFlag) {
|
|
|
4001
4085
|
p9.outro(`Entity ${className} created.`);
|
|
4002
4086
|
}
|
|
4003
4087
|
|
|
4004
|
-
// src/sync.ts
|
|
4005
|
-
import { existsSync as existsSync11, readFileSync } from "fs";
|
|
4006
|
-
import { writeFile as writeFile3, mkdir as mkdir4 } from "fs/promises";
|
|
4007
|
-
import { join as join11 } from "path";
|
|
4008
|
-
import * as p10 from "@clack/prompts";
|
|
4009
|
-
function toPascal2(s) {
|
|
4010
|
-
return s.replace(/(?:^|[_\-\s])([a-zA-Z])/g, (_, c) => c.toUpperCase());
|
|
4011
|
-
}
|
|
4012
|
-
function toCamel2(s) {
|
|
4013
|
-
return s.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
4014
|
-
}
|
|
4015
|
-
function metaTypeToTs(type, fieldType, nullable) {
|
|
4016
|
-
const base = (() => {
|
|
4017
|
-
switch (type) {
|
|
4018
|
-
case "str":
|
|
4019
|
-
return "string";
|
|
4020
|
-
case "int":
|
|
4021
|
-
case "float":
|
|
4022
|
-
return "number";
|
|
4023
|
-
case "bool":
|
|
4024
|
-
return "boolean";
|
|
4025
|
-
case "datetime":
|
|
4026
|
-
case "date":
|
|
4027
|
-
return "string";
|
|
4028
|
-
case "dict":
|
|
4029
|
-
return "Record<string, unknown>";
|
|
4030
|
-
default:
|
|
4031
|
-
return "unknown";
|
|
4032
|
-
}
|
|
4033
|
-
})();
|
|
4034
|
-
return nullable ? `${base} | null` : base;
|
|
4035
|
-
}
|
|
4036
|
-
function metaTypeToDart(type, nullable) {
|
|
4037
|
-
const base = (() => {
|
|
4038
|
-
switch (type) {
|
|
4039
|
-
case "str":
|
|
4040
|
-
return "String";
|
|
4041
|
-
case "int":
|
|
4042
|
-
return "int";
|
|
4043
|
-
case "float":
|
|
4044
|
-
return "double";
|
|
4045
|
-
case "bool":
|
|
4046
|
-
return "bool";
|
|
4047
|
-
case "datetime":
|
|
4048
|
-
case "date":
|
|
4049
|
-
return "DateTime";
|
|
4050
|
-
case "dict":
|
|
4051
|
-
return "Map<String, dynamic>";
|
|
4052
|
-
default:
|
|
4053
|
-
return "dynamic";
|
|
4054
|
-
}
|
|
4055
|
-
})();
|
|
4056
|
-
return nullable ? `${base}?` : base;
|
|
4057
|
-
}
|
|
4058
|
-
function dartFromJsonExpr(key, type, nullable) {
|
|
4059
|
-
const accessor = `json['${key}']`;
|
|
4060
|
-
const isDate = type === "datetime" || type === "date";
|
|
4061
|
-
if (isDate && nullable)
|
|
4062
|
-
return `${accessor} != null ? DateTime.parse(${accessor} as String) : null`;
|
|
4063
|
-
if (isDate) return `DateTime.parse(${accessor} as String)`;
|
|
4064
|
-
if (type === "dict" && nullable)
|
|
4065
|
-
return `${accessor} as Map<String, dynamic>?`;
|
|
4066
|
-
if (type === "dict") return `${accessor} as Map<String, dynamic>`;
|
|
4067
|
-
const dartT = (() => {
|
|
4068
|
-
switch (type) {
|
|
4069
|
-
case "str":
|
|
4070
|
-
return "String";
|
|
4071
|
-
case "int":
|
|
4072
|
-
return "int";
|
|
4073
|
-
case "float":
|
|
4074
|
-
return "double";
|
|
4075
|
-
case "bool":
|
|
4076
|
-
return "bool";
|
|
4077
|
-
default:
|
|
4078
|
-
return "dynamic";
|
|
4079
|
-
}
|
|
4080
|
-
})();
|
|
4081
|
-
return nullable ? `${accessor} as ${dartT}?` : `${accessor} as ${dartT}`;
|
|
4082
|
-
}
|
|
4083
|
-
function dartToJsonExpr(key, camel, type) {
|
|
4084
|
-
const isDate = type === "datetime" || type === "date";
|
|
4085
|
-
if (isDate) return `'${key}': ${camel}?.toIso8601String()`;
|
|
4086
|
-
return `'${key}': ${camel}`;
|
|
4087
|
-
}
|
|
4088
|
-
function generateTsInterface(entity) {
|
|
4089
|
-
const className = toPascal2(entity.name);
|
|
4090
|
-
const lines = [];
|
|
4091
|
-
lines.push(`export interface ${className} {`);
|
|
4092
|
-
for (const f of entity.fields) {
|
|
4093
|
-
lines.push(
|
|
4094
|
-
` ${f.key}: ${metaTypeToTs(f.type, f.field_type, f.nullable)};`
|
|
4095
|
-
);
|
|
4096
|
-
}
|
|
4097
|
-
lines.push(`}`);
|
|
4098
|
-
lines.push("");
|
|
4099
|
-
const createFields = entity.fields.filter((f) => f.in_create);
|
|
4100
|
-
lines.push(`export interface Create${className} {`);
|
|
4101
|
-
for (const f of createFields) {
|
|
4102
|
-
const optional = f.nullable ? "?" : "";
|
|
4103
|
-
lines.push(
|
|
4104
|
-
` ${f.key}${optional}: ${metaTypeToTs(f.type, f.field_type, f.nullable)};`
|
|
4105
|
-
);
|
|
4106
|
-
}
|
|
4107
|
-
lines.push(`}`);
|
|
4108
|
-
lines.push("");
|
|
4109
|
-
const updateFields = entity.fields.filter((f) => f.in_update);
|
|
4110
|
-
lines.push(`export interface Update${className} {`);
|
|
4111
|
-
for (const f of updateFields) {
|
|
4112
|
-
lines.push(` ${f.key}?: ${metaTypeToTs(f.type, f.field_type, true)};`);
|
|
4113
|
-
}
|
|
4114
|
-
lines.push(`}`);
|
|
4115
|
-
lines.push("");
|
|
4116
|
-
return lines.join("\n");
|
|
4117
|
-
}
|
|
4118
|
-
function generateDartModel2(entity) {
|
|
4119
|
-
const className = toPascal2(entity.name);
|
|
4120
|
-
const lines = [];
|
|
4121
|
-
const fields = entity.fields.map((f) => ({
|
|
4122
|
-
snake: f.key,
|
|
4123
|
-
camel: toCamel2(f.key),
|
|
4124
|
-
type: metaTypeToDart(f.type, f.nullable),
|
|
4125
|
-
nullable: f.nullable,
|
|
4126
|
-
metaType: f.type
|
|
4127
|
-
}));
|
|
4128
|
-
lines.push(`class ${className} {`);
|
|
4129
|
-
for (const f of fields) {
|
|
4130
|
-
lines.push(` final ${f.type} ${f.camel};`);
|
|
4131
|
-
}
|
|
4132
|
-
lines.push("");
|
|
4133
|
-
lines.push(` const ${className}({`);
|
|
4134
|
-
for (const f of fields) {
|
|
4135
|
-
if (f.nullable) {
|
|
4136
|
-
lines.push(` this.${f.camel},`);
|
|
4137
|
-
} else {
|
|
4138
|
-
lines.push(` required this.${f.camel},`);
|
|
4139
|
-
}
|
|
4140
|
-
}
|
|
4141
|
-
lines.push(` });`);
|
|
4142
|
-
lines.push("");
|
|
4143
|
-
lines.push(` factory ${className}.fromJson(Map<String, dynamic> json) {`);
|
|
4144
|
-
lines.push(` return ${className}(`);
|
|
4145
|
-
for (const f of fields) {
|
|
4146
|
-
lines.push(
|
|
4147
|
-
` ${f.camel}: ${dartFromJsonExpr(f.snake, f.metaType, f.nullable)},`
|
|
4148
|
-
);
|
|
4149
|
-
}
|
|
4150
|
-
lines.push(` );`);
|
|
4151
|
-
lines.push(` }`);
|
|
4152
|
-
lines.push("");
|
|
4153
|
-
lines.push(` Map<String, dynamic> toJson() {`);
|
|
4154
|
-
lines.push(` return {`);
|
|
4155
|
-
for (const f of fields) {
|
|
4156
|
-
lines.push(` ${dartToJsonExpr(f.snake, f.camel, f.metaType)},`);
|
|
4157
|
-
}
|
|
4158
|
-
lines.push(` };`);
|
|
4159
|
-
lines.push(` }`);
|
|
4160
|
-
lines.push("");
|
|
4161
|
-
lines.push(` ${className} copyWith({`);
|
|
4162
|
-
for (const f of fields) {
|
|
4163
|
-
lines.push(` ${f.type.replace("?", "")}? ${f.camel},`);
|
|
4164
|
-
}
|
|
4165
|
-
lines.push(` }) {`);
|
|
4166
|
-
lines.push(` return ${className}(`);
|
|
4167
|
-
for (const f of fields) {
|
|
4168
|
-
lines.push(` ${f.camel}: ${f.camel} ?? this.${f.camel},`);
|
|
4169
|
-
}
|
|
4170
|
-
lines.push(` );`);
|
|
4171
|
-
lines.push(` }`);
|
|
4172
|
-
lines.push(`}`);
|
|
4173
|
-
lines.push("");
|
|
4174
|
-
return lines.join("\n");
|
|
4175
|
-
}
|
|
4176
|
-
async function sync(cwd, url) {
|
|
4177
|
-
p10.intro("projx sync");
|
|
4178
|
-
const configPath = join11(cwd, ".projx");
|
|
4179
|
-
if (!existsSync11(configPath)) {
|
|
4180
|
-
p10.log.error("No .projx file found. Run 'npx create-projx init' first.");
|
|
4181
|
-
process.exit(1);
|
|
4182
|
-
}
|
|
4183
|
-
const { components: discovered, paths: componentPaths } = await discoverComponentsFromMarkers(cwd);
|
|
4184
|
-
const hasFrontend = discovered.includes("frontend");
|
|
4185
|
-
const hasMobile = discovered.includes("mobile");
|
|
4186
|
-
if (!hasFrontend && !hasMobile) {
|
|
4187
|
-
p10.log.error("No frontend or mobile component found. Nothing to sync.");
|
|
4188
|
-
process.exit(1);
|
|
4189
|
-
}
|
|
4190
|
-
const metaUrl = url || detectMetaUrl(cwd);
|
|
4191
|
-
const spinner7 = p10.spinner();
|
|
4192
|
-
spinner7.start(`Fetching metadata from ${metaUrl}`);
|
|
4193
|
-
let meta;
|
|
4194
|
-
try {
|
|
4195
|
-
const res = await fetch(metaUrl);
|
|
4196
|
-
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
4197
|
-
meta = await res.json();
|
|
4198
|
-
} catch (err) {
|
|
4199
|
-
spinner7.stop("Failed.");
|
|
4200
|
-
p10.log.error(`Could not fetch ${metaUrl}: ${err}`);
|
|
4201
|
-
p10.log.info("Make sure your backend is running.");
|
|
4202
|
-
p10.log.info(
|
|
4203
|
-
"Or specify URL: projx sync --url http://localhost:8000/api/v1/_meta"
|
|
4204
|
-
);
|
|
4205
|
-
process.exit(1);
|
|
4206
|
-
}
|
|
4207
|
-
spinner7.stop(`Fetched ${meta.entities.length} entity(s).`);
|
|
4208
|
-
const generated = [];
|
|
4209
|
-
if (hasFrontend) {
|
|
4210
|
-
const dir = componentPaths.frontend;
|
|
4211
|
-
const typesDir = join11(cwd, dir, "src/types");
|
|
4212
|
-
await mkdir4(typesDir, { recursive: true });
|
|
4213
|
-
const barrelExports = [];
|
|
4214
|
-
for (const entity of meta.entities) {
|
|
4215
|
-
const fileName = toKebab(toSnake(entity.name)) + ".ts";
|
|
4216
|
-
const filePath = join11(typesDir, fileName);
|
|
4217
|
-
await writeFile3(filePath, generateTsInterface(entity));
|
|
4218
|
-
generated.push(`${dir}/src/types/${fileName}`);
|
|
4219
|
-
barrelExports.push(`export * from './${toKebab(toSnake(entity.name))}';`);
|
|
4220
|
-
}
|
|
4221
|
-
await writeFile3(
|
|
4222
|
-
join11(typesDir, "index.ts"),
|
|
4223
|
-
barrelExports.join("\n") + "\n"
|
|
4224
|
-
);
|
|
4225
|
-
generated.push(`${dir}/src/types/index.ts`);
|
|
4226
|
-
}
|
|
4227
|
-
if (hasMobile) {
|
|
4228
|
-
const dir = componentPaths.mobile;
|
|
4229
|
-
for (const entity of meta.entities) {
|
|
4230
|
-
const entityDir = join11(cwd, dir, "lib/entities", toSnake(entity.name));
|
|
4231
|
-
await mkdir4(entityDir, { recursive: true });
|
|
4232
|
-
const modelPath = join11(entityDir, "model.dart");
|
|
4233
|
-
await writeFile3(modelPath, generateDartModel2(entity));
|
|
4234
|
-
generated.push(`${dir}/lib/entities/${toSnake(entity.name)}/model.dart`);
|
|
4235
|
-
}
|
|
4236
|
-
}
|
|
4237
|
-
p10.log.success(`Synced ${meta.entities.length} entity(s):`);
|
|
4238
|
-
for (const f of generated) {
|
|
4239
|
-
p10.log.info(` ${f}`);
|
|
4240
|
-
}
|
|
4241
|
-
if (hasFrontend) {
|
|
4242
|
-
p10.log.info("");
|
|
4243
|
-
p10.log.info("Frontend usage:");
|
|
4244
|
-
for (const entity of meta.entities) {
|
|
4245
|
-
const className = toPascal2(entity.name);
|
|
4246
|
-
p10.log.info(
|
|
4247
|
-
` import type { ${className} } from '../types/${toKebab(toSnake(entity.name))}';`
|
|
4248
|
-
);
|
|
4249
|
-
}
|
|
4250
|
-
}
|
|
4251
|
-
p10.outro("Types are up to date.");
|
|
4252
|
-
}
|
|
4253
|
-
function detectMetaUrl(cwd) {
|
|
4254
|
-
const envFiles = [".env", ".env.dev", ".env.local"];
|
|
4255
|
-
for (const envFile of envFiles) {
|
|
4256
|
-
const envPath = join11(cwd, envFile);
|
|
4257
|
-
if (existsSync11(envPath)) {
|
|
4258
|
-
try {
|
|
4259
|
-
const content = readFileSync(envPath, "utf-8");
|
|
4260
|
-
const match = content.match(/VITE_API_URL\s*=\s*(.+)/);
|
|
4261
|
-
if (match) {
|
|
4262
|
-
const base = match[1].trim().replace(/["']/g, "");
|
|
4263
|
-
return `${base}/api/v1/_meta`;
|
|
4264
|
-
}
|
|
4265
|
-
} catch {
|
|
4266
|
-
}
|
|
4267
|
-
}
|
|
4268
|
-
}
|
|
4269
|
-
const frontendEnvFiles = [
|
|
4270
|
-
"frontend/.env",
|
|
4271
|
-
"frontend/.env.local",
|
|
4272
|
-
"frontend/.env.dev"
|
|
4273
|
-
];
|
|
4274
|
-
for (const envFile of frontendEnvFiles) {
|
|
4275
|
-
const envPath = join11(cwd, envFile);
|
|
4276
|
-
if (existsSync11(envPath)) {
|
|
4277
|
-
try {
|
|
4278
|
-
const content = readFileSync(envPath, "utf-8");
|
|
4279
|
-
const match = content.match(/VITE_API_URL\s*=\s*(.+)/);
|
|
4280
|
-
if (match) {
|
|
4281
|
-
const base = match[1].trim().replace(/["']/g, "");
|
|
4282
|
-
return `${base}/api/v1/_meta`;
|
|
4283
|
-
}
|
|
4284
|
-
} catch {
|
|
4285
|
-
}
|
|
4286
|
-
}
|
|
4287
|
-
}
|
|
4288
|
-
return "http://localhost:8000/api/v1/_meta";
|
|
4289
|
-
}
|
|
4290
|
-
|
|
4291
4088
|
// src/index.ts
|
|
4292
4089
|
var args = process.argv.slice(2);
|
|
4293
4090
|
function matchFeatureFlag(arg, argv, i) {
|
|
@@ -4353,10 +4150,6 @@ function parseArgs() {
|
|
|
4353
4150
|
command = "gen";
|
|
4354
4151
|
continue;
|
|
4355
4152
|
}
|
|
4356
|
-
if (arg === "sync" && !name) {
|
|
4357
|
-
command = "sync";
|
|
4358
|
-
continue;
|
|
4359
|
-
}
|
|
4360
4153
|
if (arg === "--components") {
|
|
4361
4154
|
const val = args[++i];
|
|
4362
4155
|
if (val) {
|
|
@@ -4406,11 +4199,6 @@ function parseArgs() {
|
|
|
4406
4199
|
flags.backend = true;
|
|
4407
4200
|
continue;
|
|
4408
4201
|
}
|
|
4409
|
-
if (arg === "--url") {
|
|
4410
|
-
const val = args[++i];
|
|
4411
|
-
if (val) extraArgs.push(`--url=${val}`);
|
|
4412
|
-
continue;
|
|
4413
|
-
}
|
|
4414
4202
|
if (arg === "--help" || arg === "-h") {
|
|
4415
4203
|
printHelp();
|
|
4416
4204
|
process.exit(0);
|
|
@@ -4459,7 +4247,6 @@ function printHelp() {
|
|
|
4459
4247
|
projx pin --list Show all skip patterns
|
|
4460
4248
|
projx doctor [--fix] Health check for projx project
|
|
4461
4249
|
projx gen entity <name> Generate a new entity
|
|
4462
|
-
projx sync [--url <url>] Sync types from running backend
|
|
4463
4250
|
|
|
4464
4251
|
Options:
|
|
4465
4252
|
--components <list> Comma-separated: fastapi,fastify,express,frontend,mobile,e2e,infra
|
|
@@ -4475,7 +4262,7 @@ function printHelp() {
|
|
|
4475
4262
|
npx create-projx my-app
|
|
4476
4263
|
npx create-projx my-app --components fastapi,frontend,e2e
|
|
4477
4264
|
npx create-projx my-app --components express,frontend,e2e --orm drizzle
|
|
4478
|
-
npx create-projx my-app --components fastify,frontend,mobile --auth fastify
|
|
4265
|
+
npx create-projx my-app --components fastify,frontend,mobile --auth fastify
|
|
4479
4266
|
npx create-projx my-app -y
|
|
4480
4267
|
npx create-projx add frontend mobile
|
|
4481
4268
|
npx create-projx add fastify --name email-ingestor
|
|
@@ -4549,12 +4336,6 @@ async function main() {
|
|
|
4549
4336
|
await doctor(process.cwd(), flags.fix);
|
|
4550
4337
|
return;
|
|
4551
4338
|
}
|
|
4552
|
-
if (command === "sync") {
|
|
4553
|
-
const urlArg = extraArgs.find((a) => a.startsWith("--url="));
|
|
4554
|
-
const url = urlArg ? urlArg.split("=").slice(1).join("=") : void 0;
|
|
4555
|
-
await sync(process.cwd(), url);
|
|
4556
|
-
return;
|
|
4557
|
-
}
|
|
4558
4339
|
if (command === "gen") {
|
|
4559
4340
|
const subcommand = extraArgs[0];
|
|
4560
4341
|
if (subcommand !== "entity" || !extraArgs[1]) {
|
|
@@ -4567,7 +4348,7 @@ async function main() {
|
|
|
4567
4348
|
const fieldsArg = extraArgs.find((a) => a.startsWith("--fields="));
|
|
4568
4349
|
const fieldsFlag = fieldsArg ? fieldsArg.split("=").slice(1).join("=") : void 0;
|
|
4569
4350
|
const backendFlag = flags.ai ? "fastapi" : flags.backend ? "fastify" : void 0;
|
|
4570
|
-
await gen(process.cwd(), entityName, fieldsFlag, backendFlag);
|
|
4351
|
+
await gen(process.cwd(), entityName, fieldsFlag, backendFlag, localRepo);
|
|
4571
4352
|
return;
|
|
4572
4353
|
}
|
|
4573
4354
|
let opts;
|
|
@@ -4592,7 +4373,7 @@ async function main() {
|
|
|
4592
4373
|
opts.features = options.features ?? opts.features;
|
|
4593
4374
|
}
|
|
4594
4375
|
const dest = resolve(process.cwd(), opts.name);
|
|
4595
|
-
if (
|
|
4376
|
+
if (existsSync11(dest)) {
|
|
4596
4377
|
console.error(`Error: ${dest} already exists.`);
|
|
4597
4378
|
process.exit(1);
|
|
4598
4379
|
}
|