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.
Files changed (74) hide show
  1. package/README.md +13 -35
  2. package/dist/{baseline-FHOZNS4D.js → baseline-FKCXQFRD.js} +2 -2
  3. package/dist/{chunk-IMZKHDIL.js → chunk-N4WD4VN3.js} +15 -18
  4. package/dist/{chunk-HAT7D4G2.js → chunk-OLPF7FAN.js} +1 -1
  5. package/dist/index.js +166 -385
  6. package/dist/{utils-BZGSJ7XZ.js → utils-4G3HNHES.js} +1 -1
  7. package/package.json +2 -3
  8. package/src/templates/README.md.ejs +1 -1
  9. package/src/addons/orms/drizzle/express/src/app.ts +0 -81
  10. package/src/addons/orms/drizzle/express/src/modules/_base/auto-routes.ts +0 -278
  11. package/src/addons/orms/drizzle/express/src/modules/_base/index.ts +0 -20
  12. package/src/addons/orms/drizzle/express/src/server.ts +0 -32
  13. package/src/addons/orms/drizzle/express/tests/app.test.ts +0 -24
  14. package/src/addons/orms/drizzle/express/vitest.config.ts +0 -20
  15. package/src/addons/orms/drizzle/fastify/src/app.ts +0 -90
  16. package/src/addons/orms/drizzle/fastify/src/modules/_base/auto-routes.ts +0 -268
  17. package/src/addons/orms/drizzle/fastify/src/modules/_base/index.ts +0 -20
  18. package/src/addons/orms/drizzle/fastify/tests/modules/app.test.ts +0 -20
  19. package/src/addons/orms/drizzle/fastify/vitest.config.ts +0 -31
  20. package/src/addons/orms/drizzle/gen-entity/express-router.ts +0 -21
  21. package/src/addons/orms/drizzle/gen-entity/express-test.ts +0 -61
  22. package/src/addons/orms/drizzle/gen-entity/fastify-router.ts +0 -19
  23. package/src/addons/orms/drizzle/gen-entity/fastify-test.ts +0 -87
  24. package/src/addons/orms/drizzle/manifest.json +0 -52
  25. package/src/addons/orms/drizzle/shared/drizzle.config.ts +0 -12
  26. package/src/addons/orms/drizzle/shared/src/db/client.ts +0 -17
  27. package/src/addons/orms/drizzle/shared/src/db/schema.ts +0 -14
  28. package/src/addons/orms/drizzle/shared/src/modules/_base/query-engine.ts +0 -115
  29. package/src/addons/orms/drizzle/shared/src/modules/_base/registry.ts +0 -15
  30. package/src/addons/orms/sequelize/express/src/app.ts +0 -82
  31. package/src/addons/orms/sequelize/express/src/modules/_base/auto-routes.ts +0 -226
  32. package/src/addons/orms/sequelize/express/src/modules/_base/index.ts +0 -20
  33. package/src/addons/orms/sequelize/express/src/server.ts +0 -32
  34. package/src/addons/orms/sequelize/express/tests/app.test.ts +0 -24
  35. package/src/addons/orms/sequelize/express/vitest.config.ts +0 -20
  36. package/src/addons/orms/sequelize/fastify/src/app.ts +0 -83
  37. package/src/addons/orms/sequelize/fastify/src/modules/_base/auto-routes.ts +0 -216
  38. package/src/addons/orms/sequelize/fastify/src/modules/_base/index.ts +0 -20
  39. package/src/addons/orms/sequelize/fastify/tests/modules/app.test.ts +0 -20
  40. package/src/addons/orms/sequelize/fastify/vitest.config.ts +0 -31
  41. package/src/addons/orms/sequelize/gen-entity/express-router.ts +0 -17
  42. package/src/addons/orms/sequelize/gen-entity/express-test.ts +0 -65
  43. package/src/addons/orms/sequelize/gen-entity/fastify-router.ts +0 -19
  44. package/src/addons/orms/sequelize/gen-entity/fastify-test.ts +0 -89
  45. package/src/addons/orms/sequelize/gen-entity/model.ts +0 -21
  46. package/src/addons/orms/sequelize/manifest.json +0 -53
  47. package/src/addons/orms/sequelize/shared/scripts/db-sync.ts +0 -14
  48. package/src/addons/orms/sequelize/shared/src/db/client.ts +0 -19
  49. package/src/addons/orms/sequelize/shared/src/models/index.ts +0 -9
  50. package/src/addons/orms/sequelize/shared/src/modules/_base/query-engine.ts +0 -101
  51. package/src/addons/orms/sequelize/shared/src/modules/_base/registry.ts +0 -15
  52. package/src/addons/orms/typeorm/express/src/app.ts +0 -82
  53. package/src/addons/orms/typeorm/express/src/modules/_base/auto-routes.ts +0 -249
  54. package/src/addons/orms/typeorm/express/src/modules/_base/index.ts +0 -19
  55. package/src/addons/orms/typeorm/express/src/server.ts +0 -43
  56. package/src/addons/orms/typeorm/express/tests/app.test.ts +0 -24
  57. package/src/addons/orms/typeorm/express/vitest.config.ts +0 -20
  58. package/src/addons/orms/typeorm/fastify/src/app.ts +0 -86
  59. package/src/addons/orms/typeorm/fastify/src/modules/_base/auto-routes.ts +0 -239
  60. package/src/addons/orms/typeorm/fastify/src/modules/_base/index.ts +0 -19
  61. package/src/addons/orms/typeorm/fastify/tests/modules/app.test.ts +0 -20
  62. package/src/addons/orms/typeorm/fastify/vitest.config.ts +0 -31
  63. package/src/addons/orms/typeorm/gen-entity/entity.ts +0 -21
  64. package/src/addons/orms/typeorm/gen-entity/express-router.ts +0 -17
  65. package/src/addons/orms/typeorm/gen-entity/express-test.ts +0 -66
  66. package/src/addons/orms/typeorm/gen-entity/fastify-router.ts +0 -19
  67. package/src/addons/orms/typeorm/gen-entity/fastify-test.ts +0 -89
  68. package/src/addons/orms/typeorm/manifest.json +0 -53
  69. package/src/addons/orms/typeorm/shared/scripts/db-sync.ts +0 -14
  70. package/src/addons/orms/typeorm/shared/src/db/data-source.ts +0 -21
  71. package/src/addons/orms/typeorm/shared/src/entities/index.ts +0 -8
  72. package/src/addons/orms/typeorm/shared/src/modules/_base/query-engine.ts +0 -94
  73. package/src/addons/orms/typeorm/shared/src/modules/_base/registry.ts +0 -15
  74. 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-IMZKHDIL.js";
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-HAT7D4G2.js";
39
+ } from "./chunk-OLPF7FAN.js";
40
40
 
