@idevconn/create-icore 0.7.2 → 0.8.0

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 (86) hide show
  1. package/dist/cli.js +509 -353
  2. package/dist/index.cjs +644 -366
  3. package/dist/index.d.cts +9 -1
  4. package/dist/index.d.ts +9 -1
  5. package/dist/index.js +635 -358
  6. package/dist/manifest/audit.js +122 -0
  7. package/package.json +1 -1
  8. package/templates/apps/api/src/app/app.module.ts +2 -6
  9. package/templates/apps/api/src/app/features.module.ts +9 -0
  10. package/templates/apps/api/src/app/gateway-services.ts +7 -0
  11. package/templates/apps/api/src/main.ts +1 -5
  12. package/templates/apps/microservices/auth/src/app/app.module.ts +4 -93
  13. package/templates/apps/microservices/auth/src/app/auth.provider.ts +9 -0
  14. package/templates/apps/microservices/notes/src/app/app.module.ts +4 -86
  15. package/templates/apps/microservices/notes/src/app/db.provider.ts +9 -0
  16. package/templates/apps/microservices/upload/src/app/app.module.ts +4 -140
  17. package/templates/apps/microservices/upload/src/app/storage.provider.ts +9 -0
  18. package/templates/apps/templates/client-antd/src/components/layout/LayoutSider.tsx +15 -23
  19. package/templates/apps/templates/client-antd/src/nav.config.ts +17 -0
  20. package/templates/apps/templates/client-mui/src/components/layout/LayoutSider.tsx +19 -20
  21. package/templates/apps/templates/client-mui/src/nav.config.ts +17 -0
  22. package/templates/apps/templates/client-shadcn/src/components/layout/LayoutSider.tsx +20 -16
  23. package/templates/apps/templates/client-shadcn/src/nav.config.ts +17 -0
  24. package/templates/libs/auth-strategies/firebase/eslint.config.mjs +1 -0
  25. package/templates/libs/auth-strategies/firebase/package.json +4 -0
  26. package/templates/libs/auth-strategies/firebase/src/index.ts +1 -0
  27. package/templates/libs/auth-strategies/firebase/src/lib/__tests__/firebase-auth.module.unit.test.ts +49 -0
  28. package/templates/libs/auth-strategies/firebase/src/lib/firebase-auth.module.ts +41 -0
  29. package/templates/libs/auth-strategies/firebase/tsconfig.json +2 -0
  30. package/templates/libs/auth-strategies/mongodb/package.json +4 -1
  31. package/templates/libs/auth-strategies/mongodb/src/index.ts +1 -0
  32. package/templates/libs/auth-strategies/mongodb/src/lib/__tests__/mongodb-auth.module.unit.test.ts +16 -0
  33. package/templates/libs/auth-strategies/mongodb/src/lib/mongodb-auth.module.ts +45 -0
  34. package/templates/libs/auth-strategies/mongodb/tsconfig.json +2 -0
  35. package/templates/libs/auth-strategies/supabase/eslint.config.mjs +1 -0
  36. package/templates/libs/auth-strategies/supabase/package.json +3 -0
  37. package/templates/libs/auth-strategies/supabase/src/index.ts +1 -0
  38. package/templates/libs/auth-strategies/supabase/src/lib/__tests__/supabase-auth.module.unit.test.ts +43 -0
  39. package/templates/libs/auth-strategies/supabase/src/lib/supabase-auth.module.ts +41 -0
  40. package/templates/libs/auth-strategies/supabase/tsconfig.json +2 -0
  41. package/templates/libs/db-strategies/firestore/eslint.config.mjs +1 -1
  42. package/templates/libs/db-strategies/firestore/package.json +4 -0
  43. package/templates/libs/db-strategies/firestore/src/index.ts +1 -0
  44. package/templates/libs/db-strategies/firestore/src/lib/__tests__/firestore-db.module.unit.test.ts +37 -0
  45. package/templates/libs/db-strategies/firestore/src/lib/firestore-db.module.ts +41 -0
  46. package/templates/libs/db-strategies/firestore/tsconfig.json +2 -0
  47. package/templates/libs/db-strategies/mongodb/package.json +4 -1
  48. package/templates/libs/db-strategies/mongodb/src/index.ts +1 -0
  49. package/templates/libs/db-strategies/mongodb/src/lib/__tests__/mongodb-db.module.unit.test.ts +14 -0
  50. package/templates/libs/db-strategies/mongodb/src/lib/mongodb-db.module.ts +41 -0
  51. package/templates/libs/db-strategies/mongodb/tsconfig.json +2 -0
  52. package/templates/libs/db-strategies/supabase/eslint.config.mjs +6 -1
  53. package/templates/libs/db-strategies/supabase/package.json +3 -0
  54. package/templates/libs/db-strategies/supabase/src/index.ts +1 -0
  55. package/templates/libs/db-strategies/supabase/src/lib/__tests__/supabase-db.module.unit.test.ts +32 -0
  56. package/templates/libs/db-strategies/supabase/src/lib/supabase-db.module.ts +41 -0
  57. package/templates/libs/db-strategies/supabase/tsconfig.json +2 -0
  58. package/templates/libs/shared/src/strategies/__tests__/provide-strategy.unit.test.ts +73 -0
  59. package/templates/libs/shared/src/strategies/index.ts +1 -0
  60. package/templates/libs/shared/src/strategies/provide-strategy.ts +44 -0
  61. package/templates/libs/storage-strategies/cloudinary/eslint.config.mjs +1 -1
  62. package/templates/libs/storage-strategies/cloudinary/package.json +4 -0
  63. package/templates/libs/storage-strategies/cloudinary/src/index.ts +1 -0
  64. package/templates/libs/storage-strategies/cloudinary/src/lib/__tests__/cloudinary-storage.module.unit.test.ts +40 -0
  65. package/templates/libs/storage-strategies/cloudinary/src/lib/cloudinary-storage.module.ts +85 -0
  66. package/templates/libs/storage-strategies/cloudinary/tsconfig.json +2 -0
  67. package/templates/libs/storage-strategies/firebase/eslint.config.mjs +1 -1
  68. package/templates/libs/storage-strategies/firebase/package.json +4 -0
  69. package/templates/libs/storage-strategies/firebase/src/index.ts +1 -0
  70. package/templates/libs/storage-strategies/firebase/src/lib/__tests__/firebase-storage.module.unit.test.ts +42 -0
  71. package/templates/libs/storage-strategies/firebase/src/lib/firebase-storage.module.ts +46 -0
  72. package/templates/libs/storage-strategies/firebase/tsconfig.json +2 -0
  73. package/templates/libs/storage-strategies/mongodb/package.json +4 -1
  74. package/templates/libs/storage-strategies/mongodb/src/index.ts +1 -0
  75. package/templates/libs/storage-strategies/mongodb/src/lib/__tests__/mongodb-storage.module.unit.test.ts +14 -0
  76. package/templates/libs/storage-strategies/mongodb/src/lib/mongodb-storage.module.ts +41 -0
  77. package/templates/libs/storage-strategies/mongodb/tsconfig.json +2 -0
  78. package/templates/libs/storage-strategies/supabase/eslint.config.mjs +1 -0
  79. package/templates/libs/storage-strategies/supabase/package.json +3 -0
  80. package/templates/libs/storage-strategies/supabase/src/index.ts +1 -0
  81. package/templates/libs/storage-strategies/supabase/src/lib/__tests__/supabase-storage.module.unit.test.ts +46 -0
  82. package/templates/libs/storage-strategies/supabase/src/lib/supabase-storage.module.ts +46 -0
  83. package/templates/libs/storage-strategies/supabase/tsconfig.json +2 -0
  84. package/templates/package.json +1 -1
  85. package/templates/tools/create-icore/_template-shell/package.json +1 -1
  86. package/templates/tsconfig.base.json +1 -1
package/dist/cli.js CHANGED
@@ -236,9 +236,9 @@ Re-run with @latest to refresh:
236
236
  }
237
237
 
238
238
  // src/lib/scaffold.ts
239
- import { copyFile, mkdir as mkdir2, readdir as readdir2, readFile as readFile5, stat, writeFile as writeFile4, rm as rm2 } from "fs/promises";
239
+ import { copyFile, mkdir as mkdir2, readdir as readdir2, readFile as readFile7, stat, writeFile as writeFile8, rm as rm4 } from "fs/promises";
240
240
  import { readFileSync } from "fs";
241
- import { join as join5 } from "path";
241
+ import { join as join9 } from "path";
242
242
  import { spawnSync } from "child_process";
243
243
 
244
244
  // src/lib/scaffold-env.ts
@@ -279,6 +279,31 @@ async function stripGatewayTransport(targetDir, prefix) {
279
279
  } catch {
280
280
  }
281
281
  }
