@idevconn/create-icore 0.4.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/cli.js +304 -24
  2. package/dist/index.cjs +301 -23
  3. package/dist/index.d.cts +7 -1
  4. package/dist/index.d.ts +7 -1
  5. package/dist/index.js +300 -23
  6. package/package.json +1 -1
  7. package/templates/apps/api/.env.example +14 -0
  8. package/templates/apps/api/src/app/app.module.ts +5 -1
  9. package/templates/apps/api/src/main.ts +12 -6
  10. package/templates/apps/microservices/auth/package.json +1 -1
  11. package/templates/apps/microservices/auth/project.json +2 -1
  12. package/templates/apps/microservices/auth/src/app/app.module.ts +50 -39
  13. package/templates/apps/microservices/auth/src/main.ts +6 -23
  14. package/templates/apps/microservices/jobs/project.json +2 -1
  15. package/templates/apps/microservices/jobs/src/app/redis-connection.ts +35 -0
  16. package/templates/apps/microservices/jobs/src/app/workers/cleanup.worker.ts +2 -1
  17. package/templates/apps/microservices/jobs/src/app/workers/email.worker.ts +2 -1
  18. package/templates/apps/microservices/jobs/src/app/workers/image-process.worker.ts +2 -1
  19. package/templates/apps/microservices/notes/project.json +2 -1
  20. package/templates/apps/microservices/notes/src/app/app.module.ts +52 -38
  21. package/templates/apps/microservices/notes/src/main.ts +6 -23
  22. package/templates/apps/microservices/payment/project.json +2 -1
  23. package/templates/apps/microservices/payment/src/app/app.module.ts +37 -12
  24. package/templates/apps/microservices/payment/src/main.ts +6 -23
  25. package/templates/apps/microservices/upload/package.json +1 -1
  26. package/templates/apps/microservices/upload/project.json +2 -1
  27. package/templates/apps/microservices/upload/src/app/app.module.ts +50 -42
  28. package/templates/apps/microservices/upload/src/main.ts +6 -23
  29. package/templates/apps/templates/client-antd/.env.example +7 -0
  30. package/templates/apps/templates/client-antd/vite.config.mts +4 -4
  31. package/templates/apps/templates/client-mui/.env.example +7 -0
  32. package/templates/apps/templates/client-mui/vite.config.mts +4 -4
  33. package/templates/apps/templates/client-shadcn/.env.example +6 -1
  34. package/templates/apps/templates/client-shadcn/vite.config.mts +4 -4
  35. package/templates/libs/auth-client/src/index.ts +1 -0
  36. package/templates/libs/auth-client/src/lib/auth-client.module.ts +1 -1
  37. package/templates/libs/auth-client/src/lib/auth-client.service.ts +1 -1
  38. package/templates/libs/auth-client/src/lib/auth-client.tokens.ts +4 -0
  39. package/templates/libs/firebase-admin/README.md +11 -0
  40. package/templates/libs/firebase-admin/eslint.config.mjs +24 -0
  41. package/templates/libs/firebase-admin/package.json +12 -0
  42. package/templates/libs/firebase-admin/project.json +19 -0
  43. package/templates/libs/firebase-admin/src/index.ts +1 -0
  44. package/templates/libs/firebase-admin/src/lib/__tests__/firebase-admin.unit.test.ts +105 -0
  45. package/templates/libs/firebase-admin/src/lib/firebase-admin.ts +70 -0
  46. package/templates/libs/firebase-admin/tsconfig.json +23 -0
  47. package/templates/libs/firebase-admin/tsconfig.lib.json +23 -0
  48. package/templates/libs/firebase-admin/tsconfig.spec.json +22 -0
  49. package/templates/libs/firebase-admin/vitest.config.mts +21 -0
  50. package/templates/libs/jobs-client/src/index.ts +1 -0
  51. package/templates/libs/jobs-client/src/lib/jobs-client.module.ts +1 -1
  52. package/templates/libs/jobs-client/src/lib/jobs-client.service.ts +15 -3
  53. package/templates/libs/jobs-client/src/lib/jobs-client.tokens.ts +4 -0
  54. package/templates/libs/notes-client/src/index.ts +1 -0
  55. package/templates/libs/notes-client/src/lib/notes-client.module.ts +1 -1
  56. package/templates/libs/notes-client/src/lib/notes-client.service.ts +1 -1
  57. package/templates/libs/notes-client/src/lib/notes-client.tokens.ts +4 -0
  58. package/templates/libs/payment-client/src/index.ts +1 -0
  59. package/templates/libs/payment-client/src/lib/payment-client.module.ts +1 -1
  60. package/templates/libs/payment-client/src/lib/payment-client.service.ts +1 -1
  61. package/templates/libs/payment-client/src/lib/payment-client.tokens.ts +4 -0
  62. package/templates/libs/shared/src/__tests__/bootstrap.unit.test.ts +92 -0
  63. package/templates/libs/shared/src/__tests__/transport.unit.test.ts +14 -2
  64. package/templates/libs/shared/src/bootstrap.ts +79 -0
  65. package/templates/libs/shared/src/env.ts +88 -0
  66. package/templates/libs/shared/src/index.ts +2 -0
  67. package/templates/libs/shared/src/transport.ts +62 -3
  68. package/templates/libs/upload-client/src/index.ts +1 -0
  69. package/templates/libs/upload-client/src/lib/upload-client.module.ts +1 -1
  70. package/templates/libs/upload-client/src/lib/upload-client.service.ts +1 -1
  71. package/templates/libs/upload-client/src/lib/upload-client.tokens.ts +4 -0
  72. package/templates/libs/vite-plugins/src/index.d.mts +6 -0
  73. package/templates/libs/vite-plugins/src/index.mjs +50 -0
  74. package/templates/package.json +1 -0
  75. package/templates/tools/create-icore/_template-shell/package.json +1 -0
  76. package/templates/tsconfig.base.json +2 -1
