@idevconn/create-icore 0.4.0 → 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 (50) hide show
  1. package/dist/cli.js +238 -2
  2. package/dist/index.cjs +238 -2
  3. package/dist/index.js +238 -2
  4. package/package.json +1 -1
  5. package/templates/.yarn/releases/yarn-4.15.0.cjs +940 -0
  6. package/templates/.yarnrc.yml +6 -1
  7. package/templates/apps/api/src/app/app.module.ts +5 -1
  8. package/templates/apps/api/src/main.ts +12 -6
  9. package/templates/apps/microservices/auth/project.json +2 -1
  10. package/templates/apps/microservices/auth/src/app/app.module.ts +47 -23
  11. package/templates/apps/microservices/jobs/project.json +2 -1
  12. package/templates/apps/microservices/notes/project.json +2 -1
  13. package/templates/apps/microservices/notes/src/app/app.module.ts +44 -25
  14. package/templates/apps/microservices/payment/project.json +2 -1
  15. package/templates/apps/microservices/payment/src/app/app.module.ts +35 -12
  16. package/templates/apps/microservices/upload/project.json +2 -1
  17. package/templates/apps/microservices/upload/src/app/app.module.ts +48 -28
  18. package/templates/apps/templates/client-antd/.env.example +7 -0
  19. package/templates/apps/templates/client-antd/vite.config.mts +4 -4
  20. package/templates/apps/templates/client-mui/.env.example +7 -0
  21. package/templates/apps/templates/client-mui/vite.config.mts +4 -4
  22. package/templates/apps/templates/client-shadcn/.env.example +6 -1
  23. package/templates/apps/templates/client-shadcn/vite.config.mts +4 -4
  24. package/templates/libs/auth-client/src/index.ts +1 -0
  25. package/templates/libs/auth-client/src/lib/auth-client.module.ts +1 -1
  26. package/templates/libs/auth-client/src/lib/auth-client.service.ts +1 -1
  27. package/templates/libs/auth-client/src/lib/auth-client.tokens.ts +4 -0
  28. package/templates/libs/jobs-client/src/index.ts +1 -0
  29. package/templates/libs/jobs-client/src/lib/jobs-client.module.ts +1 -1
  30. package/templates/libs/jobs-client/src/lib/jobs-client.service.ts +1 -1
  31. package/templates/libs/jobs-client/src/lib/jobs-client.tokens.ts +4 -0
  32. package/templates/libs/notes-client/src/index.ts +1 -0
  33. package/templates/libs/notes-client/src/lib/notes-client.module.ts +1 -1
  34. package/templates/libs/notes-client/src/lib/notes-client.service.ts +1 -1
  35. package/templates/libs/notes-client/src/lib/notes-client.tokens.ts +4 -0
  36. package/templates/libs/payment-client/src/index.ts +1 -0
  37. package/templates/libs/payment-client/src/lib/payment-client.module.ts +1 -1
  38. package/templates/libs/payment-client/src/lib/payment-client.service.ts +1 -1
  39. package/templates/libs/payment-client/src/lib/payment-client.tokens.ts +4 -0
  40. package/templates/libs/shared/src/env.ts +88 -0
  41. package/templates/libs/shared/src/index.ts +1 -0
  42. package/templates/libs/shared/src/transport.ts +37 -0
  43. package/templates/libs/upload-client/src/index.ts +1 -0
  44. package/templates/libs/upload-client/src/lib/upload-client.module.ts +1 -1
  45. package/templates/libs/upload-client/src/lib/upload-client.service.ts +1 -1
  46. package/templates/libs/upload-client/src/lib/upload-client.tokens.ts +4 -0
  47. package/templates/libs/vite-plugins/src/index.d.mts +6 -0
  48. package/templates/libs/vite-plugins/src/index.mjs +50 -0
  49. package/templates/package.json +1 -0
  50. 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?`,
@@ -225,6 +231,7 @@ Re-run with @latest to refresh:
225
231
 
226
232
  // src/lib/scaffold.ts
227
233
  import { copyFile, mkdir, readdir, readFile as readFile2, stat, writeFile, rm } from "fs/promises";
234
+ import { readFileSync } from "fs";
228
235
  import { join as join2 } from "path";
229
236
  import { spawnSync } from "child_process";
230
237
  var IGNORE_TOP = /* @__PURE__ */ new Set([
@@ -262,6 +269,22 @@ async function rewriteRootPackageJson(targetDir, opts) {
262
269
  if (opts.packageManager !== "yarn") {
263
270
  delete pkg.packageManager;
264
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
+ }
265
288
  await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
266
289
  }
267
290
  async function writeAuthEnv(targetDir, opts) {
@@ -314,6 +337,14 @@ async function writeRootEnv(targetDir, opts) {
314
337
  ];
315
338
  await writeFile(join2(targetDir, ".env"), lines.join("\n"));
316
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
+ }
317
348
  async function writePaymentEnv(targetDir, opts) {
318
349
  if (opts.payment === "none") return;
319
350
  const envExample = join2(targetDir, "apps/microservices/payment/.env.example");
@@ -662,9 +693,23 @@ function gitInit(cwd, projectName) {
662
693
  { cwd, stdio: "inherit" }
663
694
  );
664
695
  }
696
+ function resolveYarnBin(cwd) {
697
+ try {
698
+ const yarnrc = readFileSync(join2(cwd, ".yarnrc.yml"), "utf8");
699
+ const match = yarnrc.match(/^yarnPath:\s*(.+)$/m);
700
+ if (match?.[1]) return join2(cwd, match[1].trim());
701
+ } catch {
702
+ }
703
+ return join2(cwd, ".yarn", "releases", "yarn-4.5.0.cjs");
704
+ }
665
705
  function runInstall(cwd, pm) {
666
- const [cmd, ...args] = pm === "npm" ? ["npm", "install"] : pm === "pnpm" ? ["pnpm", "install"] : ["yarn", "install"];
667
- spawnSync(cmd, args, { cwd, stdio: "inherit" });
706
+ if (pm === "yarn") {
707
+ spawnSync("node", [resolveYarnBin(cwd), "install"], { cwd, stdio: "inherit" });
708
+ } else if (pm === "npm") {
709
+ spawnSync("npm", ["install"], { cwd, stdio: "inherit" });
710
+ } else {
711
+ spawnSync("pnpm", ["install"], { cwd, stdio: "inherit" });
712
+ }
668
713
  }
669
714
  async function scaffold(opts, templatesDir2) {
670
715
  await copyTree(templatesDir2, opts.targetDir);
@@ -676,6 +721,7 @@ async function scaffold(opts, templatesDir2) {
676
721
  await writeGatewayEnv(opts.targetDir, opts);
677
722
  await writeRootEnv(opts.targetDir, opts);
678
723
  await selectClientTemplate(opts.targetDir, opts);
724
+ await writeClientEnv(opts.targetDir);
679
725
  if (opts.upload === "none") await removeUploadStack(opts.targetDir);
680
726
  if (opts.payment === "none") await removePaymentStack(opts.targetDir);
681
727
  if (opts.jobs === "none") await removeJobsStack(opts.targetDir);
@@ -686,9 +732,199 @@ async function scaffold(opts, templatesDir2) {
686
732
  if (opts.packageManager === "yarn") {
687
733
  await writeFile(join2(opts.targetDir, "yarn.lock"), "");
688
734
  }
735
+ await writeAiFiles(opts.targetDir, opts);
689
736
  if (opts.install) runInstall(opts.targetDir, opts.packageManager);
690
737
  if (opts.initGit) gitInit(opts.targetDir, opts.projectName);
691
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
+ }
692
928
 
693
929
  // src/cli.ts
694
930
  var [nodeMajor] = process.versions.node.split(".").map(Number);
package/dist/index.cjs CHANGED
@@ -41,6 +41,7 @@ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
41
41
 
42
42
  // src/lib/scaffold.ts
43
43
  var import_promises = require("fs/promises");
44
+ var import_node_fs = require("fs");
44
45
  var import_node_path = require("path");
45
46
  var import_node_child_process = require("child_process");
46
47
  var IGNORE_TOP = /* @__PURE__ */ new Set([
@@ -78,6 +79,22 @@ async function rewriteRootPackageJson(targetDir, opts) {
78
79
  if (opts.packageManager !== "yarn") {
79
80
  delete pkg.packageManager;
80
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
+ }
81
98
  await (0, import_promises.writeFile)(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
82
99
  }
83
100
  async function writeAuthEnv(targetDir, opts) {
@@ -130,6 +147,14 @@ async function writeRootEnv(targetDir, opts) {
130
147
  ];
131
148
  await (0, import_promises.writeFile)((0, import_node_path.join)(targetDir, ".env"), lines.join("\n"));
132
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
+ }
133
158
  async function writePaymentEnv(targetDir, opts) {
134
159
  if (opts.payment === "none") return;
135
160
  const envExample = (0, import_node_path.join)(targetDir, "apps/microservices/payment/.env.example");
@@ -478,9 +503,23 @@ function gitInit(cwd, projectName) {
478
503
  { cwd, stdio: "inherit" }
479
504
  );
480
505
  }
506
+ function resolveYarnBin(cwd) {
507
+ try {
508
+ const yarnrc = (0, import_node_fs.readFileSync)((0, import_node_path.join)(cwd, ".yarnrc.yml"), "utf8");
509
+ const match = yarnrc.match(/^yarnPath:\s*(.+)$/m);
510
+ if (match?.[1]) return (0, import_node_path.join)(cwd, match[1].trim());
511
+ } catch {
512
+ }
513
+ return (0, import_node_path.join)(cwd, ".yarn", "releases", "yarn-4.5.0.cjs");
514
+ }
481
515
  function runInstall(cwd, pm) {
482
- const [cmd, ...args] = pm === "npm" ? ["npm", "install"] : pm === "pnpm" ? ["pnpm", "install"] : ["yarn", "install"];
483
- (0, import_node_child_process.spawnSync)(cmd, args, { cwd, stdio: "inherit" });
516
+ if (pm === "yarn") {
517
+ (0, import_node_child_process.spawnSync)("node", [resolveYarnBin(cwd), "install"], { cwd, stdio: "inherit" });
518
+ } else if (pm === "npm") {
519
+ (0, import_node_child_process.spawnSync)("npm", ["install"], { cwd, stdio: "inherit" });
520
+ } else {
521
+ (0, import_node_child_process.spawnSync)("pnpm", ["install"], { cwd, stdio: "inherit" });
522
+ }
484
523
  }
485
524
  async function scaffold(opts, templatesDir) {
486
525
  await copyTree(templatesDir, opts.targetDir);
@@ -492,6 +531,7 @@ async function scaffold(opts, templatesDir) {
492
531
  await writeGatewayEnv(opts.targetDir, opts);
493
532
  await writeRootEnv(opts.targetDir, opts);
494
533
  await selectClientTemplate(opts.targetDir, opts);
534
+ await writeClientEnv(opts.targetDir);
495
535
  if (opts.upload === "none") await removeUploadStack(opts.targetDir);
496
536
  if (opts.payment === "none") await removePaymentStack(opts.targetDir);
497
537
  if (opts.jobs === "none") await removeJobsStack(opts.targetDir);
@@ -502,9 +542,199 @@ async function scaffold(opts, templatesDir) {
502
542
  if (opts.packageManager === "yarn") {
503
543
  await (0, import_promises.writeFile)((0, import_node_path.join)(opts.targetDir, "yarn.lock"), "");
504
544
  }
545
+ await writeAiFiles(opts.targetDir, opts);
505
546
  if (opts.install) runInstall(opts.targetDir, opts.packageManager);
506
547
  if (opts.initGit) gitInit(opts.targetDir, opts.projectName);
507
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
+ }
508
738
 
509
739
  // src/lib/prompts.ts
510
740
  var p = __toESM(require("@clack/prompts"), 1);
@@ -700,6 +930,12 @@ Re-run with @latest to refresh:
700
930
  });
701
931
  if (p.isCancel(transport)) throw new Error("cancelled");
702
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
+ }
703
939
  const initGit = flags.initGit ?? !await p.confirm({ message: "Initialise git repo?", initialValue: true }) === false;
704
940
  const install = flags.install ?? !await p.confirm({
705
941
  message: `Run ${packageManager} install?`,