282
+ var ROOT_PROVIDER_SDKS = {
283
+ supabase: ["@supabase/supabase-js"],
284
+ cloudinary: ["cloudinary"],
285
+ mongodb: ["mongoose"],
286
+ firebase: ["firebase-admin"]
287
+ };
288
+ async function pruneRootProviderDeps(targetDir, opts) {
289
+ const chosen = /* @__PURE__ */ new Set([opts.authProvider, opts.dbProvider, opts.upload]);
290
+ const drop = /* @__PURE__ */ new Set();
291
+ for (const [provider, sdks] of Object.entries(ROOT_PROVIDER_SDKS)) {
292
+ if (!chosen.has(provider)) for (const sdk of sdks) drop.add(sdk);
293
+ }
294
+ if (drop.size === 0) return;
295
+ const pkgPath = join2(targetDir, "package.json");
296
+ try {
297
+ const pkg = JSON.parse(await readFile2(pkgPath, "utf8"));
298
+ for (const field of ["dependencies", "devDependencies"]) {
299
+ const deps = pkg[field];
300
+ if (!deps) continue;
301
+ for (const sdk of drop) delete deps[sdk];
302
+ }
303
+ await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
304
+ } catch {
305
+ }
306
+ }
282
307
  async function rewriteRootPackageJson(targetDir, opts) {
283
308
  const pkgPath = join2(targetDir, "package.json");
284
309
  const raw = await readFile2(pkgPath, "utf8");
@@ -418,42 +443,26 @@ async function stripTsconfigPath(targetDir, alias) {
418
443
  } catch {
419
444
  }
420
445
  }