package/dist/index.cjs CHANGED
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
33
  collectOptions: () => collectOptions,
34
+ pmRun: () => pmRun,
34
35
  scaffold: () => scaffold
35
36
  });
36
37
  module.exports = __toCommonJS(src_exports);
@@ -39,6 +40,11 @@ module.exports = __toCommonJS(src_exports);
39
40
  var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
40
41
  var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
41
42
 
43
+ // src/lib/options.ts
44
+ function pmRun(pm, script) {
45
+ return pm === "npm" ? `npm run ${script}` : `${pm} ${script}`;
46
+ }
47
+
42
48
  // src/lib/scaffold.ts
43
49
  var import_promises = require("fs/promises");
44
50
  var import_node_fs = require("fs");
@@ -76,9 +82,29 @@ async function rewriteRootPackageJson(targetDir, opts) {
76
82
  pkg["version"] = "0.0.1";
77
83
  pkg["private"] = true;
78
84
  delete pkg.description;
85
+ if (opts.transport === "nats") {
86
+ const deps = pkg["dependencies"] ??= {};
87
+ deps["nats"] = "^2.29.3";
88
+ }
79
89
  if (opts.packageManager !== "yarn") {
80
90
  delete pkg.packageManager;
81
91
  }
92
+ if (opts.packageManager === "pnpm") {
93
+ pkg["pnpm"] = {
94
+ onlyBuiltDependencies: [
95
+ "@firebase/util",
96
+ "@nestjs/core",
97
+ "@parcel/watcher",
98
+ "@scarf/scarf",
99
+ "@swc/core",
100
+ "less",
101
+ "msgpackr-extract",
102
+ "nx",
103
+ "protobufjs",
104
+ "unrs-resolver"
105
+ ]
106
+ };
107
+ }
82
108
  await (0, import_promises.writeFile)(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
83
109
  }
84
110
  async function writeAuthEnv(targetDir, opts) {
@@ -116,9 +142,9 @@ async function writeNotesEnv(targetDir, opts) {
116
142
  async function writeGatewayEnv(targetDir, opts) {
117
143
  const envExample = (0, import_node_path.join)(targetDir, "apps/api/.env.example");
118
144
  const env = await (0, import_promises.readFile)(envExample, "utf8");
119
- let next = env.replace(/^AUTH_TRANSPORT=.*$/m, `AUTH_TRANSPORT=${opts.transport}`).replace(/^UPLOAD_TRANSPORT=.*$/m, `UPLOAD_TRANSPORT=${opts.transport}`);
145
+ let next = env.replace(/^AUTH_TRANSPORT=.*$/m, `AUTH_TRANSPORT=${opts.transport}`).replace(/^UPLOAD_TRANSPORT=.*$/m, `UPLOAD_TRANSPORT=${opts.transport}`).replace(/^NOTES_TRANSPORT=.*$/m, `NOTES_TRANSPORT=${opts.transport}`).replace(/^PAYMENT_TRANSPORT=.*$/m, `PAYMENT_TRANSPORT=${opts.transport}`);
120
146
  if (opts.transport !== "tcp") {
121
- next = next.replace(/^# (AUTH_(?:REDIS|NATS)_URL=)/m, "$1").replace(/^# (UPLOAD_(?:REDIS|NATS)_URL=)/m, "$1");
147
+ next = next.replace(/^# (AUTH_(?:REDIS|NATS)_URL=)/m, "$1").replace(/^# (UPLOAD_(?:REDIS|NATS)_URL=)/m, "$1").replace(/^# (NOTES_(?:REDIS|NATS)_URL=)/m, "$1").replace(/^# (PAYMENT_(?:REDIS|NATS)_URL=)/m, "$1");
122
148
  }
123
149
  await (0, import_promises.writeFile)((0, import_node_path.join)(targetDir, "apps/api/.env"), next);
124
150
  }
@@ -131,6 +157,25 @@ async function writeRootEnv(targetDir, opts) {
131
157
  ];
132
158
  await (0, import_promises.writeFile)((0, import_node_path.join)(targetDir, ".env"), lines.join("\n"));
133
159
  }
160
+ async function stripGatewayTransport(targetDir, prefix) {
161
+ const gatewayEnv = (0, import_node_path.join)(targetDir, "apps/api/.env");
162
+ try {
163
+ const env = await (0, import_promises.readFile)(gatewayEnv, "utf8");
164
+ const next = env.split("\n").filter(
165
+ (line) => !line.startsWith(`${prefix}_`) && !line.startsWith(`# ${prefix}_`) && !line.includes(`${prefix} MS transport`)
166
+ ).join("\n");
167
+ await (0, import_promises.writeFile)(gatewayEnv, next);
168
+ } catch {
169
+ }
170
+ }
171
+ async function writeClientEnv(targetDir) {
172
+ const envExample = (0, import_node_path.join)(targetDir, "apps/client/.env.example");
173
+ try {
174
+ const env = await (0, import_promises.readFile)(envExample, "utf8");
175
+ await (0, import_promises.writeFile)((0, import_node_path.join)(targetDir, "apps/client/.env"), env);
176
+ } catch {
177
+ }
178
+ }
134
179
  async function writePaymentEnv(targetDir, opts) {
135
180
  if (opts.payment === "none") return;
136
181
  const envExample = (0, import_node_path.join)(targetDir, "apps/microservices/payment/.env.example");
@@ -207,6 +252,7 @@ async function removePaymentStack(targetDir) {
207
252
  "@icore/payment-client",
208
253
  "@idevconn/payment"
209
254
  ]);
255
+ await stripGatewayTransport(targetDir, "PAYMENT");
210
256
  }
211
257
  async function removeNotesStack(targetDir) {
212
258
  for (const p2 of [
@@ -228,6 +274,7 @@ async function removeNotesStack(targetDir) {
228
274
  } catch {
229
275
  }
230
276
  await stripDeps((0, import_node_path.join)(targetDir, "apps/api/package.json"), ["@icore/notes-client"]);
277
+ await stripGatewayTransport(targetDir, "NOTES");
231
278
  const tsconfigPath = (0, import_node_path.join)(targetDir, "tsconfig.base.json");
232
279
  try {
233
280
  const src = await (0, import_promises.readFile)(tsconfigPath, "utf8");
@@ -284,15 +331,17 @@ async function stripTsconfigPath(targetDir, alias) {
284
331
  }
285
332
  async function removeUnusedAuthStrategies(targetDir, authProvider) {
286
333
  const modulePath = (0, import_node_path.join)(targetDir, "apps/microservices/auth/src/app/app.module.ts");
334
+ const AUTH_BRANCH = /if \(provider === 'supabase'\) return makeSupabaseAuth\(cfg\);\n\s*return makeFirebaseAuth\(cfg\);/m;
287
335
  if (authProvider === "supabase") {
288
336
  await (0, import_promises.rm)((0, import_node_path.join)(targetDir, "libs/auth-strategies/firebase"), { recursive: true, force: true });
289
337
  await stripDeps((0, import_node_path.join)(targetDir, "apps/microservices/auth/package.json"), [
290
- "@icore/auth-firebase"
338
+ "@icore/auth-firebase",
339
+ "@icore/firebase-admin"
291
340
  ]);
292
341
  await stripTsconfigPath(targetDir, "@icore/auth-firebase");
293
342
  try {
294
343
  const src = await (0, import_promises.readFile)(modulePath, "utf8");
295
- const next = src.replace(/^import \* as admin from 'firebase-admin';\n/m, "").replace(/^import \{[^}]*FirebaseAuthStrategy[^}]*\} from '@icore\/auth-firebase';\n/m, "").replace(/^function makeFirebaseStrategy\b[\s\S]*?\n^}\n/m, "").replace(/(?<=\n) *case 'firebase':\n *return makeFirebaseStrategy\(cfg\);\n/, "");
344
+ const next = src.replace(/^import \{[^}]*\} from '@icore\/firebase-admin';\n/m, "").replace(/^import \{[^}]*FirebaseAuthStrategy[^}]*\} from '@icore\/auth-firebase';\n/m, "").replace(/^ {2}firebase: \[[^\]]*\],\n/m, "").replace(/\nfunction makeFirebaseAuth[\s\S]*?\n}\n/m, "").replace(AUTH_BRANCH, "return makeSupabaseAuth(cfg);");
296
345
  await (0, import_promises.writeFile)(modulePath, next);
297
346
  } catch {
298
347
  }
@@ -305,10 +354,7 @@ async function removeUnusedAuthStrategies(targetDir, authProvider) {
305
354
  await stripTsconfigPath(targetDir, "@icore/auth-supabase");
306
355
  try {
307
356
  const src = await (0, import_promises.readFile)(modulePath, "utf8");
308
- const next = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/m, "").replace(/^import \{[^}]*SupabaseAuthStrategy[^}]*\} from '@icore\/auth-supabase';\n/m, "").replace(
309
- /\n {10}case 'supabase': \{[\s\S]*?return new SupabaseAuthStrategy\(\{ client \}\);\n {10}\}\n/m,
310
- ""
311
- );
357
+ const next = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/m, "").replace(/^import \{[^}]*SupabaseAuthStrategy[^}]*\} from '@icore\/auth-supabase';\n/m, "").replace(/\nfunction makeSupabaseAuth[\s\S]*?\n}\n/m, "").replace(AUTH_BRANCH, "return makeFirebaseAuth(cfg);");
312
358
  await (0, import_promises.writeFile)(modulePath, next);
