@idevconn/create-icore 0.3.0 → 0.3.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.
package/dist/cli.js CHANGED
@@ -13,6 +13,13 @@ import { resolve } from "path";
13
13
  import { readFile } from "fs/promises";
14
14
  import { dirname, join } from "path";
15
15
  import { fileURLToPath } from "url";
16
+ function detectPackageManager() {
17
+ const ua = process.env["npm_config_user_agent"] ?? "";
18
+ if (ua.startsWith("yarn/")) return "yarn";
19
+ if (ua.startsWith("pnpm/")) return "pnpm";
20
+ if (ua.startsWith("npm/")) return "npm";
21
+ return "yarn";
22
+ }
16
23
  async function readSelfVersion() {
17
24
  try {
18
25
  const here2 = dirname(fileURLToPath(import.meta.url));
@@ -81,6 +88,9 @@ function parseFlags(argv) {
81
88
  case "transport":
82
89
  out.transport = v;
83
90
  break;
91
+ case "package-manager":
92
+ out.packageManager = v;
93
+ break;
84
94
  case "no-git":
85
95
  out.initGit = false;
86
96
  break;
@@ -190,8 +200,12 @@ Re-run with @latest to refresh:
190
200
  initialValue: "tcp"
191
201
  });
192
202
  if (p.isCancel(transport)) throw new Error("cancelled");
203
+ const packageManager = flags.packageManager ?? detectPackageManager();
193
204
  const initGit = flags.initGit ?? !await p.confirm({ message: "Initialise git repo?", initialValue: true }) === false;
194
- const install = flags.install ?? !await p.confirm({ message: "Run yarn install?", initialValue: true }) === false;
205
+ const install = flags.install ?? !await p.confirm({
206
+ message: `Run ${packageManager} install?`,
207
+ initialValue: true
208
+ }) === false;
195
209
  return {
196
210
  projectName,
197
211
  targetDir: resolve(cwd, projectName),
@@ -203,6 +217,7 @@ Re-run with @latest to refresh:
203
217
  example,
204
218
  ui,
205
219
  transport,
220
+ packageManager,
206
221
  initGit,
207
222
  install
208
223
  };
@@ -244,6 +259,9 @@ async function rewriteRootPackageJson(targetDir, opts) {
244
259
  pkg["version"] = "0.0.1";
245
260
  pkg["private"] = true;
246
261
  delete pkg.description;
262
+ if (opts.packageManager !== "yarn") {
263
+ delete pkg.packageManager;
264
+ }
247
265
  await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
248
266
  }
249
267
  async function writeAuthEnv(targetDir, opts) {
@@ -416,6 +434,145 @@ async function removeNotesStack(targetDir) {
416
434
  } catch {
417
435
  }
418
436
  }
437
+ async function stripTsconfigPath(targetDir, alias) {
438
+ const tsconfigPath = join2(targetDir, "tsconfig.base.json");
439
+ try {
440
+ const src = await readFile2(tsconfigPath, "utf8");
441
+ const escaped = alias.replace(/[@/]/g, (c) => c === "@" ? "@" : "\\/");
442
+ const pretty = src.replace(new RegExp(`^\\s*"${escaped}": \\[[^\\]]*\\],?\\n`, "m"), "");
443
+ if (pretty !== src) {
444
+ await writeFile(tsconfigPath, pretty);
445
+ return;
446
+ }
447
+ const parsed = JSON.parse(src);
448
+ if (parsed.compilerOptions?.paths) {
449
+ delete parsed.compilerOptions.paths[alias];
450
+ }
451
+ await writeFile(tsconfigPath, JSON.stringify(parsed));
452
+ } catch {
453
+ }
454
+ }
455
+ async function removeUnusedAuthStrategies(targetDir, authProvider) {
456
+ const modulePath = join2(targetDir, "apps/microservices/auth/src/app/app.module.ts");
457
+ if (authProvider === "supabase") {
458
+ await rm(join2(targetDir, "libs/auth-strategies/firebase"), { recursive: true, force: true });
459
+ await stripDeps(join2(targetDir, "apps/microservices/auth/package.json"), [
460
+ "@icore/auth-firebase"
461
+ ]);
462
+ await stripTsconfigPath(targetDir, "@icore/auth-firebase");
463
+ try {
464
+ const src = await readFile2(modulePath, "utf8");
465
+ 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/, "");
466
+ await writeFile(modulePath, next);
467
+ } catch {
468
+ }
469
+ }
470
+ if (authProvider === "firebase") {
471
+ await rm(join2(targetDir, "libs/auth-strategies/supabase"), { recursive: true, force: true });
472
+ await stripDeps(join2(targetDir, "apps/microservices/auth/package.json"), [
473
+ "@icore/auth-supabase"
474
+ ]);
475
+ await stripTsconfigPath(targetDir, "@icore/auth-supabase");
476
+ try {
477
+ const src = await readFile2(modulePath, "utf8");
478
+ const next = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/m, "").replace(/^import \{[^}]*SupabaseAuthStrategy[^}]*\} from '@icore\/auth-supabase';\n/m, "").replace(
479
+ /\n {10}case 'supabase': \{[\s\S]*?return new SupabaseAuthStrategy\(\{ client \}\);\n {10}\}\n/m,
480
+ ""
481
+ );
482
+ await writeFile(modulePath, next);
483
+ } catch {
484
+ }
485
+ }
486
+ }
487
+ async function removeUnusedStorageStrategies(targetDir, uploadProvider) {
488
+ if (uploadProvider === "none") return;
489
+ const modulePath = join2(targetDir, "apps/microservices/upload/src/app/app.module.ts");
490
+ if (uploadProvider !== "firebase") {
491
+ await rm(join2(targetDir, "libs/storage-strategies/firebase"), { recursive: true, force: true });
492
+ await stripDeps(join2(targetDir, "apps/microservices/upload/package.json"), [
493
+ "@icore/storage-firebase"
494
+ ]);
495
+ await stripTsconfigPath(targetDir, "@icore/storage-firebase");
496
+ }
497
+ if (uploadProvider !== "cloudinary") {
498
+ await rm(join2(targetDir, "libs/storage-strategies/cloudinary"), {
499
+ recursive: true,
500
+ force: true
501
+ });
502
+ await stripDeps(join2(targetDir, "apps/microservices/upload/package.json"), [
503
+ "@icore/storage-cloudinary"
504
+ ]);
505
+ await stripTsconfigPath(targetDir, "@icore/storage-cloudinary");
506
+ }
507
+ if (uploadProvider !== "supabase") {
508
+ await rm(join2(targetDir, "libs/storage-strategies/supabase"), { recursive: true, force: true });
509
+ await stripDeps(join2(targetDir, "apps/microservices/upload/package.json"), [
510
+ "@icore/storage-supabase"
511
+ ]);
512
+ await stripTsconfigPath(targetDir, "@icore/storage-supabase");
513
+ }
514
+ try {
515
+ let src = await readFile2(modulePath, "utf8");
516
+ if (uploadProvider !== "firebase") {
517
+ src = src.replace(/^import \* as admin from 'firebase-admin';\n/m, "").replace(
518
+ /^import \{[^}]*FirebaseStorageStrategy[^}]*\} from '@icore\/storage-firebase';\n/m,
519
+ ""
520
+ ).replace(/^function makeFirebaseStorage\b[\s\S]*?\n^}\n/m, "").replace(/(?<=\n) *case 'firebase':\n *return makeFirebaseStorage\(cfg\);\n/, "");
521
+ }
522
+ if (uploadProvider !== "cloudinary") {
523
+ src = src.replace(/^import \{ v2 as cloudinary \} from 'cloudinary';\n/m, "").replace(
524
+ /^import \{[^}]*CloudinaryStorageStrategy[^}]*\} from '@icore\/storage-cloudinary';\n/m,
525
+ ""
526
+ ).replace(/^function makeCloudinaryStorage\b[\s\S]*?\n^}\n/m, "").replace(/(?<=\n) *case 'cloudinary':\n *return makeCloudinaryStorage\(cfg\);\n/, "");
527
+ }
528
+ if (uploadProvider !== "supabase") {
529
+ src = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/m, "").replace(
530
+ /^import \{[^}]*SupabaseStorageStrategy[^}]*\} from '@icore\/storage-supabase';\n/m,
531
+ ""
532
+ ).replace(
533
+ /\n {10}case 'supabase': \{[\s\S]*?bucket: requireEnv\(cfg, 'SUPABASE_STORAGE_BUCKET'\),\n {12}\}\);\n {10}\}\n/m,
534
+ ""
535
+ );
536
+ }
537
+ await writeFile(modulePath, src);
538
+ } catch {
539
+ }
540
+ }
541
+ async function removeUnusedDbStrategies(targetDir, dbProvider) {
542
+ const modulePath = join2(targetDir, "apps/microservices/notes/src/app/app.module.ts");
543
+ if (dbProvider === "supabase") {
544
+ await rm(join2(targetDir, "libs/db-strategies/firestore"), { recursive: true, force: true });
545
+ await stripDeps(join2(targetDir, "apps/microservices/notes/package.json"), [
546
+ "@icore/db-firestore"
547
+ ]);
548
+ await stripTsconfigPath(targetDir, "@icore/db-firestore");
549
+ try {
550
+ const src = await readFile2(modulePath, "utf8");
551
+ const next = src.replace(/^import \* as admin from 'firebase-admin';\n/m, "").replace(/^import \{[^}]*FirestoreDBStrategy[^}]*\} from '@icore\/db-firestore';\n/m, "").replace(
552
+ /\n {8}if \(provider === 'firestore'[\s\S]*?return new FirestoreDBStrategy\(\{[\s\S]*?\}\);\n {8}\}\n/m,
553
+ ""
554
+ );
555
+ await writeFile(modulePath, next);
556
+ } catch {
557
+ }
558
+ }
559
+ if (dbProvider === "firebase") {
560
+ await rm(join2(targetDir, "libs/db-strategies/supabase"), { recursive: true, force: true });
561
+ await stripDeps(join2(targetDir, "apps/microservices/notes/package.json"), [
562
+ "@icore/db-supabase"
563
+ ]);
564
+ await stripTsconfigPath(targetDir, "@icore/db-supabase");
565
+ try {
566
+ const src = await readFile2(modulePath, "utf8");
567
+ const next = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/m, "").replace(/^import \{[^}]*SupabaseDBStrategy[^}]*\} from '@icore\/db-supabase';\n/m, "").replace(
568
+ /\n {8}if \(provider === 'supabase'\) \{[\s\S]*?return new SupabaseDBStrategy\(\{ client \}\);\n {8}\}\n/m,
569
+ ""
570
+ );
571
+ await writeFile(modulePath, next);
572
+ } catch {
573
+ }
574
+ }
575
+ }
419
576
  async function removeUploadStack(targetDir) {
420
577
  const paths = [
421
578
  "apps/microservices/upload",
@@ -492,8 +649,9 @@ function gitInit(cwd, projectName) {
492
649
  { cwd, stdio: "inherit" }
493
650
  );
494
651
  }
495
- function yarnInstall(cwd) {
496
- spawnSync("yarn", ["install"], { cwd, stdio: "inherit" });
652
+ function runInstall(cwd, pm) {
653
+ const [cmd, ...args] = pm === "npm" ? ["npm", "install"] : pm === "pnpm" ? ["pnpm", "install"] : ["yarn", "install"];
654
+ spawnSync(cmd, args, { cwd, stdio: "inherit" });
497
655
  }
498
656
  async function scaffold(opts, templatesDir2) {
499
657
  await copyTree(templatesDir2, opts.targetDir);
@@ -508,12 +666,26 @@ async function scaffold(opts, templatesDir2) {
508
666
  if (opts.payment === "none") await removePaymentStack(opts.targetDir);
509
667
  if (opts.jobs === "none") await removeJobsStack(opts.targetDir);
510
668
  if (opts.example === "none") await removeNotesStack(opts.targetDir);
511
- await writeFile(join2(opts.targetDir, "yarn.lock"), "");
512
- if (opts.install) yarnInstall(opts.targetDir);
669
+ await removeUnusedAuthStrategies(opts.targetDir, opts.authProvider);
670
+ await removeUnusedStorageStrategies(opts.targetDir, opts.upload);
671
+ await removeUnusedDbStrategies(opts.targetDir, opts.dbProvider);
672
+ if (opts.packageManager === "yarn") {
673
+ await writeFile(join2(opts.targetDir, "yarn.lock"), "");
674
+ }
675
+ if (opts.install) runInstall(opts.targetDir, opts.packageManager);
513
676
  if (opts.initGit) gitInit(opts.targetDir, opts.projectName);
514
677
  }
515
678
 
516
679
  // src/cli.ts
680
+ var [nodeMajor] = process.versions.node.split(".").map(Number);
681
+ if (nodeMajor < 22) {
682
+ process.stderr.write(
683
+ `Error: iCore requires Node.js >= 22. You are running ${process.versions.node}.
684
+ Upgrade: https://nodejs.org
685
+ `
686
+ );
687
+ process.exit(1);
688
+ }
517
689
  var here = dirname2(fileURLToPath2(import.meta.url));
518
690
  var templatesDir = resolve2(here, "..", "templates");
519
691
  async function main() {
@@ -533,8 +705,8 @@ async function main() {
533
705
  p2.outro(kleur.green("Done."));
534
706
  p2.log.info(`Next:`);
535
707
  p2.log.info(` cd ${opts.projectName}`);
536
- if (!opts.install) p2.log.info(` yarn install`);
537
- p2.log.info(` yarn dev # gateway + auth MS + upload MS + client`);
708
+ if (!opts.install) p2.log.info(` ${opts.packageManager} install`);
709
+ p2.log.info(` ${opts.packageManager} dev # gateway + auth MS + upload MS + client`);
538
710
  p2.log.info(` open http://localhost:4200`);
539
711
  p2.log.info(` edit apps/microservices/auth/.env to plug in real ${opts.authProvider} creds`);
540
712
  }
package/dist/index.cjs CHANGED
@@ -75,6 +75,9 @@ async function rewriteRootPackageJson(targetDir, opts) {
75
75
  pkg["version"] = "0.0.1";
76
76
  pkg["private"] = true;
77
77
  delete pkg.description;
78
+ if (opts.packageManager !== "yarn") {
79
+ delete pkg.packageManager;
80
+ }
78
81
  await (0, import_promises.writeFile)(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
79
82
  }
80
83
  async function writeAuthEnv(targetDir, opts) {
@@ -247,6 +250,145 @@ async function removeNotesStack(targetDir) {
247
250
  } catch {
248
251
  }
249
252
  }
253
+ async function stripTsconfigPath(targetDir, alias) {
254
+ const tsconfigPath = (0, import_node_path.join)(targetDir, "tsconfig.base.json");
255
+ try {
256
+ const src = await (0, import_promises.readFile)(tsconfigPath, "utf8");
257
+ const escaped = alias.replace(/[@/]/g, (c) => c === "@" ? "@" : "\\/");
258
+ const pretty = src.replace(new RegExp(`^\\s*"${escaped}": \\[[^\\]]*\\],?\\n`, "m"), "");
259
+ if (pretty !== src) {
260
+ await (0, import_promises.writeFile)(tsconfigPath, pretty);
261
+ return;
262
+ }
263
+ const parsed = JSON.parse(src);
264
+ if (parsed.compilerOptions?.paths) {
265
+ delete parsed.compilerOptions.paths[alias];
266
+ }
267
+ await (0, import_promises.writeFile)(tsconfigPath, JSON.stringify(parsed));
268
+ } catch {
269
+ }
270
+ }
271
+ async function removeUnusedAuthStrategies(targetDir, authProvider) {
272
+ const modulePath = (0, import_node_path.join)(targetDir, "apps/microservices/auth/src/app/app.module.ts");
273
+ if (authProvider === "supabase") {
274
+ await (0, import_promises.rm)((0, import_node_path.join)(targetDir, "libs/auth-strategies/firebase"), { recursive: true, force: true });
275
+ await stripDeps((0, import_node_path.join)(targetDir, "apps/microservices/auth/package.json"), [
276
+ "@icore/auth-firebase"
277
+ ]);
278
+ await stripTsconfigPath(targetDir, "@icore/auth-firebase");
279
+ try {
280
+ const src = await (0, import_promises.readFile)(modulePath, "utf8");
281
+ 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/, "");
282
+ await (0, import_promises.writeFile)(modulePath, next);
283
+ } catch {
284
+ }
285
+ }
286
+ if (authProvider === "firebase") {
287
+ await (0, import_promises.rm)((0, import_node_path.join)(targetDir, "libs/auth-strategies/supabase"), { recursive: true, force: true });
288
+ await stripDeps((0, import_node_path.join)(targetDir, "apps/microservices/auth/package.json"), [
289
+ "@icore/auth-supabase"
290
+ ]);
291
+ await stripTsconfigPath(targetDir, "@icore/auth-supabase");
292
+ try {
293
+ const src = await (0, import_promises.readFile)(modulePath, "utf8");
294
+ const next = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/m, "").replace(/^import \{[^}]*SupabaseAuthStrategy[^}]*\} from '@icore\/auth-supabase';\n/m, "").replace(
295
+ /\n {10}case 'supabase': \{[\s\S]*?return new SupabaseAuthStrategy\(\{ client \}\);\n {10}\}\n/m,
296
+ ""
297
+ );
298
+ await (0, import_promises.writeFile)(modulePath, next);
299
+ } catch {
300
+ }
301
+ }
302
+ }
303
+ async function removeUnusedStorageStrategies(targetDir, uploadProvider) {
304
+ if (uploadProvider === "none") return;
305
+ const modulePath = (0, import_node_path.join)(targetDir, "apps/microservices/upload/src/app/app.module.ts");
306
+ if (uploadProvider !== "firebase") {
307
+ await (0, import_promises.rm)((0, import_node_path.join)(targetDir, "libs/storage-strategies/firebase"), { recursive: true, force: true });
308
+ await stripDeps((0, import_node_path.join)(targetDir, "apps/microservices/upload/package.json"), [
309
+ "@icore/storage-firebase"
310
+ ]);
311
+ await stripTsconfigPath(targetDir, "@icore/storage-firebase");
312
+ }
313
+ if (uploadProvider !== "cloudinary") {
314
+ await (0, import_promises.rm)((0, import_node_path.join)(targetDir, "libs/storage-strategies/cloudinary"), {
315
+ recursive: true,
316
+ force: true
317
+ });
318
+ await stripDeps((0, import_node_path.join)(targetDir, "apps/microservices/upload/package.json"), [
319
+ "@icore/storage-cloudinary"
320
+ ]);
321
+ await stripTsconfigPath(targetDir, "@icore/storage-cloudinary");
322
+ }
323
+ if (uploadProvider !== "supabase") {
324
+ await (0, import_promises.rm)((0, import_node_path.join)(targetDir, "libs/storage-strategies/supabase"), { recursive: true, force: true });
325
+ await stripDeps((0, import_node_path.join)(targetDir, "apps/microservices/upload/package.json"), [
326
+ "@icore/storage-supabase"
327
+ ]);
328
+ await stripTsconfigPath(targetDir, "@icore/storage-supabase");
329
+ }
330
+ try {
331
+ let src = await (0, import_promises.readFile)(modulePath, "utf8");
332
+ if (uploadProvider !== "firebase") {
333
+ src = src.replace(/^import \* as admin from 'firebase-admin';\n/m, "").replace(
334
+ /^import \{[^}]*FirebaseStorageStrategy[^}]*\} from '@icore\/storage-firebase';\n/m,
335
+ ""
336
+ ).replace(/^function makeFirebaseStorage\b[\s\S]*?\n^}\n/m, "").replace(/(?<=\n) *case 'firebase':\n *return makeFirebaseStorage\(cfg\);\n/, "");
337
+ }
338
+ if (uploadProvider !== "cloudinary") {
339
+ src = src.replace(/^import \{ v2 as cloudinary \} from 'cloudinary';\n/m, "").replace(
340
+ /^import \{[^}]*CloudinaryStorageStrategy[^}]*\} from '@icore\/storage-cloudinary';\n/m,
341
+ ""
342
+ ).replace(/^function makeCloudinaryStorage\b[\s\S]*?\n^}\n/m, "").replace(/(?<=\n) *case 'cloudinary':\n *return makeCloudinaryStorage\(cfg\);\n/, "");
343
+ }
344
+ if (uploadProvider !== "supabase") {
345
+ src = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/m, "").replace(
346
+ /^import \{[^}]*SupabaseStorageStrategy[^}]*\} from '@icore\/storage-supabase';\n/m,
347
+ ""
348
+ ).replace(
349
+ /\n {10}case 'supabase': \{[\s\S]*?bucket: requireEnv\(cfg, 'SUPABASE_STORAGE_BUCKET'\),\n {12}\}\);\n {10}\}\n/m,
350
+ ""
351
+ );
352
+ }
353
+ await (0, import_promises.writeFile)(modulePath, src);
354
+ } catch {
355
+ }
356
+ }
357
+ async function removeUnusedDbStrategies(targetDir, dbProvider) {
358
+ const modulePath = (0, import_node_path.join)(targetDir, "apps/microservices/notes/src/app/app.module.ts");
359
+ if (dbProvider === "supabase") {
360
+ await (0, import_promises.rm)((0, import_node_path.join)(targetDir, "libs/db-strategies/firestore"), { recursive: true, force: true });
361
+ await stripDeps((0, import_node_path.join)(targetDir, "apps/microservices/notes/package.json"), [
362
+ "@icore/db-firestore"
363
+ ]);
364
+ await stripTsconfigPath(targetDir, "@icore/db-firestore");
365
+ try {
366
+ const src = await (0, import_promises.readFile)(modulePath, "utf8");
367
+ const next = src.replace(/^import \* as admin from 'firebase-admin';\n/m, "").replace(/^import \{[^}]*FirestoreDBStrategy[^}]*\} from '@icore\/db-firestore';\n/m, "").replace(
368
+ /\n {8}if \(provider === 'firestore'[\s\S]*?return new FirestoreDBStrategy\(\{[\s\S]*?\}\);\n {8}\}\n/m,
369
+ ""
370
+ );
371
+ await (0, import_promises.writeFile)(modulePath, next);
372
+ } catch {
373
+ }
374
+ }
375
+ if (dbProvider === "firebase") {
376
+ await (0, import_promises.rm)((0, import_node_path.join)(targetDir, "libs/db-strategies/supabase"), { recursive: true, force: true });
377
+ await stripDeps((0, import_node_path.join)(targetDir, "apps/microservices/notes/package.json"), [
378
+ "@icore/db-supabase"
379
+ ]);
380
+ await stripTsconfigPath(targetDir, "@icore/db-supabase");
381
+ try {
382
+ const src = await (0, import_promises.readFile)(modulePath, "utf8");
383
+ const next = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/m, "").replace(/^import \{[^}]*SupabaseDBStrategy[^}]*\} from '@icore\/db-supabase';\n/m, "").replace(
384
+ /\n {8}if \(provider === 'supabase'\) \{[\s\S]*?return new SupabaseDBStrategy\(\{ client \}\);\n {8}\}\n/m,
385
+ ""
386
+ );
387
+ await (0, import_promises.writeFile)(modulePath, next);
388
+ } catch {
389
+ }
390
+ }
391
+ }
250
392
  async function removeUploadStack(targetDir) {
251
393
  const paths = [
252
394
  "apps/microservices/upload",
@@ -323,8 +465,9 @@ function gitInit(cwd, projectName) {
323
465
  { cwd, stdio: "inherit" }
324
466
  );
325
467
  }
326
- function yarnInstall(cwd) {
327
- (0, import_node_child_process.spawnSync)("yarn", ["install"], { cwd, stdio: "inherit" });
468
+ function runInstall(cwd, pm) {
469
+ const [cmd, ...args] = pm === "npm" ? ["npm", "install"] : pm === "pnpm" ? ["pnpm", "install"] : ["yarn", "install"];
470
+ (0, import_node_child_process.spawnSync)(cmd, args, { cwd, stdio: "inherit" });
328
471
  }
329
472
  async function scaffold(opts, templatesDir) {
330
473
  await copyTree(templatesDir, opts.targetDir);
@@ -339,8 +482,13 @@ async function scaffold(opts, templatesDir) {
339
482
  if (opts.payment === "none") await removePaymentStack(opts.targetDir);
340
483
  if (opts.jobs === "none") await removeJobsStack(opts.targetDir);
341
484
  if (opts.example === "none") await removeNotesStack(opts.targetDir);
342
- await (0, import_promises.writeFile)((0, import_node_path.join)(opts.targetDir, "yarn.lock"), "");
343
- if (opts.install) yarnInstall(opts.targetDir);
485
+ await removeUnusedAuthStrategies(opts.targetDir, opts.authProvider);
486
+ await removeUnusedStorageStrategies(opts.targetDir, opts.upload);
487
+ await removeUnusedDbStrategies(opts.targetDir, opts.dbProvider);
488
+ if (opts.packageManager === "yarn") {
489
+ await (0, import_promises.writeFile)((0, import_node_path.join)(opts.targetDir, "yarn.lock"), "");
490
+ }
491
+ if (opts.install) runInstall(opts.targetDir, opts.packageManager);
344
492
  if (opts.initGit) gitInit(opts.targetDir, opts.projectName);
345
493
  }
346
494
 
@@ -350,6 +498,13 @@ var import_node_path2 = require("path");
350
498
  var import_promises2 = require("fs/promises");
351
499
  var import_node_path3 = require("path");
352
500
  var import_node_url = require("url");
501
+ function detectPackageManager() {
502
+ const ua = process.env["npm_config_user_agent"] ?? "";
503
+ if (ua.startsWith("yarn/")) return "yarn";
504
+ if (ua.startsWith("pnpm/")) return "pnpm";
505
+ if (ua.startsWith("npm/")) return "npm";
506
+ return "yarn";
507
+ }
353
508
  async function readSelfVersion() {
354
509
  try {
355
510
  const here = (0, import_node_path3.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
@@ -418,6 +573,9 @@ function parseFlags(argv) {
418
573
  case "transport":
419
574
  out.transport = v;
420
575
  break;
576
+ case "package-manager":
577
+ out.packageManager = v;
578
+ break;
421
579
  case "no-git":
422
580
  out.initGit = false;
423
581
  break;
@@ -527,8 +685,12 @@ Re-run with @latest to refresh:
527
685
  initialValue: "tcp"
528
686
  });
529
687
  if (p.isCancel(transport)) throw new Error("cancelled");
688
+ const packageManager = flags.packageManager ?? detectPackageManager();
530
689
  const initGit = flags.initGit ?? !await p.confirm({ message: "Initialise git repo?", initialValue: true }) === false;
531
- const install = flags.install ?? !await p.confirm({ message: "Run yarn install?", initialValue: true }) === false;
690
+ const install = flags.install ?? !await p.confirm({
691
+ message: `Run ${packageManager} install?`,
692
+ initialValue: true
693
+ }) === false;
532
694
  return {
533
695
  projectName,
534
696
  targetDir: (0, import_node_path2.resolve)(cwd, projectName),
@@ -540,6 +702,7 @@ Re-run with @latest to refresh:
540
702
  example,
541
703
  ui,
542
704
  transport,
705
+ packageManager,
543
706
  initGit,
544
707
  install
545
708
  };
package/dist/index.d.cts CHANGED
@@ -6,6 +6,7 @@ type JobsProvider = 'bullmq' | 'none';
6
6
  type ExampleMode = 'notes' | 'none';
7
7
  type UiLibrary = 'shadcn' | 'antd' | 'mui';
8
8
  type MsTransport = 'tcp' | 'redis' | 'nats';
9
+ type PackageManager = 'yarn' | 'npm' | 'pnpm';
9
10
  interface CreateIcoreOptions {
10
11
  projectName: string;
11
12
  targetDir: string;
@@ -17,6 +18,7 @@ interface CreateIcoreOptions {
17
18
  example: ExampleMode;
18
19
  ui: UiLibrary;
19
20
  transport: MsTransport;
21
+ packageManager: PackageManager;
20
22
  initGit: boolean;
21
23
  install: boolean;
22
24
  }
@@ -29,4 +31,4 @@ interface PromptInput {
29
31
  }
30
32
  declare function collectOptions({ argv, cwd }: PromptInput): Promise<CreateIcoreOptions>;
31
33
 
32
- export { type AuthProvider, type CreateIcoreOptions, type DbProvider, type ExampleMode, type JobsProvider, type MsTransport, type PaymentProvider, type UiLibrary, type UploadProvider, collectOptions, scaffold };
34
+ export { type AuthProvider, type CreateIcoreOptions, type DbProvider, type ExampleMode, type JobsProvider, type MsTransport, type PackageManager, type PaymentProvider, type UiLibrary, type UploadProvider, collectOptions, scaffold };
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ type JobsProvider = 'bullmq' | 'none';
6
6
  type ExampleMode = 'notes' | 'none';
7
7
  type UiLibrary = 'shadcn' | 'antd' | 'mui';
8
8
  type MsTransport = 'tcp' | 'redis' | 'nats';
9
+ type PackageManager = 'yarn' | 'npm' | 'pnpm';
9
10
  interface CreateIcoreOptions {
10
11
  projectName: string;
11
12
  targetDir: string;
@@ -17,6 +18,7 @@ interface CreateIcoreOptions {
17
18
  example: ExampleMode;
18
19
  ui: UiLibrary;
19
20
  transport: MsTransport;
21
+ packageManager: PackageManager;
20
22
  initGit: boolean;
21
23
  install: boolean;
22
24
  }
@@ -29,4 +31,4 @@ interface PromptInput {
29
31
  }
30
32
  declare function collectOptions({ argv, cwd }: PromptInput): Promise<CreateIcoreOptions>;
31
33
 
32
- export { type AuthProvider, type CreateIcoreOptions, type DbProvider, type ExampleMode, type JobsProvider, type MsTransport, type PaymentProvider, type UiLibrary, type UploadProvider, collectOptions, scaffold };
34
+ export { type AuthProvider, type CreateIcoreOptions, type DbProvider, type ExampleMode, type JobsProvider, type MsTransport, type PackageManager, type PaymentProvider, type UiLibrary, type UploadProvider, collectOptions, scaffold };
package/dist/index.js CHANGED
@@ -34,6 +34,9 @@ async function rewriteRootPackageJson(targetDir, opts) {
34
34
  pkg["version"] = "0.0.1";
35
35
  pkg["private"] = true;
36
36
  delete pkg.description;
37
+ if (opts.packageManager !== "yarn") {
38
+ delete pkg.packageManager;
39
+ }
37
40
  await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
38
41
  }
39
42
  async function writeAuthEnv(targetDir, opts) {
@@ -206,6 +209,145 @@ async function removeNotesStack(targetDir) {
206
209
  } catch {
207
210
  }
208
211
  }
212
+ async function stripTsconfigPath(targetDir, alias) {
213
+ const tsconfigPath = join(targetDir, "tsconfig.base.json");
214
+ try {
215
+ const src = await readFile(tsconfigPath, "utf8");
216
+ const escaped = alias.replace(/[@/]/g, (c) => c === "@" ? "@" : "\\/");
217
+ const pretty = src.replace(new RegExp(`^\\s*"${escaped}": \\[[^\\]]*\\],?\\n`, "m"), "");
218
+ if (pretty !== src) {
219
+ await writeFile(tsconfigPath, pretty);
220
+ return;
221
+ }
222
+ const parsed = JSON.parse(src);
223
+ if (parsed.compilerOptions?.paths) {
224
+ delete parsed.compilerOptions.paths[alias];
225
+ }
226
+ await writeFile(tsconfigPath, JSON.stringify(parsed));
227
+ } catch {
228
+ }
229
+ }
230
+ async function removeUnusedAuthStrategies(targetDir, authProvider) {
231
+ const modulePath = join(targetDir, "apps/microservices/auth/src/app/app.module.ts");
232
+ if (authProvider === "supabase") {
233
+ await rm(join(targetDir, "libs/auth-strategies/firebase"), { recursive: true, force: true });
234
+ await stripDeps(join(targetDir, "apps/microservices/auth/package.json"), [
235
+ "@icore/auth-firebase"
236
+ ]);
237
+ await stripTsconfigPath(targetDir, "@icore/auth-firebase");
238
+ try {
239
+ const src = await readFile(modulePath, "utf8");
240
+ 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/, "");
241
+ await writeFile(modulePath, next);
242
+ } catch {
243
+ }
244
+ }
245
+ if (authProvider === "firebase") {
246
+ await rm(join(targetDir, "libs/auth-strategies/supabase"), { recursive: true, force: true });
247
+ await stripDeps(join(targetDir, "apps/microservices/auth/package.json"), [
248
+ "@icore/auth-supabase"
249
+ ]);
250
+ await stripTsconfigPath(targetDir, "@icore/auth-supabase");
251
+ try {
252
+ const src = await readFile(modulePath, "utf8");
253
+ const next = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/m, "").replace(/^import \{[^}]*SupabaseAuthStrategy[^}]*\} from '@icore\/auth-supabase';\n/m, "").replace(
254
+ /\n {10}case 'supabase': \{[\s\S]*?return new SupabaseAuthStrategy\(\{ client \}\);\n {10}\}\n/m,
255
+ ""
256
+ );
257
+ await writeFile(modulePath, next);
258
+ } catch {
259
+ }
260
+ }
261
+ }
262
+ async function removeUnusedStorageStrategies(targetDir, uploadProvider) {
263
+ if (uploadProvider === "none") return;
264
+ const modulePath = join(targetDir, "apps/microservices/upload/src/app/app.module.ts");
265
+ if (uploadProvider !== "firebase") {
266
+ await rm(join(targetDir, "libs/storage-strategies/firebase"), { recursive: true, force: true });
267
+ await stripDeps(join(targetDir, "apps/microservices/upload/package.json"), [
268
+ "@icore/storage-firebase"
269
+ ]);
270
+ await stripTsconfigPath(targetDir, "@icore/storage-firebase");
271
+ }
272
+ if (uploadProvider !== "cloudinary") {
273
+ await rm(join(targetDir, "libs/storage-strategies/cloudinary"), {
274
+ recursive: true,
275
+ force: true
276
+ });
277
+ await stripDeps(join(targetDir, "apps/microservices/upload/package.json"), [
278
+ "@icore/storage-cloudinary"
279
+ ]);
280
+ await stripTsconfigPath(targetDir, "@icore/storage-cloudinary");
281
+ }
282
+ if (uploadProvider !== "supabase") {
283
+ await rm(join(targetDir, "libs/storage-strategies/supabase"), { recursive: true, force: true });
284
+ await stripDeps(join(targetDir, "apps/microservices/upload/package.json"), [
285
+ "@icore/storage-supabase"
286
+ ]);
287
+ await stripTsconfigPath(targetDir, "@icore/storage-supabase");
288
+ }
289
+ try {
290
+ let src = await readFile(modulePath, "utf8");
291
+ if (uploadProvider !== "firebase") {
292
+ src = src.replace(/^import \* as admin from 'firebase-admin';\n/m, "").replace(
293
+ /^import \{[^}]*FirebaseStorageStrategy[^}]*\} from '@icore\/storage-firebase';\n/m,
294
+ ""
295
+ ).replace(/^function makeFirebaseStorage\b[\s\S]*?\n^}\n/m, "").replace(/(?<=\n) *case 'firebase':\n *return makeFirebaseStorage\(cfg\);\n/, "");
296
+ }
297
+ if (uploadProvider !== "cloudinary") {
298
+ src = src.replace(/^import \{ v2 as cloudinary \} from 'cloudinary';\n/m, "").replace(
299
+ /^import \{[^}]*CloudinaryStorageStrategy[^}]*\} from '@icore\/storage-cloudinary';\n/m,
300
+ ""
301
+ ).replace(/^function makeCloudinaryStorage\b[\s\S]*?\n^}\n/m, "").replace(/(?<=\n) *case 'cloudinary':\n *return makeCloudinaryStorage\(cfg\);\n/, "");
302
+ }
303
+ if (uploadProvider !== "supabase") {
304
+ src = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/m, "").replace(
305
+ /^import \{[^}]*SupabaseStorageStrategy[^}]*\} from '@icore\/storage-supabase';\n/m,
306
+ ""
307
+ ).replace(
308
+ /\n {10}case 'supabase': \{[\s\S]*?bucket: requireEnv\(cfg, 'SUPABASE_STORAGE_BUCKET'\),\n {12}\}\);\n {10}\}\n/m,
309
+ ""
310
+ );
311
+ }
312
+ await writeFile(modulePath, src);
313
+ } catch {
314
+ }
315
+ }
316
+ async function removeUnusedDbStrategies(targetDir, dbProvider) {
317
+ const modulePath = join(targetDir, "apps/microservices/notes/src/app/app.module.ts");
318
+ if (dbProvider === "supabase") {
319
+ await rm(join(targetDir, "libs/db-strategies/firestore"), { recursive: true, force: true });
320
+ await stripDeps(join(targetDir, "apps/microservices/notes/package.json"), [
321
+ "@icore/db-firestore"
322
+ ]);
323
+ await stripTsconfigPath(targetDir, "@icore/db-firestore");
324
+ try {
325
+ const src = await readFile(modulePath, "utf8");
326
+ const next = src.replace(/^import \* as admin from 'firebase-admin';\n/m, "").replace(/^import \{[^}]*FirestoreDBStrategy[^}]*\} from '@icore\/db-firestore';\n/m, "").replace(
327
+ /\n {8}if \(provider === 'firestore'[\s\S]*?return new FirestoreDBStrategy\(\{[\s\S]*?\}\);\n {8}\}\n/m,
328
+ ""
329
+ );
330
+ await writeFile(modulePath, next);
331
+ } catch {
332
+ }
333
+ }
334
+ if (dbProvider === "firebase") {
335
+ await rm(join(targetDir, "libs/db-strategies/supabase"), { recursive: true, force: true });
336
+ await stripDeps(join(targetDir, "apps/microservices/notes/package.json"), [
337
+ "@icore/db-supabase"
338
+ ]);
339
+ await stripTsconfigPath(targetDir, "@icore/db-supabase");
340
+ try {
341
+ const src = await readFile(modulePath, "utf8");
342
+ const next = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/m, "").replace(/^import \{[^}]*SupabaseDBStrategy[^}]*\} from '@icore\/db-supabase';\n/m, "").replace(
343
+ /\n {8}if \(provider === 'supabase'\) \{[\s\S]*?return new SupabaseDBStrategy\(\{ client \}\);\n {8}\}\n/m,
344
+ ""
345
+ );
346
+ await writeFile(modulePath, next);
347
+ } catch {
348
+ }
349
+ }
350
+ }
209
351
  async function removeUploadStack(targetDir) {
210
352
  const paths = [
211
353
  "apps/microservices/upload",
@@ -282,8 +424,9 @@ function gitInit(cwd, projectName) {
282
424
  { cwd, stdio: "inherit" }
283
425
  );
284
426
  }
285
- function yarnInstall(cwd) {
286
- spawnSync("yarn", ["install"], { cwd, stdio: "inherit" });
427
+ function runInstall(cwd, pm) {
428
+ const [cmd, ...args] = pm === "npm" ? ["npm", "install"] : pm === "pnpm" ? ["pnpm", "install"] : ["yarn", "install"];
429
+ spawnSync(cmd, args, { cwd, stdio: "inherit" });
287
430
  }
288
431
  async function scaffold(opts, templatesDir) {
289
432
  await copyTree(templatesDir, opts.targetDir);
@@ -298,8 +441,13 @@ async function scaffold(opts, templatesDir) {
298
441
  if (opts.payment === "none") await removePaymentStack(opts.targetDir);
299
442
  if (opts.jobs === "none") await removeJobsStack(opts.targetDir);
300
443
  if (opts.example === "none") await removeNotesStack(opts.targetDir);
301
- await writeFile(join(opts.targetDir, "yarn.lock"), "");
302
- if (opts.install) yarnInstall(opts.targetDir);
444
+ await removeUnusedAuthStrategies(opts.targetDir, opts.authProvider);
445
+ await removeUnusedStorageStrategies(opts.targetDir, opts.upload);
446
+ await removeUnusedDbStrategies(opts.targetDir, opts.dbProvider);
447
+ if (opts.packageManager === "yarn") {
448
+ await writeFile(join(opts.targetDir, "yarn.lock"), "");
449
+ }
450
+ if (opts.install) runInstall(opts.targetDir, opts.packageManager);
303
451
  if (opts.initGit) gitInit(opts.targetDir, opts.projectName);
304
452
  }
305
453
 
@@ -309,6 +457,13 @@ import { resolve } from "path";
309
457
  import { readFile as readFile2 } from "fs/promises";
310
458
  import { dirname, join as join2 } from "path";
311
459
  import { fileURLToPath } from "url";
460
+ function detectPackageManager() {
461
+ const ua = process.env["npm_config_user_agent"] ?? "";
462
+ if (ua.startsWith("yarn/")) return "yarn";
463
+ if (ua.startsWith("pnpm/")) return "pnpm";
464
+ if (ua.startsWith("npm/")) return "npm";
465
+ return "yarn";
466
+ }
312
467
  async function readSelfVersion() {
313
468
  try {
314
469
  const here = dirname(fileURLToPath(import.meta.url));
@@ -377,6 +532,9 @@ function parseFlags(argv) {
377
532
  case "transport":
378
533
  out.transport = v;
379
534
  break;
535
+ case "package-manager":
536
+ out.packageManager = v;
537
+ break;
380
538
  case "no-git":
381
539
  out.initGit = false;
382
540
  break;
@@ -486,8 +644,12 @@ Re-run with @latest to refresh:
486
644
  initialValue: "tcp"
487
645
  });
488
646
  if (p.isCancel(transport)) throw new Error("cancelled");
647
+ const packageManager = flags.packageManager ?? detectPackageManager();
489
648
  const initGit = flags.initGit ?? !await p.confirm({ message: "Initialise git repo?", initialValue: true }) === false;
490
- const install = flags.install ?? !await p.confirm({ message: "Run yarn install?", initialValue: true }) === false;
649
+ const install = flags.install ?? !await p.confirm({
650
+ message: `Run ${packageManager} install?`,
651
+ initialValue: true
652
+ }) === false;
491
653
  return {
492
654
  projectName,
493
655
  targetDir: resolve(cwd, projectName),
@@ -499,6 +661,7 @@ Re-run with @latest to refresh:
499
661
  example,
500
662
  ui,
501
663
  transport,
664
+ packageManager,
502
665
  initGit,
503
666
  install
504
667
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idevconn/create-icore",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Bootstrap a new project from the iCore scaffold (Nx + NestJS + React + Vite + shadcn/Tailwind, swappable auth + storage providers).",
5
5
  "license": "Apache-2.0",
6
6
  "author": "iDEVconn",
@@ -8,20 +8,24 @@ import { FirebaseAuthStrategy, HttpIdentityToolkitClient } from '@icore/auth-fir
8
8
  import type { AuthStrategy } from '@icore/shared';
9
9
  import { AuthController } from './auth.controller';
10
10
 
11
+ function requireEnv(cfg: ConfigService, key: string): string {
12
+ const val = cfg.getOrThrow<string>(key);
13
+ if (!val) throw new Error(`${key} is not set — check apps/microservices/auth/.env`);
14
+ return val;
15
+ }
16
+
11
17
  function makeFirebaseStrategy(cfg: ConfigService): AuthStrategy {
12
- const projectId = cfg.getOrThrow<string>('FB_ADMIN_PROJECT_ID');
18
+ const projectId = requireEnv(cfg, 'FB_ADMIN_PROJECT_ID');
13
19
  if (admin.apps.length === 0) {
14
20
  admin.initializeApp({
15
21
  credential: admin.credential.cert({
16
22
  projectId,
17
- clientEmail: cfg.getOrThrow<string>('FB_ADMIN_CLIENT_EMAIL'),
18
- privateKey: cfg.getOrThrow<string>('FB_ADMIN_PRIVATE_KEY').replace(/\\n/g, '\n'),
23
+ clientEmail: requireEnv(cfg, 'FB_ADMIN_CLIENT_EMAIL'),
24
+ privateKey: requireEnv(cfg, 'FB_ADMIN_PRIVATE_KEY').replace(/\\n/g, '\n'),
19
25
  }),
20
26
  });
21
27
  }
22
- const identityToolkit = new HttpIdentityToolkitClient(
23
- cfg.getOrThrow<string>('FIREBASE_WEB_API_KEY'),
24
- );
28
+ const identityToolkit = new HttpIdentityToolkitClient(requireEnv(cfg, 'FIREBASE_WEB_API_KEY'));
25
29
  return new FirebaseAuthStrategy({
26
30
  identityToolkit,
27
31
  adminAuth: admin.auth(),
@@ -43,12 +47,12 @@ function makeFirebaseStrategy(cfg: ConfigService): AuthStrategy {
43
47
  {
44
48
  provide: 'AuthStrategy',
45
49
  useFactory: (cfg: ConfigService): AuthStrategy => {
46
- const provider = cfg.getOrThrow<string>('AUTH_PROVIDER');
50
+ const provider = requireEnv(cfg, 'AUTH_PROVIDER');
47
51
  switch (provider) {
48
52
  case 'supabase': {
49
53
  const client = createClient(
50
- cfg.getOrThrow<string>('SUPABASE_URL'),
51
- cfg.getOrThrow<string>('SUPABASE_SERVICE_ROLE_KEY'),
54
+ requireEnv(cfg, 'SUPABASE_URL'),
55
+ requireEnv(cfg, 'SUPABASE_SERVICE_ROLE_KEY'),
52
56
  { auth: { autoRefreshToken: false, persistSession: false } },
53
57
  );
54
58
  return new SupabaseAuthStrategy({ client });
@@ -8,6 +8,12 @@ import { FirestoreDBStrategy } from '@icore/db-firestore';
8
8
  import type { DBStrategy } from '@icore/shared';
9
9
  import { NotesController } from './notes.controller';
10
10
 
11
+ function requireEnv(cfg: ConfigService, key: string): string {
12
+ const val = cfg.getOrThrow<string>(key);
13
+ if (!val) throw new Error(`${key} is not set — check apps/microservices/notes/.env`);
14
+ return val;
15
+ }
16
+
11
17
  @Module({
12
18
  imports: [
13
19
  ConfigModule.forRoot({
@@ -23,11 +29,11 @@ import { NotesController } from './notes.controller';
23
29
  {
24
30
  provide: 'DBStrategy',
25
31
  useFactory: (cfg: ConfigService): DBStrategy => {
26
- const provider = cfg.getOrThrow<string>('DB_PROVIDER');
32
+ const provider = requireEnv(cfg, 'DB_PROVIDER');
27
33
  if (provider === 'supabase') {
28
34
  const client = createClient(
29
- cfg.getOrThrow<string>('SUPABASE_URL'),
30
- cfg.getOrThrow<string>('SUPABASE_SERVICE_ROLE_KEY'),
35
+ requireEnv(cfg, 'SUPABASE_URL'),
36
+ requireEnv(cfg, 'SUPABASE_SERVICE_ROLE_KEY'),
31
37
  { auth: { autoRefreshToken: false, persistSession: false } },
32
38
  );
33
39
  return new SupabaseDBStrategy({ client });
@@ -36,9 +42,9 @@ import { NotesController } from './notes.controller';
36
42
  if (admin.apps.length === 0) {
37
43
  admin.initializeApp({
38
44
  credential: admin.credential.cert({
39
- projectId: cfg.getOrThrow<string>('FB_ADMIN_PROJECT_ID'),
40
- clientEmail: cfg.getOrThrow<string>('FB_ADMIN_CLIENT_EMAIL'),
41
- privateKey: cfg.getOrThrow<string>('FB_ADMIN_PRIVATE_KEY').replace(/\\n/g, '\n'),
45
+ projectId: requireEnv(cfg, 'FB_ADMIN_PROJECT_ID'),
46
+ clientEmail: requireEnv(cfg, 'FB_ADMIN_CLIENT_EMAIL'),
47
+ privateKey: requireEnv(cfg, 'FB_ADMIN_PRIVATE_KEY').replace(/\\n/g, '\n'),
42
48
  }),
43
49
  });
44
50
  }
@@ -10,14 +10,20 @@ import { CloudinaryStorageStrategy, type CloudinaryApiLike } from '@icore/storag
10
10
  import type { StorageStrategy } from '@icore/shared';
11
11
  import { StorageController } from './storage.controller';
12
12
 
13
+ function requireEnv(cfg: ConfigService, key: string): string {
14
+ const val = cfg.getOrThrow<string>(key);
15
+ if (!val) throw new Error(`${key} is not set — check apps/microservices/upload/.env`);
16
+ return val;
17
+ }
18
+
13
19
  function makeFirebaseStorage(cfg: ConfigService): StorageStrategy {
14
- const bucketName = cfg.getOrThrow<string>('FIREBASE_STORAGE_BUCKET');
20
+ const bucketName = requireEnv(cfg, 'FIREBASE_STORAGE_BUCKET');
15
21
  if (admin.apps.length === 0) {
16
22
  admin.initializeApp({
17
23
  credential: admin.credential.cert({
18
- projectId: cfg.getOrThrow<string>('FB_ADMIN_PROJECT_ID'),
19
- clientEmail: cfg.getOrThrow<string>('FB_ADMIN_CLIENT_EMAIL'),
20
- privateKey: cfg.getOrThrow<string>('FB_ADMIN_PRIVATE_KEY').replace(/\\n/g, '\n'),
24
+ projectId: requireEnv(cfg, 'FB_ADMIN_PROJECT_ID'),
25
+ clientEmail: requireEnv(cfg, 'FB_ADMIN_CLIENT_EMAIL'),
26
+ privateKey: requireEnv(cfg, 'FB_ADMIN_PRIVATE_KEY').replace(/\\n/g, '\n'),
21
27
  }),
22
28
  });
23
29
  }
@@ -30,9 +36,9 @@ function makeFirebaseStorage(cfg: ConfigService): StorageStrategy {
30
36
 
31
37
  function makeCloudinaryStorage(cfg: ConfigService): StorageStrategy {
32
38
  cloudinary.config({
33
- cloud_name: cfg.getOrThrow<string>('CLOUDINARY_CLOUD_NAME'),
34
- api_key: cfg.getOrThrow<string>('CLOUDINARY_API_KEY'),
35
- api_secret: cfg.getOrThrow<string>('CLOUDINARY_API_SECRET'),
39
+ cloud_name: requireEnv(cfg, 'CLOUDINARY_CLOUD_NAME'),
40
+ api_key: requireEnv(cfg, 'CLOUDINARY_API_KEY'),
41
+ api_secret: requireEnv(cfg, 'CLOUDINARY_API_SECRET'),
36
42
  secure: true,
37
43
  });
38
44
 
@@ -89,17 +95,17 @@ function makeCloudinaryStorage(cfg: ConfigService): StorageStrategy {
89
95
  {
90
96
  provide: 'StorageStrategy',
91
97
  useFactory: (cfg: ConfigService): StorageStrategy => {
92
- const provider = cfg.getOrThrow<string>('STORAGE_PROVIDER');
98
+ const provider = requireEnv(cfg, 'STORAGE_PROVIDER');
93
99
  switch (provider) {
94
100
  case 'supabase': {
95
101
  const client = createClient(
96
- cfg.getOrThrow<string>('SUPABASE_URL'),
97
- cfg.getOrThrow<string>('SUPABASE_SERVICE_ROLE_KEY'),
102
+ requireEnv(cfg, 'SUPABASE_URL'),
103
+ requireEnv(cfg, 'SUPABASE_SERVICE_ROLE_KEY'),
98
104
  { auth: { autoRefreshToken: false, persistSession: false } },
99
105
  );
100
106
  return new SupabaseStorageStrategy({
101
107
  client,
102
- bucket: cfg.getOrThrow<string>('SUPABASE_STORAGE_BUCKET'),
108
+ bucket: requireEnv(cfg, 'SUPABASE_STORAGE_BUCKET'),
103
109
  });
104
110
  }
105
111
  case 'firebase':
@@ -1,4 +1,3 @@
1
- import { randomUUID } from 'node:crypto';
2
1
  import type {
3
2
  AuthSession,
4
3
  AuthStrategy,
@@ -33,7 +32,7 @@ export class FakeAuthStrategy implements AuthStrategy {
33
32
 
34
33
  async signUp(email: string, password: string): Promise<AuthSession> {
35
34
  if (this.users.has(email)) throw new Error('user_exists');
36
- const user: StoredUser = { id: randomUUID(), email, password };
35
+ const user: StoredUser = { id: globalThis.crypto.randomUUID(), email, password };
37
36
  this.users.set(email, user);
38
37
  return this.issueSession(user);
39
38
  }
@@ -72,10 +71,10 @@ export class FakeAuthStrategy implements AuthStrategy {
72
71
  async sendMagicLink(req: MagicLinkRequest): Promise<void> {
73
72
  let user = this.users.get(req.email);
74
73
  if (!user) {
75
- user = { id: randomUUID(), email: req.email, password: '' };
74
+ user = { id: globalThis.crypto.randomUUID(), email: req.email, password: '' };
76
75
  this.users.set(req.email, user);
77
76
  }
78
- const token = randomUUID();
77
+ const token = globalThis.crypto.randomUUID();
79
78
  this.magicLinkTokens.set(token, user.id);
80
79
  this.magicLinkByEmail.set(req.email, token);
81
80
  }
@@ -95,7 +94,7 @@ export class FakeAuthStrategy implements AuthStrategy {
95
94
  }
96
95
 
97
96
  async startOAuth(provider: OAuthProvider, callbackUrl: string): Promise<OAuthStartResult> {
98
- const state = randomUUID();
97
+ const state = globalThis.crypto.randomUUID();
99
98
  this.lastOAuthState = state;
100
99
  const url = new URL(`https://fake-${provider}.example.com/authorize`);
101
100
  url.searchParams.set('redirect_uri', callbackUrl);
@@ -111,7 +110,7 @@ export class FakeAuthStrategy implements AuthStrategy {
111
110
  this.oauthCodes.delete(code);
112
111
  let user = this.users.get(pending.email);
113
112
  if (!user) {
114
- user = { id: randomUUID(), email: pending.email, password: '' };
113
+ user = { id: globalThis.crypto.randomUUID(), email: pending.email, password: '' };
115
114
  this.users.set(pending.email, user);
116
115
  }
117
116
  return this.issueSession(user);
@@ -123,7 +122,7 @@ export class FakeAuthStrategy implements AuthStrategy {
123
122
  getLastOAuthChallenge(provider: OAuthProvider, email: string): { code: string; state: string } {
124
123
  if (!this.lastOAuthState) throw new Error('no startOAuth called yet');
125
124
  const state = this.lastOAuthState;
126
- const code = randomUUID();
125
+ const code = globalThis.crypto.randomUUID();
127
126
  this.oauthStates.set(state, { provider, email });
128
127
  this.oauthCodes.set(code, state);
129
128
  return { code, state };
@@ -137,8 +136,8 @@ export class FakeAuthStrategy implements AuthStrategy {
137
136
  }
138
137
 
139
138
  private issueSession(user: StoredUser): AuthSession {
140
- const accessToken = randomUUID();
141
- const refreshToken = randomUUID();
139
+ const accessToken = globalThis.crypto.randomUUID();
140
+ const refreshToken = globalThis.crypto.randomUUID();
142
141
  this.tokensToUid.set(accessToken, user.id);
143
142
  this.refreshToUid.set(refreshToken, user.id);
144
143
  return {
@@ -1,4 +1,3 @@
1
- import { randomUUID } from 'node:crypto';
2
1
  import type { FileInput, StorageRef, StorageStrategy } from '../storage';
3
2
 
4
3
  interface StoredFile {
@@ -13,7 +12,7 @@ export class FakeStorageStrategy implements StorageStrategy {
13
12
  private readonly files = new Map<string, StoredFile>();
14
13
 
15
14
  async upload(userId: string, file: FileInput): Promise<StorageRef> {
16
- const path = `${userId}/${randomUUID()}-${file.filename}`;
15
+ const path = `${userId}/${globalThis.crypto.randomUUID()}-${file.filename}`;
17
16
  const ref: StorageRef = { bucket: this.bucket, path };
18
17
  this.files.set(this.key(ref), {
19
18
  ownerId: userId,