421
- async function removeJobsStack(targetDir) {
422
- const paths = [
423
- "apps/microservices/jobs",
424
- "libs/jobs-client",
425
- "apps/api/src/app/admin",
426
- "Dockerfile.ms-jobs"
427
- ];
428
- for (const p3 of paths) {
429
- await rm(join3(targetDir, p3), { recursive: true, force: true });
430
- }
431
- const appModulePath = join3(targetDir, "apps/api/src/app/app.module.ts");
432
- try {
433
- const appModule = await readFile3(appModulePath, "utf8");
434
- const next = appModule.replace(/^import \{ AdminModule \} from '\.\/admin\/admin\.module';\n/m, "").replace(/,\s*AdminModule/g, "");
435
- await writeFile2(appModulePath, next);
436
- } catch {
437
- }
438
- await stripDeps(join3(targetDir, "apps/api/package.json"), [
439
- "@icore/jobs-client",
440
- "@bull-board/api",
441
- "@bull-board/express"
446
+ async function removeFirebaseAdminLib(targetDir) {
447
+ await rm(join3(targetDir, "libs/firebase-admin"), { recursive: true, force: true });
448
+ await stripTsconfigPath(targetDir, "@icore/firebase-admin");
449
+ await stripDeps(join3(targetDir, "apps/microservices/auth/package.json"), [
450
+ "@icore/firebase-admin"
451
+ ]);
452
+ await stripDeps(join3(targetDir, "apps/microservices/upload/package.json"), [
453
+ "@icore/firebase-admin"
454
+ ]);
455
+ await stripDeps(join3(targetDir, "apps/microservices/notes/package.json"), [
456
+ "@icore/firebase-admin"
442
457
  ]);
443
- const composePath = join3(targetDir, "docker-compose.yml");
444
- try {
445
- const compose = await readFile3(composePath, "utf8");
446
- const next = compose.replace(/\n {2}jobs:[\s\S]+?(?=\n {2}\w+:|\nnetworks:)/m, "\n").replace(/\n {6}jobs:\n {8}condition: service_started/g, "").replace(/\n {6}JOBS_REDIS_URL:[^\n]*/g, "");
447
- await writeFile2(composePath, next);
448
- } catch {
449
- }
450
458
  }
451
- async function removePaymentStack(targetDir) {
459
+ async function removeUploadStack(targetDir) {
452
460
  const paths = [
453
- "apps/microservices/payment",
454
- "apps/microservices/payment-e2e",
455
- "libs/payment-client",
456
- "apps/api/src/app/payment"
461
+ "apps/microservices/upload",
462
+ "apps/microservices/upload-e2e",
463
+ "libs/storage-strategies",
464
+ "libs/upload-client",
465
+ "apps/api/src/app/storage"
457
466
  ];
458
467
  for (const p3 of paths) {
459
468
  await rm(join3(targetDir, p3), { recursive: true, force: true });
@@ -461,319 +470,456 @@ async function removePaymentStack(targetDir) {
461
470
  const appModulePath = join3(targetDir, "apps/api/src/app/app.module.ts");
462
471
  try {
463
472
  const appModule = await readFile3(appModulePath, "utf8");
464
- const next = appModule.replace(/^import \{ PaymentModule \} from '\.\/payment\/payment\.module';\n/m, "").replace(/,\s*PaymentModule/g, "");
473
+ const next = appModule.replace(/^import \{ StorageModule \} from '\.\/storage\/storage\.module';\n/m, "").replace(/,\s*StorageModule/g, "");
465
474
  await writeFile2(appModulePath, next);
466
475
  } catch {
467
476
  }
468
- await stripDeps(join3(targetDir, "apps/api/package.json"), [
469
- "@icore/payment-client",
470
- "@idevconn/payment"
471
- ]);
472
- await stripGatewayTransport(targetDir, "PAYMENT");
473
- const mainTsPath = join3(targetDir, "apps/api/src/main.ts");
474
- try {
475
- const src = await readFile3(mainTsPath, "utf8");
476
- const next = src.replace(/\n\s*\{ name: 'payment', prefix: 'PAYMENT' \},/, "");
477
- await writeFile2(mainTsPath, next);
478
- } catch {
479
- }
480
- }
481
- async function removeNotesStack(targetDir) {
482
- for (const p3 of [
483
- "apps/microservices/notes",
484
- "apps/microservices/notes-e2e",
485
- "libs/notes-client",
486
- "apps/api/src/app/notes",
487
- "apps/client/src/components/notes"
488
- ]) {
489
- await rm(join3(targetDir, p3), { recursive: true, force: true });
490
- }
491
- await rm(join3(targetDir, "apps/client/src/routes/_dashboard/notes.tsx"), { force: true });
492
- await rm(join3(targetDir, "apps/client/src/queries/notes.ts"), { force: true });
493
- const appModulePath = join3(targetDir, "apps/api/src/app/app.module.ts");
477
+ const gatewayEnv = join3(targetDir, "apps/api/.env");
494
478
  try {
495
- const src = await readFile3(appModulePath, "utf8");
496
- const next = src.replace(/^import \{ NotesModule \} from '\.\/notes\/notes\.module';\n/m, "").replace(/,\s*NotesModule/g, "");
497
- await writeFile2(appModulePath, next);
479
+ const env = await readFile3(gatewayEnv, "utf8");
480
+ const next = env.split("\n").filter(
481
+ (line) => !line.startsWith("UPLOAD_") && !line.startsWith("# UPLOAD_") && !line.startsWith("MAX_FILE_SIZE_KB")
482
+ ).join("\n");
483
+ await writeFile2(gatewayEnv, next);
498
484
  } catch {
499
485
  }
500
486
  await stripDeps(join3(targetDir, "apps/api/package.json"), [
501
- "@icore/notes-client",
502
- "@casl/ability"
487
+ "@icore/upload-client",
488
+ "@types/multer"
503
489
  ]);
504
- await stripGatewayTransport(targetDir, "NOTES");
505
- const mainTsPath = join3(targetDir, "apps/api/src/main.ts");
506
- try {
507
- const src = await readFile3(mainTsPath, "utf8");
508
- const next = src.replace(/\n\s*\{ name: 'notes', prefix: 'NOTES' \},/, "");
509
- await writeFile2(mainTsPath, next);
510
- } catch {
511
- }
512
- const tsconfigPath = join3(targetDir, "tsconfig.base.json");
513
- try {
514
- const src = await readFile3(tsconfigPath, "utf8");
515
- const next = src.replace(/^\s*"@icore\/notes-client": \[[^\]]*\],?\n/m, "");
516
- await writeFile2(tsconfigPath, next);
517
- } catch {
490
+ }
491
+
492
+ // src/manifest/wire-features.ts
493
+ import { readFile as readFile5, writeFile as writeFile4, rm as rm3 } from "fs/promises";
494
+ import { join as join5 } from "path";
495
+
496
+ // src/manifest/index.ts
497
+ var EMPTY = { libDirs: [], deps: {}, tsPaths: {} };
498
+ var MANIFEST = {
499
+ auth: {
500
+ supabase: {
501
+ libDirs: ["libs/auth-strategies/supabase"],
502
+ deps: { "@supabase/supabase-js": "^2.106.2" },
503
+ tsPaths: { "@icore/auth-supabase": ["libs/auth-strategies/supabase/src/index.ts"] },
504
+ nestModule: {
505
+ importFrom: "@icore/auth-supabase",
506
+ symbol: "SupabaseAuthModule",
507
+ into: "auth"
508
+ },
509
+ appTests: [
510
+ "apps/microservices/auth/src/app/__tests__/auth.controller.supabase.integration.unit.test.ts"
511
+ ]
512
+ },
513
+ firebase: {
514
+ libDirs: ["libs/auth-strategies/firebase"],
515
+ deps: {},
516
+ tsPaths: { "@icore/auth-firebase": ["libs/auth-strategies/firebase/src/index.ts"] },
517
+ nestModule: {
518
+ importFrom: "@icore/auth-firebase",
519
+ symbol: "FirebaseAuthModule",
520
+ into: "auth"
521
+ },
522
+ appTests: [
523
+ "apps/microservices/auth/src/app/__tests__/auth.controller.firebase.integration.unit.test.ts"
524
+ ]
525
+ },
526
+ mongodb: {
527
+ libDirs: ["libs/auth-strategies/mongodb"],
528
+ deps: { mongoose: "^9.6.3" },
529
+ tsPaths: { "@icore/auth-mongodb": ["libs/auth-strategies/mongodb/src/index.ts"] },
530
+ nestModule: { importFrom: "@icore/auth-mongodb", symbol: "MongoDbAuthModule", into: "auth" }
531
+ }
532
+ },
533
+ storage: {
534
+ supabase: {
535
+ libDirs: ["libs/storage-strategies/supabase"],
536
+ deps: { "@supabase/supabase-js": "^2.106.2" },
537
+ tsPaths: { "@icore/storage-supabase": ["libs/storage-strategies/supabase/src/index.ts"] },
538
+ nestModule: {
539
+ importFrom: "@icore/storage-supabase",
540
+ symbol: "SupabaseStorageModule",
541
+ into: "upload"
542
+ }
543
+ },
544
+ firebase: {
545
+ libDirs: ["libs/storage-strategies/firebase"],
546
+ deps: {},
547
+ tsPaths: { "@icore/storage-firebase": ["libs/storage-strategies/firebase/src/index.ts"] },
548
+ nestModule: {
549
+ importFrom: "@icore/storage-firebase",
550
+ symbol: "FirebaseStorageModule",
551
+ into: "upload"
552
+ }
553
+ },
554
+ cloudinary: {
555
+ libDirs: ["libs/storage-strategies/cloudinary"],
556
+ deps: { cloudinary: "^2.10.0" },
557
+ tsPaths: { "@icore/storage-cloudinary": ["libs/storage-strategies/cloudinary/src/index.ts"] },
558
+ nestModule: {
559
+ importFrom: "@icore/storage-cloudinary",
560
+ symbol: "CloudinaryStorageModule",
561
+ into: "upload"
562
+ }
563
+ },
564
+ mongodb: {
565
+ libDirs: ["libs/storage-strategies/mongodb"],
566
+ deps: { mongoose: "^9.6.3" },
567
+ tsPaths: { "@icore/storage-mongodb": ["libs/storage-strategies/mongodb/src/index.ts"] },
568
+ nestModule: {
569
+ importFrom: "@icore/storage-mongodb",
570
+ symbol: "MongoDbStorageModule",
571
+ into: "upload"
572
+ }
573
+ }
574
+ },
575
+ db: {
576
+ supabase: {
577
+ libDirs: ["libs/db-strategies/supabase"],
578
+ deps: { "@supabase/supabase-js": "^2.106.2" },
579
+ tsPaths: { "@icore/db-supabase": ["libs/db-strategies/supabase/src/index.ts"] },
580
+ nestModule: { importFrom: "@icore/db-supabase", symbol: "SupabaseDbModule", into: "notes" }
581
+ },
582
+ firebase: {
583
+ libDirs: ["libs/db-strategies/firestore"],
584
+ deps: {},
585
+ tsPaths: { "@icore/db-firestore": ["libs/db-strategies/firestore/src/index.ts"] },
586
+ nestModule: { importFrom: "@icore/db-firestore", symbol: "FirestoreDbModule", into: "notes" }
587
+ },
588
+ mongodb: {
589
+ libDirs: ["libs/db-strategies/mongodb"],
590
+ deps: { mongoose: "^9.6.3" },
591
+ tsPaths: { "@icore/db-mongodb": ["libs/db-strategies/mongodb/src/index.ts"] },
592
+ nestModule: { importFrom: "@icore/db-mongodb", symbol: "MongoDbDbModule", into: "notes" }
593
+ }
594
+ },
595
+ feature: {
596
+ notes: {
597
+ libDirs: [
598
+ "apps/microservices/notes",
599
+ "apps/microservices/notes-e2e",
600
+ "libs/notes-client",
601
+ "libs/db-strategies",
602
+ "apps/api/src/app/notes",
603
+ "apps/client/src/components/notes",
604
+ "apps/client/src/routes/_dashboard/notes.tsx",
605
+ "apps/client/src/queries/notes.ts"
606
+ ],
607
+ deps: { "@icore/notes-client": "*", "@casl/ability": "^7.0.0" },
608
+ tsPaths: { "@icore/notes-client": ["libs/notes-client/src/index.ts"] },
609
+ gatewayModule: { importFrom: "./notes/notes.module", symbol: "NotesModule" },
610
+ gatewayService: { name: "notes", prefix: "NOTES" },
611
+ clientNav: { route: "/notes", labelKey: "nav.notes", iconName: "notes" }
612
+ },
613
+ payment: {
614
+ libDirs: [
615
+ "apps/microservices/payment",
616
+ "apps/microservices/payment-e2e",
617
+ "libs/payment-client",
618
+ "apps/api/src/app/payment"
619
+ ],
620
+ deps: { "@icore/payment-client": "*", "@idevconn/payment": "^1.2.0" },
621
+ tsPaths: { "@icore/payment-client": ["libs/payment-client/src/index.ts"] },
622
+ gatewayModule: { importFrom: "./payment/payment.module", symbol: "PaymentModule" },
623
+ gatewayService: { name: "payment", prefix: "PAYMENT" }
624
+ },
625
+ jobs: {
626
+ libDirs: [
627
+ "apps/microservices/jobs",
628
+ "libs/jobs-client",
629
+ "apps/api/src/app/admin",
630
+ "Dockerfile.ms-jobs"
631
+ ],
632
+ deps: {
633
+ "@icore/jobs-client": "*",
634
+ "@bull-board/api": "^7.1.5",
635
+ "@bull-board/express": "^7.1.5"
636
+ },
637
+ tsPaths: { "@icore/jobs-client": ["libs/jobs-client/src/index.ts"] },
638
+ gatewayModule: { importFrom: "./admin/admin.module", symbol: "AdminModule" },
639
+ dockerService: "jobs"
640
+ }
641
+ },
642
+ ui: { shadcn: EMPTY, antd: EMPTY, mui: EMPTY },
643
+ transport: { tcp: EMPTY, redis: EMPTY, nats: EMPTY, mqtt: EMPTY, rmq: EMPTY, kafka: EMPTY },
644
+ shared: {
645
+ firebaseAdmin: {
646
+ libDirs: ["libs/firebase-admin"],
647
+ deps: { "firebase-admin": "^13.10.0" },
648
+ tsPaths: { "@icore/firebase-admin": ["libs/firebase-admin/src/index.ts"] }
649
+ }
518
650
  }
519
- const siderPath = join3(targetDir, "apps/client/src/components/layout/LayoutSider.tsx");
651
+ };
652
+
653
+ // src/manifest/wire-provider.ts
654
+ import { readFile as readFile4, writeFile as writeFile3, rm as rm2 } from "fs/promises";
655
+ import { join as join4 } from "path";
656
+ async function writeProvider(targetDir, axis, provider) {
657
+ const nestModule = axis.section[provider]?.nestModule;
658
+ if (!nestModule) throw new Error(`provider "${provider}" has no nestModule in the manifest`);
659
+ const { importFrom, symbol } = nestModule;
660
+ const content = `import { ${symbol} } from '${importFrom}';
661
+
662
+ const ENV_PATH = '${axis.envPath}';
663
+
664
+ export const ${axis.exportConst} = ${symbol}.forRoot(ENV_PATH);
665
+ `;
666
+ await writeFile3(join4(targetDir, axis.providerFile), content);
667
+ }
668
+ async function stripJsonKeys(path, drop) {
520
669
  try {
521
- const src = await readFile3(siderPath, "utf8");
522
- const next = src.replace(", StickyNote", "").replace(/\n {8}<Link\n {10}to="\/(?:_dashboard\/)?notes"[\s\S]*?<\/Link>/, "").replace(", FileTextOutlined", "").replace(
523
- "const selectedKey = pathname.includes('/notes')\n ? 'notes'\n : pathname.includes('/profile')",
524
- "const selectedKey = pathname.includes('/profile')"
525
- ).replace(
526
- /\n {4}\{\n {6}key: 'notes',\n {6}icon: <FileTextOutlined \/>,\n {6}label: <Link to="\/(?:_dashboard\/)?notes">\{t\('notes\.title'\)\}<\/Link>,\n {4}\},/,
527
- ""
528
- ).replace("import NoteOutlinedIcon from '@mui/icons-material/NoteOutlined';\n", "").replace(
529
- /\n {8}<ListItemButton\n {10}component=\{Link\}\n {10}to="\/(?:_dashboard\/)?notes"[\s\S]*?<\/ListItemButton>/,
530
- ""
531
- ).replace(/\n\s*<Link to="\/(?:_dashboard\/)?notes">[\s\S]*?<\/Link>/m, "");
532
- await writeFile2(siderPath, next);
670
+ const pkg = JSON.parse(await readFile4(path, "utf8"));
671
+ for (const field of ["dependencies", "devDependencies"]) {
672
+ const deps = pkg[field];
673
+ if (!deps) continue;
674
+ for (const k of Object.keys(deps)) if (drop(k)) delete deps[k];
675
+ }
676
+ await writeFile3(path, JSON.stringify(pkg, null, 2) + "\n");
533
677
  } catch {
534
678
  }
535
- const keysPath = join3(targetDir, "libs/template-shared/src/lib/i18n/keys.ts");
679
+ }
680
+ async function stripTsconfigKeys(targetDir, aliases) {
681
+ const path = join4(targetDir, "tsconfig.base.json");
536
682
  try {
537
- const src = await readFile3(keysPath, "utf8");
538
- const next = src.replace(/^\s{4}notes: \{\n(?:\s+.*\n)*?\s{4}\},\n/m, "");
539
- await writeFile2(keysPath, next);
683
+ const parsed = JSON.parse(await readFile4(path, "utf8"));
684
+ const paths = parsed.compilerOptions?.paths;
685
+ if (paths) for (const a of aliases) delete paths[a];
686
+ await writeFile3(path, JSON.stringify(parsed, null, 2) + "\n");
540
687
  } catch {
541
688
  }
542
689
  }
543
- async function removeUnusedAuthStrategies(targetDir, authProvider) {
544
- const modulePath = join3(targetDir, "apps/microservices/auth/src/app/app.module.ts");
545
- const AUTH_BRANCH = /if \(provider === 'supabase'\) return makeSupabaseAuth\(cfg\);\n\s*if \(provider === 'mongodb'\) return makeMongoDbAuth\(connection, cfg\);\n\s*return makeFirebaseAuth\(cfg\);/m;
546
- if (authProvider === "supabase") {
547
- await rm(join3(targetDir, "libs/auth-strategies/firebase"), { recursive: true, force: true });
548
- await rm(join3(targetDir, "libs/auth-strategies/mongodb"), { recursive: true, force: true });
549
- await stripDeps(join3(targetDir, "apps/microservices/auth/package.json"), [
550
- "@icore/auth-firebase",
551
- "@icore/firebase-admin",
552
- "@icore/auth-mongodb"
553
- ]);
554
- await stripTsconfigPath(targetDir, "@icore/auth-firebase");
555
- await stripTsconfigPath(targetDir, "@icore/auth-mongodb");
556
- try {
557
- const src = await readFile3(modulePath, "utf8");
558
- const next = src.replace(/^import \{[^}]*\} from '@icore\/firebase-admin';\n/gm, "").replace(/^import \{[^}]*FirebaseAuthStrategy[^}]*\} from '@icore\/auth-firebase';\n/gm, "").replace(/^import \{[^}]*MongoDbAuthStrategy[^}]*\} from '@icore\/auth-mongodb';\n/gm, "").replace(
559
- /^import \{ MongooseModule, getConnectionToken \} from '@nestjs\/mongoose';\n/gm,
560
- ""
561
- ).replace(/^import \{ Connection \} from 'mongoose';\n/gm, "").replace(/^ {2}firebase: \[[^\]]*\],\n/gm, "").replace(/^ {2}mongodb: \[[^\]]*\],\n/gm, "").replace(/\nfunction makeFirebaseAuth[\s\S]*?\n}\n/gm, "").replace(/\nfunction makeMongoDbAuth[\s\S]*?\n}\n/gm, "").replace(/^ {4}MongooseModule\.forRootAsync[\s\S]*?\n {4}\}\),\n/gm, "").replace(AUTH_BRANCH, "return makeSupabaseAuth(cfg);").replace(/, connection: Connection/, "").replace(/, getConnectionToken\(\)/, "");
562
- await writeFile2(modulePath, next);
563
- } catch {
564
- }
565
- }
566
- if (authProvider === "firebase") {
567
- await rm(join3(targetDir, "libs/auth-strategies/supabase"), { recursive: true, force: true });
568
- await rm(join3(targetDir, "libs/auth-strategies/mongodb"), { recursive: true, force: true });
569
- await stripDeps(join3(targetDir, "apps/microservices/auth/package.json"), [
570
- "@icore/auth-supabase",
571
- "@icore/auth-mongodb"
572
- ]);
573
- await stripTsconfigPath(targetDir, "@icore/auth-supabase");
574
- await stripTsconfigPath(targetDir, "@icore/auth-mongodb");
575
- try {
576
- const src = await readFile3(modulePath, "utf8");
577
- const next = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/gm, "").replace(/^import \{[^}]*SupabaseAuthStrategy[^}]*\} from '@icore\/auth-supabase';\n/gm, "").replace(/^import \{[^}]*MongoDbAuthStrategy[^}]*\} from '@icore\/auth-mongodb';\n/gm, "").replace(
578
- /^import \{ MongooseModule, getConnectionToken \} from '@nestjs\/mongoose';\n/gm,
579
- ""
580
- ).replace(/^import \{ Connection \} from 'mongoose';\n/gm, "").replace(/^ {2}supabase: \[[^\]]*\],\n/gm, "").replace(/^ {2}mongodb: \[[^\]]*\],\n/gm, "").replace(/\nfunction makeSupabaseAuth[\s\S]*?\n}\n/gm, "").replace(/\nfunction makeMongoDbAuth[\s\S]*?\n}\n/gm, "").replace(/^ {4}MongooseModule\.forRootAsync[\s\S]*?\n {4}\}\),\n/gm, "").replace(AUTH_BRANCH, "return makeFirebaseAuth(cfg);").replace(/, connection: Connection/, "").replace(/, getConnectionToken\(\)/, "");
581
- await writeFile2(modulePath, next);
582
- } catch {
583
- }
584
- }
585
- if (authProvider === "mongodb") {
586
- await rm(join3(targetDir, "libs/auth-strategies/supabase"), { recursive: true, force: true });
587
- await rm(join3(targetDir, "libs/auth-strategies/firebase"), { recursive: true, force: true });
588
- await stripDeps(join3(targetDir, "apps/microservices/auth/package.json"), [
589
- "@icore/auth-supabase",
590
- "@icore/auth-firebase",
591
- "@icore/firebase-admin"
592
- ]);
593
- await stripTsconfigPath(targetDir, "@icore/auth-supabase");
594
- await stripTsconfigPath(targetDir, "@icore/auth-firebase");
595
- try {
596
- const src = await readFile3(modulePath, "utf8");
597
- const next = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/gm, "").replace(/^import \{[^}]*SupabaseAuthStrategy[^}]*\} from '@icore\/auth-supabase';\n/gm, "").replace(/^import \{[^}]*\} from '@icore\/firebase-admin';\n/gm, "").replace(/^import \{[^}]*FirebaseAuthStrategy[^}]*\} from '@icore\/auth-firebase';\n/gm, "").replace(/^ {2}supabase: \[[^\]]*\],\n/gm, "").replace(/^ {2}firebase: \[[^\]]*\],\n/gm, "").replace(/\nfunction makeSupabaseAuth[\s\S]*?\n}\n/gm, "").replace(/\nfunction makeFirebaseAuth[\s\S]*?\n}\n/gm, "").replace(AUTH_BRANCH, "return makeMongoDbAuth(connection, cfg);");
598
- await writeFile2(modulePath, next);
599
- } catch {
600
- }
690
+ async function cleanupUnusedAxis(targetDir, axis, chosen) {
691
+ for (const provider of Object.keys(axis.section)) {
692
+ if (provider === chosen) continue;
693
+ const unit = axis.section[provider];
694
+ for (const dir of unit.libDirs)
695
+ await rm2(join4(targetDir, dir), { recursive: true, force: true });
696
+ for (const t of unit.appTests ?? []) await rm2(join4(targetDir, t), { force: true });
697
+ const dropKeys = /* @__PURE__ */ new Set([...Object.keys(unit.tsPaths), ...Object.keys(unit.deps)]);
698
+ await stripJsonKeys(join4(targetDir, axis.msPackageJson), (k) => dropKeys.has(k));
699
+ await stripTsconfigKeys(targetDir, Object.keys(unit.tsPaths));
601
700
  }
602
701
  }
