@idevconn/create-icore 0.7.2 → 0.9.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 (87) hide show
  1. package/README.md +44 -9
  2. package/dist/cli.js +689 -363
  3. package/dist/index.cjs +806 -358
  4. package/dist/index.d.cts +12 -3
  5. package/dist/index.d.ts +12 -3
  6. package/dist/index.js +798 -351
  7. package/dist/manifest/audit.js +122 -0
  8. package/package.json +1 -1
  9. package/templates/apps/api/src/app/app.module.ts +2 -6
  10. package/templates/apps/api/src/app/features.module.ts +9 -0
  11. package/templates/apps/api/src/app/gateway-services.ts +7 -0
  12. package/templates/apps/api/src/main.ts +1 -5
  13. package/templates/apps/microservices/auth/src/app/app.module.ts +4 -93
  14. package/templates/apps/microservices/auth/src/app/auth.provider.ts +9 -0
  15. package/templates/apps/microservices/notes/src/app/app.module.ts +4 -86
  16. package/templates/apps/microservices/notes/src/app/db.provider.ts +9 -0
  17. package/templates/apps/microservices/upload/src/app/app.module.ts +4 -140
  18. package/templates/apps/microservices/upload/src/app/storage.provider.ts +9 -0
  19. package/templates/apps/templates/client-antd/src/components/layout/LayoutSider.tsx +15 -23
  20. package/templates/apps/templates/client-antd/src/nav.config.ts +17 -0
  21. package/templates/apps/templates/client-mui/src/components/layout/LayoutSider.tsx +19 -20
  22. package/templates/apps/templates/client-mui/src/nav.config.ts +17 -0
  23. package/templates/apps/templates/client-shadcn/src/components/layout/LayoutSider.tsx +20 -16
  24. package/templates/apps/templates/client-shadcn/src/nav.config.ts +17 -0
  25. package/templates/libs/auth-strategies/firebase/eslint.config.mjs +1 -0
  26. package/templates/libs/auth-strategies/firebase/package.json +4 -0
  27. package/templates/libs/auth-strategies/firebase/src/index.ts +1 -0
  28. package/templates/libs/auth-strategies/firebase/src/lib/__tests__/firebase-auth.module.unit.test.ts +49 -0
  29. package/templates/libs/auth-strategies/firebase/src/lib/firebase-auth.module.ts +41 -0
  30. package/templates/libs/auth-strategies/firebase/tsconfig.json +2 -0
  31. package/templates/libs/auth-strategies/mongodb/package.json +8 -1
  32. package/templates/libs/auth-strategies/mongodb/src/index.ts +1 -0
  33. package/templates/libs/auth-strategies/mongodb/src/lib/__tests__/mongodb-auth.module.unit.test.ts +16 -0
  34. package/templates/libs/auth-strategies/mongodb/src/lib/mongodb-auth.module.ts +45 -0
  35. package/templates/libs/auth-strategies/mongodb/tsconfig.json +2 -0
  36. package/templates/libs/auth-strategies/supabase/eslint.config.mjs +1 -0
  37. package/templates/libs/auth-strategies/supabase/package.json +3 -0
  38. package/templates/libs/auth-strategies/supabase/src/index.ts +1 -0
  39. package/templates/libs/auth-strategies/supabase/src/lib/__tests__/supabase-auth.module.unit.test.ts +43 -0
  40. package/templates/libs/auth-strategies/supabase/src/lib/supabase-auth.module.ts +41 -0
  41. package/templates/libs/auth-strategies/supabase/tsconfig.json +2 -0
  42. package/templates/libs/db-strategies/firestore/eslint.config.mjs +1 -1
  43. package/templates/libs/db-strategies/firestore/package.json +4 -0
  44. package/templates/libs/db-strategies/firestore/src/index.ts +1 -0
  45. package/templates/libs/db-strategies/firestore/src/lib/__tests__/firestore-db.module.unit.test.ts +37 -0
  46. package/templates/libs/db-strategies/firestore/src/lib/firestore-db.module.ts +41 -0
  47. package/templates/libs/db-strategies/firestore/tsconfig.json +2 -0
  48. package/templates/libs/db-strategies/mongodb/package.json +4 -1
  49. package/templates/libs/db-strategies/mongodb/src/index.ts +1 -0
  50. package/templates/libs/db-strategies/mongodb/src/lib/__tests__/mongodb-db.module.unit.test.ts +14 -0
  51. package/templates/libs/db-strategies/mongodb/src/lib/mongodb-db.module.ts +41 -0
  52. package/templates/libs/db-strategies/mongodb/tsconfig.json +2 -0
  53. package/templates/libs/db-strategies/supabase/eslint.config.mjs +6 -1
  54. package/templates/libs/db-strategies/supabase/package.json +3 -0
  55. package/templates/libs/db-strategies/supabase/src/index.ts +1 -0
  56. package/templates/libs/db-strategies/supabase/src/lib/__tests__/supabase-db.module.unit.test.ts +32 -0
  57. package/templates/libs/db-strategies/supabase/src/lib/supabase-db.module.ts +41 -0
  58. package/templates/libs/db-strategies/supabase/tsconfig.json +2 -0
  59. package/templates/libs/shared/src/strategies/__tests__/provide-strategy.unit.test.ts +73 -0
  60. package/templates/libs/shared/src/strategies/index.ts +1 -0
  61. package/templates/libs/shared/src/strategies/provide-strategy.ts +44 -0
  62. package/templates/libs/storage-strategies/cloudinary/eslint.config.mjs +1 -1
  63. package/templates/libs/storage-strategies/cloudinary/package.json +4 -0
  64. package/templates/libs/storage-strategies/cloudinary/src/index.ts +1 -0
  65. package/templates/libs/storage-strategies/cloudinary/src/lib/__tests__/cloudinary-storage.module.unit.test.ts +40 -0
  66. package/templates/libs/storage-strategies/cloudinary/src/lib/cloudinary-storage.module.ts +85 -0
  67. package/templates/libs/storage-strategies/cloudinary/tsconfig.json +2 -0
  68. package/templates/libs/storage-strategies/firebase/eslint.config.mjs +1 -1
  69. package/templates/libs/storage-strategies/firebase/package.json +4 -0
  70. package/templates/libs/storage-strategies/firebase/src/index.ts +1 -0
  71. package/templates/libs/storage-strategies/firebase/src/lib/__tests__/firebase-storage.module.unit.test.ts +42 -0
  72. package/templates/libs/storage-strategies/firebase/src/lib/firebase-storage.module.ts +46 -0
  73. package/templates/libs/storage-strategies/firebase/tsconfig.json +2 -0
  74. package/templates/libs/storage-strategies/mongodb/package.json +4 -1
  75. package/templates/libs/storage-strategies/mongodb/src/index.ts +1 -0
  76. package/templates/libs/storage-strategies/mongodb/src/lib/__tests__/mongodb-storage.module.unit.test.ts +14 -0
  77. package/templates/libs/storage-strategies/mongodb/src/lib/mongodb-storage.module.ts +41 -0
  78. package/templates/libs/storage-strategies/mongodb/tsconfig.json +2 -0
  79. package/templates/libs/storage-strategies/supabase/eslint.config.mjs +1 -0
  80. package/templates/libs/storage-strategies/supabase/package.json +3 -0
  81. package/templates/libs/storage-strategies/supabase/src/index.ts +1 -0
  82. package/templates/libs/storage-strategies/supabase/src/lib/__tests__/supabase-storage.module.unit.test.ts +46 -0
  83. package/templates/libs/storage-strategies/supabase/src/lib/supabase-storage.module.ts +46 -0
  84. package/templates/libs/storage-strategies/supabase/tsconfig.json +2 -0
  85. package/templates/package.json +1 -1
  86. package/templates/tools/create-icore/_template-shell/package.json +1 -1
  87. package/templates/tsconfig.base.json +1 -1
package/dist/cli.js CHANGED
@@ -10,9 +10,98 @@ import * as p2 from "@clack/prompts";
10
10
  // src/lib/prompts.ts
11
11
  import * as p from "@clack/prompts";
12
12
  import { resolve } from "path";
13
- import { readFile } from "fs/promises";
13
+ import { readFile as readFile2 } from "fs/promises";
14
14
  import { dirname, join } from "path";
15
15
  import { fileURLToPath } from "url";
