@idevconn/create-icore 0.4.1 → 0.5.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 (48) hide show
  1. package/dist/cli.js +221 -0
  2. package/dist/index.cjs +221 -0
  3. package/dist/index.js +221 -0
  4. package/package.json +1 -1
  5. package/templates/apps/api/src/app/app.module.ts +5 -1
  6. package/templates/apps/api/src/main.ts +12 -6
  7. package/templates/apps/microservices/auth/project.json +2 -1
  8. package/templates/apps/microservices/auth/src/app/app.module.ts +47 -23
  9. package/templates/apps/microservices/jobs/project.json +2 -1
  10. package/templates/apps/microservices/notes/project.json +2 -1
  11. package/templates/apps/microservices/notes/src/app/app.module.ts +44 -25
  12. package/templates/apps/microservices/payment/project.json +2 -1
  13. package/templates/apps/microservices/payment/src/app/app.module.ts +35 -12
  14. package/templates/apps/microservices/upload/project.json +2 -1
  15. package/templates/apps/microservices/upload/src/app/app.module.ts +48 -28
  16. package/templates/apps/templates/client-antd/.env.example +7 -0
  17. package/templates/apps/templates/client-antd/vite.config.mts +4 -4
  18. package/templates/apps/templates/client-mui/.env.example +7 -0
  19. package/templates/apps/templates/client-mui/vite.config.mts +4 -4
  20. package/templates/apps/templates/client-shadcn/.env.example +6 -1
  21. package/templates/apps/templates/client-shadcn/vite.config.mts +4 -4
  22. package/templates/libs/auth-client/src/index.ts +1 -0
  23. package/templates/libs/auth-client/src/lib/auth-client.module.ts +1 -1
  24. package/templates/libs/auth-client/src/lib/auth-client.service.ts +1 -1
  25. package/templates/libs/auth-client/src/lib/auth-client.tokens.ts +4 -0
  26. package/templates/libs/jobs-client/src/index.ts +1 -0
  27. package/templates/libs/jobs-client/src/lib/jobs-client.module.ts +1 -1
  28. package/templates/libs/jobs-client/src/lib/jobs-client.service.ts +1 -1
  29. package/templates/libs/jobs-client/src/lib/jobs-client.tokens.ts +4 -0
  30. package/templates/libs/notes-client/src/index.ts +1 -0
  31. package/templates/libs/notes-client/src/lib/notes-client.module.ts +1 -1
  32. package/templates/libs/notes-client/src/lib/notes-client.service.ts +1 -1
  33. package/templates/libs/notes-client/src/lib/notes-client.tokens.ts +4 -0
  34. package/templates/libs/payment-client/src/index.ts +1 -0
  35. package/templates/libs/payment-client/src/lib/payment-client.module.ts +1 -1
  36. package/templates/libs/payment-client/src/lib/payment-client.service.ts +1 -1
  37. package/templates/libs/payment-client/src/lib/payment-client.tokens.ts +4 -0
  38. package/templates/libs/shared/src/env.ts +88 -0
  39. package/templates/libs/shared/src/index.ts +1 -0
  40. package/templates/libs/shared/src/transport.ts +37 -0
  41. package/templates/libs/upload-client/src/index.ts +1 -0
  42. package/templates/libs/upload-client/src/lib/upload-client.module.ts +1 -1
  43. package/templates/libs/upload-client/src/lib/upload-client.service.ts +1 -1
  44. package/templates/libs/upload-client/src/lib/upload-client.tokens.ts +4 -0
  45. package/templates/libs/vite-plugins/src/index.d.mts +6 -0
  46. package/templates/libs/vite-plugins/src/index.mjs +50 -0
  47. package/templates/package.json +1 -0
  48. package/templates/tools/create-icore/_template-shell/package.json +1 -0
package/dist/cli.js CHANGED
@@ -201,6 +201,12 @@ Re-run with @latest to refresh:
201
201
  });
202
202
  if (p.isCancel(transport)) throw new Error("cancelled");
203
203
  const packageManager = flags.packageManager ?? detectPackageManager();