603
- async function removeUnusedStorageStrategies(targetDir, uploadProvider) {
604
- if (uploadProvider === "none") return;
605
- const modulePath = join3(targetDir, "apps/microservices/upload/src/app/app.module.ts");
606
- const STORAGE_BRANCH = /if \(provider === 'supabase'\) return makeSupabaseStorage\(cfg\);\n\s*if \(provider === 'firebase'\) return makeFirebaseStorage\(cfg\);\n\s*if \(provider === 'mongodb'\) return makeMongoDbStorage\(connection\);\n\s*return makeCloudinaryStorage\(cfg\);/m;
607
- if (uploadProvider !== "firebase") {
608
- await rm(join3(targetDir, "libs/storage-strategies/firebase"), { recursive: true, force: true });
609
- await stripDeps(join3(targetDir, "apps/microservices/upload/package.json"), [
610
- "@icore/storage-firebase",
611
- "@icore/firebase-admin"
612
- ]);
613
- await stripTsconfigPath(targetDir, "@icore/storage-firebase");
614
- }
615
- if (uploadProvider !== "cloudinary") {
616
- await rm(join3(targetDir, "libs/storage-strategies/cloudinary"), {
617
- recursive: true,
618
- force: true
619
- });
620
- await stripDeps(join3(targetDir, "apps/microservices/upload/package.json"), [
621
- "@icore/storage-cloudinary"
622
- ]);
623
- await stripTsconfigPath(targetDir, "@icore/storage-cloudinary");
624
- }
625
- if (uploadProvider !== "supabase") {
626
- await rm(join3(targetDir, "libs/storage-strategies/supabase"), { recursive: true, force: true });
627
- await stripDeps(join3(targetDir, "apps/microservices/upload/package.json"), [
628
- "@icore/storage-supabase"
629
- ]);
630
- await stripTsconfigPath(targetDir, "@icore/storage-supabase");
631
- }
632
- if (uploadProvider !== "mongodb") {
633
- await rm(join3(targetDir, "libs/storage-strategies/mongodb"), { recursive: true, force: true });
634
- await stripDeps(join3(targetDir, "apps/microservices/upload/package.json"), [
635
- "@icore/storage-mongodb"
636
- ]);
637
- await stripTsconfigPath(targetDir, "@icore/storage-mongodb");
702
+
703
+ // src/manifest/wire-features.ts
704
+ var FEATURES_MODULE = "apps/api/src/app/features.module.ts";
705
+ var GATEWAY_SERVICES = "apps/api/src/app/gateway-services.ts";
706
+ var API_PKG = "apps/api/package.json";
707
+ var FEATURES = MANIFEST.feature;
708
+ function selectedFeatures(opts) {
709
+ const out = [];
710
+ if (opts.example === "notes") out.push("notes");
711
+ if (opts.payment !== "none") out.push("payment");
712
+ if (opts.jobs !== "none") out.push("jobs");
713
+ return out;
714
+ }
715
+ async function writeFeaturesWiring(targetDir, opts) {
716
+ const chosen = selectedFeatures(opts);
717
+ const mods = chosen.map((k) => FEATURES[k].gatewayModule).filter((m) => !!m);
718
+ const imports = mods.map((m) => `import { ${m.symbol} } from '${m.importFrom}';`).join("\n");
719
+ const symbols = mods.map((m) => m.symbol).join(", ");
720
+ const featuresModule = `import { Module } from '@nestjs/common';
721
+ ` + (imports ? imports + "\n" : "") + `
722
+ @Module({
723
+ imports: [${symbols}],
724
+ })
725
+ export class FeaturesModule {}
726
+ `;
727
+ await writeFile4(join5(targetDir, FEATURES_MODULE), featuresModule);
728
+ const services = [{ name: "auth", prefix: "AUTH" }];
729
+ if (opts.upload !== "none") services.push({ name: "upload", prefix: "UPLOAD" });
730
+ for (const k of chosen) {
731
+ const svc = FEATURES[k].gatewayService;
732
+ if (svc) services.push(svc);
638
733
  }
734
+ const entries = services.map((s) => ` { name: '${s.name}', prefix: '${s.prefix}' },`).join("\n");
735
+ const gatewayServices = `/** Microservices the gateway proxies. Generated by create-icore. */
736
+ export const GATEWAY_SERVICES = [
737
+ ${entries}
738
+ ];
739
+ `;
740
+ await writeFile4(join5(targetDir, GATEWAY_SERVICES), gatewayServices);
741
+ }
742
+ async function stripJobsDockerCompose(targetDir) {
743
+ const composePath = join5(targetDir, "docker-compose.yml");
639
744
  try {
640
- let src = await readFile3(modulePath, "utf8");
641
- if (uploadProvider !== "firebase") {
642
- src = src.replace(/^import \{[^}]*\} from '@icore\/firebase-admin';\n/gm, "").replace(
643
- /^import \{[^}]*FirebaseStorageStrategy[^}]*\} from '@icore\/storage-firebase';\n/gm,
644
- ""
645
- ).replace(/^ {2}firebase: \[[^\]]*\],\n/gm, "").replace(/\nfunction makeFirebaseStorage[\s\S]*?\n}\n/gm, "");
646
- }
647
- if (uploadProvider !== "cloudinary") {
648
- src = src.replace(/^import \{ v2 as cloudinary \} from 'cloudinary';\n/gm, "").replace(
649
- /^import \{[^}]*CloudinaryStorageStrategy[^}]*\} from '@icore\/storage-cloudinary';\n/gm,
650
- ""
651
- ).replace(/\nfunction makeCloudinaryStorage[\s\S]*?\n}\n/gm, "");
652
- }
653
- if (uploadProvider !== "supabase") {
654
- src = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/gm, "").replace(
655
- /^import \{[^}]*SupabaseStorageStrategy[^}]*\} from '@icore\/storage-supabase';\n/gm,
656
- ""
657
- ).replace(/\nfunction makeSupabaseStorage[\s\S]*?\n}\n/gm, "");
658
- }
659
- if (uploadProvider !== "mongodb") {
660
- src = src.replace(
661
- /^import \{[^}]*MongoDbStorageStrategy[^}]*\} from '@icore\/storage-mongodb';\n/gm,
662
- ""
663
- ).replace(
664
- /^import \{ MongooseModule, getConnectionToken \} from '@nestjs\/mongoose';\n/gm,
665
- ""
666
- ).replace(/^import \{ Connection \} from 'mongoose';\n/gm, "").replace(/^ {2}mongodb: \[[^\]]*\],\n/gm, "").replace(/\nfunction makeMongoDbStorage[\s\S]*?\n}\n/gm, "").replace(/^ {4}MongooseModule\.forRootAsync[\s\S]*?\n {4}\}\),\n/gm, "");
667
- }
668
- const chosenReturn = uploadProvider === "mongodb" ? `return makeMongoDbStorage(connection);` : `return make${uploadProvider.charAt(0).toUpperCase() + uploadProvider.slice(1)}Storage(cfg);`;
669
- src = src.replace(STORAGE_BRANCH, chosenReturn);
670
- if (uploadProvider !== "mongodb") {
671
- src = src.replace(/, connection: Connection/, "").replace(/, getConnectionToken\(\)/, "");
672
- }
673
- await writeFile2(modulePath, src);
745
+ const compose = await readFile5(composePath, "utf8");
746
+ const next = compose.replace(/\n {2}jobs:[\s\S]+?(?=\n {2}\w+:|\nnetworks:)/m, "\n").replace(/\n {6}jobs:\n {8}condition: service_started/g, "").replace(/\n {6}JOBS_REDIS_URL:[^\n]*/g, "");
747
+ await writeFile4(composePath, next);
674
748
  } catch {
675
749
  }