313
359
  } catch {
314
360
  }
@@ -320,7 +366,8 @@ async function removeUnusedStorageStrategies(targetDir, uploadProvider) {
320
366
  if (uploadProvider !== "firebase") {
321
367
  await (0, import_promises.rm)((0, import_node_path.join)(targetDir, "libs/storage-strategies/firebase"), { recursive: true, force: true });
322
368
  await stripDeps((0, import_node_path.join)(targetDir, "apps/microservices/upload/package.json"), [
323
- "@icore/storage-firebase"
369
+ "@icore/storage-firebase",
370
+ "@icore/firebase-admin"
324
371
  ]);
325
372
  await stripTsconfigPath(targetDir, "@icore/storage-firebase");
326
373
  }
@@ -344,26 +391,26 @@ async function removeUnusedStorageStrategies(targetDir, uploadProvider) {
344
391
  try {
345
392
  let src = await (0, import_promises.readFile)(modulePath, "utf8");
346
393
  if (uploadProvider !== "firebase") {
347
- src = src.replace(/^import \* as admin from 'firebase-admin';\n/m, "").replace(
394
+ src = src.replace(/^import \{[^}]*\} from '@icore\/firebase-admin';\n/m, "").replace(
348
395
  /^import \{[^}]*FirebaseStorageStrategy[^}]*\} from '@icore\/storage-firebase';\n/m,
349
396
  ""
350
- ).replace(/^function makeFirebaseStorage\b[\s\S]*?\n^}\n/m, "").replace(/(?<=\n) *case 'firebase':\n *return makeFirebaseStorage\(cfg\);\n/, "");
397
+ ).replace(/^ {2}firebase: \[[^\]]*\],\n/m, "").replace(/\nfunction makeFirebaseStorage[\s\S]*?\n}\n/m, "");
351
398
  }