204
+ if (packageManager === "yarn") {
205
+ p.note(
206
+ "yarn 4.15+ enforces a 24h publish-age gate (npmMinimalAgeGate=1d), so a\n`yarn create @idevconn/icore@latest` run within 24h of a release resolves an\nolder version. If the banner above shows an unexpectedly old version, either:\n \u2022 wait \u2014 the version auto-unlocks 24h after publish, or\n \u2022 bypass once: yarn config set npmMinimalAgeGate 0 (then re-run), or\n \u2022 use npm/pnpm: npm init @idevconn/icore@latest <name> -- [flags]",
207
+ "\u26A0 yarn 24h age-gate"
208
+ );
209
+ }
204
210
  const initGit = flags.initGit ?? !await p.confirm({ message: "Initialise git repo?", initialValue: true }) === false;
205
211
  const install = flags.install ?? !await p.confirm({
206
212
  message: `Run ${packageManager} install?`,
@@ -263,6 +269,22 @@ async function rewriteRootPackageJson(targetDir, opts) {
263
269
  if (opts.packageManager !== "yarn") {
264
270
  delete pkg.packageManager;
265
271
  }
272
+ if (opts.packageManager === "pnpm") {
273
+ pkg["pnpm"] = {
274
+ onlyBuiltDependencies: [
275
+ "@firebase/util",
276
+ "@nestjs/core",
277
+ "@parcel/watcher",
278
+ "@scarf/scarf",
279
+ "@swc/core",
280
+ "less",
281
+ "msgpackr-extract",
282
+ "nx",
283
+ "protobufjs",
284
+ "unrs-resolver"
285
+ ]
286
+ };
287
+ }
266
288
  await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
267
289
  }
268
290
  async function writeAuthEnv(targetDir, opts) {
@@ -315,6 +337,14 @@ async function writeRootEnv(targetDir, opts) {
315
337
  ];
316
338
  await writeFile(join2(targetDir, ".env"), lines.join("\n"));
317
339
  }
340
+ async function writeClientEnv(targetDir) {
341
+ const envExample = join2(targetDir, "apps/client/.env.example");
342
+ try {
343
+ const env = await readFile2(envExample, "utf8");
344
+ await writeFile(join2(targetDir, "apps/client/.env"), env);
345
+ } catch {
346
+ }
347
+ }
318
348
  async function writePaymentEnv(targetDir, opts) {
319
349
  if (opts.payment === "none") return;
320
350
  const envExample = join2(targetDir, "apps/microservices/payment/.env.example");
@@ -691,6 +721,7 @@ async function scaffold(opts, templatesDir2) {
691
721
  await writeGatewayEnv(opts.targetDir, opts);
692
722
  await writeRootEnv(opts.targetDir, opts);
693
723
  await selectClientTemplate(opts.targetDir, opts);
724
+ await writeClientEnv(opts.targetDir);
694
725
  if (opts.upload === "none") await removeUploadStack(opts.targetDir);
695
726
  if (opts.payment === "none") await removePaymentStack(opts.targetDir);
696
727
  if (opts.jobs === "none") await removeJobsStack(opts.targetDir);
@@ -701,9 +732,199 @@ async function scaffold(opts, templatesDir2) {
701
732
  if (opts.packageManager === "yarn") {
702
733
  await writeFile(join2(opts.targetDir, "yarn.lock"), "");
703
734
  }
735
+ await writeAiFiles(opts.targetDir, opts);
704
736
  if (opts.install) runInstall(opts.targetDir, opts.packageManager);
705
737
  if (opts.initGit) gitInit(opts.targetDir, opts.projectName);
706
738
  }
739
+ async function writeAiFiles(targetDir, opts) {
740
+ const pm = opts.packageManager;
741
+ const nx = pm === "npm" ? "npx nx" : `${pm} nx`;
742
+ const devCmd = `${pm} dev`;
743
+ const activeMSes = ["auth (port 4001)"];
744
+ if (opts.upload !== "none") activeMSes.push(`upload (port 4002)`);
745
+ if (opts.payment !== "none") activeMSes.push(`payment (port 4003)`);
746
+ if (opts.example !== "none") activeMSes.push(`notes (port 4004)`);
747
+ if (opts.jobs !== "none") activeMSes.push(`jobs (standalone)`);
748
+ const usesSupabase = opts.authProvider === "supabase" || opts.dbProvider === "supabase" || opts.upload === "supabase";
749
+ const usesFirebase = opts.authProvider === "firebase" || opts.dbProvider === "firebase" || opts.upload === "firebase";
750
+ await writeFile(join2(targetDir, "CLAUDE.md"), "@AGENTS.md\n");
751
+ const uiLabel = { shadcn: "shadcn/ui + Tailwind", antd: "Ant Design 6", mui: "MUI 6" }[opts.ui];
752
+ const readme = `# ${opts.projectName}
753
+
754
+ > Scaffolded with [iCore](https://github.com/iDEVconn/create-icore) \u2014 Nx + NestJS + React full-stack template.
755
+
756
+ ## Stack
757
+
758
+ | Layer | Technology |
759
+ |-------|-----------|
760
+ | Monorepo | Nx + ${pm} |
761
+ | Gateway | NestJS 11 + Swagger |
762
+ | Auth | ${opts.authProvider} |
763
+ | Database | ${opts.dbProvider} |
764
+ | Upload | ${opts.upload === "none" ? "\u2014" : opts.upload} |
765
+ | UI | ${uiLabel} + TanStack Router + Query |
766
+ | i18n | i18next (en / ru / he) |
767
+
768
+ ## Quick start
769
+
770
+ \`\`\`bash
771
+ # 1. Fill in provider credentials
772
+ # apps/microservices/auth/.env
773
+ # apps/microservices/upload/.env (if upload is enabled)
774
+ # apps/client/.env (VITE_API_URL \u2014 already defaults to /api)
775
+
776
+ # 2. Start everything
777
+ ${devCmd}
778
+ # \u2192 http://localhost:4200 client
779
+ # \u2192 http://localhost:3001/api/docs Swagger
780
+ \`\`\`
781
+
782
+ ## Commands
783
+
784
+ \`\`\`bash
785
+ ${nx} run <project>:serve # start a single service
786
+ ${nx} test <project> # unit tests
787
+ ${nx} lint <project> # lint
788
+ ${nx} build <project> # production build
789
+ ${pm === "yarn" ? "yarn remove-notes" : pm === "pnpm" ? "pnpm remove-notes" : "npm run remove-notes"} # strip the notes sample feature
790
+ \`\`\`
791
+
792
+ ## Scaffolded by
793
+
794
+ [iCore](https://github.com/iDEVconn/create-icore) \u2014 [@idevconn/create-icore](https://www.npmjs.com/package/@idevconn/create-icore)
795
+
796
+ ## License
797
+
798
+ Apache-2.0
799
+ `;
800
+ await writeFile(join2(targetDir, "README.md"), readme);
801
+ const agents = `# ${opts.projectName} \u2014 Agent Instructions
802
+
803
+ ## Stack snapshot
804
+
805
+ | Dimension | Choice |
806
+ |------------|--------|
807
+ | Auth | ${opts.authProvider} |
808
+ | Database | ${opts.dbProvider} |
809
+ | Upload | ${opts.upload} |
810
+ | Payment | ${opts.payment} |
811
+ | Jobs | ${opts.jobs} |
812
+ | UI | ${opts.ui} |
813
+ | Transport | ${opts.transport} |
814
+ | PM | ${pm} |
815
+
816
+ ## \u{1F680} Mandatory Workflow
817
+
818
+ - **Branch strategy**: \`dev\` is default. Cut \`feature/<name>\` or \`bug/<name>\` from dev. PRs only target dev. Never push directly to main.
819
+ - **No code without approval**: Propose changes first, wait for go-ahead.
820
+ - **\u0417\u0410\u041A\u041E\u041D \u2014 no crash on missing .env**: MS factories must catch config errors, print a boxed banner with ALL missing vars, and return a Fake strategy in dev. In prod (\`NODE_ENV=production\`) throw the same banner. The \`formatEnvBanner\` + \`missingEnv\` helpers from \`@icore/shared\` handle this.
821
+ - **Post-coding routine**: \`npx prettier --write <files>\` \u2192 \`${nx} lint <project>\` \u2192 \`${nx} build <project>\` \u2014 all green before committing.
822
+ - **Nx generators only**: never hand-write \`project.json\` / tsconfig stacks. Use \`${nx} g @nx/<plugin>:<schematic>\`.
823
+
824
+ ## Architecture
825
+
826
+ \`\`\`
827
+ apps/
828
+ \u251C\u2500\u2500 api/ NestJS gateway \u2014 all client traffic enters here (:3001)
829
+ \u251C\u2500\u2500 microservices/
830
+ ${activeMSes.map((s) => `\u2502 \u251C\u2500\u2500 ${s.split(" ")[0]}/`).join("\n")}
831
+ \u2514\u2500\u2500 client/ Vite + React 19 + ${opts.ui} (:4200)
832
+ libs/
833
+ \u251C\u2500\u2500 shared/ contracts, CASL, transport helpers, env banner utils
834
+ \u251C\u2500\u2500 auth-strategies/${opts.authProvider}/
835
+ ${opts.upload !== "none" ? `\u251C\u2500\u2500 storage-strategies/${opts.upload}/
836
+ ` : ""}\u251C\u2500\u2500 db-strategies/${opts.dbProvider === "firebase" ? "firestore" : opts.dbProvider}/
837
+ \u251C\u2500\u2500 auth-client/ gateway \u2192 auth MS (TCP/Redis/NATS)
838
+ ${opts.upload !== "none" ? `\u251C\u2500\u2500 upload-client/ gateway \u2192 upload MS
839
+ ` : ""}\u2514\u2500\u2500 template-shared/ browser-safe React foundation (stores, i18n, CASL)
840
+ \`\`\`
841
+
842
+ ## Key patterns
843
+
844
+ **Strategy swap** \u2014 provider is chosen at runtime via env. Never import a concrete strategy in app code; always inject via the factory token (\`AuthStrategy\`, \`StorageStrategy\`, \`DBStrategy\`).
845
+
846
+ **Transport** \u2014 \`buildTransport(prefix)\` reads \`${opts.transport.toUpperCase()}*\` vars. Same helper on gateway client-modules and each MS \`main.ts\`. Supports tcp / redis / nats \u2014 change by flipping \`*_TRANSPORT\` in \`.env\`.
847
+
848
+ **Env layering**:
849
+ 1. Root \`.env\` \u2014 \`DB_PROVIDER\`
850
+ 2. \`apps/api/.env\` \u2014 gateway transport endpoints
851
+ 3. \`apps/microservices/<name>/.env\` \u2014 each MS provider + transport
852
+ 4. \`apps/client/.env\` \u2014 \`VITE_API_URL\`
853
+
854
+ ## Commands
855
+
856
+ \`\`\`bash
857
+ ${devCmd} # start all services
858
+ ${nx} run api:serve # gateway only
859
+ ${nx} run auth:serve # auth MS only
860
+ ${nx} test <project> # run tests
861
+ ${nx} lint <project> # lint
862
+ ${nx} build <project> # build
863
+ ${nx} g @nx/nest:resource # generate NestJS resource
864
+ \`\`\`
865
+
866
+ ## .env files to configure
867
+
868
+ | File | Key vars |
869
+ |------|----------|
870
+ | \`apps/microservices/auth/.env\` | \`AUTH_PROVIDER=${opts.authProvider}\`, ${opts.authProvider === "supabase" ? "`SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`" : "`FB_ADMIN_*`, `FIREBASE_WEB_API_KEY`"} |
871
+ ${opts.upload !== "none" ? `| \`apps/microservices/upload/.env\` | \`STORAGE_PROVIDER=${opts.upload}\`, provider creds |
872
+ ` : ""}| \`apps/microservices/notes/.env\` | \`DB_PROVIDER=${opts.dbProvider}\`, DB creds |
873
+ | \`apps/client/.env\` | \`VITE_API_URL=/api\` (proxied to :3001 in dev) |
874
+
875
+ ## Testing
876
+
877
+ - Unit tests: Vitest, files named \`*.unit.test.ts(x)\` in \`__tests__/\` next to source.
878
+ - Test behaviour, not implementation. Fake strategies from \`@icore/shared\` (FakeAuthStrategy etc.) serve as test doubles.
879
+ - Run: \`${nx} test <project>\`
880
+ `;
881
+ await writeFile(join2(targetDir, "AGENTS.md"), agents);
882
+ await mkdir(join2(targetDir, ".claude"), { recursive: true });
883
+ const mcpServers = {
884
+ nx: {
885
+ command: "npx",
886
+ args: ["-y", "@nx/mcp@latest", "--directory", "."],
887
+ type: "stdio"
888
+ }
889
+ };
890
+ if (usesSupabase) {
891
+ mcpServers["supabase"] = {
892
+ command: "npx",
893
+ args: [
894
+ "-y",
895
+ "@supabase/mcp-server-supabase@latest",
896
+ "--access-token",
897
+ "<SUPABASE_PERSONAL_ACCESS_TOKEN>"
898
+ ],
899
+ type: "stdio"
900
+ };
901
+ }
902
+ if (usesFirebase) {
903
+ mcpServers["firebase"] = {
904
+ command: "npx",
905
+ args: ["-y", "firebase-tools@latest", "experimental:mcp"],
906
+ type: "stdio"
907
+ };
908
+ }
909
+ const nxCmds = [`Bash(${nx} *)`, `Bash(${devCmd})`];
910
+ if (pm !== "npm") nxCmds.push(`Bash(npx nx *)`);
911
+ const settings = {
912
+ mcpServers,
913
+ permissions: {
914
+ allow: [
915
+ ...nxCmds,
916
+ "Bash(npx prettier *)",
917
+ "Bash(git status)",
918
+ "Bash(git diff *)",
919
+ "Bash(git log *)"
920
+ ]
921
+ }
922
+ };
923
+ await writeFile(
924
+ join2(targetDir, ".claude", "settings.json"),
925
+ JSON.stringify(settings, null, 2) + "\n"
926
+ );
927
+ }
707
928
 
708
929
  // src/cli.ts
709
930
  var [nodeMajor] = process.versions.node.split(".").map(Number);
package/dist/index.cjs CHANGED
@@ -79,6 +79,22 @@ async function rewriteRootPackageJson(targetDir, opts) {
79
79
  if (opts.packageManager !== "yarn") {
80
80
  delete pkg.packageManager;
81
81
  }
82
+ if (opts.packageManager === "pnpm") {
83
+ pkg["pnpm"] = {
84
+ onlyBuiltDependencies: [
85
+ "@firebase/util",
86
+ "@nestjs/core",
87
+ "@parcel/watcher",
88
+ "@scarf/scarf",
89
+ "@swc/core",
90
+ "less",
91
+ "msgpackr-extract",
92
+ "nx",
93
+ "protobufjs",
94
+ "unrs-resolver"
95
+ ]
96
+ };
97
+ }
82
98
  await (0, import_promises.writeFile)(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
83
99
  }
84
100
  async function writeAuthEnv(targetDir, opts) {
@@ -131,6 +147,14 @@ async function writeRootEnv(targetDir, opts) {
131
147
  ];
132
148
  await (0, import_promises.writeFile)((0, import_node_path.join)(targetDir, ".env"), lines.join("\n"));
133
149
  }
150
+ async function writeClientEnv(targetDir) {
151
+ const envExample = (0, import_node_path.join)(targetDir, "apps/client/.env.example");
152
+ try {
153
+ const env = await (0, import_promises.readFile)(envExample, "utf8");
154
+ await (0, import_promises.writeFile)((0, import_node_path.join)(targetDir, "apps/client/.env"), env);
155
+ } catch {
156
+ }
157
+ }
134
158
  async function writePaymentEnv(targetDir, opts) {
135
159
  if (opts.payment === "none") return;
136
160
  const envExample = (0, import_node_path.join)(targetDir, "apps/microservices/payment/.env.example");
@@ -507,6 +531,7 @@ async function scaffold(opts, templatesDir) {
507
531
  await writeGatewayEnv(opts.targetDir, opts);
508
532
  await writeRootEnv(opts.targetDir, opts);
509
533
  await selectClientTemplate(opts.targetDir, opts);
534
+ await writeClientEnv(opts.targetDir);
510
535
  if (opts.upload === "none") await removeUploadStack(opts.targetDir);
511
536
  if (opts.payment === "none") await removePaymentStack(opts.targetDir);
512
537
  if (opts.jobs === "none") await removeJobsStack(opts.targetDir);
@@ -517,9 +542,199 @@ async function scaffold(opts, templatesDir) {
517
542
  if (opts.packageManager === "yarn") {
518
543
  await (0, import_promises.writeFile)((0, import_node_path.join)(opts.targetDir, "yarn.lock"), "");
519
544
  }
545
+ await writeAiFiles(opts.targetDir, opts);
520
546
  if (opts.install) runInstall(opts.targetDir, opts.packageManager);
521
547
  if (opts.initGit) gitInit(opts.targetDir, opts.projectName);
522
548
  }
549
+ async function writeAiFiles(targetDir, opts) {
550
+ const pm = opts.packageManager;
551
+ const nx = pm === "npm" ? "npx nx" : `${pm} nx`;
552
+ const devCmd = `${pm} dev`;
553
+ const activeMSes = ["auth (port 4001)"];
554
+ if (opts.upload !== "none") activeMSes.push(`upload (port 4002)`);
555
+ if (opts.payment !== "none") activeMSes.push(`payment (port 4003)`);
556
+ if (opts.example !== "none") activeMSes.push(`notes (port 4004)`);
557
+ if (opts.jobs !== "none") activeMSes.push(`jobs (standalone)`);
558
+ const usesSupabase = opts.authProvider === "supabase" || opts.dbProvider === "supabase" || opts.upload === "supabase";
559
+ const usesFirebase = opts.authProvider === "firebase" || opts.dbProvider === "firebase" || opts.upload === "firebase";
560
+ await (0, import_promises.writeFile)((0, import_node_path.join)(targetDir, "CLAUDE.md"), "@AGENTS.md\n");
561
+ const uiLabel = { shadcn: "shadcn/ui + Tailwind", antd: "Ant Design 6", mui: "MUI 6" }[opts.ui];
562
+ const readme = `# ${opts.projectName}
563
+
564
+ > Scaffolded with [iCore](https://github.com/iDEVconn/create-icore) \u2014 Nx + NestJS + React full-stack template.
565
+
566
+ ## Stack
567
+
568
+ | Layer | Technology |
569
+ |-------|-----------|
570
+ | Monorepo | Nx + ${pm} |
571
+ | Gateway | NestJS 11 + Swagger |
572
+ | Auth | ${opts.authProvider} |
573
+ | Database | ${opts.dbProvider} |
574
+ | Upload | ${opts.upload === "none" ? "\u2014" : opts.upload} |
575
+ | UI | ${uiLabel} + TanStack Router + Query |
576
+ | i18n | i18next (en / ru / he) |
577
+
578
+ ## Quick start
579
+
580
+ \`\`\`bash
581
+ # 1. Fill in provider credentials
582
+ # apps/microservices/auth/.env
583
+ # apps/microservices/upload/.env (if upload is enabled)
584
+ # apps/client/.env (VITE_API_URL \u2014 already defaults to /api)
585
+
586
+ # 2. Start everything
587
+ ${devCmd}
588
+ # \u2192 http://localhost:4200 client
589
+ # \u2192 http://localhost:3001/api/docs Swagger
590
+ \`\`\`
591
+
592
+ ## Commands
593
+
594
+ \`\`\`bash
595
+ ${nx} run <project>:serve # start a single service
596
+ ${nx} test <project> # unit tests
597
+ ${nx} lint <project> # lint
598
+ ${nx} build <project> # production build
599
+ ${pm === "yarn" ? "yarn remove-notes" : pm === "pnpm" ? "pnpm remove-notes" : "npm run remove-notes"} # strip the notes sample feature
600
+ \`\`\`
601
+
602
+ ## Scaffolded by
603
+
604
+ [iCore](https://github.com/iDEVconn/create-icore) \u2014 [@idevconn/create-icore](https://www.npmjs.com/package/@idevconn/create-icore)
605
+
606
+ ## License
607
+
608
+ Apache-2.0
609
+ `;
610
+ await (0, import_promises.writeFile)((0, import_node_path.join)(targetDir, "README.md"), readme);
611
+ const agents = `# ${opts.projectName} \u2014 Agent Instructions
612
+
613
+ ## Stack snapshot
614
+
615
+ | Dimension | Choice |
616
+ |------------|--------|
617
+ | Auth | ${opts.authProvider} |
618
+ | Database | ${opts.dbProvider} |
619
+ | Upload | ${opts.upload} |
620
+ | Payment | ${opts.payment} |
621
+ | Jobs | ${opts.jobs} |
622
+ | UI | ${opts.ui} |
623
+ | Transport | ${opts.transport} |
624
+ | PM | ${pm} |
625
+
626
+ ## \u{1F680} Mandatory Workflow
627
+
628
+ - **Branch strategy**: \`dev\` is default. Cut \`feature/<name>\` or \`bug/<name>\` from dev. PRs only target dev. Never push directly to main.
629
+ - **No code without approval**: Propose changes first, wait for go-ahead.
630
+ - **\u0417\u0410\u041A\u041E\u041D \u2014 no crash on missing .env**: MS factories must catch config errors, print a boxed banner with ALL missing vars, and return a Fake strategy in dev. In prod (\`NODE_ENV=production\`) throw the same banner. The \`formatEnvBanner\` + \`missingEnv\` helpers from \`@icore/shared\` handle this.
631
+ - **Post-coding routine**: \`npx prettier --write <files>\` \u2192 \`${nx} lint <project>\` \u2192 \`${nx} build <project>\` \u2014 all green before committing.
632
+ - **Nx generators only**: never hand-write \`project.json\` / tsconfig stacks. Use \`${nx} g @nx/<plugin>:<schematic>\`.
633
+
634
+ ## Architecture
635
+
636
+ \`\`\`
637
+ apps/
638
+ \u251C\u2500\u2500 api/ NestJS gateway \u2014 all client traffic enters here (:3001)
639
+ \u251C\u2500\u2500 microservices/
640
+ ${activeMSes.map((s) => `\u2502 \u251C\u2500\u2500 ${s.split(" ")[0]}/`).join("\n")}
641
+ \u2514\u2500\u2500 client/ Vite + React 19 + ${opts.ui} (:4200)
642
+ libs/
643
+ \u251C\u2500\u2500 shared/ contracts, CASL, transport helpers, env banner utils
644
+ \u251C\u2500\u2500 auth-strategies/${opts.authProvider}/
645
+ ${opts.upload !== "none" ? `\u251C\u2500\u2500 storage-strategies/${opts.upload}/
646
+ ` : ""}\u251C\u2500\u2500 db-strategies/${opts.dbProvider === "firebase" ? "firestore" : opts.dbProvider}/
647
+ \u251C\u2500\u2500 auth-client/ gateway \u2192 auth MS (TCP/Redis/NATS)
648
+ ${opts.upload !== "none" ? `\u251C\u2500\u2500 upload-client/ gateway \u2192 upload MS
649
+ ` : ""}\u2514\u2500\u2500 template-shared/ browser-safe React foundation (stores, i18n, CASL)
650
+ \`\`\`
651
+
652
+ ## Key patterns
653
+
654
+ **Strategy swap** \u2014 provider is chosen at runtime via env. Never import a concrete strategy in app code; always inject via the factory token (\`AuthStrategy\`, \`StorageStrategy\`, \`DBStrategy\`).
655
+
656
+ **Transport** \u2014 \`buildTransport(prefix)\` reads \`${opts.transport.toUpperCase()}*\` vars. Same helper on gateway client-modules and each MS \`main.ts\`. Supports tcp / redis / nats \u2014 change by flipping \`*_TRANSPORT\` in \`.env\`.
657
+
658
+ **Env layering**:
659
+ 1. Root \`.env\` \u2014 \`DB_PROVIDER\`
660
+ 2. \`apps/api/.env\` \u2014 gateway transport endpoints
661
+ 3. \`apps/microservices/<name>/.env\` \u2014 each MS provider + transport
662
+ 4. \`apps/client/.env\` \u2014 \`VITE_API_URL\`
663
+
664
+ ## Commands
665
+
666
+ \`\`\`bash
667
+ ${devCmd} # start all services
668
+ ${nx} run api:serve # gateway only
669
+ ${nx} run auth:serve # auth MS only
670
+ ${nx} test <project> # run tests
671
+ ${nx} lint <project> # lint
672
+ ${nx} build <project> # build
673
+ ${nx} g @nx/nest:resource # generate NestJS resource
674
+ \`\`\`
675
+
676
+ ## .env files to configure
677
+
678
+ | File | Key vars |
679
+ |------|----------|
680
+ | \`apps/microservices/auth/.env\` | \`AUTH_PROVIDER=${opts.authProvider}\`, ${opts.authProvider === "supabase" ? "`SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`" : "`FB_ADMIN_*`, `FIREBASE_WEB_API_KEY`"} |
681
+ ${opts.upload !== "none" ? `| \`apps/microservices/upload/.env\` | \`STORAGE_PROVIDER=${opts.upload}\`, provider creds |
682
+ ` : ""}| \`apps/microservices/notes/.env\` | \`DB_PROVIDER=${opts.dbProvider}\`, DB creds |
683
+ | \`apps/client/.env\` | \`VITE_API_URL=/api\` (proxied to :3001 in dev) |
684
+
685
+ ## Testing
686
+
687
+ - Unit tests: Vitest, files named \`*.unit.test.ts(x)\` in \`__tests__/\` next to source.
688
+ - Test behaviour, not implementation. Fake strategies from \`@icore/shared\` (FakeAuthStrategy etc.) serve as test doubles.
689
+ - Run: \`${nx} test <project>\`
690
+ `;
691
+ await (0, import_promises.writeFile)((0, import_node_path.join)(targetDir, "AGENTS.md"), agents);
692
+ await (0, import_promises.mkdir)((0, import_node_path.join)(targetDir, ".claude"), { recursive: true });
693
+ const mcpServers = {
694
+ nx: {
695
+ command: "npx",
696
+ args: ["-y", "@nx/mcp@latest", "--directory", "."],
697
+ type: "stdio"
698
+ }
699
+ };
700
+ if (usesSupabase) {
701
+ mcpServers["supabase"] = {
702
+ command: "npx",
703
+ args: [
704
+ "-y",
705
+ "@supabase/mcp-server-supabase@latest",
706
+ "--access-token",
707
+ "<SUPABASE_PERSONAL_ACCESS_TOKEN>"
708
+ ],
709
+ type: "stdio"
710
+ };
711
+ }
712
+ if (usesFirebase) {
713
+ mcpServers["firebase"] = {
714
+ command: "npx",
715
+ args: ["-y", "firebase-tools@latest", "experimental:mcp"],
716
+ type: "stdio"
717
+ };
718
+ }
719
+ const nxCmds = [`Bash(${nx} *)`, `Bash(${devCmd})`];
720
+ if (pm !== "npm") nxCmds.push(`Bash(npx nx *)`);
721
+ const settings = {
722
+ mcpServers,
723
+ permissions: {
724
+ allow: [
725
+ ...nxCmds,
726
+ "Bash(npx prettier *)",
727
+ "Bash(git status)",
728
+ "Bash(git diff *)",
729
+ "Bash(git log *)"
730
+ ]
731
+ }
732
+ };
733
+ await (0, import_promises.writeFile)(
734
+ (0, import_node_path.join)(targetDir, ".claude", "settings.json"),
735
+ JSON.stringify(settings, null, 2) + "\n"
736
+ );
737
+ }
523
738
 
524
739
  // src/lib/prompts.ts
525
740
  var p = __toESM(require("@clack/prompts"), 1);
@@ -715,6 +930,12 @@ Re-run with @latest to refresh:
715
930
  });
716
931
  if (p.isCancel(transport)) throw new Error("cancelled");
717
932
  const packageManager = flags.packageManager ?? detectPackageManager();
933
+ if (packageManager === "yarn") {
934
+ p.note(
935
+ "yarn 4.15+ enforces a 24h publish-age gate (npmMinimalAgeGate=1d), so a\n`yarn create @idevconn/icore@latest` run within 24h of a release resolves an\nolder version. If the banner above shows an unexpectedly old version, either:\n \u2022 wait \u2014 the version auto-unlocks 24h after publish, or\n \u2022 bypass once: yarn config set npmMinimalAgeGate 0 (then re-run), or\n \u2022 use npm/pnpm: npm init @idevconn/icore@latest <name> -- [flags]",
936
+ "\u26A0 yarn 24h age-gate"
937
+ );
938
+ }
718
939
  const initGit = flags.initGit ?? !await p.confirm({ message: "Initialise git repo?", initialValue: true }) === false;
719
940
  const install = flags.install ?? !await p.confirm({
720
941
  message: `Run ${packageManager} install?`,