676
750
  }
677
- async function removeUnusedDbStrategies(targetDir, dbProvider) {
678
- const modulePath = join3(targetDir, "apps/microservices/notes/src/app/app.module.ts");
679
- const DB_BRANCH = /if \(provider === 'supabase'\) return makeSupabaseDB\(cfg\);\n\s*if \(provider === 'mongodb'\) return makeMongoDb\(connection\);\n\s*return makeFirestoreDB\(cfg\);/m;
680
- if (dbProvider === "supabase") {
681
- await rm(join3(targetDir, "libs/db-strategies/firestore"), { recursive: true, force: true });
682
- await rm(join3(targetDir, "libs/db-strategies/mongodb"), { recursive: true, force: true });
683
- await stripDeps(join3(targetDir, "apps/microservices/notes/package.json"), [
684
- "@icore/db-firestore",
685
- "@icore/firebase-admin",
686
- "@icore/db-mongodb"
687
- ]);
688
- await stripTsconfigPath(targetDir, "@icore/db-firestore");
689
- await stripTsconfigPath(targetDir, "@icore/db-mongodb");
690
- try {
691
- const src = await readFile3(modulePath, "utf8");
692
- const next = src.replace(/^import \{[^}]*\} from '@icore\/firebase-admin';\n/gm, "").replace(/^import \{[^}]*FirestoreDBStrategy[^}]*\} from '@icore\/db-firestore';\n/gm, "").replace(/^import \{[^}]*MongoDbDBStrategy[^}]*\} from '@icore\/db-mongodb';\n/gm, "").replace(
693
- /^import \{ MongooseModule, getConnectionToken \} from '@nestjs\/mongoose';\n/gm,
694
- ""
695
- ).replace(/^import \{ Connection \} from 'mongoose';\n/gm, "").replace(/^ {2}firestore: \[[^\]]*\],\n/gm, "").replace(/^ {2}firebase: \[[^\]]*\],\n/gm, "").replace(/^ {2}mongodb: \[[^\]]*\],\n/gm, "").replace(/\nfunction makeFirestoreDB[\s\S]*?\n}\n/gm, "").replace(/\nfunction makeMongoDb[\s\S]*?\n}\n/gm, "").replace(/^ {4}MongooseModule\.forRootAsync[\s\S]*?\n {4}\}\),\n/gm, "").replace(DB_BRANCH, "return makeSupabaseDB(cfg);").replace(/, connection: Connection/, "").replace(/, getConnectionToken\(\)/, "");
696
- await writeFile2(modulePath, next);
697
- } catch {
698
- }
699
- }
700
- if (dbProvider === "firebase") {
701
- await rm(join3(targetDir, "libs/db-strategies/supabase"), { recursive: true, force: true });
702
- await rm(join3(targetDir, "libs/db-strategies/mongodb"), { recursive: true, force: true });
703
- await stripDeps(join3(targetDir, "apps/microservices/notes/package.json"), [
704
- "@icore/db-supabase",
705
- "@icore/db-mongodb"
706
- ]);
707
- await stripTsconfigPath(targetDir, "@icore/db-supabase");
708
- await stripTsconfigPath(targetDir, "@icore/db-mongodb");
709
- try {
710
- const src = await readFile3(modulePath, "utf8");
711
- const next = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/gm, "").replace(/^import \{[^}]*SupabaseDBStrategy[^}]*\} from '@icore\/db-supabase';\n/gm, "").replace(/^import \{[^}]*MongoDbDBStrategy[^}]*\} from '@icore\/db-mongodb';\n/gm, "").replace(
712
- /^import \{ MongooseModule, getConnectionToken \} from '@nestjs\/mongoose';\n/gm,
713
- ""
714
- ).replace(/^import \{ Connection \} from 'mongoose';\n/gm, "").replace(/^ {2}supabase: \[[^\]]*\],\n/gm, "").replace(/^ {2}mongodb: \[[^\]]*\],\n/gm, "").replace(/\nfunction makeSupabaseDB[\s\S]*?\n}\n/gm, "").replace(/\nfunction makeMongoDb[\s\S]*?\n}\n/gm, "").replace(/^ {4}MongooseModule\.forRootAsync[\s\S]*?\n {4}\}\),\n/gm, "").replace(/\nfunction requireEnv[\s\S]*?\n}\n/gm, "").replace(DB_BRANCH, "return makeFirestoreDB(cfg);").replace(/, connection: Connection/, "").replace(/, getConnectionToken\(\)/, "");
715
- await writeFile2(modulePath, next);
716
- } catch {
717
- }
751
+ async function cleanupUnusedFeatures(targetDir, opts) {
752
+ const chosen = new Set(selectedFeatures(opts));
753
+ for (const key of ["notes", "payment", "jobs"]) {
754
+ if (chosen.has(key)) continue;
755
+ const unit = FEATURES[key];
756
+ for (const dir of unit.libDirs)
757
+ await rm3(join5(targetDir, dir), { recursive: true, force: true });
758
+ const dropKeys = /* @__PURE__ */ new Set([...Object.keys(unit.tsPaths), ...Object.keys(unit.deps)]);
759
+ await stripJsonKeys(join5(targetDir, API_PKG), (k) => dropKeys.has(k));
760
+ await stripTsconfigKeys(targetDir, Object.keys(unit.tsPaths));
761
+ if (unit.gatewayService) await stripGatewayTransport(targetDir, unit.gatewayService.prefix);
762
+ if (unit.dockerService === "jobs") await stripJobsDockerCompose(targetDir);
718
763
  }
719
- if (dbProvider === "mongodb") {
720
- await rm(join3(targetDir, "libs/db-strategies/supabase"), { recursive: true, force: true });
721
- await rm(join3(targetDir, "libs/db-strategies/firestore"), { recursive: true, force: true });
722
- await stripDeps(join3(targetDir, "apps/microservices/notes/package.json"), [
723
- "@icore/db-supabase",
724
- "@icore/db-firestore",
725
- "@icore/firebase-admin"
726
- ]);
727
- await stripTsconfigPath(targetDir, "@icore/db-supabase");
728
- await stripTsconfigPath(targetDir, "@icore/db-firestore");
729
- try {
730
- const src = await readFile3(modulePath, "utf8");
731
- const next = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/gm, "").replace(/^import \{[^}]*SupabaseDBStrategy[^}]*\} from '@icore\/db-supabase';\n/gm, "").replace(/^import \{[^}]*\} from '@icore\/firebase-admin';\n/gm, "").replace(/^import \{[^}]*FirestoreDBStrategy[^}]*\} from '@icore\/db-firestore';\n/gm, "").replace(/^ {2}supabase: \[[^\]]*\],\n/gm, "").replace(/^ {2}firestore: \[[^\]]*\],\n/gm, "").replace(/^ {2}firebase: \[[^\]]*\],\n/gm, "").replace(/\nfunction makeSupabaseDB[\s\S]*?\n}\n/gm, "").replace(/\nfunction makeFirestoreDB[\s\S]*?\n}\n/gm, "").replace(/\nfunction requireEnv[\s\S]*?\n}\n/gm, "").replace(DB_BRANCH, "return makeMongoDb(connection);");
732
- await writeFile2(modulePath, next);
733
- } catch {
734
- }
764
+ }
765
+
766
+ // src/manifest/wire-client.ts
767
+ import { writeFile as writeFile5 } from "fs/promises";
768
+ import { join as join6 } from "path";
769
+ var NAV_CONFIG_FILE = "apps/client/src/nav.config.ts";
770
+ var BASE_NAV = [
771
+ { to: "/dashboard", labelKey: "nav.dashboard", iconName: "dashboard", exact: true }
772
+ ];
773
+ var PROFILE_NAV = { to: "/profile", labelKey: "nav.profile", iconName: "profile" };
774
+ function renderEntry(n) {
775
+ const parts = [`to: '${n.to}'`, `labelKey: '${n.labelKey}'`, `iconName: '${n.iconName}'`];
776
+ if (n.exact) parts.push("exact: true");
777
+ return ` { ${parts.join(", ")} },`;
778
+ }
779
+ async function writeNavConfig(targetDir, opts) {
780
+ const entries = [...BASE_NAV];
781
+ const notesNav = MANIFEST.feature.notes.clientNav;
782
+ if (opts.example === "notes" && notesNav) {
783
+ entries.push({
784
+ to: notesNav.route,
785
+ labelKey: notesNav.labelKey,
786
+ iconName: notesNav.iconName,
787
+ exact: notesNav.exact
788
+ });
735
789
  }
790
+ entries.push(PROFILE_NAV);
791
+ const content = `/**
792
+ * Sidebar navigation. UI-agnostic: each LayoutSider maps \`iconName\` to its own
793
+ * icon library. Generated by create-icore.
794
+ */
795
+ export interface NavItem {
796
+ to: string;
797
+ labelKey: string;
798
+ iconName: 'dashboard' | 'notes' | 'profile';
799
+ exact?: boolean;
736
800
  }
737
- async function removeFirebaseAdminLib(targetDir) {
738
- await rm(join3(targetDir, "libs/firebase-admin"), { recursive: true, force: true });
739
- await stripTsconfigPath(targetDir, "@icore/firebase-admin");
801
+
802
+ export const NAV_CONFIG: NavItem[] = [
803
+ ` + entries.map(renderEntry).join("\n") + `
804
+ ];
805
+ `;
806
+ await writeFile5(join6(targetDir, NAV_CONFIG_FILE), content);
740
807
  }