352
399
  if (uploadProvider !== "cloudinary") {
353
400
  src = src.replace(/^import \{ v2 as cloudinary \} from 'cloudinary';\n/m, "").replace(
354
401
  /^import \{[^}]*CloudinaryStorageStrategy[^}]*\} from '@icore\/storage-cloudinary';\n/m,
355
402
  ""
356
- ).replace(/^function makeCloudinaryStorage\b[\s\S]*?\n^}\n/m, "").replace(/(?<=\n) *case 'cloudinary':\n *return makeCloudinaryStorage\(cfg\);\n/, "");
403
+ ).replace(/\nfunction makeCloudinaryStorage[\s\S]*?\n}\n/m, "");
357
404
  }
358
405
  if (uploadProvider !== "supabase") {
359
406
  src = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/m, "").replace(
360
407
  /^import \{[^}]*SupabaseStorageStrategy[^}]*\} from '@icore\/storage-supabase';\n/m,
361
408
  ""
362
- ).replace(
363
- /\n {10}case 'supabase': \{[\s\S]*?bucket: requireEnv\(cfg, 'SUPABASE_STORAGE_BUCKET'\),\n {12}\}\);\n {10}\}\n/m,
364
- ""
365
- );
409
+ ).replace(/\nfunction makeSupabaseStorage[\s\S]*?\n}\n/m, "");
366
410
  }
411
+ const STORAGE_BRANCH = /if \(provider === 'supabase'\) return makeSupabaseStorage\(cfg\);\n\s*if \(provider === 'firebase'\) return makeFirebaseStorage\(cfg\);\n\s*return makeCloudinaryStorage\(cfg\);/m;
412
+ const chosenReturn = `return make${uploadProvider.charAt(0).toUpperCase() + uploadProvider.slice(1)}Storage(cfg);`;
413
+ src = src.replace(STORAGE_BRANCH, chosenReturn);
367
414
  await (0, import_promises.writeFile)(modulePath, src);
368
415
  } catch {
369
416
  }