16
+
17
+ // src/lib/config.ts
18
+ import { readFile } from "fs/promises";
19
+ var ConfigFileError = class extends Error {
20
+ constructor(message) {
21
+ super(message);
22
+ this.name = "ConfigFileError";
23
+ }
24
+ };
25
+ var AUTH_PROVIDERS = ["supabase", "firebase", "mongodb", "none"];
26
+ var DB_PROVIDERS = ["supabase", "firebase", "mongodb", "none"];
27
+ var UPLOAD_PROVIDERS = [
28
+ "supabase",
29
+ "firebase",
30
+ "cloudinary",
31
+ "mongodb",
32
+ "none"
33
+ ];
34
+ var PAYMENT_PROVIDERS = ["paypal", "none"];
35
+ var JOBS_PROVIDERS = ["bullmq", "none"];
36
+ var EXAMPLE_MODES = ["notes", "none"];
37
+ var UI_LIBRARIES = ["shadcn", "antd", "mui"];
38
+ var MS_TRANSPORTS = ["tcp", "redis", "nats", "mqtt", "rmq", "kafka"];
39
+ var PACKAGE_MANAGERS = ["yarn", "npm", "pnpm"];
40
+ function assertEnum(field, value, valid) {
41
+ if (typeof value !== "string" || !valid.includes(value)) {
42
+ throw new ConfigFileError(
43
+ `config field "${field}" got "${String(value)}", expected one of: ${valid.join(", ")}`
44
+ );
45
+ }
46
+ return value;
47
+ }
48
+ function assertBoolean(field, value) {
49
+ if (typeof value !== "boolean") {
50
+ throw new ConfigFileError(`config field "${field}" must be a boolean, got ${typeof value}`);
51
+ }
52
+ return value;
53
+ }
54
+ function validateConfig(raw) {
55
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
56
+ throw new ConfigFileError("config file must be a JSON object");
57
+ }
58
+ const obj = raw;
59
+ const result = {};
60
+ if ("projectName" in obj) {
61
+ const v = obj["projectName"];
62
+ if (typeof v !== "string" || !/^[a-z0-9-]+$/i.test(v)) {
63
+ throw new ConfigFileError(
64
+ `config field "projectName" must match /^[a-z0-9-]+$/i, got "${String(v)}"`
65
+ );
66
+ }
67
+ result.projectName = v;
68
+ }
69
+ if ("authProvider" in obj)
70
+ result.authProvider = assertEnum("authProvider", obj["authProvider"], AUTH_PROVIDERS);
71
+ if ("dbProvider" in obj)
72
+ result.dbProvider = assertEnum("dbProvider", obj["dbProvider"], DB_PROVIDERS);
73
+ if ("upload" in obj) result.upload = assertEnum("upload", obj["upload"], UPLOAD_PROVIDERS);
74
+ if ("payment" in obj) result.payment = assertEnum("payment", obj["payment"], PAYMENT_PROVIDERS);
75
+ if ("jobs" in obj) result.jobs = assertEnum("jobs", obj["jobs"], JOBS_PROVIDERS);
76
+ if ("example" in obj) result.example = assertEnum("example", obj["example"], EXAMPLE_MODES);
77
+ if ("ui" in obj) result.ui = assertEnum("ui", obj["ui"], UI_LIBRARIES);
78
+ if ("transport" in obj)
79
+ result.transport = assertEnum("transport", obj["transport"], MS_TRANSPORTS);
80
+ if ("packageManager" in obj)
81
+ result.packageManager = assertEnum("packageManager", obj["packageManager"], PACKAGE_MANAGERS);
82
+ if ("initGit" in obj) result.initGit = assertBoolean("initGit", obj["initGit"]);
83
+ if ("install" in obj) result.install = assertBoolean("install", obj["install"]);
84
+ return result;
85
+ }
86
+ async function loadConfig(filePath) {
87
+ let raw;
88
+ try {
89
+ raw = await readFile(filePath, "utf8");
90
+ } catch {
91
+ throw new ConfigFileError(`config file not found: ${filePath}`);
92
+ }
93
+ let parsed;
94
+ try {
95
+ parsed = JSON.parse(raw);
96
+ } catch (e) {
97
+ throw new ConfigFileError(
98
+ `config file is not valid JSON: ${e instanceof Error ? e.message : String(e)}`
99
+ );
100
+ }
101
+ return validateConfig(parsed);
102
+ }
103
+
104
+ // src/lib/prompts.ts
16
105
  function detectPackageManager() {
17
106
  const ua = process.env["npm_config_user_agent"] ?? "";
18
107
  if (ua.startsWith("yarn/")) return "yarn";
@@ -23,7 +112,7 @@ function detectPackageManager() {
23
112
  async function readSelfVersion() {
24
113
  try {
25
114
  const here2 = dirname(fileURLToPath(import.meta.url));
26
- const pkgRaw = await readFile(join(here2, "..", "package.json"), "utf8");
115
+ const pkgRaw = await readFile2(join(here2, "..", "package.json"), "utf8");
27
116
  const pkg = JSON.parse(pkgRaw);
28
117
  return pkg.version ?? null;
29
118
  } catch {
@@ -97,12 +186,21 @@ function parseFlags(argv) {
97
186
  case "no-install":
98
187
  out.install = false;
99
188
  break;
189
+ case "config":
190
+ out._configPath = v;
191
+ break;
100
192
  }
101
193
  }
102
194
  return out;
103
195
  }
104
196
  async function collectOptions({ argv, cwd }) {
105
197
  const flags = parseFlags(argv);
198
+ const configPath = flags._configPath;
199
+ delete flags._configPath;
200
+ if (configPath) {
201
+ const configValues = await loadConfig(configPath);
202
+ Object.assign(flags, { ...configValues, ...flags });
203
+ }
106
204
  const [selfVersion, latestVersion] = await Promise.all([readSelfVersion(), fetchLatestVersion()]);
107
205
  const versionTag = selfVersion ? ` v${selfVersion}` : "";
108
206
  p.intro(`iCore${versionTag} \u2014 bootstrap a new project`);
@@ -125,11 +223,12 @@ Re-run with @latest to refresh:
125
223
  options: [
126
224
  { value: "supabase", label: "Supabase" },
127
225
  { value: "firebase", label: "Firebase" },
128
- { value: "mongodb", label: "MongoDB (Custom Auth)" }
226
+ { value: "mongodb", label: "MongoDB (Custom Auth)" },
227
+ { value: "none", label: "None \u2014 no login, open API (simple SPA)" }
129
228
  ]
130
229
  });
131
230
  if (p.isCancel(authProvider)) throw new Error("cancelled");
132
- const dbProvider = flags.dbProvider ?? await p.select({
231
+ const dbProvider = authProvider === "none" ? "none" : flags.dbProvider ?? await p.select({
133
232
  message: "Database backend",
134
233
  options: [
135
234
  { value: "supabase", label: "Supabase Postgres" },
@@ -168,7 +267,7 @@ Re-run with @latest to refresh:
168
267
  initialValue: "none"
169
268
  });
170
269
  if (p.isCancel(jobs)) throw new Error("cancelled");
171
- const example = flags.example ?? await p.select({
270
+ const example = authProvider === "none" ? "none" : flags.example ?? await p.select({
172
271
  message: "Include notes sample feature? (CRUD demo \u2014 remove before production)",
173
272
  options: [
174
273
  { value: "notes", label: "Yes \u2014 include notes sample" },
@@ -193,7 +292,8 @@ Re-run with @latest to refresh:
193
292
  initialValue: "shadcn"
194
293
  });
195
294
  if (p.isCancel(ui)) throw new Error("cancelled");
196
- const transport = flags.transport ?? await p.select({
295
+ const noMicroservices = authProvider === "none" && upload === "none" && payment === "none";
296
+ const transport = flags.transport ?? (noMicroservices ? "tcp" : await p.select({
197
297
  message: "Microservice transport",
198
298
  options: [
199
299
  { value: "tcp", label: "TCP (default, no broker required)" },
@@ -204,7 +304,7 @@ Re-run with @latest to refresh:
204
304
  { value: "kafka", label: "Kafka" }
205
305
  ],
206
306
  initialValue: "tcp"
207
- });
307
+ }));
208
308
  if (p.isCancel(transport)) throw new Error("cancelled");
209
309
  const packageManager = flags.packageManager ?? detectPackageManager();
210
310
  if (packageManager === "yarn") {
@@ -236,13 +336,13 @@ Re-run with @latest to refresh:
236
336
  }
237
337
 
238
338
  // src/lib/scaffold.ts
239
- import { copyFile, mkdir as mkdir2, readdir as readdir2, readFile as readFile5, stat, writeFile as writeFile4, rm as rm2 } from "fs/promises";
339
+ import { copyFile, mkdir as mkdir2, readdir as readdir2, readFile as readFile8, stat, writeFile as writeFile8, rm as rm4 } from "fs/promises";
240
340
  import { readFileSync } from "fs";
241
- import { join as join5 } from "path";
341
+ import { join as join9 } from "path";
242
342
  import { spawnSync } from "child_process";
243
343
 
244
344
  // src/lib/scaffold-env.ts
245
- import { readFile as readFile2, writeFile } from "fs/promises";
345
+ import { readFile as readFile3, writeFile } from "fs/promises";
246
346
  import { join as join2 } from "path";
247
347
  var TRANSPORT_ENV_TOKEN = {
248
348
  redis: "REDIS",
@@ -271,7 +371,7 @@ function uncommentTransportEnv(text2, prefix, transport) {
271
371
  async function stripGatewayTransport(targetDir, prefix) {
272
372
  const gatewayEnv = join2(targetDir, "apps/api/.env");
273
373
  try {
274
- const env = await readFile2(gatewayEnv, "utf8");
374
+ const env = await readFile3(gatewayEnv, "utf8");
275
375
  const next = env.split("\n").filter(
276
376
  (line) => !line.startsWith(`${prefix}_`) && !line.startsWith(`# ${prefix}_`) && !line.includes(`${prefix} MS transport`)
277
377
  ).join("\n");
@@ -279,9 +379,34 @@ async function stripGatewayTransport(targetDir, prefix) {
279
379
  } catch {
280
380
  }
281
381
  }
382
+ var ROOT_PROVIDER_SDKS = {
383
+ supabase: ["@supabase/supabase-js"],
384
+ cloudinary: ["cloudinary"],
385
+ mongodb: ["mongoose"],
386
+ firebase: ["firebase-admin"]
387
+ };
388
+ async function pruneRootProviderDeps(targetDir, opts) {
389
+ const chosen = /* @__PURE__ */ new Set([opts.authProvider, opts.dbProvider, opts.upload]);
390
+ const drop = /* @__PURE__ */ new Set();
391
+ for (const [provider, sdks] of Object.entries(ROOT_PROVIDER_SDKS)) {
392
+ if (!chosen.has(provider)) for (const sdk of sdks) drop.add(sdk);
393
+ }
394
+ if (drop.size === 0) return;
395
+ const pkgPath = join2(targetDir, "package.json");
396
+ try {
397
+ const pkg = JSON.parse(await readFile3(pkgPath, "utf8"));
398
+ for (const field of ["dependencies", "devDependencies"]) {
399
+ const deps = pkg[field];
400
+ if (!deps) continue;
401
+ for (const sdk of drop) delete deps[sdk];
402
+ }
403
+ await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
404
+ } catch {
405
+ }
406
+ }
282
407
  async function rewriteRootPackageJson(targetDir, opts) {
283
408
  const pkgPath = join2(targetDir, "package.json");
284
- const raw = await readFile2(pkgPath, "utf8");
409
+ const raw = await readFile3(pkgPath, "utf8");
285
410
  const pkg = JSON.parse(raw);
286
411
  pkg["name"] = opts.projectName;
287
412
  pkg["version"] = "0.0.1";
@@ -296,11 +421,16 @@ async function rewriteRootPackageJson(targetDir, opts) {
296
421
  const deps = pkg["dependencies"] ??= {};
297
422
  Object.assign(deps, MONGODB_DEPS);
298
423
  }
424
+ if (opts.authProvider === "mongodb") {
425
+ const devDeps = pkg["devDependencies"] ??= {};
426
+ devDeps["@types/bcrypt"] = "^6.0.0";
427
+ devDeps["@types/jsonwebtoken"] = "^9.0.10";
428
+ }
299
429
  if (opts.packageManager !== "yarn") {
300
430
  delete pkg.packageManager;
301
431
  } else {
302
432
  try {
303
- const yarnrc = await readFile2(join2(targetDir, ".yarnrc.yml"), "utf8");
433
+ const yarnrc = await readFile3(join2(targetDir, ".yarnrc.yml"), "utf8");
304
434
  const match = yarnrc.match(/^yarnPath:\s*.+yarn-(\d+\.\d+\.\d+)\.cjs/m);
305
435
  if (match?.[1]) {
306
436
  pkg["packageManager"] = `yarn@${match[1]}`;
@@ -313,7 +443,7 @@ async function rewriteRootPackageJson(targetDir, opts) {
313
443
  }
314
444
  async function writeAuthEnv(targetDir, opts) {
315
445
  const envExample = join2(targetDir, "apps/microservices/auth/.env.example");
316
- const env = await readFile2(envExample, "utf8");
446
+ const env = await readFile3(envExample, "utf8");
317
447
  let next = env.replace(/^AUTH_PROVIDER=.*$/m, `AUTH_PROVIDER=${opts.authProvider}`).replace(/^AUTH_TRANSPORT=.*$/m, `AUTH_TRANSPORT=${opts.transport}`);
318
448
  next = uncommentTransportEnv(next, "AUTH", opts.transport);
319
449
  if (opts.authProvider === "mongodb") {
@@ -324,7 +454,7 @@ async function writeAuthEnv(targetDir, opts) {
324
454
  async function writeUploadEnv(targetDir, opts) {
325
455
  if (opts.upload === "none") return;
326
456
  const envExample = join2(targetDir, "apps/microservices/upload/.env.example");
327
- const env = await readFile2(envExample, "utf8");
457
+ const env = await readFile3(envExample, "utf8");
328
458
  let next = env.replace(/^STORAGE_PROVIDER=.*$/m, `STORAGE_PROVIDER=${opts.upload}`).replace(/^UPLOAD_TRANSPORT=.*$/m, `UPLOAD_TRANSPORT=${opts.transport}`);
329
459
  next = uncommentTransportEnv(next, "UPLOAD", opts.transport);
330
460
  if (opts.upload === "mongodb") {
@@ -336,7 +466,7 @@ async function writeNotesEnv(targetDir, opts) {
336
466
  if (opts.example === "none") return;
337
467
  const envExample = join2(targetDir, "apps/microservices/notes/.env.example");
338
468
  try {
339
- const env = await readFile2(envExample, "utf8");
469
+ const env = await readFile3(envExample, "utf8");
340
470
  let next = env.replace(/^NOTES_TRANSPORT=.*$/m, `NOTES_TRANSPORT=${opts.transport}`);
341
471
  next = uncommentTransportEnv(next, "NOTES", opts.transport);
342
472
  await writeFile(join2(targetDir, "apps/microservices/notes/.env"), next);
@@ -345,7 +475,7 @@ async function writeNotesEnv(targetDir, opts) {
345
475
  }
346
476
  async function writeGatewayEnv(targetDir, opts) {
347
477
  const envExample = join2(targetDir, "apps/api/.env.example");
348
- const env = await readFile2(envExample, "utf8");
478
+ const env = await readFile3(envExample, "utf8");
349
479
  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}`);
350
480
  for (const prefix of ["AUTH", "UPLOAD", "NOTES", "PAYMENT"]) {
351
481
  next = uncommentTransportEnv(next, prefix, opts.transport);
@@ -368,7 +498,7 @@ async function writeRootEnv(targetDir, opts) {
368
498
  async function writeClientEnv(targetDir) {
369
499
  const envExample = join2(targetDir, "apps/client/.env.example");
370
500
  try {
371
- const env = await readFile2(envExample, "utf8");
501
+ const env = await readFile3(envExample, "utf8");
372
502
  await writeFile(join2(targetDir, "apps/client/.env"), env);
373
503
  } catch {
374
504
  }
@@ -377,7 +507,7 @@ async function writePaymentEnv(targetDir, opts) {
377
507
  if (opts.payment === "none") return;
378
508
  const envExample = join2(targetDir, "apps/microservices/payment/.env.example");
379
509
  try {
380
- const env = await readFile2(envExample, "utf8");
510
+ const env = await readFile3(envExample, "utf8");
381
511
  let next = env.replace(/^PAYMENT_PROVIDER=.*$/m, `PAYMENT_PROVIDER=${opts.payment}`).replace(/^PAYMENT_TRANSPORT=.*$/m, `PAYMENT_TRANSPORT=${opts.transport}`);
382
512
  next = uncommentTransportEnv(next, "PAYMENT", opts.transport);
383
513
  await writeFile(join2(targetDir, "apps/microservices/payment/.env"), next);
@@ -386,11 +516,11 @@ async function writePaymentEnv(targetDir, opts) {
386
516
  }
387
517
 
388
518
  // src/lib/scaffold-strip.ts
389
- import { readFile as readFile3, writeFile as writeFile2, rm } from "fs/promises";
519
+ import { readFile as readFile4, writeFile as writeFile2, rm } from "fs/promises";
390
520
  import { join as join3 } from "path";
391
521
  async function stripDeps(pkgPath, names) {
392
522
  try {
393
- const raw = await readFile3(pkgPath, "utf8");
523
+ const raw = await readFile4(pkgPath, "utf8");
394
524
  const pkg = JSON.parse(raw);
395
525
  for (const n of names) {
396
526
  if (pkg.dependencies) delete pkg.dependencies[n];
@@ -403,7 +533,7 @@ async function stripDeps(pkgPath, names) {
403
533
  async function stripTsconfigPath(targetDir, alias) {
404
534
  const tsconfigPath = join3(targetDir, "tsconfig.base.json");
405
535
  try {
406
- const src = await readFile3(tsconfigPath, "utf8");
536
+ const src = await readFile4(tsconfigPath, "utf8");
407
537
  const escaped = alias.replace(/[@/]/g, (c) => c === "@" ? "@" : "\\/");
408
538
  const pretty = src.replace(new RegExp(`^\\s*"${escaped}": \\[[^\\]]*\\],?\\n`, "m"), "");
409
539
  if (pretty !== src) {
@@ -418,326 +548,75 @@ async function stripTsconfigPath(targetDir, alias) {
418
548
  } catch {
419
549
  }
420
550
  }
421
- async function removeJobsStack(targetDir) {
422
- const paths = [
423
- "apps/microservices/jobs",
424
- "libs/jobs-client",
425
- "apps/api/src/app/admin",
426
- "Dockerfile.ms-jobs"
427
- ];
428
- for (const p3 of paths) {
429
- await rm(join3(targetDir, p3), { recursive: true, force: true });
430
- }
431
- const appModulePath = join3(targetDir, "apps/api/src/app/app.module.ts");
432
- try {
433
- const appModule = await readFile3(appModulePath, "utf8");
434
- const next = appModule.replace(/^import \{ AdminModule \} from '\.\/admin\/admin\.module';\n/m, "").replace(/,\s*AdminModule/g, "");
435
- await writeFile2(appModulePath, next);
436
- } catch {
437
- }
438
- await stripDeps(join3(targetDir, "apps/api/package.json"), [
439
- "@icore/jobs-client",
440
- "@bull-board/api",
441
- "@bull-board/express"
551
+ async function removeFirebaseAdminLib(targetDir) {
552
+ await rm(join3(targetDir, "libs/firebase-admin"), { recursive: true, force: true });
553
+ await stripTsconfigPath(targetDir, "@icore/firebase-admin");
554
+ await stripDeps(join3(targetDir, "apps/microservices/auth/package.json"), [
555
+ "@icore/firebase-admin"
556
+ ]);
557
+ await stripDeps(join3(targetDir, "apps/microservices/upload/package.json"), [
558
+ "@icore/firebase-admin"
559
+ ]);
560
+ await stripDeps(join3(targetDir, "apps/microservices/notes/package.json"), [
561
+ "@icore/firebase-admin"
442
562
  ]);
443
- const composePath = join3(targetDir, "docker-compose.yml");
444
- try {
445
- const compose = await readFile3(composePath, "utf8");
446
- const next = compose.replace(/\n {2}jobs:[\s\S]+?(?=\n {2}\w+:|\nnetworks:)/m, "\n").replace(/\n {6}jobs:\n {8}condition: service_started/g, "").replace(/\n {6}JOBS_REDIS_URL:[^\n]*/g, "");
447
- await writeFile2(composePath, next);
448
- } catch {
449
- }
450
563
  }
451
- async function removePaymentStack(targetDir) {
452
- const paths = [
453
- "apps/microservices/payment",
454
- "apps/microservices/payment-e2e",
455
- "libs/payment-client",
456
- "apps/api/src/app/payment"
564
+ async function removeAuthStack(targetDir) {
565
+ const rmPaths = [
566
+ "apps/microservices/auth",
567
+ "libs/auth-strategies",
568
+ "libs/auth-client",
569
+ "Dockerfile.ms-auth",
570
+ "apps/api/src/app/auth",
571
+ "apps/api/src/app/profile",
572
+ "apps/api/src/app/abilities",
573
+ "apps/client/src/components/auth",
574
+ "apps/client/src/routes/login.tsx",
575
+ "apps/client/src/routes/auth.callback.tsx",
576
+ "apps/client/src/routes/auth.oauth.callback.tsx",
577
+ "apps/client/src/routes/_dashboard/profile.tsx"
457
578
  ];
458
- for (const p3 of paths) {
579
+ for (const p3 of rmPaths) {
459
580
  await rm(join3(targetDir, p3), { recursive: true, force: true });
460
581
  }
461
582
  const appModulePath = join3(targetDir, "apps/api/src/app/app.module.ts");
462
583
  try {
463
- const appModule = await readFile3(appModulePath, "utf8");
464
- const next = appModule.replace(/^import \{ PaymentModule \} from '\.\/payment\/payment\.module';\n/m, "").replace(/,\s*PaymentModule/g, "");
584
+ const src = await readFile4(appModulePath, "utf8");
585
+ const next = src.replace(/^import \{ AuthModule \} from '\.\/auth\/auth\.module';\n/m, "").replace(/^import \{ ProfileModule \} from '\.\/profile\/profile\.module';\n/m, "").replace(/^import \{ AbilitiesModule \} from '\.\/abilities\/abilities\.module';\n/m, "").replace(/\bAuthModule,\s*/g, "").replace(/,\s*AuthModule\b/g, "").replace(/\bProfileModule,\s*/g, "").replace(/,\s*ProfileModule\b/g, "").replace(/\bAbilitiesModule,\s*/g, "").replace(/,\s*AbilitiesModule\b/g, "");
465
586
  await writeFile2(appModulePath, next);
466
587
  } catch {
467
588
  }
468
- await stripDeps(join3(targetDir, "apps/api/package.json"), [
469
- "@icore/payment-client",
470
- "@idevconn/payment"
471
- ]);
472
- await stripGatewayTransport(targetDir, "PAYMENT");
473
- const mainTsPath = join3(targetDir, "apps/api/src/main.ts");
589
+ const dashboardPath = join3(targetDir, "apps/client/src/routes/_dashboard.tsx");
474
590
  try {
475
- const src = await readFile3(mainTsPath, "utf8");
476
- const next = src.replace(/\n\s*\{ name: 'payment', prefix: 'PAYMENT' \},/, "");
477
- await writeFile2(mainTsPath, next);
591
+ const src = await readFile4(dashboardPath, "utf8");
592
+ const next = src.replace(/^import \{ useAuthStore \} from '@icore\/template-shared';\n/m, "").replace(/, redirect/g, "").replace(/\n {2}beforeLoad: \(\) => \{[\s\S]*?\n {2}\},/m, "");
593
+ await writeFile2(dashboardPath, next);
478
594
  } catch {
479
595
  }
480
- }
481
- async function removeNotesStack(targetDir) {
482
- for (const p3 of [
483
- "apps/microservices/notes",
484
- "apps/microservices/notes-e2e",
485
- "libs/notes-client",
486
- "apps/api/src/app/notes",
487
- "apps/client/src/components/notes"
596
+ for (const alias of [
597
+ "@icore/auth-client",
598
+ "@icore/auth-supabase",
599
+ "@icore/auth-firebase",
600
+ "@icore/auth-mongodb"
488
601
  ]) {
489
- await rm(join3(targetDir, p3), { recursive: true, force: true });
490
- }
491
- await rm(join3(targetDir, "apps/client/src/routes/_dashboard/notes.tsx"), { force: true });
492
- await rm(join3(targetDir, "apps/client/src/queries/notes.ts"), { force: true });
493
- const appModulePath = join3(targetDir, "apps/api/src/app/app.module.ts");
494
- try {
495
- const src = await readFile3(appModulePath, "utf8");
496
- const next = src.replace(/^import \{ NotesModule \} from '\.\/notes\/notes\.module';\n/m, "").replace(/,\s*NotesModule/g, "");
497
- await writeFile2(appModulePath, next);
498
- } catch {
499
- }
500
- await stripDeps(join3(targetDir, "apps/api/package.json"), [
501
- "@icore/notes-client",
502
- "@casl/ability"
503
- ]);
504
- await stripGatewayTransport(targetDir, "NOTES");
505
- const mainTsPath = join3(targetDir, "apps/api/src/main.ts");
506
- try {
507
- const src = await readFile3(mainTsPath, "utf8");
508
- const next = src.replace(/\n\s*\{ name: 'notes', prefix: 'NOTES' \},/, "");
509
- await writeFile2(mainTsPath, next);
510
- } catch {
511
- }
512
- const tsconfigPath = join3(targetDir, "tsconfig.base.json");
513
- try {
514
- const src = await readFile3(tsconfigPath, "utf8");
515
- const next = src.replace(/^\s*"@icore\/notes-client": \[[^\]]*\],?\n/m, "");
516
- await writeFile2(tsconfigPath, next);
517
- } catch {
518
- }
519
- const siderPath = join3(targetDir, "apps/client/src/components/layout/LayoutSider.tsx");
520
- try {
521
- const src = await readFile3(siderPath, "utf8");
522
- const next = src.replace(", StickyNote", "").replace(/\n {8}<Link\n {10}to="\/(?:_dashboard\/)?notes"[\s\S]*?<\/Link>/, "").replace(", FileTextOutlined", "").replace(
523
- "const selectedKey = pathname.includes('/notes')\n ? 'notes'\n : pathname.includes('/profile')",
524
- "const selectedKey = pathname.includes('/profile')"
525
- ).replace(
526
- /\n {4}\{\n {6}key: 'notes',\n {6}icon: <FileTextOutlined \/>,\n {6}label: <Link to="\/(?:_dashboard\/)?notes">\{t\('notes\.title'\)\}<\/Link>,\n {4}\},/,
527
- ""
528
- ).replace("import NoteOutlinedIcon from '@mui/icons-material/NoteOutlined';\n", "").replace(
529
- /\n {8}<ListItemButton\n {10}component=\{Link\}\n {10}to="\/(?:_dashboard\/)?notes"[\s\S]*?<\/ListItemButton>/,
530
- ""
531
- ).replace(/\n\s*<Link to="\/(?:_dashboard\/)?notes">[\s\S]*?<\/Link>/m, "");
532
- await writeFile2(siderPath, next);
533
- } catch {
602
+ await stripTsconfigPath(targetDir, alias);
534
603
  }
535
- const keysPath = join3(targetDir, "libs/template-shared/src/lib/i18n/keys.ts");
604
+ await stripDeps(join3(targetDir, "apps/api/package.json"), ["@icore/auth-client"]);
605
+ const gatewayEnv = join3(targetDir, "apps/api/.env");
536
606
  try {
537
- const src = await readFile3(keysPath, "utf8");
538
- const next = src.replace(/^\s{4}notes: \{\n(?:\s+.*\n)*?\s{4}\},\n/m, "");
539
- await writeFile2(keysPath, next);
607
+ const env = await readFile4(gatewayEnv, "utf8");
608
+ const next = env.split("\n").filter((line) => !line.startsWith("AUTH_") && !line.startsWith("# AUTH_")).join("\n");
609
+ await writeFile2(gatewayEnv, next);
540
610
  } catch {
541
611
  }
542
- }
543
- async function removeUnusedAuthStrategies(targetDir, authProvider) {
544
- const modulePath = join3(targetDir, "apps/microservices/auth/src/app/app.module.ts");
545
- const AUTH_BRANCH = /if \(provider === 'supabase'\) return makeSupabaseAuth\(cfg\);\n\s*if \(provider === 'mongodb'\) return makeMongoDbAuth\(connection, cfg\);\n\s*return makeFirebaseAuth\(cfg\);/m;
546
- if (authProvider === "supabase") {
547
- await rm(join3(targetDir, "libs/auth-strategies/firebase"), { recursive: true, force: true });
548
- await rm(join3(targetDir, "libs/auth-strategies/mongodb"), { recursive: true, force: true });
549
- await stripDeps(join3(targetDir, "apps/microservices/auth/package.json"), [
550
- "@icore/auth-firebase",
551
- "@icore/firebase-admin",
552
- "@icore/auth-mongodb"
553
- ]);
554
- await stripTsconfigPath(targetDir, "@icore/auth-firebase");
555
- await stripTsconfigPath(targetDir, "@icore/auth-mongodb");
556
- try {
557
- const src = await readFile3(modulePath, "utf8");
558
- const next = src.replace(/^import \{[^}]*\} from '@icore\/firebase-admin';\n/gm, "").replace(/^import \{[^}]*FirebaseAuthStrategy[^}]*\} from '@icore\/auth-firebase';\n/gm, "").replace(/^import \{[^}]*MongoDbAuthStrategy[^}]*\} from '@icore\/auth-mongodb';\n/gm, "").replace(
559
- /^import \{ MongooseModule, getConnectionToken \} from '@nestjs\/mongoose';\n/gm,
560
- ""
561
- ).replace(/^import \{ Connection \} from 'mongoose';\n/gm, "").replace(/^ {2}firebase: \[[^\]]*\],\n/gm, "").replace(/^ {2}mongodb: \[[^\]]*\],\n/gm, "").replace(/\nfunction makeFirebaseAuth[\s\S]*?\n}\n/gm, "").replace(/\nfunction makeMongoDbAuth[\s\S]*?\n}\n/gm, "").replace(/^ {4}MongooseModule\.forRootAsync[\s\S]*?\n {4}\}\),\n/gm, "").replace(AUTH_BRANCH, "return makeSupabaseAuth(cfg);").replace(/, connection: Connection/, "").replace(/, getConnectionToken\(\)/, "");
562
- await writeFile2(modulePath, next);
563
- } catch {
564
- }
565
- }
566
- if (authProvider === "firebase") {
567
- await rm(join3(targetDir, "libs/auth-strategies/supabase"), { recursive: true, force: true });
568
- await rm(join3(targetDir, "libs/auth-strategies/mongodb"), { recursive: true, force: true });
569
- await stripDeps(join3(targetDir, "apps/microservices/auth/package.json"), [
570
- "@icore/auth-supabase",
571
- "@icore/auth-mongodb"
572
- ]);
573
- await stripTsconfigPath(targetDir, "@icore/auth-supabase");
574
- await stripTsconfigPath(targetDir, "@icore/auth-mongodb");
575
- try {
576
- const src = await readFile3(modulePath, "utf8");
577
- const next = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/gm, "").replace(/^import \{[^}]*SupabaseAuthStrategy[^}]*\} from '@icore\/auth-supabase';\n/gm, "").replace(/^import \{[^}]*MongoDbAuthStrategy[^}]*\} from '@icore\/auth-mongodb';\n/gm, "").replace(
578
- /^import \{ MongooseModule, getConnectionToken \} from '@nestjs\/mongoose';\n/gm,
579
- ""
580
- ).replace(/^import \{ Connection \} from 'mongoose';\n/gm, "").replace(/^ {2}supabase: \[[^\]]*\],\n/gm, "").replace(/^ {2}mongodb: \[[^\]]*\],\n/gm, "").replace(/\nfunction makeSupabaseAuth[\s\S]*?\n}\n/gm, "").replace(/\nfunction makeMongoDbAuth[\s\S]*?\n}\n/gm, "").replace(/^ {4}MongooseModule\.forRootAsync[\s\S]*?\n {4}\}\),\n/gm, "").replace(AUTH_BRANCH, "return makeFirebaseAuth(cfg);").replace(/, connection: Connection/, "").replace(/, getConnectionToken\(\)/, "");
581
- await writeFile2(modulePath, next);
582
- } catch {
583
- }
584
- }
585
- if (authProvider === "mongodb") {
586
- await rm(join3(targetDir, "libs/auth-strategies/supabase"), { recursive: true, force: true });
587
- await rm(join3(targetDir, "libs/auth-strategies/firebase"), { recursive: true, force: true });
588
- await stripDeps(join3(targetDir, "apps/microservices/auth/package.json"), [
589
- "@icore/auth-supabase",
590
- "@icore/auth-firebase",
591
- "@icore/firebase-admin"
592
- ]);
593
- await stripTsconfigPath(targetDir, "@icore/auth-supabase");
594
- await stripTsconfigPath(targetDir, "@icore/auth-firebase");
595
- try {
596
- const src = await readFile3(modulePath, "utf8");
597
- const next = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/gm, "").replace(/^import \{[^}]*SupabaseAuthStrategy[^}]*\} from '@icore\/auth-supabase';\n/gm, "").replace(/^import \{[^}]*\} from '@icore\/firebase-admin';\n/gm, "").replace(/^import \{[^}]*FirebaseAuthStrategy[^}]*\} from '@icore\/auth-firebase';\n/gm, "").replace(/^ {2}supabase: \[[^\]]*\],\n/gm, "").replace(/^ {2}firebase: \[[^\]]*\],\n/gm, "").replace(/\nfunction makeSupabaseAuth[\s\S]*?\n}\n/gm, "").replace(/\nfunction makeFirebaseAuth[\s\S]*?\n}\n/gm, "").replace(AUTH_BRANCH, "return makeMongoDbAuth(connection, cfg);");
598
- await writeFile2(modulePath, next);
599
- } catch {
600
- }
601
- }
602
- }
603
- async function removeUnusedStorageStrategies(targetDir, uploadProvider) {
604
- if (uploadProvider === "none") return;
605
- const modulePath = join3(targetDir, "apps/microservices/upload/src/app/app.module.ts");
606
- const STORAGE_BRANCH = /if \(provider === 'supabase'\) return makeSupabaseStorage\(cfg\);\n\s*if \(provider === 'firebase'\) return makeFirebaseStorage\(cfg\);\n\s*if \(provider === 'mongodb'\) return makeMongoDbStorage\(connection\);\n\s*return makeCloudinaryStorage\(cfg\);/m;
607
- if (uploadProvider !== "firebase") {
608
- await rm(join3(targetDir, "libs/storage-strategies/firebase"), { recursive: true, force: true });
609
- await stripDeps(join3(targetDir, "apps/microservices/upload/package.json"), [
610
- "@icore/storage-firebase",
611
- "@icore/firebase-admin"
612
- ]);
613
- await stripTsconfigPath(targetDir, "@icore/storage-firebase");
614
- }
615
- if (uploadProvider !== "cloudinary") {
616
- await rm(join3(targetDir, "libs/storage-strategies/cloudinary"), {
617
- recursive: true,
618
- force: true
619
- });
620
- await stripDeps(join3(targetDir, "apps/microservices/upload/package.json"), [
621
- "@icore/storage-cloudinary"
622
- ]);
623
- await stripTsconfigPath(targetDir, "@icore/storage-cloudinary");
624
- }
625
- if (uploadProvider !== "supabase") {
626
- await rm(join3(targetDir, "libs/storage-strategies/supabase"), { recursive: true, force: true });
627
- await stripDeps(join3(targetDir, "apps/microservices/upload/package.json"), [
628
- "@icore/storage-supabase"
629
- ]);
630
- await stripTsconfigPath(targetDir, "@icore/storage-supabase");
631
- }
632
- if (uploadProvider !== "mongodb") {
633
- await rm(join3(targetDir, "libs/storage-strategies/mongodb"), { recursive: true, force: true });
634
- await stripDeps(join3(targetDir, "apps/microservices/upload/package.json"), [
635
- "@icore/storage-mongodb"
636
- ]);
637
- await stripTsconfigPath(targetDir, "@icore/storage-mongodb");
638
- }
612
+ const composePath = join3(targetDir, "docker-compose.yml");
639
613
  try {
640
- let src = await readFile3(modulePath, "utf8");
641
- if (uploadProvider !== "firebase") {
642
- src = src.replace(/^import \{[^}]*\} from '@icore\/firebase-admin';\n/gm, "").replace(
643
- /^import \{[^}]*FirebaseStorageStrategy[^}]*\} from '@icore\/storage-firebase';\n/gm,
644
- ""
645
- ).replace(/^ {2}firebase: \[[^\]]*\],\n/gm, "").replace(/\nfunction makeFirebaseStorage[\s\S]*?\n}\n/gm, "");
646
- }
647
- if (uploadProvider !== "cloudinary") {
648
- src = src.replace(/^import \{ v2 as cloudinary \} from 'cloudinary';\n/gm, "").replace(
649
- /^import \{[^}]*CloudinaryStorageStrategy[^}]*\} from '@icore\/storage-cloudinary';\n/gm,
650
- ""
651
- ).replace(/\nfunction makeCloudinaryStorage[\s\S]*?\n}\n/gm, "");
652
- }
653
- if (uploadProvider !== "supabase") {
654
- src = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/gm, "").replace(
655
- /^import \{[^}]*SupabaseStorageStrategy[^}]*\} from '@icore\/storage-supabase';\n/gm,
656
- ""
657
- ).replace(/\nfunction makeSupabaseStorage[\s\S]*?\n}\n/gm, "");
658
- }
659
- if (uploadProvider !== "mongodb") {
660
- src = src.replace(
661
- /^import \{[^}]*MongoDbStorageStrategy[^}]*\} from '@icore\/storage-mongodb';\n/gm,
662
- ""
663
- ).replace(
664
- /^import \{ MongooseModule, getConnectionToken \} from '@nestjs\/mongoose';\n/gm,
665
- ""
666
- ).replace(/^import \{ Connection \} from 'mongoose';\n/gm, "").replace(/^ {2}mongodb: \[[^\]]*\],\n/gm, "").replace(/\nfunction makeMongoDbStorage[\s\S]*?\n}\n/gm, "").replace(/^ {4}MongooseModule\.forRootAsync[\s\S]*?\n {4}\}\),\n/gm, "");
667
- }
668
- const chosenReturn = uploadProvider === "mongodb" ? `return makeMongoDbStorage(connection);` : `return make${uploadProvider.charAt(0).toUpperCase() + uploadProvider.slice(1)}Storage(cfg);`;
669
- src = src.replace(STORAGE_BRANCH, chosenReturn);
670
- if (uploadProvider !== "mongodb") {
671
- src = src.replace(/, connection: Connection/, "").replace(/, getConnectionToken\(\)/, "");
672
- }
673
- await writeFile2(modulePath, src);
614
+ const compose = await readFile4(composePath, "utf8");
615
+ const next = compose.replace(/\n {2}auth:[\s\S]+?(?=\n {2}\w|\nnetworks:)/m, "\n").replace(/\n {6}auth:\n {8}condition: service_started/g, "").replace(/\n {6}AUTH_TRANSPORT:[^\n]*/g, "").replace(/\n {6}AUTH_REDIS_URL:[^\n]*/g, "");
616
+ await writeFile2(composePath, next);
674
617
  } catch {
675
618
  }
676
619
  }
677
- async function removeUnusedDbStrategies(targetDir, dbProvider) {
678
- const modulePath = join3(targetDir, "apps/microservices/notes/src/app/app.module.ts");
679
- const DB_BRANCH = /if \(provider === 'supabase'\) return makeSupabaseDB\(cfg\);\n\s*if \(provider === 'mongodb'\) return makeMongoDb\(connection\);\n\s*return makeFirestoreDB\(cfg\);/m;
680
- if (dbProvider === "supabase") {
681
- await rm(join3(targetDir, "libs/db-strategies/firestore"), { recursive: true, force: true });
682
- await rm(join3(targetDir, "libs/db-strategies/mongodb"), { recursive: true, force: true });
683
- await stripDeps(join3(targetDir, "apps/microservices/notes/package.json"), [
684
- "@icore/db-firestore",
685
- "@icore/firebase-admin",
686
- "@icore/db-mongodb"
687
- ]);
688
- await stripTsconfigPath(targetDir, "@icore/db-firestore");
689
- await stripTsconfigPath(targetDir, "@icore/db-mongodb");
690
- try {
691
- const src = await readFile3(modulePath, "utf8");
692
- const next = src.replace(/^import \{[^}]*\} from '@icore\/firebase-admin';\n/gm, "").replace(/^import \{[^}]*FirestoreDBStrategy[^}]*\} from '@icore\/db-firestore';\n/gm, "").replace(/^import \{[^}]*MongoDbDBStrategy[^}]*\} from '@icore\/db-mongodb';\n/gm, "").replace(
693
- /^import \{ MongooseModule, getConnectionToken \} from '@nestjs\/mongoose';\n/gm,
694
- ""
695
- ).replace(/^import \{ Connection \} from 'mongoose';\n/gm, "").replace(/^ {2}firestore: \[[^\]]*\],\n/gm, "").replace(/^ {2}firebase: \[[^\]]*\],\n/gm, "").replace(/^ {2}mongodb: \[[^\]]*\],\n/gm, "").replace(/\nfunction makeFirestoreDB[\s\S]*?\n}\n/gm, "").replace(/\nfunction makeMongoDb[\s\S]*?\n}\n/gm, "").replace(/^ {4}MongooseModule\.forRootAsync[\s\S]*?\n {4}\}\),\n/gm, "").replace(DB_BRANCH, "return makeSupabaseDB(cfg);").replace(/, connection: Connection/, "").replace(/, getConnectionToken\(\)/, "");
696
- await writeFile2(modulePath, next);
697
- } catch {
698
- }
699
- }
700
- if (dbProvider === "firebase") {
701
- await rm(join3(targetDir, "libs/db-strategies/supabase"), { recursive: true, force: true });
702
- await rm(join3(targetDir, "libs/db-strategies/mongodb"), { recursive: true, force: true });
703
- await stripDeps(join3(targetDir, "apps/microservices/notes/package.json"), [
704
- "@icore/db-supabase",
705
- "@icore/db-mongodb"
706
- ]);
707
- await stripTsconfigPath(targetDir, "@icore/db-supabase");
708
- await stripTsconfigPath(targetDir, "@icore/db-mongodb");
709
- try {
710
- const src = await readFile3(modulePath, "utf8");
711
- const next = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/gm, "").replace(/^import \{[^}]*SupabaseDBStrategy[^}]*\} from '@icore\/db-supabase';\n/gm, "").replace(/^import \{[^}]*MongoDbDBStrategy[^}]*\} from '@icore\/db-mongodb';\n/gm, "").replace(
712
- /^import \{ MongooseModule, getConnectionToken \} from '@nestjs\/mongoose';\n/gm,
713
- ""
714
- ).replace(/^import \{ Connection \} from 'mongoose';\n/gm, "").replace(/^ {2}supabase: \[[^\]]*\],\n/gm, "").replace(/^ {2}mongodb: \[[^\]]*\],\n/gm, "").replace(/\nfunction makeSupabaseDB[\s\S]*?\n}\n/gm, "").replace(/\nfunction makeMongoDb[\s\S]*?\n}\n/gm, "").replace(/^ {4}MongooseModule\.forRootAsync[\s\S]*?\n {4}\}\),\n/gm, "").replace(/\nfunction requireEnv[\s\S]*?\n}\n/gm, "").replace(DB_BRANCH, "return makeFirestoreDB(cfg);").replace(/, connection: Connection/, "").replace(/, getConnectionToken\(\)/, "");
715
- await writeFile2(modulePath, next);
716
- } catch {
717
- }
718
- }
719
- if (dbProvider === "mongodb") {
720
- await rm(join3(targetDir, "libs/db-strategies/supabase"), { recursive: true, force: true });
721
- await rm(join3(targetDir, "libs/db-strategies/firestore"), { recursive: true, force: true });
722
- await stripDeps(join3(targetDir, "apps/microservices/notes/package.json"), [
723
- "@icore/db-supabase",
724
- "@icore/db-firestore",
725
- "@icore/firebase-admin"
726
- ]);
727
- await stripTsconfigPath(targetDir, "@icore/db-supabase");
728
- await stripTsconfigPath(targetDir, "@icore/db-firestore");
729
- try {
730
- const src = await readFile3(modulePath, "utf8");
731
- const next = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/gm, "").replace(/^import \{[^}]*SupabaseDBStrategy[^}]*\} from '@icore\/db-supabase';\n/gm, "").replace(/^import \{[^}]*\} from '@icore\/firebase-admin';\n/gm, "").replace(/^import \{[^}]*FirestoreDBStrategy[^}]*\} from '@icore\/db-firestore';\n/gm, "").replace(/^ {2}supabase: \[[^\]]*\],\n/gm, "").replace(/^ {2}firestore: \[[^\]]*\],\n/gm, "").replace(/^ {2}firebase: \[[^\]]*\],\n/gm, "").replace(/\nfunction makeSupabaseDB[\s\S]*?\n}\n/gm, "").replace(/\nfunction makeFirestoreDB[\s\S]*?\n}\n/gm, "").replace(/\nfunction requireEnv[\s\S]*?\n}\n/gm, "").replace(DB_BRANCH, "return makeMongoDb(connection);");
732
- await writeFile2(modulePath, next);
733
- } catch {
734
- }
735
- }
736
- }
737
- async function removeFirebaseAdminLib(targetDir) {
738
- await rm(join3(targetDir, "libs/firebase-admin"), { recursive: true, force: true });
739
- await stripTsconfigPath(targetDir, "@icore/firebase-admin");
740
- }
741
620
  async function removeUploadStack(targetDir) {
742
621
  const paths = [
743
622
  "apps/microservices/upload",
@@ -751,14 +630,14 @@ async function removeUploadStack(targetDir) {
751
630
  }
752
631
  const appModulePath = join3(targetDir, "apps/api/src/app/app.module.ts");
753
632
  try {
754
- const appModule = await readFile3(appModulePath, "utf8");
633
+ const appModule = await readFile4(appModulePath, "utf8");
755
634
  const next = appModule.replace(/^import \{ StorageModule \} from '\.\/storage\/storage\.module';\n/m, "").replace(/,\s*StorageModule/g, "");
756
635
  await writeFile2(appModulePath, next);
757
636
  } catch {
758
637
  }
759
638
  const gatewayEnv = join3(targetDir, "apps/api/.env");
760
639
  try {
761
- const env = await readFile3(gatewayEnv, "utf8");
640
+ const env = await readFile4(gatewayEnv, "utf8");
762
641
  const next = env.split("\n").filter(
763
642
  (line) => !line.startsWith("UPLOAD_") && !line.startsWith("# UPLOAD_") && !line.startsWith("MAX_FILE_SIZE_KB")
764
643
  ).join("\n");
@@ -771,9 +650,440 @@ async function removeUploadStack(targetDir) {
771
650
  ]);
772
651
  }
773
652
 
774
- // src/lib/scaffold-pkg.ts
775
- import { readFile as readFile4, writeFile as writeFile3, mkdir, readdir } from "fs/promises";
653
+ // src/manifest/wire-features.ts
654
+ import { readFile as readFile6, writeFile as writeFile4, rm as rm3 } from "fs/promises";
655
+ import { join as join5 } from "path";
656
+
657
+ // src/manifest/index.ts
658
+ var EMPTY = { libDirs: [], deps: {}, tsPaths: {} };
659
+ var MANIFEST = {
660
+ auth: {
661
+ supabase: {
662
+ libDirs: ["libs/auth-strategies/supabase"],
663
+ deps: { "@supabase/supabase-js": "^2.106.2" },
664
+ tsPaths: { "@icore/auth-supabase": ["libs/auth-strategies/supabase/src/index.ts"] },
665
+ nestModule: {
666
+ importFrom: "@icore/auth-supabase",
667
+ symbol: "SupabaseAuthModule",
668
+ into: "auth"
669
+ },
670
+ appTests: [
671
+ "apps/microservices/auth/src/app/__tests__/auth.controller.supabase.integration.unit.test.ts"
672
+ ]
673
+ },
674
+ firebase: {
675
+ libDirs: ["libs/auth-strategies/firebase"],
676
+ deps: {},
677
+ tsPaths: { "@icore/auth-firebase": ["libs/auth-strategies/firebase/src/index.ts"] },
678
+ nestModule: {
679
+ importFrom: "@icore/auth-firebase",
680
+ symbol: "FirebaseAuthModule",
681
+ into: "auth"
682
+ },
683
+ appTests: [
684
+ "apps/microservices/auth/src/app/__tests__/auth.controller.firebase.integration.unit.test.ts"
685
+ ]
686
+ },
687
+ mongodb: {
688
+ libDirs: ["libs/auth-strategies/mongodb"],
689
+ deps: { mongoose: "^9.6.3" },
690
+ tsPaths: { "@icore/auth-mongodb": ["libs/auth-strategies/mongodb/src/index.ts"] },
691
+ nestModule: { importFrom: "@icore/auth-mongodb", symbol: "MongoDbAuthModule", into: "auth" }
692
+ }
693
+ },
694
+ storage: {
695
+ supabase: {
696
+ libDirs: ["libs/storage-strategies/supabase"],
697
+ deps: { "@supabase/supabase-js": "^2.106.2" },
698
+ tsPaths: { "@icore/storage-supabase": ["libs/storage-strategies/supabase/src/index.ts"] },
699
+ nestModule: {
700
+ importFrom: "@icore/storage-supabase",
701
+ symbol: "SupabaseStorageModule",
702
+ into: "upload"
703
+ }
704
+ },
705
+ firebase: {
706
+ libDirs: ["libs/storage-strategies/firebase"],
707
+ deps: {},
708
+ tsPaths: { "@icore/storage-firebase": ["libs/storage-strategies/firebase/src/index.ts"] },
709
+ nestModule: {
710
+ importFrom: "@icore/storage-firebase",
711
+ symbol: "FirebaseStorageModule",
712
+ into: "upload"
713
+ }
714
+ },
715
+ cloudinary: {
716
+ libDirs: ["libs/storage-strategies/cloudinary"],
717
+ deps: { cloudinary: "^2.10.0" },
718
+ tsPaths: { "@icore/storage-cloudinary": ["libs/storage-strategies/cloudinary/src/index.ts"] },
719
+ nestModule: {
720
+ importFrom: "@icore/storage-cloudinary",
721
+ symbol: "CloudinaryStorageModule",
722
+ into: "upload"
723
+ }
724
+ },
725
+ mongodb: {
726
+ libDirs: ["libs/storage-strategies/mongodb"],
727
+ deps: { mongoose: "^9.6.3" },
728
+ tsPaths: { "@icore/storage-mongodb": ["libs/storage-strategies/mongodb/src/index.ts"] },
729
+ nestModule: {
730
+ importFrom: "@icore/storage-mongodb",
731
+ symbol: "MongoDbStorageModule",
732
+ into: "upload"
733
+ }
734
+ }
735
+ },
736
+ db: {
737
+ supabase: {
738
+ libDirs: ["libs/db-strategies/supabase"],
739
+ deps: { "@supabase/supabase-js": "^2.106.2" },
740
+ tsPaths: { "@icore/db-supabase": ["libs/db-strategies/supabase/src/index.ts"] },
741
+ nestModule: { importFrom: "@icore/db-supabase", symbol: "SupabaseDbModule", into: "notes" }
742
+ },
743
+ firebase: {
744
+ libDirs: ["libs/db-strategies/firestore"],
745
+ deps: {},
746
+ tsPaths: { "@icore/db-firestore": ["libs/db-strategies/firestore/src/index.ts"] },
747
+ nestModule: { importFrom: "@icore/db-firestore", symbol: "FirestoreDbModule", into: "notes" }
748
+ },
749
+ mongodb: {
750
+ libDirs: ["libs/db-strategies/mongodb"],
751
+ deps: { mongoose: "^9.6.3" },
752
+ tsPaths: { "@icore/db-mongodb": ["libs/db-strategies/mongodb/src/index.ts"] },
753
+ nestModule: { importFrom: "@icore/db-mongodb", symbol: "MongoDbDbModule", into: "notes" }
754
+ }
755
+ },
756
+ feature: {
757
+ notes: {
758
+ libDirs: [
759
+ "apps/microservices/notes",
760
+ "apps/microservices/notes-e2e",
761
+ "libs/notes-client",
762
+ "libs/db-strategies",
763
+ "apps/api/src/app/notes",
764
+ "apps/client/src/components/notes",
765
+ "apps/client/src/routes/_dashboard/notes.tsx",
766
+ "apps/client/src/queries/notes.ts"
767
+ ],
768
+ deps: { "@icore/notes-client": "*", "@casl/ability": "^7.0.0" },
769
+ tsPaths: { "@icore/notes-client": ["libs/notes-client/src/index.ts"] },
770
+ gatewayModule: { importFrom: "./notes/notes.module", symbol: "NotesModule" },
771
+ gatewayService: { name: "notes", prefix: "NOTES" },
772
+ clientNav: { route: "/notes", labelKey: "nav.notes", iconName: "notes" }
773
+ },
774
+ payment: {
775
+ libDirs: [
776
+ "apps/microservices/payment",
777
+ "apps/microservices/payment-e2e",
778
+ "libs/payment-client",
779
+ "apps/api/src/app/payment"
780
+ ],
781
+ deps: { "@icore/payment-client": "*", "@idevconn/payment": "^1.2.0" },
782
+ tsPaths: { "@icore/payment-client": ["libs/payment-client/src/index.ts"] },
783
+ gatewayModule: { importFrom: "./payment/payment.module", symbol: "PaymentModule" },
784
+ gatewayService: { name: "payment", prefix: "PAYMENT" }
785
+ },
786
+ jobs: {
787
+ libDirs: [
788
+ "apps/microservices/jobs",
789
+ "libs/jobs-client",
790
+ "apps/api/src/app/admin",
791
+ "Dockerfile.ms-jobs"
792
+ ],
793
+ deps: {
794
+ "@icore/jobs-client": "*",
795
+ "@bull-board/api": "^7.1.5",
796
+ "@bull-board/express": "^7.1.5"
797
+ },
798
+ tsPaths: { "@icore/jobs-client": ["libs/jobs-client/src/index.ts"] },
799
+ gatewayModule: { importFrom: "./admin/admin.module", symbol: "AdminModule" },
800
+ dockerService: "jobs"
801
+ }
802
+ },
803
+ ui: { shadcn: EMPTY, antd: EMPTY, mui: EMPTY },
804
+ transport: { tcp: EMPTY, redis: EMPTY, nats: EMPTY, mqtt: EMPTY, rmq: EMPTY, kafka: EMPTY },
805
+ shared: {
806
+ firebaseAdmin: {
807
+ libDirs: ["libs/firebase-admin"],
808
+ deps: { "firebase-admin": "^13.10.0" },
809
+ tsPaths: { "@icore/firebase-admin": ["libs/firebase-admin/src/index.ts"] }
810
+ }
811
+ }
812
+ };
813
+
814
+ // src/manifest/wire-provider.ts
815
+ import { readFile as readFile5, writeFile as writeFile3, rm as rm2 } from "fs/promises";
776
816
  import { join as join4 } from "path";
817
+ async function writeProvider(targetDir, axis, provider) {
818
+ const nestModule = axis.section[provider]?.nestModule;
819
+ if (!nestModule) throw new Error(`provider "${provider}" has no nestModule in the manifest`);
820
+ const { importFrom, symbol } = nestModule;
821
+ const content = `import { ${symbol} } from '${importFrom}';
822
+
823
+ const ENV_PATH = '${axis.envPath}';
824
+
825
+ export const ${axis.exportConst} = ${symbol}.forRoot(ENV_PATH);
826
+ `;
827
+ await writeFile3(join4(targetDir, axis.providerFile), content);
828
+ }
829
+ async function stripJsonKeys(path, drop) {
830
+ try {
831
+ const pkg = JSON.parse(await readFile5(path, "utf8"));
832
+ for (const field of ["dependencies", "devDependencies"]) {
833
+ const deps = pkg[field];
834
+ if (!deps) continue;
835
+ for (const k of Object.keys(deps)) if (drop(k)) delete deps[k];
836
+ }
837
+ await writeFile3(path, JSON.stringify(pkg, null, 2) + "\n");
838
+ } catch {
839
+ }
840
+ }
841
+ async function stripTsconfigKeys(targetDir, aliases) {
842
+ const path = join4(targetDir, "tsconfig.base.json");
843
+ try {
844
+ const parsed = JSON.parse(await readFile5(path, "utf8"));
845
+ const paths = parsed.compilerOptions?.paths;
846
+ if (paths) for (const a of aliases) delete paths[a];
847
+ await writeFile3(path, JSON.stringify(parsed, null, 2) + "\n");
848
+ } catch {
849
+ }
850
+ }
851
+ async function cleanupUnusedAxis(targetDir, axis, chosen) {
852
+ for (const provider of Object.keys(axis.section)) {
853
+ if (provider === chosen) continue;
854
+ const unit = axis.section[provider];
855
+ for (const dir of unit.libDirs)
856
+ await rm2(join4(targetDir, dir), { recursive: true, force: true });
857
+ for (const t of unit.appTests ?? []) await rm2(join4(targetDir, t), { force: true });
858
+ const dropKeys = /* @__PURE__ */ new Set([...Object.keys(unit.tsPaths), ...Object.keys(unit.deps)]);
859
+ await stripJsonKeys(join4(targetDir, axis.msPackageJson), (k) => dropKeys.has(k));
860
+ await stripTsconfigKeys(targetDir, Object.keys(unit.tsPaths));
861
+ }
862
+ }
863
+
864
+ // src/manifest/wire-features.ts
865
+ var FEATURES_MODULE = "apps/api/src/app/features.module.ts";
866
+ var GATEWAY_SERVICES = "apps/api/src/app/gateway-services.ts";
867
+ var API_PKG = "apps/api/package.json";
868
+ var FEATURES = MANIFEST.feature;
869
+ function selectedFeatures(opts) {
870
+ const out = [];
871
+ if (opts.example === "notes") out.push("notes");
872
+ if (opts.payment !== "none") out.push("payment");
873
+ if (opts.jobs !== "none") out.push("jobs");
874
+ return out;
875
+ }
876
+ async function writeFeaturesWiring(targetDir, opts) {
877
+ const chosen = selectedFeatures(opts);
878
+ const mods = chosen.map((k) => FEATURES[k].gatewayModule).filter((m) => !!m);
879
+ const imports = mods.map((m) => `import { ${m.symbol} } from '${m.importFrom}';`).join("\n");
880
+ const symbols = mods.map((m) => m.symbol).join(", ");
881
+ const featuresModule = `import { Module } from '@nestjs/common';
882
+ ` + (imports ? imports + "\n" : "") + `
883
+ @Module({
884
+ imports: [${symbols}],
885
+ })
886
+ export class FeaturesModule {}
887
+ `;
888
+ await writeFile4(join5(targetDir, FEATURES_MODULE), featuresModule);
889
+ const services = [];
890
+ if (opts.authProvider !== "none") services.push({ name: "auth", prefix: "AUTH" });
891
+ if (opts.upload !== "none") services.push({ name: "upload", prefix: "UPLOAD" });
892
+ for (const k of chosen) {
893
+ const svc = FEATURES[k].gatewayService;
894
+ if (svc) services.push(svc);
895
+ }
896
+ const entries = services.map((s) => ` { name: '${s.name}', prefix: '${s.prefix}' },`).join("\n");
897
+ const gatewayServices = `/** Microservices the gateway proxies. Generated by create-icore. */
898
+ export const GATEWAY_SERVICES = [
899
+ ${entries}
900
+ ];
901
+ `;
902
+ await writeFile4(join5(targetDir, GATEWAY_SERVICES), gatewayServices);
903
+ }
904
+ async function stripJobsDockerCompose(targetDir) {
905
+ const composePath = join5(targetDir, "docker-compose.yml");
906
+ try {
907
+ const compose = await readFile6(composePath, "utf8");
908
+ const next = compose.replace(/\n {2}jobs:[\s\S]+?(?=\n {2}\w+:|\nnetworks:)/m, "\n").replace(/\n {6}jobs:\n {8}condition: service_started/g, "").replace(/\n {6}JOBS_REDIS_URL:[^\n]*/g, "");
909
+ await writeFile4(composePath, next);
910
+ } catch {
911
+ }
912
+ }
913
+ async function cleanupUnusedFeatures(targetDir, opts) {
914
+ const chosen = new Set(selectedFeatures(opts));
915
+ for (const key of ["notes", "payment", "jobs"]) {
916
+ if (chosen.has(key)) continue;
917
+ const unit = FEATURES[key];
918
+ for (const dir of unit.libDirs)
919
+ await rm3(join5(targetDir, dir), { recursive: true, force: true });
920
+ const dropKeys = /* @__PURE__ */ new Set([...Object.keys(unit.tsPaths), ...Object.keys(unit.deps)]);
921
+ await stripJsonKeys(join5(targetDir, API_PKG), (k) => dropKeys.has(k));
922
+ await stripTsconfigKeys(targetDir, Object.keys(unit.tsPaths));
923
+ if (unit.gatewayService) await stripGatewayTransport(targetDir, unit.gatewayService.prefix);
924
+ if (unit.dockerService === "jobs") await stripJobsDockerCompose(targetDir);
925
+ }
926
+ }
927
+
928
+ // src/manifest/wire-client.ts
929
+ import { writeFile as writeFile5 } from "fs/promises";
930
+ import { join as join6 } from "path";
931
+ var NAV_CONFIG_FILE = "apps/client/src/nav.config.ts";
932
+ var BASE_NAV = [
933
+ { to: "/dashboard", labelKey: "nav.dashboard", iconName: "dashboard", exact: true }
934
+ ];
935
+ var PROFILE_NAV = { to: "/profile", labelKey: "nav.profile", iconName: "profile" };
936
+ function renderEntry(n) {
937
+ const parts = [`to: '${n.to}'`, `labelKey: '${n.labelKey}'`, `iconName: '${n.iconName}'`];
938
+ if (n.exact) parts.push("exact: true");
939
+ return ` { ${parts.join(", ")} },`;
940
+ }
941
+ async function writeNavConfig(targetDir, opts) {
942
+ const entries = [...BASE_NAV];
943
+ const notesNav = MANIFEST.feature.notes.clientNav;
944
+ if (opts.example === "notes" && notesNav) {
945
+ entries.push({
946
+ to: notesNav.route,
947
+ labelKey: notesNav.labelKey,
948
+ iconName: notesNav.iconName,
949
+ exact: notesNav.exact
950
+ });
951
+ }
952
+ entries.push(PROFILE_NAV);
953
+ const content = `/**
954
+ * Sidebar navigation. UI-agnostic: each LayoutSider maps \`iconName\` to its own
955
+ * icon library. Generated by create-icore.
956
+ */
957
+ export interface NavItem {
958
+ to: string;
959
+ labelKey: string;
960
+ iconName: 'dashboard' | 'notes' | 'profile';
961
+ exact?: boolean;
962
+ }
963
+
964
+ export const NAV_CONFIG: NavItem[] = [
965
+ ` + entries.map(renderEntry).join("\n") + `
966
+ ];
967
+ `;
968
+ await writeFile5(join6(targetDir, NAV_CONFIG_FILE), content);
969
+ }
970
+
971
+ // src/manifest/blueprint.ts
972
+ import { writeFile as writeFile6 } from "fs/promises";
973
+ import { join as join7 } from "path";
974
+ async function writeBlueprintJson(targetDir, opts) {
975
+ const blueprint = {
976
+ schemaVersion: 1,
977
+ projectName: opts.projectName,
978
+ authProvider: opts.authProvider,
979
+ dbProvider: opts.dbProvider,
980
+ upload: opts.upload,
981
+ payment: opts.payment,
982
+ jobs: opts.jobs,
983
+ example: opts.example,
984
+ ui: opts.ui,
985
+ transport: opts.transport,
986
+ packageManager: opts.packageManager
987
+ };
988
+ await writeFile6(join7(targetDir, "blueprint.json"), JSON.stringify(blueprint, null, 2) + "\n");
989
+ }
990
+ async function writeJson(targetDir, rel, data) {
991
+ await writeFile6(join7(targetDir, rel, "blueprint.json"), JSON.stringify(data, null, 2) + "\n");
992
+ }
993
+ async function writeServiceBlueprints(targetDir, opts) {
994
+ const t = opts.transport;
995
+ if (opts.authProvider !== "none") {
996
+ await writeJson(targetDir, "apps/microservices/auth", {
997
+ schemaVersion: 1,
998
+ service: "auth",
999
+ authProvider: opts.authProvider,
1000
+ transport: t
1001
+ });
1002
+ }
1003
+ if (opts.upload !== "none") {
1004
+ await writeJson(targetDir, "apps/microservices/upload", {
1005
+ schemaVersion: 1,
1006
+ service: "upload",
1007
+ storageProvider: opts.upload,
1008
+ transport: t
1009
+ });
1010
+ }
1011
+ if (opts.example !== "none") {
1012
+ await writeJson(targetDir, "apps/microservices/notes", {
1013
+ schemaVersion: 1,
1014
+ service: "notes",
1015
+ dbProvider: opts.dbProvider,
1016
+ transport: t
1017
+ });
1018
+ }
1019
+ if (opts.payment !== "none") {
1020
+ await writeJson(targetDir, "apps/microservices/payment", {
1021
+ schemaVersion: 1,
1022
+ service: "payment",
1023
+ paymentProvider: opts.payment,
1024
+ transport: t
1025
+ });
1026
+ }
1027
+ if (opts.jobs !== "none") {
1028
+ await writeJson(targetDir, "apps/microservices/jobs", {
1029
+ schemaVersion: 1,
1030
+ service: "jobs",
1031
+ jobsProvider: opts.jobs
1032
+ });
1033
+ }
1034
+ const features = [];
1035
+ if (opts.example !== "none") features.push("notes");
1036
+ if (opts.payment !== "none") features.push("payment");
1037
+ if (opts.jobs !== "none") features.push("jobs");
1038
+ await writeJson(targetDir, "apps/api", {
1039
+ schemaVersion: 1,
1040
+ service: "api",
1041
+ features,
1042
+ transport: t
1043
+ });
1044
+ await writeJson(targetDir, "apps/client", {
1045
+ schemaVersion: 1,
1046
+ service: "client",
1047
+ ui: opts.ui
1048
+ });
1049
+ }
1050
+
1051
+ // src/manifest/wire-auth.ts
1052
+ var AUTH = {
1053
+ section: MANIFEST.auth,
1054
+ providerFile: "apps/microservices/auth/src/app/auth.provider.ts",
1055
+ exportConst: "AuthProviderModule",
1056
+ msPackageJson: "apps/microservices/auth/package.json",
1057
+ envPath: "apps/microservices/auth/.env"
1058
+ };
1059
+ var writeAuthProvider = (targetDir, provider) => writeProvider(targetDir, AUTH, provider);
1060
+ var cleanupUnusedAuth = (targetDir, chosen) => cleanupUnusedAxis(targetDir, AUTH, chosen);
1061
+
1062
+ // src/manifest/wire-storage.ts
1063
+ var STORAGE = {
1064
+ section: MANIFEST.storage,
1065
+ providerFile: "apps/microservices/upload/src/app/storage.provider.ts",
1066
+ exportConst: "StorageProviderModule",
1067
+ msPackageJson: "apps/microservices/upload/package.json",
1068
+ envPath: "apps/microservices/upload/.env"
1069
+ };
1070
+ var writeStorageProvider = (targetDir, provider) => writeProvider(targetDir, STORAGE, provider);
1071
+ var cleanupUnusedStorage = (targetDir, chosen) => cleanupUnusedAxis(targetDir, STORAGE, chosen);
1072
+
1073
+ // src/manifest/wire-db.ts
1074
+ var DB = {
1075
+ section: MANIFEST.db,
1076
+ providerFile: "apps/microservices/notes/src/app/db.provider.ts",
1077
+ exportConst: "DbProviderModule",
1078
+ msPackageJson: "apps/microservices/notes/package.json",
1079
+ envPath: "apps/microservices/notes/.env"
1080
+ };
1081
+ var writeDbProvider = (targetDir, provider) => writeProvider(targetDir, DB, provider);
1082
+ var cleanupUnusedDb = (targetDir, chosen) => cleanupUnusedAxis(targetDir, DB, chosen);
1083
+
1084
+ // src/lib/scaffold-pkg.ts
1085
+ import { readFile as readFile7, writeFile as writeFile7, mkdir, readdir } from "fs/promises";
1086
+ import { join as join8 } from "path";
777
1087
 
778
1088
  // src/lib/options.ts
779
1089
  function pmRun(pm, script) {
@@ -782,8 +1092,8 @@ function pmRun(pm, script) {
782
1092
 
783
1093
  // src/lib/scaffold-pkg.ts
784
1094
  async function writePnpmWorkspace(targetDir) {
785
- const pkgPath = join4(targetDir, "package.json");
786
- const pkg = JSON.parse(await readFile4(pkgPath, "utf8"));
1095
+ const pkgPath = join8(targetDir, "package.json");
1096
+ const pkg = JSON.parse(await readFile7(pkgPath, "utf8"));
787
1097
  const workspaces = pkg.workspaces ?? [];
788
1098
  const packagesBlock = workspaces.map((p3) => ` - '${p3}'`).join("\n");
789
1099
  const allowBuilds = [
@@ -792,7 +1102,9 @@ async function writePnpmWorkspace(targetDir) {
792
1102
  "@parcel/watcher",
793
1103
  "@scarf/scarf",
794
1104
  "@swc/core",
1105
+ "bcrypt",
795
1106
  "less",
1107
+ "mongodb-memory-server",
796
1108
  "msgpackr-extract",
797
1109
  "nx",
798
1110
  "protobufjs",
@@ -804,7 +1116,7 @@ ${packagesBlock}
804
1116
  allowBuilds:
805
1117
  ${allowBuilds}
806
1118
  `;
807
- await writeFile3(join4(targetDir, "pnpm-workspace.yaml"), content);
1119
+ await writeFile7(join8(targetDir, "pnpm-workspace.yaml"), content);
808
1120
  }
809
1121
  async function rewritePnpmWorkspaceDeps(targetDir) {
810
1122
  async function walk(dir) {
@@ -817,9 +1129,9 @@ async function rewritePnpmWorkspaceDeps(targetDir) {
817
1129
  }
818
1130
  for (const e of entries) {
819
1131
  if (e.isDirectory() && e.name !== "node_modules") {
820
- found.push(...await walk(join4(dir, e.name)));
1132
+ found.push(...await walk(join8(dir, e.name)));
821
1133
  } else if (e.isFile() && e.name === "package.json") {
822
- found.push(join4(dir, e.name));
1134
+ found.push(join8(dir, e.name));
823
1135
  }
824
1136
  }
825
1137
  return found;
@@ -827,17 +1139,17 @@ async function rewritePnpmWorkspaceDeps(targetDir) {
827
1139
  const pkgFiles = await walk(targetDir);
828
1140
  for (const f of pkgFiles) {
829
1141
  try {
830
- const raw = await readFile4(f, "utf8");
1142
+ const raw = await readFile7(f, "utf8");
831
1143
  const next = raw.replace(/"(@icore\/[^"]+)":\s*"\*"/g, '"$1": "workspace:*"');
832
- if (next !== raw) await writeFile3(f, next);
1144
+ if (next !== raw) await writeFile7(f, next);
833
1145
  } catch {
834
1146
  }
835
1147
  }
836
1148
  }
837
1149
  async function patchGitignoreForPm(targetDir, pm) {
838
- const giPath = join4(targetDir, ".gitignore");
1150
+ const giPath = join8(targetDir, ".gitignore");
839
1151
  try {
840
- let src = await readFile4(giPath, "utf8");
1152
+ let src = await readFile7(giPath, "utf8");
841
1153
  src = src.replace(/^# Build artifacts.*\ntools\/create-icore\/templates\/\s*\n/m, "");
842
1154
  if (pm !== "yarn") {
843
1155
  src = src.replace(/^\.yarn\/\*\s*\n/m, "").replace(/^!\.yarn\/patches\s*\n/m, "").replace(/^!\.yarn\/plugins\s*\n/m, "").replace(/^!\.yarn\/releases\s*\n/m, "").replace(/^!\.yarn\/sdks\s*\n/m, "").replace(/^!\.yarn\/versions\s*\n/m, "").replace(/^\.pnp\.\*\s*\n/m, "");
@@ -852,7 +1164,7 @@ async function patchGitignoreForPm(targetDir, pm) {
852
1164
  src += "\n# npm\nnpm-debug.log*\n";
853
1165
  }
854
1166
  }
855
- await writeFile3(giPath, src);
1167
+ await writeFile7(giPath, src);
856
1168
  } catch {
857
1169
  }
858
1170
  }
@@ -867,7 +1179,7 @@ async function writeAiFiles(targetDir, opts) {
867
1179
  if (opts.jobs !== "none") activeMSes.push(`jobs (standalone)`);
868
1180
  const usesSupabase = opts.authProvider === "supabase" || opts.dbProvider === "supabase" || opts.upload === "supabase";
869
1181
  const usesFirebase = opts.authProvider === "firebase" || opts.dbProvider === "firebase" || opts.upload === "firebase";
870
- await writeFile3(join4(targetDir, "CLAUDE.md"), "@AGENTS.md\n");
1182
+ await writeFile7(join8(targetDir, "CLAUDE.md"), "@AGENTS.md\n");
871
1183
  const uiLabel = { shadcn: "shadcn/ui + Tailwind", antd: "Ant Design 6", mui: "MUI 6" }[opts.ui];
872
1184
  const readme = `# ${opts.projectName}
873
1185
 
@@ -917,7 +1229,7 @@ ${pm === "yarn" ? "yarn remove-notes" : pm === "pnpm" ? "pnpm remove-notes" : "n
917
1229
 
918
1230
  Apache-2.0
919
1231
  `;
920
- await writeFile3(join4(targetDir, "README.md"), readme);
1232
+ await writeFile7(join8(targetDir, "README.md"), readme);
921
1233
  const agents = `# ${opts.projectName} \u2014 Agent Instructions
922
1234
 
923
1235
  ## Stack snapshot
@@ -998,8 +1310,8 @@ ${opts.upload !== "none" ? `| \`apps/microservices/upload/.env\` | \`STORAGE_PRO
998
1310
  - Test behaviour, not implementation. Fake strategies from \`@icore/shared\` (FakeAuthStrategy etc.) serve as test doubles.
999
1311
  - Run: \`${nx} test <project>\`
1000
1312
  `;
1001
- await writeFile3(join4(targetDir, "AGENTS.md"), agents);
1002
- await mkdir(join4(targetDir, ".claude"), { recursive: true });
1313
+ await writeFile7(join8(targetDir, "AGENTS.md"), agents);
1314
+ await mkdir(join8(targetDir, ".claude"), { recursive: true });
1003
1315
  const mcpServers = {
1004
1316
  nx: {
1005
1317
  command: "npx",
@@ -1040,8 +1352,8 @@ ${opts.upload !== "none" ? `| \`apps/microservices/upload/.env\` | \`STORAGE_PRO
1040
1352
  ]
1041
1353
  }
1042
1354
  };
1043
- await writeFile3(
1044
- join4(targetDir, ".claude", "settings.json"),
1355
+ await writeFile7(
1356
+ join8(targetDir, ".claude", "settings.json"),
1045
1357
  JSON.stringify(settings, null, 2) + "\n"
1046
1358
  );
1047
1359
  }
@@ -1065,16 +1377,16 @@ async function copyTree(src, dest) {
1065
1377
  const entries = await readdir2(src, { withFileTypes: true });
1066
1378
  for (const entry of entries) {
1067
1379
  if (IGNORE_TOP.has(entry.name)) continue;
1068
- const s = join5(src, entry.name);
1069
- const d = join5(dest, entry.name);
1380
+ const s = join9(src, entry.name);
1381
+ const d = join9(dest, entry.name);
1070
1382
  if (entry.isDirectory()) await copyTree(s, d);
1071
1383
  else if (entry.isFile()) await copyFile(s, d);
1072
1384
  }
1073
1385
  }
1074
1386
  async function selectClientTemplate(targetDir, opts) {
1075
- const templatesRoot = join5(targetDir, "apps/templates");
1076
- const chosen = join5(templatesRoot, `client-${opts.ui}`);
1077
- const destClient = join5(targetDir, "apps/client");
1387
+ const templatesRoot = join9(targetDir, "apps/templates");
1388
+ const chosen = join9(templatesRoot, `client-${opts.ui}`);
1389
+ const destClient = join9(targetDir, "apps/client");
1078
1390
  let chosenUi = opts.ui;
1079
1391
  try {
1080
1392
  const s = await stat(chosen);
@@ -1082,9 +1394,9 @@ async function selectClientTemplate(targetDir, opts) {
1082
1394
  await copyTree(chosen, destClient);
1083
1395
  } catch {
1084
1396
  chosenUi = "shadcn";
1085
- await copyTree(join5(templatesRoot, "client-shadcn"), destClient);
1397
+ await copyTree(join9(templatesRoot, "client-shadcn"), destClient);
1086
1398
  }
1087
- await rm2(templatesRoot, { recursive: true, force: true });
1399
+ await rm4(templatesRoot, { recursive: true, force: true });
1088
1400
  await rewriteClientPaths(destClient, chosenUi);
1089
1401
  }
1090
1402
  async function rewriteClientPaths(clientDir, ui) {
@@ -1097,11 +1409,11 @@ async function rewriteClientPaths(clientDir, ui) {
1097
1409
  "eslint.config.mjs"
1098
1410
  ];
1099
1411
  for (const rel of candidates) {
1100
- const path = join5(clientDir, rel);
1412
+ const path = join9(clientDir, rel);
1101
1413
  try {
1102
- const raw = await readFile5(path, "utf8");
1414
+ const raw = await readFile8(path, "utf8");
1103
1415
  const next = raw.replaceAll("../../../", "../../").replaceAll(`apps/templates/client-${ui}`, "apps/client").replaceAll(`client-${ui}`, "client");
1104
- if (next !== raw) await writeFile4(path, next);
1416
+ if (next !== raw) await writeFile8(path, next);
1105
1417
  } catch {
1106
1418
  }
1107
1419
  }
@@ -1117,12 +1429,12 @@ function gitInit(cwd, projectName) {
1117
1429
  }
1118
1430
  function resolveYarnBin(cwd) {
1119
1431
  try {
1120
- const yarnrc = readFileSync(join5(cwd, ".yarnrc.yml"), "utf8");
1432
+ const yarnrc = readFileSync(join9(cwd, ".yarnrc.yml"), "utf8");
1121
1433
  const match = yarnrc.match(/^yarnPath:\s*(.+)$/m);
1122
- if (match?.[1]) return join5(cwd, match[1].trim());
1434
+ if (match?.[1]) return join9(cwd, match[1].trim());
1123
1435
  } catch {
1124
1436
  }
1125
- return join5(cwd, ".yarn", "releases", "yarn-4.5.0.cjs");
1437
+ return join9(cwd, ".yarn", "releases", "yarn-4.5.0.cjs");
1126
1438
  }
1127
1439
  function runInstall(cwd, pm) {
1128
1440
  if (pm === "yarn") {
@@ -1136,7 +1448,7 @@ function runInstall(cwd, pm) {
1136
1448
  async function scaffold(opts, templatesDir2) {
1137
1449
  await copyTree(templatesDir2, opts.targetDir);
1138
1450
  await rewriteRootPackageJson(opts.targetDir, opts);
1139
- await writeAuthEnv(opts.targetDir, opts);
1451
+ if (opts.authProvider !== "none") await writeAuthEnv(opts.targetDir, opts);
1140
1452
  await writeUploadEnv(opts.targetDir, opts);
1141
1453
  await writeNotesEnv(opts.targetDir, opts);
1142
1454
  await writePaymentEnv(opts.targetDir, opts);
@@ -1145,19 +1457,33 @@ async function scaffold(opts, templatesDir2) {
1145
1457
  await selectClientTemplate(opts.targetDir, opts);
1146
1458
  await writeClientEnv(opts.targetDir);
1147
1459
  if (opts.upload === "none") await removeUploadStack(opts.targetDir);
1148
- if (opts.payment === "none") await removePaymentStack(opts.targetDir);
1149
- if (opts.jobs === "none") await removeJobsStack(opts.targetDir);
1150
- if (opts.example === "none") await removeNotesStack(opts.targetDir);
1151
- await removeUnusedAuthStrategies(opts.targetDir, opts.authProvider);
1152
- await removeUnusedStorageStrategies(opts.targetDir, opts.upload);
1153
- await removeUnusedDbStrategies(opts.targetDir, opts.dbProvider);
1460
+ await cleanupUnusedFeatures(opts.targetDir, opts);
1461
+ await writeFeaturesWiring(opts.targetDir, opts);
1462
+ await writeNavConfig(opts.targetDir, opts);
1463
+ if (opts.authProvider !== "none") {
1464
+ await cleanupUnusedAuth(opts.targetDir, opts.authProvider);
1465
+ await writeAuthProvider(opts.targetDir, opts.authProvider);
1466
+ } else {
1467
+ await removeAuthStack(opts.targetDir);
1468
+ }
1469
+ if (opts.upload !== "none") {
1470
+ await cleanupUnusedStorage(opts.targetDir, opts.upload);
1471
+ await writeStorageProvider(opts.targetDir, opts.upload);
1472
+ }
1154
1473
  const firebaseUsed = opts.authProvider === "firebase" || opts.dbProvider === "firebase" || opts.upload === "firebase";
1155
1474
  if (!firebaseUsed) await removeFirebaseAdminLib(opts.targetDir);
1475
+ await cleanupUnusedDb(opts.targetDir, opts.dbProvider);
1476
+ if (opts.dbProvider !== "none" && opts.example !== "none") {
1477
+ await writeDbProvider(opts.targetDir, opts.dbProvider);
1478
+ }
1479
+ await pruneRootProviderDeps(opts.targetDir, opts);
1480
+ await writeBlueprintJson(opts.targetDir, opts);
1481
+ await writeServiceBlueprints(opts.targetDir, opts);
1156
1482
  if (opts.packageManager === "yarn") {
1157
- await writeFile4(join5(opts.targetDir, "yarn.lock"), "");
1483
+ await writeFile8(join9(opts.targetDir, "yarn.lock"), "");
1158
1484
  } else {
1159
- await rm2(join5(opts.targetDir, ".yarn"), { recursive: true, force: true });
1160
- await rm2(join5(opts.targetDir, ".yarnrc.yml"), { force: true });
1485
+ await rm4(join9(opts.targetDir, ".yarn"), { recursive: true, force: true });
1486
+ await rm4(join9(opts.targetDir, ".yarnrc.yml"), { force: true });
1161
1487
  }
1162
1488
  if (opts.packageManager === "pnpm") {
1163
1489
  await writePnpmWorkspace(opts.targetDir);