741
- async function removeUploadStack(targetDir) {
742
- const paths = [
743
- "apps/microservices/upload",
744
- "apps/microservices/upload-e2e",
745
- "libs/storage-strategies",
746
- "libs/upload-client",
747
- "apps/api/src/app/storage"
748
- ];
749
- for (const p3 of paths) {
750
- await rm(join3(targetDir, p3), { recursive: true, force: true });
808
+
809
+ // src/manifest/blueprint.ts
810
+ import { writeFile as writeFile6 } from "fs/promises";
811
+ import { join as join7 } from "path";
812
+ async function writeBlueprintJson(targetDir, opts) {
813
+ const blueprint = {
814
+ schemaVersion: 1,
815
+ projectName: opts.projectName,
816
+ authProvider: opts.authProvider,
817
+ dbProvider: opts.dbProvider,
818
+ upload: opts.upload,
819
+ payment: opts.payment,
820
+ jobs: opts.jobs,
821
+ example: opts.example,
822
+ ui: opts.ui,
823
+ transport: opts.transport,
824
+ packageManager: opts.packageManager
825
+ };
826
+ await writeFile6(join7(targetDir, "blueprint.json"), JSON.stringify(blueprint, null, 2) + "\n");
827
+ }
828
+ async function writeJson(targetDir, rel, data) {
829
+ await writeFile6(join7(targetDir, rel, "blueprint.json"), JSON.stringify(data, null, 2) + "\n");
830
+ }
831
+ async function writeServiceBlueprints(targetDir, opts) {
832
+ const t = opts.transport;
833
+ await writeJson(targetDir, "apps/microservices/auth", {
834
+ schemaVersion: 1,
835
+ service: "auth",
836
+ authProvider: opts.authProvider,
837
+ transport: t
838
+ });
839
+ if (opts.upload !== "none") {
840
+ await writeJson(targetDir, "apps/microservices/upload", {
841
+ schemaVersion: 1,
842
+ service: "upload",
843
+ storageProvider: opts.upload,
844
+ transport: t
845
+ });
751
846
  }
752
- const appModulePath = join3(targetDir, "apps/api/src/app/app.module.ts");
753
- try {
754
- const appModule = await readFile3(appModulePath, "utf8");
755
- const next = appModule.replace(/^import \{ StorageModule \} from '\.\/storage\/storage\.module';\n/m, "").replace(/,\s*StorageModule/g, "");
756
- await writeFile2(appModulePath, next);
757
- } catch {
847
+ if (opts.example !== "none") {
848
+ await writeJson(targetDir, "apps/microservices/notes", {
849
+ schemaVersion: 1,
850
+ service: "notes",
851
+ dbProvider: opts.dbProvider,
852
+ transport: t
853
+ });
758
854
  }
759
- const gatewayEnv = join3(targetDir, "apps/api/.env");
760
- try {
761
- const env = await readFile3(gatewayEnv, "utf8");
762
- const next = env.split("\n").filter(
763
- (line) => !line.startsWith("UPLOAD_") && !line.startsWith("# UPLOAD_") && !line.startsWith("MAX_FILE_SIZE_KB")
764
- ).join("\n");
765
- await writeFile2(gatewayEnv, next);
766
- } catch {
855
+ if (opts.payment !== "none") {
856
+ await writeJson(targetDir, "apps/microservices/payment", {
857
+ schemaVersion: 1,
858
+ service: "payment",
859
+ paymentProvider: opts.payment,
860
+ transport: t
861
+ });
767
862
  }
768
- await stripDeps(join3(targetDir, "apps/api/package.json"), [
769
- "@icore/upload-client",
770
- "@types/multer"
771
- ]);
863
+ if (opts.jobs !== "none") {
864
+ await writeJson(targetDir, "apps/microservices/jobs", {
865
+ schemaVersion: 1,
866
+ service: "jobs",
867
+ jobsProvider: opts.jobs
868
+ });
869
+ }
870
+ const features = [];
871
+ if (opts.example !== "none") features.push("notes");
872
+ if (opts.payment !== "none") features.push("payment");
873
+ if (opts.jobs !== "none") features.push("jobs");
874
+ await writeJson(targetDir, "apps/api", {
875
+ schemaVersion: 1,
876
+ service: "api",
877
+ features,
878
+ transport: t
879
+ });
880
+ await writeJson(targetDir, "apps/client", {
881
+ schemaVersion: 1,
882
+ service: "client",
883
+ ui: opts.ui
884
+ });
772
885
  }
773
886
 
887
+ // src/manifest/wire-auth.ts
888
+ var AUTH = {
889
+ section: MANIFEST.auth,
890
+ providerFile: "apps/microservices/auth/src/app/auth.provider.ts",
891
+ exportConst: "AuthProviderModule",
892
+ msPackageJson: "apps/microservices/auth/package.json",
893
+ envPath: "apps/microservices/auth/.env"
894
+ };
895
+ var writeAuthProvider = (targetDir, provider) => writeProvider(targetDir, AUTH, provider);
896
+ var cleanupUnusedAuth = (targetDir, chosen) => cleanupUnusedAxis(targetDir, AUTH, chosen);
897
+
898
+ // src/manifest/wire-storage.ts
899
+ var STORAGE = {
900
+ section: MANIFEST.storage,
901
+ providerFile: "apps/microservices/upload/src/app/storage.provider.ts",
902
+ exportConst: "StorageProviderModule",
903
+ msPackageJson: "apps/microservices/upload/package.json",
904
+ envPath: "apps/microservices/upload/.env"
905
+ };
906
+ var writeStorageProvider = (targetDir, provider) => writeProvider(targetDir, STORAGE, provider);
907
+ var cleanupUnusedStorage = (targetDir, chosen) => cleanupUnusedAxis(targetDir, STORAGE, chosen);
908
+
909
+ // src/manifest/wire-db.ts
910
+ var DB = {
911
+ section: MANIFEST.db,
912
+ providerFile: "apps/microservices/notes/src/app/db.provider.ts",
913
+ exportConst: "DbProviderModule",
914
+ msPackageJson: "apps/microservices/notes/package.json",
915
+ envPath: "apps/microservices/notes/.env"
916
+ };
917
+ var writeDbProvider = (targetDir, provider) => writeProvider(targetDir, DB, provider);
918
+ var cleanupUnusedDb = (targetDir, chosen) => cleanupUnusedAxis(targetDir, DB, chosen);
919
+
774
920
  // src/lib/scaffold-pkg.ts
775
- import { readFile as readFile4, writeFile as writeFile3, mkdir, readdir } from "fs/promises";
776
- import { join as join4 } from "path";
921
+ import { readFile as readFile6, writeFile as writeFile7, mkdir, readdir } from "fs/promises";
922
+ import { join as join8 } from "path";
777
923
 
778
924
  // src/lib/options.ts
779
925
  function pmRun(pm, script) {
@@ -782,8 +928,8 @@ function pmRun(pm, script) {
782
928
 
783
929
  // src/lib/scaffold-pkg.ts
784
930
  async function writePnpmWorkspace(targetDir) {
785
- const pkgPath = join4(targetDir, "package.json");
786
- const pkg = JSON.parse(await readFile4(pkgPath, "utf8"));
931
+ const pkgPath = join8(targetDir, "package.json");
932
+ const pkg = JSON.parse(await readFile6(pkgPath, "utf8"));
787
933
  const workspaces = pkg.workspaces ?? [];
788
934
  const packagesBlock = workspaces.map((p3) => ` - '${p3}'`).join("\n");
789
935
  const allowBuilds = [
@@ -804,7 +950,7 @@ ${packagesBlock}
804
950
  allowBuilds:
805
951
  ${allowBuilds}
806
952
  `;
807
- await writeFile3(join4(targetDir, "pnpm-workspace.yaml"), content);
953
+ await writeFile7(join8(targetDir, "pnpm-workspace.yaml"), content);
808
954
  }
809
955
  async function rewritePnpmWorkspaceDeps(targetDir) {
810
956
  async function walk(dir) {
@@ -817,9 +963,9 @@ async function rewritePnpmWorkspaceDeps(targetDir) {
817
963
  }
818
964
  for (const e of entries) {
819
965
  if (e.isDirectory() && e.name !== "node_modules") {
820
- found.push(...await walk(join4(dir, e.name)));
966
+ found.push(...await walk(join8(dir, e.name)));
821
967
  } else if (e.isFile() && e.name === "package.json") {
822
- found.push(join4(dir, e.name));
968
+ found.push(join8(dir, e.name));
823
969
  }
824
970
  }
825
971
  return found;
@@ -827,17 +973,17 @@ async function rewritePnpmWorkspaceDeps(targetDir) {
827
973
  const pkgFiles = await walk(targetDir);
828
974
  for (const f of pkgFiles) {
829
975
  try {
830
- const raw = await readFile4(f, "utf8");
976
+ const raw = await readFile6(f, "utf8");
831
977
  const next = raw.replace(/"(@icore\/[^"]+)":\s*"\*"/g, '"$1": "workspace:*"');
832
- if (next !== raw) await writeFile3(f, next);
978
+ if (next !== raw) await writeFile7(f, next);
833
979
  } catch {
834
980
  }
835
981
  }
836
982
  }
837
983
  async function patchGitignoreForPm(targetDir, pm) {
838
- const giPath = join4(targetDir, ".gitignore");
984
+ const giPath = join8(targetDir, ".gitignore");
839
985
  try {
840
- let src = await readFile4(giPath, "utf8");
986
+ let src = await readFile6(giPath, "utf8");
841
987
  src = src.replace(/^# Build artifacts.*\ntools\/create-icore\/templates\/\s*\n/m, "");
842
988
  if (pm !== "yarn") {
843
989
  src = src.replace(/^\.yarn\/\*\s*\n/m, "").replace(/^!\.yarn\/patches\s*\n/m, "").replace(/^!\.yarn\/plugins\s*\n/m, "").replace(/^!\.yarn\/releases\s*\n/m, "").replace(/^!\.yarn\/sdks\s*\n/m, "").replace(/^!\.yarn\/versions\s*\n/m, "").replace(/^\.pnp\.\*\s*\n/m, "");
@@ -852,7 +998,7 @@ async function patchGitignoreForPm(targetDir, pm) {
852
998
  src += "\n# npm\nnpm-debug.log*\n";
853
999
  }
854
1000
  }
855
- await writeFile3(giPath, src);
1001
+ await writeFile7(giPath, src);
856
1002
  } catch {
857
1003
  }
858
1004
  }
@@ -867,7 +1013,7 @@ async function writeAiFiles(targetDir, opts) {
867
1013
  if (opts.jobs !== "none") activeMSes.push(`jobs (standalone)`);
868
1014
  const usesSupabase = opts.authProvider === "supabase" || opts.dbProvider === "supabase" || opts.upload === "supabase";
869
1015
  const usesFirebase = opts.authProvider === "firebase" || opts.dbProvider === "firebase" || opts.upload === "firebase";
870
- await writeFile3(join4(targetDir, "CLAUDE.md"), "@AGENTS.md\n");
1016
+ await writeFile7(join8(targetDir, "CLAUDE.md"), "@AGENTS.md\n");
871
1017
  const uiLabel = { shadcn: "shadcn/ui + Tailwind", antd: "Ant Design 6", mui: "MUI 6" }[opts.ui];
872
1018
  const readme = `# ${opts.projectName}
873
1019
 
@@ -917,7 +1063,7 @@ ${pm === "yarn" ? "yarn remove-notes" : pm === "pnpm" ? "pnpm remove-notes" : "n
917
1063
 
918
1064
  Apache-2.0
919
1065
  `;
920
- await writeFile3(join4(targetDir, "README.md"), readme);
1066
+ await writeFile7(join8(targetDir, "README.md"), readme);
921
1067
  const agents = `# ${opts.projectName} \u2014 Agent Instructions
922
1068
 
923
1069
  ## Stack snapshot
@@ -998,8 +1144,8 @@ ${opts.upload !== "none" ? `| \`apps/microservices/upload/.env\` | \`STORAGE_PRO
998
1144
  - Test behaviour, not implementation. Fake strategies from \`@icore/shared\` (FakeAuthStrategy etc.) serve as test doubles.
999
1145
  - Run: \`${nx} test <project>\`
1000
1146
  `;
1001
- await writeFile3(join4(targetDir, "AGENTS.md"), agents);
1002
- await mkdir(join4(targetDir, ".claude"), { recursive: true });
1147
+ await writeFile7(join8(targetDir, "AGENTS.md"), agents);
1148
+ await mkdir(join8(targetDir, ".claude"), { recursive: true });
1003
1149
  const mcpServers = {
1004
1150
  nx: {
1005
1151
  command: "npx",
@@ -1040,8 +1186,8 @@ ${opts.upload !== "none" ? `| \`apps/microservices/upload/.env\` | \`STORAGE_PRO
1040
1186
  ]
1041
1187
  }
1042
1188
  };
1043
- await writeFile3(
1044
- join4(targetDir, ".claude", "settings.json"),
1189
+ await writeFile7(
1190
+ join8(targetDir, ".claude", "settings.json"),
1045
1191
  JSON.stringify(settings, null, 2) + "\n"
1046
1192
  );
1047
1193
  }
@@ -1065,16 +1211,16 @@ async function copyTree(src, dest) {
1065
1211
  const entries = await readdir2(src, { withFileTypes: true });
1066
1212
  for (const entry of entries) {
1067
1213
  if (IGNORE_TOP.has(entry.name)) continue;
1068
- const s = join5(src, entry.name);
1069
- const d = join5(dest, entry.name);
1214
+ const s = join9(src, entry.name);
1215
+ const d = join9(dest, entry.name);
1070
1216
  if (entry.isDirectory()) await copyTree(s, d);
1071
1217
  else if (entry.isFile()) await copyFile(s, d);
1072
1218
  }
1073
1219
  }
1074
1220
  async function selectClientTemplate(targetDir, opts) {
1075
- const templatesRoot = join5(targetDir, "apps/templates");
1076
- const chosen = join5(templatesRoot, `client-${opts.ui}`);
1077
- const destClient = join5(targetDir, "apps/client");
1221
+ const templatesRoot = join9(targetDir, "apps/templates");
1222
+ const chosen = join9(templatesRoot, `client-${opts.ui}`);
1223
+ const destClient = join9(targetDir, "apps/client");
1078
1224
  let chosenUi = opts.ui;
1079
1225
  try {
1080
1226
  const s = await stat(chosen);
@@ -1082,9 +1228,9 @@ async function selectClientTemplate(targetDir, opts) {
1082
1228
  await copyTree(chosen, destClient);
1083
1229
  } catch {
1084
1230
  chosenUi = "shadcn";
1085
- await copyTree(join5(templatesRoot, "client-shadcn"), destClient);
1231
+ await copyTree(join9(templatesRoot, "client-shadcn"), destClient);
1086
1232
  }
1087
- await rm2(templatesRoot, { recursive: true, force: true });
1233
+ await rm4(templatesRoot, { recursive: true, force: true });
1088
1234
  await rewriteClientPaths(destClient, chosenUi);
1089
1235
  }
1090
1236
  async function rewriteClientPaths(clientDir, ui) {
@@ -1097,11 +1243,11 @@ async function rewriteClientPaths(clientDir, ui) {
1097
1243
  "eslint.config.mjs"
1098
1244
  ];
1099
1245
  for (const rel of candidates) {
1100
- const path = join5(clientDir, rel);
1246
+ const path = join9(clientDir, rel);
1101
1247
  try {
1102
- const raw = await readFile5(path, "utf8");
1248
+ const raw = await readFile7(path, "utf8");
1103
1249
  const next = raw.replaceAll("../../../", "../../").replaceAll(`apps/templates/client-${ui}`, "apps/client").replaceAll(`client-${ui}`, "client");
1104
- if (next !== raw) await writeFile4(path, next);
1250
+ if (next !== raw) await writeFile8(path, next);
1105
1251
  } catch {
1106
1252
  }
1107
1253
  }
@@ -1117,12 +1263,12 @@ function gitInit(cwd, projectName) {
1117
1263
  }
1118
1264
  function resolveYarnBin(cwd) {
1119
1265
  try {
1120
- const yarnrc = readFileSync(join5(cwd, ".yarnrc.yml"), "utf8");
1266
+ const yarnrc = readFileSync(join9(cwd, ".yarnrc.yml"), "utf8");
1121
1267
  const match = yarnrc.match(/^yarnPath:\s*(.+)$/m);
1122
- if (match?.[1]) return join5(cwd, match[1].trim());
1268
+ if (match?.[1]) return join9(cwd, match[1].trim());
1123
1269
  } catch {
1124
1270
  }
1125
- return join5(cwd, ".yarn", "releases", "yarn-4.5.0.cjs");
1271
+ return join9(cwd, ".yarn", "releases", "yarn-4.5.0.cjs");
1126
1272
  }
1127
1273
  function runInstall(cwd, pm) {
1128
1274
  if (pm === "yarn") {
@@ -1145,19 +1291,29 @@ async function scaffold(opts, templatesDir2) {
1145
1291
  await selectClientTemplate(opts.targetDir, opts);
1146
1292
  await writeClientEnv(opts.targetDir);
1147
1293
  if (opts.upload === "none") await removeUploadStack(opts.targetDir);
1148
- if (opts.payment === "none") await removePaymentStack(opts.targetDir);
1149
- if (opts.jobs === "none") await removeJobsStack(opts.targetDir);
1150
- if (opts.example === "none") await removeNotesStack(opts.targetDir);
1151
- await removeUnusedAuthStrategies(opts.targetDir, opts.authProvider);
1152
- await removeUnusedStorageStrategies(opts.targetDir, opts.upload);
1153
- await removeUnusedDbStrategies(opts.targetDir, opts.dbProvider);
1294
+ await cleanupUnusedFeatures(opts.targetDir, opts);
1295
+ await writeFeaturesWiring(opts.targetDir, opts);
1296
+ await writeNavConfig(opts.targetDir, opts);
1297
+ await cleanupUnusedAuth(opts.targetDir, opts.authProvider);
1298
+ await writeAuthProvider(opts.targetDir, opts.authProvider);
1299
+ if (opts.upload !== "none") {
1300
+ await cleanupUnusedStorage(opts.targetDir, opts.upload);
1301
+ await writeStorageProvider(opts.targetDir, opts.upload);
1302
+ }
1303
+ if (opts.example !== "none") {
1304
+ await cleanupUnusedDb(opts.targetDir, opts.dbProvider);
1305
+ await writeDbProvider(opts.targetDir, opts.dbProvider);
1306
+ }
1154
1307
  const firebaseUsed = opts.authProvider === "firebase" || opts.dbProvider === "firebase" || opts.upload === "firebase";
1155
1308
  if (!firebaseUsed) await removeFirebaseAdminLib(opts.targetDir);
1309
+ await pruneRootProviderDeps(opts.targetDir, opts);
1310
+ await writeBlueprintJson(opts.targetDir, opts);
1311
+ await writeServiceBlueprints(opts.targetDir, opts);
1156
1312
  if (opts.packageManager === "yarn") {
1157
- await writeFile4(join5(opts.targetDir, "yarn.lock"), "");
1313
+ await writeFile8(join9(opts.targetDir, "yarn.lock"), "");
1158
1314
  } else {
1159
- await rm2(join5(opts.targetDir, ".yarn"), { recursive: true, force: true });
1160
- await rm2(join5(opts.targetDir, ".yarnrc.yml"), { force: true });
1315
+ await rm4(join9(opts.targetDir, ".yarn"), { recursive: true, force: true });
1316
+ await rm4(join9(opts.targetDir, ".yarnrc.yml"), { force: true });
1161
1317
  }
1162
1318
  if (opts.packageManager === "pnpm") {
1163
1319
  await writePnpmWorkspace(opts.targetDir);