@@ -373,14 +420,15 @@ async function removeUnusedDbStrategies(targetDir, dbProvider) {
373
420
  if (dbProvider === "supabase") {
374
421
  await (0, import_promises.rm)((0, import_node_path.join)(targetDir, "libs/db-strategies/firestore"), { recursive: true, force: true });
375
422
  await stripDeps((0, import_node_path.join)(targetDir, "apps/microservices/notes/package.json"), [
376
- "@icore/db-firestore"
423
+ "@icore/db-firestore",
424
+ "@icore/firebase-admin"
377
425
  ]);
378
426
  await stripTsconfigPath(targetDir, "@icore/db-firestore");
379
427
  try {
380
428
  const src = await (0, import_promises.readFile)(modulePath, "utf8");
381
- const next = src.replace(/^import \* as admin from 'firebase-admin';\n/m, "").replace(/^import \{[^}]*FirestoreDBStrategy[^}]*\} from '@icore\/db-firestore';\n/m, "").replace(
382
- /\n {8}if \(provider === 'firestore'[\s\S]*?return new FirestoreDBStrategy\(\{[\s\S]*?\}\);\n {8}\}\n/m,
383
- ""
429
+ const next = src.replace(/^import \{[^}]*\} from '@icore\/firebase-admin';\n/m, "").replace(/^import \{[^}]*FirestoreDBStrategy[^}]*\} from '@icore\/db-firestore';\n/m, "").replace(/^ {2}firestore: \[[^\]]*\],\n/m, "").replace(/^ {2}firebase: \[[^\]]*\],\n/m, "").replace(/\nfunction makeFirestoreDB[\s\S]*?\n}\n/m, "").replace(
430
+ /if \(provider === 'supabase'\) return makeSupabaseDB\(cfg\);\n\s*return makeFirestoreDB\(cfg\);/m,
431
+ "return makeSupabaseDB(cfg);"
384
432
  );
385
433
  await (0, import_promises.writeFile)(modulePath, next);
386
434
  } catch {
@@ -394,15 +442,19 @@ async function removeUnusedDbStrategies(targetDir, dbProvider) {
394
442
  await stripTsconfigPath(targetDir, "@icore/db-supabase");
395
443
  try {
396
444
  const src = await (0, import_promises.readFile)(modulePath, "utf8");
397
- const next = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/m, "").replace(/^import \{[^}]*SupabaseDBStrategy[^}]*\} from '@icore\/db-supabase';\n/m, "").replace(
398
- /\n {8}if \(provider === 'supabase'\) \{[\s\S]*?return new SupabaseDBStrategy\(\{ client \}\);\n {8}\}\n/m,
399
- ""
445
+ const next = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/m, "").replace(/^import \{[^}]*SupabaseDBStrategy[^}]*\} from '@icore\/db-supabase';\n/m, "").replace(/\nfunction makeSupabaseDB[\s\S]*?\n}\n/m, "").replace(
446
+ /if \(provider === 'supabase'\) return makeSupabaseDB\(cfg\);\n\s*return makeFirestoreDB\(cfg\);/m,
447
+ "return makeFirestoreDB(cfg);"
400
448
  );
401
449
  await (0, import_promises.writeFile)(modulePath, next);
402
450
  } catch {
403
451
  }
404
452
  }
405
453
  }