41
41
  // src/index.ts
42
- import { existsSync as existsSync12 } from "fs";
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((p11) => p11.trim());
62
- if (parts.length > 2 || parts.some((p11) => !p11)) {
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 filesDir = join(stackDir, "files");
199
- if (existsSync(filesDir)) {
200
- await renderFilesInto(filesDir, targetPath, args2.vars);
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
- const patchesDir = join(stackDir, "patches");
203
- if (existsSync(patchesDir)) {
204
- await applyPatches(patchesDir, targetPath, args2.featureName);
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(targetPath, patch, featureName);
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 spinner7 = p2.spinner();
446
- spinner7.start("Scaffolding project");
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
- spinner7.stop("Scaffold complete.");
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 spinner7 = p2.spinner();
541
+ const spinner6 = p2.spinner();
502
542
  try {
503
543
  switch (component) {
504
544
  case "fastapi":
505
545
  if (hasCommand("uv")) {
506
- spinner7.start("Installing FastAPI dependencies (uv sync)");
546
+ spinner6.start("Installing FastAPI dependencies (uv sync)");
507
547
  exec("uv sync --all-extras", join2(dest, "fastapi"));
508
- spinner7.stop("FastAPI dependencies installed.");
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
- spinner7.start(`Installing Fastify dependencies (${cmds.install})`);
555
+ spinner6.start(`Installing Fastify dependencies (${cmds.install})`);
516
556
  exec(cmds.install, join2(dest, "fastify"));
517
- spinner7.stop("Fastify dependencies installed.");
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
- spinner7.start(`Installing Express dependencies (${cmds.install})`);
566
+ spinner6.start(`Installing Express dependencies (${cmds.install})`);
527
567
  exec(cmds.install, join2(dest, "express"));
528
- spinner7.stop("Express dependencies installed.");
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
- spinner7.start(`Installing Frontend dependencies (${cmds.install})`);
577
+ spinner6.start(`Installing Frontend dependencies (${cmds.install})`);
538
578
  exec(cmds.install, join2(dest, "frontend"));
539
- spinner7.stop("Frontend dependencies installed.");
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
- spinner7.start(`Installing E2E dependencies (${cmds.install})`);
588
+ spinner6.start(`Installing E2E dependencies (${cmds.install})`);
549
589
  exec(cmds.install, join2(dest, "e2e"));
550
- spinner7.stop("E2E dependencies installed.");
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
- spinner7.start("Installing Flutter dependencies");
599
+ spinner6.start("Installing Flutter dependencies");
560
600
  exec("flutter pub get", join2(dest, "mobile"));
561
- spinner7.stop("Flutter dependencies installed.");
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
- spinner7.stop(`Failed to install ${component} dependencies.`);
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 spinner7 = p3.spinner();
708
- spinner7.start("Applying template update");
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
- spinner7.stop("Template applied.");
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-FHOZNS4D.js");
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 spinner7 = p4.spinner();
1076
- spinner7.start("Adding components");
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
- spinner7.stop("Components added.");
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((p11) => !INSTANCE_AWARE_ROOT.has(p11));
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 spinner7 = p4.spinner();
1158
- spinner7.start(`Scaffolding ${customName}/`);
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
- spinner7.stop(`Scaffolded ${customName}/.`);
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 spinner7 = p4.spinner();
1248
+ const spinner6 = p4.spinner();
1209
1249
  try {
1210
1250
  switch (type) {
1211
1251
  case "fastapi":
1212
1252
  if (hasCommand("uv")) {
1213
- spinner7.start(`Installing FastAPI dependencies (${path}/)`);
1253
+ spinner6.start(`Installing FastAPI dependencies (${path}/)`);
1214
1254
  exec("uv sync --all-extras", dir);
1215
- spinner7.stop(`FastAPI dependencies installed (${path}/).`);
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
- spinner7.start(
1262
+ spinner6.start(
1223
1263
  `Installing Fastify dependencies (${path}/, ${cmds.install})`
1224
1264
  );
1225
1265
  exec(cmds.install, dir);
1226
- spinner7.stop(`Fastify dependencies installed (${path}/).`);
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
- spinner7.start(
1275
+ spinner6.start(
1236
1276
  `Installing Express dependencies (${path}/, ${cmds.install})`
1237
1277
  );
1238
1278
  exec(cmds.install, dir);
1239
- spinner7.stop(`Express dependencies installed (${path}/).`);
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
- spinner7.start(
1288
+ spinner6.start(
1249
1289
  `Installing Frontend dependencies (${path}/, ${cmds.install})`
1250
1290
  );
1251
1291
  exec(cmds.install, dir);
1252
- spinner7.stop(`Frontend dependencies installed (${path}/).`);
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
- spinner7.start(
1301
+ spinner6.start(
1262
1302
  `Installing E2E dependencies (${path}/, ${cmds.install})`
1263
1303
  );
1264
1304
  exec(cmds.install, dir);
1265
- spinner7.stop(`E2E dependencies installed (${path}/).`);
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
- spinner7.start(`Installing Flutter dependencies (${path}/)`);
1314
+ spinner6.start(`Installing Flutter dependencies (${path}/)`);
1275
1315
  exec("flutter pub get", dir);
1276
- spinner7.stop(`Flutter dependencies installed (${path}/).`);
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
- spinner7.stop(`Failed to install ${type} dependencies (${path}/).`);
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 spinner7 = p5.spinner();
1414
- spinner7.start("Scanning for components");
1453
+ const spinner6 = p5.spinner();
1454
+ spinner6.start("Scanning for components");
1415
1455
  const detected = await detectComponents(cwd);
1416
- spinner7.stop(
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 spinner7 = p8.spinner();
2101
- spinner7.start("Analyzing changes");
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
- spinner7.stop("Analysis complete.");
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
- const thisFile = fileURLToPath(import.meta.url);
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("typeorm", "entity.ts", vars);
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("sequelize", "model.ts", vars);
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,frontend,mobile
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 (existsSync12(dest)) {
4376
+ if (existsSync11(dest)) {
4596
4377
  console.error(`Error: ${dest} already exists.`);
4597
4378
  process.exit(1);
4598
4379
  }