454
+ async function removeFirebaseAdminLib(targetDir) {
455
+ await (0, import_promises.rm)((0, import_node_path.join)(targetDir, "libs/firebase-admin"), { recursive: true, force: true });
456
+ await stripTsconfigPath(targetDir, "@icore/firebase-admin");
457
+ }
406
458
  async function removeUploadStack(targetDir) {
407
459
  const paths = [
408
460
  "apps/microservices/upload",
@@ -507,6 +559,7 @@ async function scaffold(opts, templatesDir) {
507
559
  await writeGatewayEnv(opts.targetDir, opts);
508
560
  await writeRootEnv(opts.targetDir, opts);
509
561
  await selectClientTemplate(opts.targetDir, opts);
562
+ await writeClientEnv(opts.targetDir);
510
563
  if (opts.upload === "none") await removeUploadStack(opts.targetDir);
511
564
  if (opts.payment === "none") await removePaymentStack(opts.targetDir);
512
565
  if (opts.jobs === "none") await removeJobsStack(opts.targetDir);
@@ -514,12 +567,230 @@ async function scaffold(opts, templatesDir) {
514
567
  await removeUnusedAuthStrategies(opts.targetDir, opts.authProvider);
515
568
  await removeUnusedStorageStrategies(opts.targetDir, opts.upload);
516
569
  await removeUnusedDbStrategies(opts.targetDir, opts.dbProvider);
570
+ const firebaseUsed = opts.authProvider === "firebase" || opts.dbProvider === "firebase" || opts.upload === "firebase";
571
+ if (!firebaseUsed) await removeFirebaseAdminLib(opts.targetDir);
517
572
  if (opts.packageManager === "yarn") {
518
573
  await (0, import_promises.writeFile)((0, import_node_path.join)(opts.targetDir, "yarn.lock"), "");
574
+ } else {
575
+ await (0, import_promises.rm)((0, import_node_path.join)(opts.targetDir, ".yarn"), { recursive: true, force: true });
576
+ await (0, import_promises.rm)((0, import_node_path.join)(opts.targetDir, ".yarnrc.yml"), { force: true });
519
577
  }
578
+ await patchGitignoreForPm(opts.targetDir, opts.packageManager);
579
+ await writeAiFiles(opts.targetDir, opts);
520
580
  if (opts.install) runInstall(opts.targetDir, opts.packageManager);
521
581
  if (opts.initGit) gitInit(opts.targetDir, opts.projectName);
522
582
  }
583
+ async function patchGitignoreForPm(targetDir, pm) {
584
+ const giPath = (0, import_node_path.join)(targetDir, ".gitignore");
585
+ try {
586
+ let src = await (0, import_promises.readFile)(giPath, "utf8");
587
+ src = src.replace(/^# Build artifacts.*\ntools\/create-icore\/templates\/\s*\n/m, "");
588
+ if (pm !== "yarn") {
589
+ 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, "");
590
+ }
591
+ if (pm === "pnpm") {
592
+ if (!src.includes(".pnpm-debug.log")) {
593
+ src += "\n# pnpm\n.pnpm-debug.log*\n";
594
+ }
595
+ }
596
+ if (pm === "npm") {
597
+ if (!src.includes("npm-debug.log")) {
598
+ src += "\n# npm\nnpm-debug.log*\n";
599
+ }
600
+ }
601
+ await (0, import_promises.writeFile)(giPath, src);
602
+ } catch {
603
+ }
604
+ }
605
+ async function writeAiFiles(targetDir, opts) {
606
+ const pm = opts.packageManager;
607
+ const nx = pm === "npm" ? "npx nx" : `${pm} nx`;
608
+ const devCmd = pmRun(pm, "dev");
609
+ const activeMSes = ["auth (port 4001)"];
610
+ if (opts.upload !== "none") activeMSes.push(`upload (port 4002)`);
611
+ if (opts.payment !== "none") activeMSes.push(`payment (port 4003)`);
612
+ if (opts.example !== "none") activeMSes.push(`notes (port 4004)`);
613
+ if (opts.jobs !== "none") activeMSes.push(`jobs (standalone)`);
614
+ const usesSupabase = opts.authProvider === "supabase" || opts.dbProvider === "supabase" || opts.upload === "supabase";
615
+ const usesFirebase = opts.authProvider === "firebase" || opts.dbProvider === "firebase" || opts.upload === "firebase";
616
+ await (0, import_promises.writeFile)((0, import_node_path.join)(targetDir, "CLAUDE.md"), "@AGENTS.md\n");
617
+ const uiLabel = { shadcn: "shadcn/ui + Tailwind", antd: "Ant Design 6", mui: "MUI 6" }[opts.ui];
618
+ const readme = `# ${opts.projectName}
619
+
620
+ > Scaffolded with [iCore](https://github.com/iDEVconn/create-icore) \u2014 Nx + NestJS + React full-stack template.
621
+
622
+ ## Stack
623
+
624
+ | Layer | Technology |
625
+ |-------|-----------|
626
+ | Monorepo | Nx + ${pm} |
627
+ | Gateway | NestJS 11 + Swagger |
628
+ | Auth | ${opts.authProvider} |
629
+ | Database | ${opts.dbProvider} |
630
+ | Upload | ${opts.upload === "none" ? "\u2014" : opts.upload} |
631
+ | UI | ${uiLabel} + TanStack Router + Query |
632
+ | i18n | i18next (en / ru / he) |
633
+
634
+ ## Quick start
635
+
636
+ \`\`\`bash
637
+ # 1. Fill in provider credentials
638
+ # apps/microservices/auth/.env
639
+ # apps/microservices/upload/.env (if upload is enabled)
640
+ # apps/client/.env (VITE_API_URL \u2014 already defaults to /api)
641
+
642
+ # 2. Start everything
643
+ ${devCmd}
644
+ # \u2192 http://localhost:4200 client
645
+ # \u2192 http://localhost:3001/api/docs Swagger
646
+ \`\`\`
647
+
648
+ ## Commands
649
+
650
+ \`\`\`bash
651
+ ${nx} run <project>:serve # start a single service
652
+ ${nx} test <project> # unit tests
653
+ ${nx} lint <project> # lint
654
+ ${nx} build <project> # production build
655
+ ${pm === "yarn" ? "yarn remove-notes" : pm === "pnpm" ? "pnpm remove-notes" : "npm run remove-notes"} # strip the notes sample feature
656
+ \`\`\`
657
+
658
+ ## Scaffolded by
659
+
660
+ [iCore](https://github.com/iDEVconn/create-icore) \u2014 [@idevconn/create-icore](https://www.npmjs.com/package/@idevconn/create-icore)
661
+
662
+ ## License
663
+
664
+ Apache-2.0
665
+ `;
666
+ await (0, import_promises.writeFile)((0, import_node_path.join)(targetDir, "README.md"), readme);
667
+ const agents = `# ${opts.projectName} \u2014 Agent Instructions
668
+
669
+ ## Stack snapshot
670
+
671
+ | Dimension | Choice |
672
+ |------------|--------|
673
+ | Auth | ${opts.authProvider} |
674
+ | Database | ${opts.dbProvider} |
675
+ | Upload | ${opts.upload} |
676
+ | Payment | ${opts.payment} |
677
+ | Jobs | ${opts.jobs} |
678
+ | UI | ${opts.ui} |
679
+ | Transport | ${opts.transport} |
680
+ | PM | ${pm} |
681
+
682
+ ## \u{1F680} Mandatory Workflow
683
+
684
+ - **Branch strategy**: \`dev\` is default. Cut \`feature/<name>\` or \`bug/<name>\` from dev. PRs only target dev. Never push directly to main.
685
+ - **No code without approval**: Propose changes first, wait for go-ahead.
686
+ - **\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.
687
+ - **Post-coding routine**: \`npx prettier --write <files>\` \u2192 \`${nx} lint <project>\` \u2192 \`${nx} build <project>\` \u2014 all green before committing.
688
+ - **Nx generators only**: never hand-write \`project.json\` / tsconfig stacks. Use \`${nx} g @nx/<plugin>:<schematic>\`.
689
+
690
+ ## Architecture
691
+
692
+ \`\`\`
693
+ apps/
694
+ \u251C\u2500\u2500 api/ NestJS gateway \u2014 all client traffic enters here (:3001)
695
+ \u251C\u2500\u2500 microservices/
696
+ ${activeMSes.map((s) => `\u2502 \u251C\u2500\u2500 ${s.split(" ")[0]}/`).join("\n")}
697
+ \u2514\u2500\u2500 client/ Vite + React 19 + ${opts.ui} (:4200)
698
+ libs/
699
+ \u251C\u2500\u2500 shared/ contracts, CASL, transport helpers, env banner utils
700
+ \u251C\u2500\u2500 auth-strategies/${opts.authProvider}/
701
+ ${opts.upload !== "none" ? `\u251C\u2500\u2500 storage-strategies/${opts.upload}/
702
+ ` : ""}\u251C\u2500\u2500 db-strategies/${opts.dbProvider === "firebase" ? "firestore" : opts.dbProvider}/
703
+ \u251C\u2500\u2500 auth-client/ gateway \u2192 auth MS (TCP/Redis/NATS)
704
+ ${opts.upload !== "none" ? `\u251C\u2500\u2500 upload-client/ gateway \u2192 upload MS
705
+ ` : ""}\u2514\u2500\u2500 template-shared/ browser-safe React foundation (stores, i18n, CASL)
706
+ \`\`\`
707
+
708
+ ## Key patterns
709
+
710
+ **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\`).
711
+
712
+ **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\`.
713
+
714
+ **Env layering**:
715
+ 1. Root \`.env\` \u2014 \`DB_PROVIDER\`
716
+ 2. \`apps/api/.env\` \u2014 gateway transport endpoints
717
+ 3. \`apps/microservices/<name>/.env\` \u2014 each MS provider + transport
718
+ 4. \`apps/client/.env\` \u2014 \`VITE_API_URL\`
719
+
720
+ ## Commands
721
+
722
+ \`\`\`bash
723
+ ${devCmd} # start all services
724
+ ${nx} run api:serve # gateway only
725
+ ${nx} run auth:serve # auth MS only
726
+ ${nx} test <project> # run tests
727
+ ${nx} lint <project> # lint
728
+ ${nx} build <project> # build
729
+ ${nx} g @nx/nest:resource # generate NestJS resource
730
+ \`\`\`
731
+
732
+ ## .env files to configure
733
+
734
+ | File | Key vars |
735
+ |------|----------|
736
+ | \`apps/microservices/auth/.env\` | \`AUTH_PROVIDER=${opts.authProvider}\`, ${opts.authProvider === "supabase" ? "`SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`" : "`FB_ADMIN_*`, `FIREBASE_WEB_API_KEY`"} |
737
+ ${opts.upload !== "none" ? `| \`apps/microservices/upload/.env\` | \`STORAGE_PROVIDER=${opts.upload}\`, provider creds |
738
+ ` : ""}| \`apps/microservices/notes/.env\` | \`DB_PROVIDER=${opts.dbProvider}\`, DB creds |
739
+ | \`apps/client/.env\` | \`VITE_API_URL=/api\` (proxied to :3001 in dev) |
740
+
741
+ ## Testing
742
+
743
+ - Unit tests: Vitest, files named \`*.unit.test.ts(x)\` in \`__tests__/\` next to source.
744
+ - Test behaviour, not implementation. Fake strategies from \`@icore/shared\` (FakeAuthStrategy etc.) serve as test doubles.
745
+ - Run: \`${nx} test <project>\`
746
+ `;
747
+ await (0, import_promises.writeFile)((0, import_node_path.join)(targetDir, "AGENTS.md"), agents);
748
+ await (0, import_promises.mkdir)((0, import_node_path.join)(targetDir, ".claude"), { recursive: true });
749
+ const mcpServers = {
750
+ nx: {
751
+ command: "npx",
752
+ args: ["-y", "@nx/mcp@latest", "--directory", "."],
753
+ type: "stdio"
754
+ }
755
+ };
756
+ if (usesSupabase) {
757
+ mcpServers["supabase"] = {
758
+ command: "npx",
759
+ args: [
760
+ "-y",
761
+ "@supabase/mcp-server-supabase@latest",
762
+ "--access-token",
763
+ "<SUPABASE_PERSONAL_ACCESS_TOKEN>"
764
+ ],
765
+ type: "stdio"
766
+ };
767
+ }
768
+ if (usesFirebase) {
769
+ mcpServers["firebase"] = {
770
+ command: "npx",
771
+ args: ["-y", "firebase-tools@latest", "experimental:mcp"],
772
+ type: "stdio"
773
+ };
774
+ }
775
+ const nxCmds = [`Bash(${nx} *)`, `Bash(${devCmd})`];
776
+ if (pm !== "npm") nxCmds.push(`Bash(npx nx *)`);
777
+ const settings = {
778
+ mcpServers,
779
+ permissions: {
780
+ allow: [
781
+ ...nxCmds,
782
+ "Bash(npx prettier *)",
783
+ "Bash(git status)",
784
+ "Bash(git diff *)",
785
+ "Bash(git log *)"
786
+ ]
787
+ }
788
+ };
789
+ await (0, import_promises.writeFile)(
790
+ (0, import_node_path.join)(targetDir, ".claude", "settings.json"),
791
+ JSON.stringify(settings, null, 2) + "\n"
792
+ );
793
+ }
523
794
 
524
795
  // src/lib/prompts.ts
525
796
  var p = __toESM(require("@clack/prompts"), 1);
@@ -715,6 +986,12 @@ Re-run with @latest to refresh:
715
986
  });
716
987
  if (p.isCancel(transport)) throw new Error("cancelled");
717
988
  const packageManager = flags.packageManager ?? detectPackageManager();
989
+ if (packageManager === "yarn") {
990
+ p.note(
991
+ "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]",
992
+ "\u26A0 yarn 24h age-gate"
993
+ );
994
+ }
718
995
  const initGit = flags.initGit ?? !await p.confirm({ message: "Initialise git repo?", initialValue: true }) === false;
719
996
  const install = flags.install ?? !await p.confirm({
720
997
  message: `Run ${packageManager} install?`,
@@ -739,5 +1016,6 @@ Re-run with @latest to refresh:
739
1016
  // Annotate the CommonJS export names for ESM import in node:
740
1017
  0 && (module.exports = {
741
1018
  collectOptions,
1019
+ pmRun,
742
1020
  scaffold
743
1021
  });
package/dist/index.d.cts CHANGED
@@ -7,6 +7,12 @@ type ExampleMode = 'notes' | 'none';
7
7
  type UiLibrary = 'shadcn' | 'antd' | 'mui';
8
8
  type MsTransport = 'tcp' | 'redis' | 'nats';
9
9
  type PackageManager = 'yarn' | 'npm' | 'pnpm';
10
+ /**
11
+ * Returns the correct invocation for a package.json script.
12
+ * yarn/pnpm: `yarn <script>` / `pnpm <script>`
13
+ * npm: `npm run <script>` (npm requires the `run` keyword for custom scripts)
14
+ */
15
+ declare function pmRun(pm: PackageManager, script: string): string;
10
16
  interface CreateIcoreOptions {
11
17
  projectName: string;
12
18
  targetDir: string;
@@ -31,4 +37,4 @@ interface PromptInput {
31
37
  }
32
38
  declare function collectOptions({ argv, cwd }: PromptInput): Promise<CreateIcoreOptions>;
33
39
 
34
- export { type AuthProvider, type CreateIcoreOptions, type DbProvider, type ExampleMode, type JobsProvider, type MsTransport, type PackageManager, type PaymentProvider, type UiLibrary, type UploadProvider, collectOptions, scaffold };
40
+ export { type AuthProvider, type CreateIcoreOptions, type DbProvider, type ExampleMode, type JobsProvider, type MsTransport, type PackageManager, type PaymentProvider, type UiLibrary, type UploadProvider, collectOptions, pmRun, scaffold };
package/dist/index.d.ts CHANGED
@@ -7,6 +7,12 @@ type ExampleMode = 'notes' | 'none';
7
7
  type UiLibrary = 'shadcn' | 'antd' | 'mui';
8
8
  type MsTransport = 'tcp' | 'redis' | 'nats';
9
9
  type PackageManager = 'yarn' | 'npm' | 'pnpm';
10
+ /**
11
+ * Returns the correct invocation for a package.json script.
12
+ * yarn/pnpm: `yarn <script>` / `pnpm <script>`
13
+ * npm: `npm run <script>` (npm requires the `run` keyword for custom scripts)
14
+ */
15
+ declare function pmRun(pm: PackageManager, script: string): string;
10
16
  interface CreateIcoreOptions {
11
17
  projectName: string;
12
18
  targetDir: string;
@@ -31,4 +37,4 @@ interface PromptInput {
31
37
  }
32
38
  declare function collectOptions({ argv, cwd }: PromptInput): Promise<CreateIcoreOptions>;
33
39
 
34
- export { type AuthProvider, type CreateIcoreOptions, type DbProvider, type ExampleMode, type JobsProvider, type MsTransport, type PackageManager, type PaymentProvider, type UiLibrary, type UploadProvider, collectOptions, scaffold };
40
+ export { type AuthProvider, type CreateIcoreOptions, type DbProvider, type ExampleMode, type JobsProvider, type MsTransport, type PackageManager, type PaymentProvider, type UiLibrary, type UploadProvider, collectOptions, pmRun, scaffold };