@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/index.js CHANGED
@@ -4,9 +4,9 @@ function pmRun(pm, script) {
4
4
  }
5
5
 
6
6
  // src/lib/scaffold.ts
7
- import { copyFile, mkdir as mkdir2, readdir as readdir2, readFile as readFile4, stat, writeFile as writeFile4, rm as rm2 } from "fs/promises";
7
+ import { copyFile, mkdir as mkdir2, readdir as readdir2, readFile as readFile6, stat, writeFile as writeFile8, rm as rm4 } from "fs/promises";
8
8
  import { readFileSync } from "fs";
9
- import { join as join4 } from "path";
9
+ import { join as join8 } from "path";
10
10
  import { spawnSync } from "child_process";
11
11
 
12
12
  // src/lib/scaffold-env.ts
@@ -47,6 +47,31 @@ async function stripGatewayTransport(targetDir, prefix) {
47
47
  } catch {
48
48
  }
49
49
  }
50
+ var ROOT_PROVIDER_SDKS = {
51
+ supabase: ["@supabase/supabase-js"],
52
+ cloudinary: ["cloudinary"],
53
+ mongodb: ["mongoose"],
54
+ firebase: ["firebase-admin"]
55
+ };
56
+ async function pruneRootProviderDeps(targetDir, opts) {
57
+ const chosen = /* @__PURE__ */ new Set([opts.authProvider, opts.dbProvider, opts.upload]);
58
+ const drop = /* @__PURE__ */ new Set();
59
+ for (const [provider, sdks] of Object.entries(ROOT_PROVIDER_SDKS)) {
60
+ if (!chosen.has(provider)) for (const sdk of sdks) drop.add(sdk);
61
+ }
62
+ if (drop.size === 0) return;
63
+ const pkgPath = join(targetDir, "package.json");
64
+ try {
65
+ const pkg = JSON.parse(await readFile(pkgPath, "utf8"));
66
+ for (const field of ["dependencies", "devDependencies"]) {
67
+ const deps = pkg[field];
68
+ if (!deps) continue;
69
+ for (const sdk of drop) delete deps[sdk];
70
+ }
71
+ await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
72
+ } catch {
73
+ }
74
+ }
50
75
  async function rewriteRootPackageJson(targetDir, opts) {
51
76
  const pkgPath = join(targetDir, "package.json");
52
77
  const raw = await readFile(pkgPath, "utf8");
@@ -64,6 +89,11 @@ async function rewriteRootPackageJson(targetDir, opts) {
64
89
  const deps = pkg["dependencies"] ??= {};
65
90
  Object.assign(deps, MONGODB_DEPS);
66
91
  }
92
+ if (opts.authProvider === "mongodb") {
93
+ const devDeps = pkg["devDependencies"] ??= {};
94
+ devDeps["@types/bcrypt"] = "^6.0.0";
95
+ devDeps["@types/jsonwebtoken"] = "^9.0.10";
96
+ }
67
97
  if (opts.packageManager !== "yarn") {
68
98
  delete pkg.packageManager;
69
99
  } else {
@@ -186,326 +216,75 @@ async function stripTsconfigPath(targetDir, alias) {
186
216
  } catch {
187
217
  }
188
218
  }
189
- async function removeJobsStack(targetDir) {
190
- const paths = [
191
- "apps/microservices/jobs",
192
- "libs/jobs-client",
193
- "apps/api/src/app/admin",
194
- "Dockerfile.ms-jobs"
195
- ];
196
- for (const p2 of paths) {
197
- await rm(join2(targetDir, p2), { recursive: true, force: true });
198
- }
199
- const appModulePath = join2(targetDir, "apps/api/src/app/app.module.ts");
200
- try {
201
- const appModule = await readFile2(appModulePath, "utf8");
202
- const next = appModule.replace(/^import \{ AdminModule \} from '\.\/admin\/admin\.module';\n/m, "").replace(/,\s*AdminModule/g, "");
203
- await writeFile2(appModulePath, next);
204
- } catch {
205
- }
206
- await stripDeps(join2(targetDir, "apps/api/package.json"), [
207
- "@icore/jobs-client",
208
- "@bull-board/api",
209
- "@bull-board/express"
219
+ async function removeFirebaseAdminLib(targetDir) {
220
+ await rm(join2(targetDir, "libs/firebase-admin"), { recursive: true, force: true });
221
+ await stripTsconfigPath(targetDir, "@icore/firebase-admin");
222
+ await stripDeps(join2(targetDir, "apps/microservices/auth/package.json"), [
223
+ "@icore/firebase-admin"
210
224
  ]);
211
- const composePath = join2(targetDir, "docker-compose.yml");
212
- try {
213
- const compose = await readFile2(composePath, "utf8");
214
- 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, "");
215
- await writeFile2(composePath, next);
216
- } catch {
217
- }
218
- }
219
- async function removePaymentStack(targetDir) {
220
- const paths = [
221
- "apps/microservices/payment",
222
- "apps/microservices/payment-e2e",
223
- "libs/payment-client",
224
- "apps/api/src/app/payment"
225
- ];
226
- for (const p2 of paths) {
227
- await rm(join2(targetDir, p2), { recursive: true, force: true });
228
- }
229
- const appModulePath = join2(targetDir, "apps/api/src/app/app.module.ts");
230
- try {
231
- const appModule = await readFile2(appModulePath, "utf8");
232
- const next = appModule.replace(/^import \{ PaymentModule \} from '\.\/payment\/payment\.module';\n/m, "").replace(/,\s*PaymentModule/g, "");
233
- await writeFile2(appModulePath, next);
234
- } catch {
235
- }
236
- await stripDeps(join2(targetDir, "apps/api/package.json"), [
237
- "@icore/payment-client",
238
- "@idevconn/payment"
225
+ await stripDeps(join2(targetDir, "apps/microservices/upload/package.json"), [
226
+ "@icore/firebase-admin"
227
+ ]);
228
+ await stripDeps(join2(targetDir, "apps/microservices/notes/package.json"), [
229
+ "@icore/firebase-admin"
239
230
  ]);
240
- await stripGatewayTransport(targetDir, "PAYMENT");
241
- const mainTsPath = join2(targetDir, "apps/api/src/main.ts");
242
- try {
243
- const src = await readFile2(mainTsPath, "utf8");
244
- const next = src.replace(/\n\s*\{ name: 'payment', prefix: 'PAYMENT' \},/, "");
245
- await writeFile2(mainTsPath, next);
246
- } catch {
247
- }
248
231
  }
249
- async function removeNotesStack(targetDir) {
250
- for (const p2 of [
251
- "apps/microservices/notes",
252
- "apps/microservices/notes-e2e",
253
- "libs/notes-client",
254
- "apps/api/src/app/notes",
255
- "apps/client/src/components/notes"
256
- ]) {
232
+ async function removeAuthStack(targetDir) {
233
+ const rmPaths = [
234
+ "apps/microservices/auth",
235
+ "libs/auth-strategies",
236
+ "libs/auth-client",
237
+ "Dockerfile.ms-auth",
238
+ "apps/api/src/app/auth",
239
+ "apps/api/src/app/profile",
240
+ "apps/api/src/app/abilities",
241
+ "apps/client/src/components/auth",
242
+ "apps/client/src/routes/login.tsx",
243
+ "apps/client/src/routes/auth.callback.tsx",
244
+ "apps/client/src/routes/auth.oauth.callback.tsx",
245
+ "apps/client/src/routes/_dashboard/profile.tsx"
246
+ ];
247
+ for (const p2 of rmPaths) {
257
248
  await rm(join2(targetDir, p2), { recursive: true, force: true });
258
249
  }
259
- await rm(join2(targetDir, "apps/client/src/routes/_dashboard/notes.tsx"), { force: true });
260
- await rm(join2(targetDir, "apps/client/src/queries/notes.ts"), { force: true });
261
250
  const appModulePath = join2(targetDir, "apps/api/src/app/app.module.ts");
262
251
  try {
263
252
  const src = await readFile2(appModulePath, "utf8");
264
- const next = src.replace(/^import \{ NotesModule \} from '\.\/notes\/notes\.module';\n/m, "").replace(/,\s*NotesModule/g, "");
253
+ 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, "");
265
254
  await writeFile2(appModulePath, next);
266
255
  } catch {
267
256
  }
268
- await stripDeps(join2(targetDir, "apps/api/package.json"), [
269
- "@icore/notes-client",
270
- "@casl/ability"
271
- ]);
272
- await stripGatewayTransport(targetDir, "NOTES");
273
- const mainTsPath = join2(targetDir, "apps/api/src/main.ts");
257
+ const dashboardPath = join2(targetDir, "apps/client/src/routes/_dashboard.tsx");
274
258
  try {
275
- const src = await readFile2(mainTsPath, "utf8");
276
- const next = src.replace(/\n\s*\{ name: 'notes', prefix: 'NOTES' \},/, "");
277
- await writeFile2(mainTsPath, next);
259
+ const src = await readFile2(dashboardPath, "utf8");
260
+ const next = src.replace(/^import \{ useAuthStore \} from '@icore\/template-shared';\n/m, "").replace(/, redirect/g, "").replace(/\n {2}beforeLoad: \(\) => \{[\s\S]*?\n {2}\},/m, "");
261
+ await writeFile2(dashboardPath, next);
278
262
  } catch {
279
263
  }
280
- const tsconfigPath = join2(targetDir, "tsconfig.base.json");
281
- try {
282
- const src = await readFile2(tsconfigPath, "utf8");
283
- const next = src.replace(/^\s*"@icore\/notes-client": \[[^\]]*\],?\n/m, "");
284
- await writeFile2(tsconfigPath, next);
285
- } catch {
286
- }
287
- const siderPath = join2(targetDir, "apps/client/src/components/layout/LayoutSider.tsx");
288
- try {
289
- const src = await readFile2(siderPath, "utf8");
290
- const next = src.replace(", StickyNote", "").replace(/\n {8}<Link\n {10}to="\/(?:_dashboard\/)?notes"[\s\S]*?<\/Link>/, "").replace(", FileTextOutlined", "").replace(
291
- "const selectedKey = pathname.includes('/notes')\n ? 'notes'\n : pathname.includes('/profile')",
292
- "const selectedKey = pathname.includes('/profile')"
293
- ).replace(
294
- /\n {4}\{\n {6}key: 'notes',\n {6}icon: <FileTextOutlined \/>,\n {6}label: <Link to="\/(?:_dashboard\/)?notes">\{t\('notes\.title'\)\}<\/Link>,\n {4}\},/,
295
- ""
296
- ).replace("import NoteOutlinedIcon from '@mui/icons-material/NoteOutlined';\n", "").replace(
297
- /\n {8}<ListItemButton\n {10}component=\{Link\}\n {10}to="\/(?:_dashboard\/)?notes"[\s\S]*?<\/ListItemButton>/,
298
- ""
299
- ).replace(/\n\s*<Link to="\/(?:_dashboard\/)?notes">[\s\S]*?<\/Link>/m, "");
300
- await writeFile2(siderPath, next);
301
- } catch {
264
+ for (const alias of [
265
+ "@icore/auth-client",
266
+ "@icore/auth-supabase",
267
+ "@icore/auth-firebase",
268
+ "@icore/auth-mongodb"
269
+ ]) {
270
+ await stripTsconfigPath(targetDir, alias);
302
271
  }
303
- const keysPath = join2(targetDir, "libs/template-shared/src/lib/i18n/keys.ts");
272
+ await stripDeps(join2(targetDir, "apps/api/package.json"), ["@icore/auth-client"]);
273
+ const gatewayEnv = join2(targetDir, "apps/api/.env");
304
274
  try {
305
- const src = await readFile2(keysPath, "utf8");
306
- const next = src.replace(/^\s{4}notes: \{\n(?:\s+.*\n)*?\s{4}\},\n/m, "");
307
- await writeFile2(keysPath, next);
275
+ const env = await readFile2(gatewayEnv, "utf8");
276
+ const next = env.split("\n").filter((line) => !line.startsWith("AUTH_") && !line.startsWith("# AUTH_")).join("\n");
277
+ await writeFile2(gatewayEnv, next);
308
278
  } catch {
309
279
  }
310
- }
311
- async function removeUnusedAuthStrategies(targetDir, authProvider) {
312
- const modulePath = join2(targetDir, "apps/microservices/auth/src/app/app.module.ts");
313
- const AUTH_BRANCH = /if \(provider === 'supabase'\) return makeSupabaseAuth\(cfg\);\n\s*if \(provider === 'mongodb'\) return makeMongoDbAuth\(connection, cfg\);\n\s*return makeFirebaseAuth\(cfg\);/m;
314
- if (authProvider === "supabase") {
315
- await rm(join2(targetDir, "libs/auth-strategies/firebase"), { recursive: true, force: true });
316
- await rm(join2(targetDir, "libs/auth-strategies/mongodb"), { recursive: true, force: true });
317
- await stripDeps(join2(targetDir, "apps/microservices/auth/package.json"), [
318
- "@icore/auth-firebase",
319
- "@icore/firebase-admin",
320
- "@icore/auth-mongodb"
321
- ]);
322
- await stripTsconfigPath(targetDir, "@icore/auth-firebase");
323
- await stripTsconfigPath(targetDir, "@icore/auth-mongodb");
324
- try {
325
- const src = await readFile2(modulePath, "utf8");
326
- 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(
327
- /^import \{ MongooseModule, getConnectionToken \} from '@nestjs\/mongoose';\n/gm,
328
- ""
329
- ).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\(\)/, "");
330
- await writeFile2(modulePath, next);
331
- } catch {
332
- }
333
- }
334
- if (authProvider === "firebase") {
335
- await rm(join2(targetDir, "libs/auth-strategies/supabase"), { recursive: true, force: true });
336
- await rm(join2(targetDir, "libs/auth-strategies/mongodb"), { recursive: true, force: true });
337
- await stripDeps(join2(targetDir, "apps/microservices/auth/package.json"), [
338
- "@icore/auth-supabase",
339
- "@icore/auth-mongodb"
340
- ]);
341
- await stripTsconfigPath(targetDir, "@icore/auth-supabase");
342
- await stripTsconfigPath(targetDir, "@icore/auth-mongodb");
343
- try {
344
- const src = await readFile2(modulePath, "utf8");
345
- 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(
346
- /^import \{ MongooseModule, getConnectionToken \} from '@nestjs\/mongoose';\n/gm,
347
- ""
348
- ).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\(\)/, "");
349
- await writeFile2(modulePath, next);
350
- } catch {
351
- }
352
- }
353
- if (authProvider === "mongodb") {
354
- await rm(join2(targetDir, "libs/auth-strategies/supabase"), { recursive: true, force: true });
355
- await rm(join2(targetDir, "libs/auth-strategies/firebase"), { recursive: true, force: true });
356
- await stripDeps(join2(targetDir, "apps/microservices/auth/package.json"), [
357
- "@icore/auth-supabase",
358
- "@icore/auth-firebase",
359
- "@icore/firebase-admin"
360
- ]);
361
- await stripTsconfigPath(targetDir, "@icore/auth-supabase");
362
- await stripTsconfigPath(targetDir, "@icore/auth-firebase");
363
- try {
364
- const src = await readFile2(modulePath, "utf8");
365
- 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);");
366
- await writeFile2(modulePath, next);
367
- } catch {
368
- }
369
- }
370
- }
371
- async function removeUnusedStorageStrategies(targetDir, uploadProvider) {
372
- if (uploadProvider === "none") return;
373
- const modulePath = join2(targetDir, "apps/microservices/upload/src/app/app.module.ts");
374
- 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;
375
- if (uploadProvider !== "firebase") {
376
- await rm(join2(targetDir, "libs/storage-strategies/firebase"), { recursive: true, force: true });
377
- await stripDeps(join2(targetDir, "apps/microservices/upload/package.json"), [
378
- "@icore/storage-firebase",
379
- "@icore/firebase-admin"
380
- ]);
381
- await stripTsconfigPath(targetDir, "@icore/storage-firebase");
382
- }
383
- if (uploadProvider !== "cloudinary") {
384
- await rm(join2(targetDir, "libs/storage-strategies/cloudinary"), {
385
- recursive: true,
386
- force: true
387
- });
388
- await stripDeps(join2(targetDir, "apps/microservices/upload/package.json"), [
389
- "@icore/storage-cloudinary"
390
- ]);
391
- await stripTsconfigPath(targetDir, "@icore/storage-cloudinary");
392
- }
393
- if (uploadProvider !== "supabase") {
394
- await rm(join2(targetDir, "libs/storage-strategies/supabase"), { recursive: true, force: true });
395
- await stripDeps(join2(targetDir, "apps/microservices/upload/package.json"), [
396
- "@icore/storage-supabase"
397
- ]);
398
- await stripTsconfigPath(targetDir, "@icore/storage-supabase");
399
- }
400
- if (uploadProvider !== "mongodb") {
401
- await rm(join2(targetDir, "libs/storage-strategies/mongodb"), { recursive: true, force: true });
402
- await stripDeps(join2(targetDir, "apps/microservices/upload/package.json"), [
403
- "@icore/storage-mongodb"
404
- ]);
405
- await stripTsconfigPath(targetDir, "@icore/storage-mongodb");
406
- }
280
+ const composePath = join2(targetDir, "docker-compose.yml");
407
281
  try {
408
- let src = await readFile2(modulePath, "utf8");
409
- if (uploadProvider !== "firebase") {
410
- src = src.replace(/^import \{[^}]*\} from '@icore\/firebase-admin';\n/gm, "").replace(
411
- /^import \{[^}]*FirebaseStorageStrategy[^}]*\} from '@icore\/storage-firebase';\n/gm,
412
- ""
413
- ).replace(/^ {2}firebase: \[[^\]]*\],\n/gm, "").replace(/\nfunction makeFirebaseStorage[\s\S]*?\n}\n/gm, "");
414
- }
415
- if (uploadProvider !== "cloudinary") {
416
- src = src.replace(/^import \{ v2 as cloudinary \} from 'cloudinary';\n/gm, "").replace(
417
- /^import \{[^}]*CloudinaryStorageStrategy[^}]*\} from '@icore\/storage-cloudinary';\n/gm,
418
- ""
419
- ).replace(/\nfunction makeCloudinaryStorage[\s\S]*?\n}\n/gm, "");
420
- }
421
- if (uploadProvider !== "supabase") {
422
- src = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/gm, "").replace(
423
- /^import \{[^}]*SupabaseStorageStrategy[^}]*\} from '@icore\/storage-supabase';\n/gm,
424
- ""
425
- ).replace(/\nfunction makeSupabaseStorage[\s\S]*?\n}\n/gm, "");
426
- }
427
- if (uploadProvider !== "mongodb") {
428
- src = src.replace(
429
- /^import \{[^}]*MongoDbStorageStrategy[^}]*\} from '@icore\/storage-mongodb';\n/gm,
430
- ""
431
- ).replace(
432
- /^import \{ MongooseModule, getConnectionToken \} from '@nestjs\/mongoose';\n/gm,
433
- ""
434
- ).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, "");
435
- }
436
- const chosenReturn = uploadProvider === "mongodb" ? `return makeMongoDbStorage(connection);` : `return make${uploadProvider.charAt(0).toUpperCase() + uploadProvider.slice(1)}Storage(cfg);`;
437
- src = src.replace(STORAGE_BRANCH, chosenReturn);
438
- if (uploadProvider !== "mongodb") {
439
- src = src.replace(/, connection: Connection/, "").replace(/, getConnectionToken\(\)/, "");
440
- }
441
- await writeFile2(modulePath, src);
282
+ const compose = await readFile2(composePath, "utf8");
283
+ 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, "");
284
+ await writeFile2(composePath, next);
442
285
  } catch {
443
286
  }
444
287
  }
445
- async function removeUnusedDbStrategies(targetDir, dbProvider) {
446
- const modulePath = join2(targetDir, "apps/microservices/notes/src/app/app.module.ts");
447
- const DB_BRANCH = /if \(provider === 'supabase'\) return makeSupabaseDB\(cfg\);\n\s*if \(provider === 'mongodb'\) return makeMongoDb\(connection\);\n\s*return makeFirestoreDB\(cfg\);/m;
448
- if (dbProvider === "supabase") {
449
- await rm(join2(targetDir, "libs/db-strategies/firestore"), { recursive: true, force: true });
450
- await rm(join2(targetDir, "libs/db-strategies/mongodb"), { recursive: true, force: true });
451
- await stripDeps(join2(targetDir, "apps/microservices/notes/package.json"), [
452
- "@icore/db-firestore",
453
- "@icore/firebase-admin",
454
- "@icore/db-mongodb"
455
- ]);
456
- await stripTsconfigPath(targetDir, "@icore/db-firestore");
457
- await stripTsconfigPath(targetDir, "@icore/db-mongodb");
458
- try {
459
- const src = await readFile2(modulePath, "utf8");
460
- 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(
461
- /^import \{ MongooseModule, getConnectionToken \} from '@nestjs\/mongoose';\n/gm,
462
- ""
463
- ).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\(\)/, "");
464
- await writeFile2(modulePath, next);
465
- } catch {
466
- }
467
- }
468
- if (dbProvider === "firebase") {
469
- await rm(join2(targetDir, "libs/db-strategies/supabase"), { recursive: true, force: true });
470
- await rm(join2(targetDir, "libs/db-strategies/mongodb"), { recursive: true, force: true });
471
- await stripDeps(join2(targetDir, "apps/microservices/notes/package.json"), [
472
- "@icore/db-supabase",
473
- "@icore/db-mongodb"
474
- ]);
475
- await stripTsconfigPath(targetDir, "@icore/db-supabase");
476
- await stripTsconfigPath(targetDir, "@icore/db-mongodb");
477
- try {
478
- const src = await readFile2(modulePath, "utf8");
479
- 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(
480
- /^import \{ MongooseModule, getConnectionToken \} from '@nestjs\/mongoose';\n/gm,
481
- ""
482
- ).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\(\)/, "");
483
- await writeFile2(modulePath, next);
484
- } catch {
485
- }
486
- }
487
- if (dbProvider === "mongodb") {
488
- await rm(join2(targetDir, "libs/db-strategies/supabase"), { recursive: true, force: true });
489
- await rm(join2(targetDir, "libs/db-strategies/firestore"), { recursive: true, force: true });
490
- await stripDeps(join2(targetDir, "apps/microservices/notes/package.json"), [
491
- "@icore/db-supabase",
492
- "@icore/db-firestore",
493
- "@icore/firebase-admin"
494
- ]);
495
- await stripTsconfigPath(targetDir, "@icore/db-supabase");
496
- await stripTsconfigPath(targetDir, "@icore/db-firestore");
497
- try {
498
- const src = await readFile2(modulePath, "utf8");
499
- 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);");
500
- await writeFile2(modulePath, next);
501
- } catch {
502
- }
503
- }
504
- }
505
- async function removeFirebaseAdminLib(targetDir) {
506
- await rm(join2(targetDir, "libs/firebase-admin"), { recursive: true, force: true });
507
- await stripTsconfigPath(targetDir, "@icore/firebase-admin");
508
- }
509
288
  async function removeUploadStack(targetDir) {
510
289
  const paths = [
511
290
  "apps/microservices/upload",
@@ -539,12 +318,443 @@ async function removeUploadStack(targetDir) {
539
318
  ]);
540
319
  }
541
320
 
542
- // src/lib/scaffold-pkg.ts
543
- import { readFile as readFile3, writeFile as writeFile3, mkdir, readdir } from "fs/promises";
321
+ // src/manifest/wire-features.ts
322
+ import { readFile as readFile4, writeFile as writeFile4, rm as rm3 } from "fs/promises";
323
+ import { join as join4 } from "path";
324
+
325
+ // src/manifest/index.ts
326
+ var EMPTY = { libDirs: [], deps: {}, tsPaths: {} };
327
+ var MANIFEST = {
328
+ auth: {
329
+ supabase: {
330
+ libDirs: ["libs/auth-strategies/supabase"],
331
+ deps: { "@supabase/supabase-js": "^2.106.2" },
332
+ tsPaths: { "@icore/auth-supabase": ["libs/auth-strategies/supabase/src/index.ts"] },
333
+ nestModule: {
334
+ importFrom: "@icore/auth-supabase",
335
+ symbol: "SupabaseAuthModule",
336
+ into: "auth"
337
+ },
338
+ appTests: [
339
+ "apps/microservices/auth/src/app/__tests__/auth.controller.supabase.integration.unit.test.ts"
340
+ ]
341
+ },
342
+ firebase: {
343
+ libDirs: ["libs/auth-strategies/firebase"],
344
+ deps: {},
345
+ tsPaths: { "@icore/auth-firebase": ["libs/auth-strategies/firebase/src/index.ts"] },
346
+ nestModule: {
347
+ importFrom: "@icore/auth-firebase",
348
+ symbol: "FirebaseAuthModule",
349
+ into: "auth"
350
+ },
351
+ appTests: [
352
+ "apps/microservices/auth/src/app/__tests__/auth.controller.firebase.integration.unit.test.ts"
353
+ ]
354
+ },
355
+ mongodb: {
356
+ libDirs: ["libs/auth-strategies/mongodb"],
357
+ deps: { mongoose: "^9.6.3" },
358
+ tsPaths: { "@icore/auth-mongodb": ["libs/auth-strategies/mongodb/src/index.ts"] },
359
+ nestModule: { importFrom: "@icore/auth-mongodb", symbol: "MongoDbAuthModule", into: "auth" }
360
+ }
361
+ },
362
+ storage: {
363
+ supabase: {
364
+ libDirs: ["libs/storage-strategies/supabase"],
365
+ deps: { "@supabase/supabase-js": "^2.106.2" },
366
+ tsPaths: { "@icore/storage-supabase": ["libs/storage-strategies/supabase/src/index.ts"] },
367
+ nestModule: {
368
+ importFrom: "@icore/storage-supabase",
369
+ symbol: "SupabaseStorageModule",
370
+ into: "upload"
371
+ }
372
+ },
373
+ firebase: {
374
+ libDirs: ["libs/storage-strategies/firebase"],
375
+ deps: {},
376
+ tsPaths: { "@icore/storage-firebase": ["libs/storage-strategies/firebase/src/index.ts"] },
377
+ nestModule: {
378
+ importFrom: "@icore/storage-firebase",
379
+ symbol: "FirebaseStorageModule",
380
+ into: "upload"
381
+ }
382
+ },
383
+ cloudinary: {
384
+ libDirs: ["libs/storage-strategies/cloudinary"],
385
+ deps: { cloudinary: "^2.10.0" },
386
+ tsPaths: { "@icore/storage-cloudinary": ["libs/storage-strategies/cloudinary/src/index.ts"] },
387
+ nestModule: {
388
+ importFrom: "@icore/storage-cloudinary",
389
+ symbol: "CloudinaryStorageModule",
390
+ into: "upload"
391
+ }
392
+ },
393
+ mongodb: {
394
+ libDirs: ["libs/storage-strategies/mongodb"],
395
+ deps: { mongoose: "^9.6.3" },
396
+ tsPaths: { "@icore/storage-mongodb": ["libs/storage-strategies/mongodb/src/index.ts"] },
397
+ nestModule: {
398
+ importFrom: "@icore/storage-mongodb",
399
+ symbol: "MongoDbStorageModule",
400
+ into: "upload"
401
+ }
402
+ }
403
+ },
404
+ db: {
405
+ supabase: {
406
+ libDirs: ["libs/db-strategies/supabase"],
407
+ deps: { "@supabase/supabase-js": "^2.106.2" },
408
+ tsPaths: { "@icore/db-supabase": ["libs/db-strategies/supabase/src/index.ts"] },
409
+ nestModule: { importFrom: "@icore/db-supabase", symbol: "SupabaseDbModule", into: "notes" }
410
+ },
411
+ firebase: {
412
+ libDirs: ["libs/db-strategies/firestore"],
413
+ deps: {},
414
+ tsPaths: { "@icore/db-firestore": ["libs/db-strategies/firestore/src/index.ts"] },
415
+ nestModule: { importFrom: "@icore/db-firestore", symbol: "FirestoreDbModule", into: "notes" }
416
+ },
417
+ mongodb: {
418
+ libDirs: ["libs/db-strategies/mongodb"],
419
+ deps: { mongoose: "^9.6.3" },
420
+ tsPaths: { "@icore/db-mongodb": ["libs/db-strategies/mongodb/src/index.ts"] },
421
+ nestModule: { importFrom: "@icore/db-mongodb", symbol: "MongoDbDbModule", into: "notes" }
422
+ }
423
+ },
424
+ feature: {
425
+ notes: {
426
+ libDirs: [
427
+ "apps/microservices/notes",
428
+ "apps/microservices/notes-e2e",
429
+ "libs/notes-client",
430
+ "libs/db-strategies",
431
+ "apps/api/src/app/notes",
432
+ "apps/client/src/components/notes",
433
+ "apps/client/src/routes/_dashboard/notes.tsx",
434
+ "apps/client/src/queries/notes.ts"
435
+ ],
436
+ deps: { "@icore/notes-client": "*", "@casl/ability": "^7.0.0" },
437
+ tsPaths: { "@icore/notes-client": ["libs/notes-client/src/index.ts"] },
438
+ gatewayModule: { importFrom: "./notes/notes.module", symbol: "NotesModule" },
439
+ gatewayService: { name: "notes", prefix: "NOTES" },
440
+ clientNav: { route: "/notes", labelKey: "nav.notes", iconName: "notes" }
441
+ },
442
+ payment: {
443
+ libDirs: [
444
+ "apps/microservices/payment",
445
+ "apps/microservices/payment-e2e",
446
+ "libs/payment-client",
447
+ "apps/api/src/app/payment"
448
+ ],
449
+ deps: { "@icore/payment-client": "*", "@idevconn/payment": "^1.2.0" },
450
+ tsPaths: { "@icore/payment-client": ["libs/payment-client/src/index.ts"] },
451
+ gatewayModule: { importFrom: "./payment/payment.module", symbol: "PaymentModule" },
452
+ gatewayService: { name: "payment", prefix: "PAYMENT" }
453
+ },
454
+ jobs: {
455
+ libDirs: [
456
+ "apps/microservices/jobs",
457
+ "libs/jobs-client",
458
+ "apps/api/src/app/admin",
459
+ "Dockerfile.ms-jobs"
460
+ ],
461
+ deps: {
462
+ "@icore/jobs-client": "*",
463
+ "@bull-board/api": "^7.1.5",
464
+ "@bull-board/express": "^7.1.5"
465
+ },
466
+ tsPaths: { "@icore/jobs-client": ["libs/jobs-client/src/index.ts"] },
467
+ gatewayModule: { importFrom: "./admin/admin.module", symbol: "AdminModule" },
468
+ dockerService: "jobs"
469
+ }
470
+ },
471
+ ui: { shadcn: EMPTY, antd: EMPTY, mui: EMPTY },
472
+ transport: { tcp: EMPTY, redis: EMPTY, nats: EMPTY, mqtt: EMPTY, rmq: EMPTY, kafka: EMPTY },
473
+ shared: {
474
+ firebaseAdmin: {
475
+ libDirs: ["libs/firebase-admin"],
476
+ deps: { "firebase-admin": "^13.10.0" },
477
+ tsPaths: { "@icore/firebase-admin": ["libs/firebase-admin/src/index.ts"] }
478
+ }
479
+ }
480
+ };
481
+
482
+ // src/manifest/wire-provider.ts
483
+ import { readFile as readFile3, writeFile as writeFile3, rm as rm2 } from "fs/promises";
544
484
  import { join as join3 } from "path";
485
+ async function writeProvider(targetDir, axis, provider) {
486
+ const nestModule = axis.section[provider]?.nestModule;
487
+ if (!nestModule) throw new Error(`provider "${provider}" has no nestModule in the manifest`);
488
+ const { importFrom, symbol } = nestModule;
489
+ const content = `import { ${symbol} } from '${importFrom}';
490
+
491
+ const ENV_PATH = '${axis.envPath}';
492
+
493
+ export const ${axis.exportConst} = ${symbol}.forRoot(ENV_PATH);
494
+ `;
495
+ await writeFile3(join3(targetDir, axis.providerFile), content);
496
+ }
497
+ async function stripJsonKeys(path, drop) {
498
+ try {
499
+ const pkg = JSON.parse(await readFile3(path, "utf8"));
500
+ for (const field of ["dependencies", "devDependencies"]) {
501
+ const deps = pkg[field];
502
+ if (!deps) continue;
503
+ for (const k of Object.keys(deps)) if (drop(k)) delete deps[k];
504
+ }
505
+ await writeFile3(path, JSON.stringify(pkg, null, 2) + "\n");
506
+ } catch {
507
+ }
508
+ }
509
+ async function stripTsconfigKeys(targetDir, aliases) {
510
+ const path = join3(targetDir, "tsconfig.base.json");
511
+ try {
512
+ const parsed = JSON.parse(await readFile3(path, "utf8"));
513
+ const paths = parsed.compilerOptions?.paths;
514
+ if (paths) for (const a of aliases) delete paths[a];
515
+ await writeFile3(path, JSON.stringify(parsed, null, 2) + "\n");
516
+ } catch {
517
+ }
518
+ }
519
+ async function cleanupUnusedAxis(targetDir, axis, chosen) {
520
+ for (const provider of Object.keys(axis.section)) {
521
+ if (provider === chosen) continue;
522
+ const unit = axis.section[provider];
523
+ for (const dir of unit.libDirs)
524
+ await rm2(join3(targetDir, dir), { recursive: true, force: true });
525
+ for (const t of unit.appTests ?? []) await rm2(join3(targetDir, t), { force: true });
526
+ const dropKeys = /* @__PURE__ */ new Set([...Object.keys(unit.tsPaths), ...Object.keys(unit.deps)]);
527
+ await stripJsonKeys(join3(targetDir, axis.msPackageJson), (k) => dropKeys.has(k));
528
+ await stripTsconfigKeys(targetDir, Object.keys(unit.tsPaths));
529
+ }
530
+ }
531
+
532
+ // src/manifest/wire-features.ts
533
+ var FEATURES_MODULE = "apps/api/src/app/features.module.ts";
534
+ var GATEWAY_SERVICES = "apps/api/src/app/gateway-services.ts";
535
+ var API_PKG = "apps/api/package.json";
536
+ var FEATURES = MANIFEST.feature;
537
+ function selectedFeatures(opts) {
538
+ const out = [];
539
+ if (opts.example === "notes") out.push("notes");
540
+ if (opts.payment !== "none") out.push("payment");
541
+ if (opts.jobs !== "none") out.push("jobs");
542
+ return out;
543
+ }
544
+ async function writeFeaturesWiring(targetDir, opts) {
545
+ const chosen = selectedFeatures(opts);
546
+ const mods = chosen.map((k) => FEATURES[k].gatewayModule).filter((m) => !!m);
547
+ const imports = mods.map((m) => `import { ${m.symbol} } from '${m.importFrom}';`).join("\n");
548
+ const symbols = mods.map((m) => m.symbol).join(", ");
549
+ const featuresModule = `import { Module } from '@nestjs/common';
550
+ ` + (imports ? imports + "\n" : "") + `
551
+ @Module({
552
+ imports: [${symbols}],
553
+ })
554
+ export class FeaturesModule {}
555
+ `;
556
+ await writeFile4(join4(targetDir, FEATURES_MODULE), featuresModule);
557
+ const services = [];
558
+ if (opts.authProvider !== "none") services.push({ name: "auth", prefix: "AUTH" });
559
+ if (opts.upload !== "none") services.push({ name: "upload", prefix: "UPLOAD" });
560
+ for (const k of chosen) {
561
+ const svc = FEATURES[k].gatewayService;
562
+ if (svc) services.push(svc);
563
+ }
564
+ const entries = services.map((s) => ` { name: '${s.name}', prefix: '${s.prefix}' },`).join("\n");
565
+ const gatewayServices = `/** Microservices the gateway proxies. Generated by create-icore. */
566
+ export const GATEWAY_SERVICES = [
567
+ ${entries}
568
+ ];
569
+ `;
570
+ await writeFile4(join4(targetDir, GATEWAY_SERVICES), gatewayServices);
571
+ }
572
+ async function stripJobsDockerCompose(targetDir) {
573
+ const composePath = join4(targetDir, "docker-compose.yml");
574
+ try {
575
+ const compose = await readFile4(composePath, "utf8");
576
+ 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, "");
577
+ await writeFile4(composePath, next);
578
+ } catch {
579
+ }
580
+ }
581
+ async function cleanupUnusedFeatures(targetDir, opts) {
582
+ const chosen = new Set(selectedFeatures(opts));
583
+ for (const key of ["notes", "payment", "jobs"]) {
584
+ if (chosen.has(key)) continue;
585
+ const unit = FEATURES[key];
586
+ for (const dir of unit.libDirs)
587
+ await rm3(join4(targetDir, dir), { recursive: true, force: true });
588
+ const dropKeys = /* @__PURE__ */ new Set([...Object.keys(unit.tsPaths), ...Object.keys(unit.deps)]);
589
+ await stripJsonKeys(join4(targetDir, API_PKG), (k) => dropKeys.has(k));
590
+ await stripTsconfigKeys(targetDir, Object.keys(unit.tsPaths));
591
+ if (unit.gatewayService) await stripGatewayTransport(targetDir, unit.gatewayService.prefix);
592
+ if (unit.dockerService === "jobs") await stripJobsDockerCompose(targetDir);
593
+ }
594
+ }
595
+
596
+ // src/manifest/wire-client.ts
597
+ import { writeFile as writeFile5 } from "fs/promises";
598
+ import { join as join5 } from "path";
599
+ var NAV_CONFIG_FILE = "apps/client/src/nav.config.ts";
600
+ var BASE_NAV = [
601
+ { to: "/dashboard", labelKey: "nav.dashboard", iconName: "dashboard", exact: true }
602
+ ];
603
+ var PROFILE_NAV = { to: "/profile", labelKey: "nav.profile", iconName: "profile" };
604
+ function renderEntry(n) {
605
+ const parts = [`to: '${n.to}'`, `labelKey: '${n.labelKey}'`, `iconName: '${n.iconName}'`];
606
+ if (n.exact) parts.push("exact: true");
607
+ return ` { ${parts.join(", ")} },`;
608
+ }
609
+ async function writeNavConfig(targetDir, opts) {
610
+ const entries = [...BASE_NAV];
611
+ const notesNav = MANIFEST.feature.notes.clientNav;
612
+ if (opts.example === "notes" && notesNav) {
613
+ entries.push({
614
+ to: notesNav.route,
615
+ labelKey: notesNav.labelKey,
616
+ iconName: notesNav.iconName,
617
+ exact: notesNav.exact
618
+ });
619
+ }
620
+ entries.push(PROFILE_NAV);
621
+ const content = `/**
622
+ * Sidebar navigation. UI-agnostic: each LayoutSider maps \`iconName\` to its own
623
+ * icon library. Generated by create-icore.
624
+ */
625
+ export interface NavItem {
626
+ to: string;
627
+ labelKey: string;
628
+ iconName: 'dashboard' | 'notes' | 'profile';
629
+ exact?: boolean;
630
+ }
631
+
632
+ export const NAV_CONFIG: NavItem[] = [
633
+ ` + entries.map(renderEntry).join("\n") + `
634
+ ];
635
+ `;
636
+ await writeFile5(join5(targetDir, NAV_CONFIG_FILE), content);
637
+ }
638
+
639
+ // src/manifest/blueprint.ts
640
+ import { writeFile as writeFile6 } from "fs/promises";
641
+ import { join as join6 } from "path";
642
+ async function writeBlueprintJson(targetDir, opts) {
643
+ const blueprint = {
644
+ schemaVersion: 1,
645
+ projectName: opts.projectName,
646
+ authProvider: opts.authProvider,
647
+ dbProvider: opts.dbProvider,
648
+ upload: opts.upload,
649
+ payment: opts.payment,
650
+ jobs: opts.jobs,
651
+ example: opts.example,
652
+ ui: opts.ui,
653
+ transport: opts.transport,
654
+ packageManager: opts.packageManager
655
+ };
656
+ await writeFile6(join6(targetDir, "blueprint.json"), JSON.stringify(blueprint, null, 2) + "\n");
657
+ }
658
+ async function writeJson(targetDir, rel, data) {
659
+ await writeFile6(join6(targetDir, rel, "blueprint.json"), JSON.stringify(data, null, 2) + "\n");
660
+ }
661
+ async function writeServiceBlueprints(targetDir, opts) {
662
+ const t = opts.transport;
663
+ if (opts.authProvider !== "none") {
664
+ await writeJson(targetDir, "apps/microservices/auth", {
665
+ schemaVersion: 1,
666
+ service: "auth",
667
+ authProvider: opts.authProvider,
668
+ transport: t
669
+ });
670
+ }
671
+ if (opts.upload !== "none") {
672
+ await writeJson(targetDir, "apps/microservices/upload", {
673
+ schemaVersion: 1,
674
+ service: "upload",
675
+ storageProvider: opts.upload,
676
+ transport: t
677
+ });
678
+ }
679
+ if (opts.example !== "none") {
680
+ await writeJson(targetDir, "apps/microservices/notes", {
681
+ schemaVersion: 1,
682
+ service: "notes",
683
+ dbProvider: opts.dbProvider,
684
+ transport: t
685
+ });
686
+ }
687
+ if (opts.payment !== "none") {
688
+ await writeJson(targetDir, "apps/microservices/payment", {
689
+ schemaVersion: 1,
690
+ service: "payment",
691
+ paymentProvider: opts.payment,
692
+ transport: t
693
+ });
694
+ }
695
+ if (opts.jobs !== "none") {
696
+ await writeJson(targetDir, "apps/microservices/jobs", {
697
+ schemaVersion: 1,
698
+ service: "jobs",
699
+ jobsProvider: opts.jobs
700
+ });
701
+ }
702
+ const features = [];
703
+ if (opts.example !== "none") features.push("notes");
704
+ if (opts.payment !== "none") features.push("payment");
705
+ if (opts.jobs !== "none") features.push("jobs");
706
+ await writeJson(targetDir, "apps/api", {
707
+ schemaVersion: 1,
708
+ service: "api",
709
+ features,
710
+ transport: t
711
+ });
712
+ await writeJson(targetDir, "apps/client", {
713
+ schemaVersion: 1,
714
+ service: "client",
715
+ ui: opts.ui
716
+ });
717
+ }
718
+
719
+ // src/manifest/wire-auth.ts
720
+ var AUTH = {
721
+ section: MANIFEST.auth,
722
+ providerFile: "apps/microservices/auth/src/app/auth.provider.ts",
723
+ exportConst: "AuthProviderModule",
724
+ msPackageJson: "apps/microservices/auth/package.json",
725
+ envPath: "apps/microservices/auth/.env"
726
+ };
727
+ var writeAuthProvider = (targetDir, provider) => writeProvider(targetDir, AUTH, provider);
728
+ var cleanupUnusedAuth = (targetDir, chosen) => cleanupUnusedAxis(targetDir, AUTH, chosen);
729
+
730
+ // src/manifest/wire-storage.ts
731
+ var STORAGE = {
732
+ section: MANIFEST.storage,
733
+ providerFile: "apps/microservices/upload/src/app/storage.provider.ts",
734
+ exportConst: "StorageProviderModule",
735
+ msPackageJson: "apps/microservices/upload/package.json",
736
+ envPath: "apps/microservices/upload/.env"
737
+ };
738
+ var writeStorageProvider = (targetDir, provider) => writeProvider(targetDir, STORAGE, provider);
739
+ var cleanupUnusedStorage = (targetDir, chosen) => cleanupUnusedAxis(targetDir, STORAGE, chosen);
740
+
741
+ // src/manifest/wire-db.ts
742
+ var DB = {
743
+ section: MANIFEST.db,
744
+ providerFile: "apps/microservices/notes/src/app/db.provider.ts",
745
+ exportConst: "DbProviderModule",
746
+ msPackageJson: "apps/microservices/notes/package.json",
747
+ envPath: "apps/microservices/notes/.env"
748
+ };
749
+ var writeDbProvider = (targetDir, provider) => writeProvider(targetDir, DB, provider);
750
+ var cleanupUnusedDb = (targetDir, chosen) => cleanupUnusedAxis(targetDir, DB, chosen);
751
+
752
+ // src/lib/scaffold-pkg.ts
753
+ import { readFile as readFile5, writeFile as writeFile7, mkdir, readdir } from "fs/promises";
754
+ import { join as join7 } from "path";
545
755
  async function writePnpmWorkspace(targetDir) {
546
- const pkgPath = join3(targetDir, "package.json");
547
- const pkg = JSON.parse(await readFile3(pkgPath, "utf8"));
756
+ const pkgPath = join7(targetDir, "package.json");
757
+ const pkg = JSON.parse(await readFile5(pkgPath, "utf8"));
548
758
  const workspaces = pkg.workspaces ?? [];
549
759
  const packagesBlock = workspaces.map((p2) => ` - '${p2}'`).join("\n");
550
760
  const allowBuilds = [
@@ -553,7 +763,9 @@ async function writePnpmWorkspace(targetDir) {
553
763
  "@parcel/watcher",
554
764
  "@scarf/scarf",
555
765
  "@swc/core",
766
+ "bcrypt",
556
767
  "less",
768
+ "mongodb-memory-server",
557
769
  "msgpackr-extract",
558
770
  "nx",
559
771
  "protobufjs",
@@ -565,10 +777,10 @@ ${packagesBlock}
565
777
  allowBuilds:
566
778
  ${allowBuilds}
567
779
  `;
568
- await writeFile3(join3(targetDir, "pnpm-workspace.yaml"), content);
780
+ await writeFile7(join7(targetDir, "pnpm-workspace.yaml"), content);
569
781
  }
570
782
  async function rewritePnpmWorkspaceDeps(targetDir) {
571
- async function walk(dir) {
783
+ async function walk2(dir) {
572
784
  const found = [];
573
785
  let entries;
574
786
  try {
@@ -578,27 +790,27 @@ async function rewritePnpmWorkspaceDeps(targetDir) {
578
790
  }
579
791
  for (const e of entries) {
580
792
  if (e.isDirectory() && e.name !== "node_modules") {
581
- found.push(...await walk(join3(dir, e.name)));
793
+ found.push(...await walk2(join7(dir, e.name)));
582
794
  } else if (e.isFile() && e.name === "package.json") {
583
- found.push(join3(dir, e.name));
795
+ found.push(join7(dir, e.name));
584
796
  }
585
797
  }
586
798
  return found;
587
799
  }
588
- const pkgFiles = await walk(targetDir);
800
+ const pkgFiles = await walk2(targetDir);
589
801
  for (const f of pkgFiles) {
590
802
  try {
591
- const raw = await readFile3(f, "utf8");
803
+ const raw = await readFile5(f, "utf8");
592
804
  const next = raw.replace(/"(@icore\/[^"]+)":\s*"\*"/g, '"$1": "workspace:*"');
593
- if (next !== raw) await writeFile3(f, next);
805
+ if (next !== raw) await writeFile7(f, next);
594
806
  } catch {
595
807
  }
596
808
  }
597
809
  }
598
810
  async function patchGitignoreForPm(targetDir, pm) {
599
- const giPath = join3(targetDir, ".gitignore");
811
+ const giPath = join7(targetDir, ".gitignore");
600
812
  try {
601
- let src = await readFile3(giPath, "utf8");
813
+ let src = await readFile5(giPath, "utf8");
602
814
  src = src.replace(/^# Build artifacts.*\ntools\/create-icore\/templates\/\s*\n/m, "");
603
815
  if (pm !== "yarn") {
604
816
  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, "");
@@ -613,7 +825,7 @@ async function patchGitignoreForPm(targetDir, pm) {
613
825
  src += "\n# npm\nnpm-debug.log*\n";
614
826
  }
615
827
  }
616
- await writeFile3(giPath, src);
828
+ await writeFile7(giPath, src);
617
829
  } catch {
618
830
  }
619
831
  }
@@ -628,7 +840,7 @@ async function writeAiFiles(targetDir, opts) {
628
840
  if (opts.jobs !== "none") activeMSes.push(`jobs (standalone)`);
629
841
  const usesSupabase = opts.authProvider === "supabase" || opts.dbProvider === "supabase" || opts.upload === "supabase";
630
842
  const usesFirebase = opts.authProvider === "firebase" || opts.dbProvider === "firebase" || opts.upload === "firebase";
631
- await writeFile3(join3(targetDir, "CLAUDE.md"), "@AGENTS.md\n");
843
+ await writeFile7(join7(targetDir, "CLAUDE.md"), "@AGENTS.md\n");
632
844
  const uiLabel = { shadcn: "shadcn/ui + Tailwind", antd: "Ant Design 6", mui: "MUI 6" }[opts.ui];
633
845
  const readme = `# ${opts.projectName}
634
846
 
@@ -678,7 +890,7 @@ ${pm === "yarn" ? "yarn remove-notes" : pm === "pnpm" ? "pnpm remove-notes" : "n
678
890
 
679
891
  Apache-2.0
680
892
  `;
681
- await writeFile3(join3(targetDir, "README.md"), readme);
893
+ await writeFile7(join7(targetDir, "README.md"), readme);
682
894
  const agents = `# ${opts.projectName} \u2014 Agent Instructions
683
895
 
684
896
  ## Stack snapshot
@@ -759,8 +971,8 @@ ${opts.upload !== "none" ? `| \`apps/microservices/upload/.env\` | \`STORAGE_PRO
759
971
  - Test behaviour, not implementation. Fake strategies from \`@icore/shared\` (FakeAuthStrategy etc.) serve as test doubles.
760
972
  - Run: \`${nx} test <project>\`
761
973
  `;
762
- await writeFile3(join3(targetDir, "AGENTS.md"), agents);
763
- await mkdir(join3(targetDir, ".claude"), { recursive: true });
974
+ await writeFile7(join7(targetDir, "AGENTS.md"), agents);
975
+ await mkdir(join7(targetDir, ".claude"), { recursive: true });
764
976
  const mcpServers = {
765
977
  nx: {
766
978
  command: "npx",
@@ -801,8 +1013,8 @@ ${opts.upload !== "none" ? `| \`apps/microservices/upload/.env\` | \`STORAGE_PRO
801
1013
  ]
802
1014
  }
803
1015
  };
804
- await writeFile3(
805
- join3(targetDir, ".claude", "settings.json"),
1016
+ await writeFile7(
1017
+ join7(targetDir, ".claude", "settings.json"),
806
1018
  JSON.stringify(settings, null, 2) + "\n"
807
1019
  );
808
1020
  }
@@ -826,16 +1038,16 @@ async function copyTree(src, dest) {
826
1038
  const entries = await readdir2(src, { withFileTypes: true });
827
1039
  for (const entry of entries) {
828
1040
  if (IGNORE_TOP.has(entry.name)) continue;
829
- const s = join4(src, entry.name);
830
- const d = join4(dest, entry.name);
1041
+ const s = join8(src, entry.name);
1042
+ const d = join8(dest, entry.name);
831
1043
  if (entry.isDirectory()) await copyTree(s, d);
832
1044
  else if (entry.isFile()) await copyFile(s, d);
833
1045
  }
834
1046
  }
835
1047
  async function selectClientTemplate(targetDir, opts) {
836
- const templatesRoot = join4(targetDir, "apps/templates");
837
- const chosen = join4(templatesRoot, `client-${opts.ui}`);
838
- const destClient = join4(targetDir, "apps/client");
1048
+ const templatesRoot = join8(targetDir, "apps/templates");
1049
+ const chosen = join8(templatesRoot, `client-${opts.ui}`);
1050
+ const destClient = join8(targetDir, "apps/client");
839
1051
  let chosenUi = opts.ui;
840
1052
  try {
841
1053
  const s = await stat(chosen);
@@ -843,9 +1055,9 @@ async function selectClientTemplate(targetDir, opts) {
843
1055
  await copyTree(chosen, destClient);
844
1056
  } catch {
845
1057
  chosenUi = "shadcn";
846
- await copyTree(join4(templatesRoot, "client-shadcn"), destClient);
1058
+ await copyTree(join8(templatesRoot, "client-shadcn"), destClient);
847
1059
  }
848
- await rm2(templatesRoot, { recursive: true, force: true });
1060
+ await rm4(templatesRoot, { recursive: true, force: true });
849
1061
  await rewriteClientPaths(destClient, chosenUi);
850
1062
  }
851
1063
  async function rewriteClientPaths(clientDir, ui) {
@@ -858,11 +1070,11 @@ async function rewriteClientPaths(clientDir, ui) {
858
1070
  "eslint.config.mjs"
859
1071
  ];
860
1072
  for (const rel of candidates) {
861
- const path = join4(clientDir, rel);
1073
+ const path = join8(clientDir, rel);
862
1074
  try {
863
- const raw = await readFile4(path, "utf8");
1075
+ const raw = await readFile6(path, "utf8");
864
1076
  const next = raw.replaceAll("../../../", "../../").replaceAll(`apps/templates/client-${ui}`, "apps/client").replaceAll(`client-${ui}`, "client");
865
- if (next !== raw) await writeFile4(path, next);
1077
+ if (next !== raw) await writeFile8(path, next);
866
1078
  } catch {
867
1079
  }
868
1080
  }
@@ -878,12 +1090,12 @@ function gitInit(cwd, projectName) {
878
1090
  }
879
1091
  function resolveYarnBin(cwd) {
880
1092
  try {
881
- const yarnrc = readFileSync(join4(cwd, ".yarnrc.yml"), "utf8");
1093
+ const yarnrc = readFileSync(join8(cwd, ".yarnrc.yml"), "utf8");
882
1094
  const match = yarnrc.match(/^yarnPath:\s*(.+)$/m);
883
- if (match?.[1]) return join4(cwd, match[1].trim());
1095
+ if (match?.[1]) return join8(cwd, match[1].trim());
884
1096
  } catch {
885
1097
  }
886
- return join4(cwd, ".yarn", "releases", "yarn-4.5.0.cjs");
1098
+ return join8(cwd, ".yarn", "releases", "yarn-4.5.0.cjs");
887
1099
  }
888
1100
  function runInstall(cwd, pm) {
889
1101
  if (pm === "yarn") {
@@ -897,7 +1109,7 @@ function runInstall(cwd, pm) {
897
1109
  async function scaffold(opts, templatesDir) {
898
1110
  await copyTree(templatesDir, opts.targetDir);
899
1111
  await rewriteRootPackageJson(opts.targetDir, opts);
900
- await writeAuthEnv(opts.targetDir, opts);
1112
+ if (opts.authProvider !== "none") await writeAuthEnv(opts.targetDir, opts);
901
1113
  await writeUploadEnv(opts.targetDir, opts);
902
1114
  await writeNotesEnv(opts.targetDir, opts);
903
1115
  await writePaymentEnv(opts.targetDir, opts);
@@ -906,19 +1118,33 @@ async function scaffold(opts, templatesDir) {
906
1118
  await selectClientTemplate(opts.targetDir, opts);
907
1119
  await writeClientEnv(opts.targetDir);
908
1120
  if (opts.upload === "none") await removeUploadStack(opts.targetDir);
909
- if (opts.payment === "none") await removePaymentStack(opts.targetDir);
910
- if (opts.jobs === "none") await removeJobsStack(opts.targetDir);
911
- if (opts.example === "none") await removeNotesStack(opts.targetDir);
912
- await removeUnusedAuthStrategies(opts.targetDir, opts.authProvider);
913
- await removeUnusedStorageStrategies(opts.targetDir, opts.upload);
914
- await removeUnusedDbStrategies(opts.targetDir, opts.dbProvider);
1121
+ await cleanupUnusedFeatures(opts.targetDir, opts);
1122
+ await writeFeaturesWiring(opts.targetDir, opts);
1123
+ await writeNavConfig(opts.targetDir, opts);
1124
+ if (opts.authProvider !== "none") {
1125
+ await cleanupUnusedAuth(opts.targetDir, opts.authProvider);
1126
+ await writeAuthProvider(opts.targetDir, opts.authProvider);
1127
+ } else {
1128
+ await removeAuthStack(opts.targetDir);
1129
+ }
1130
+ if (opts.upload !== "none") {
1131
+ await cleanupUnusedStorage(opts.targetDir, opts.upload);
1132
+ await writeStorageProvider(opts.targetDir, opts.upload);
1133
+ }
915
1134
  const firebaseUsed = opts.authProvider === "firebase" || opts.dbProvider === "firebase" || opts.upload === "firebase";
916
1135
  if (!firebaseUsed) await removeFirebaseAdminLib(opts.targetDir);
1136
+ await cleanupUnusedDb(opts.targetDir, opts.dbProvider);
1137
+ if (opts.dbProvider !== "none" && opts.example !== "none") {
1138
+ await writeDbProvider(opts.targetDir, opts.dbProvider);
1139
+ }
1140
+ await pruneRootProviderDeps(opts.targetDir, opts);
1141
+ await writeBlueprintJson(opts.targetDir, opts);
1142
+ await writeServiceBlueprints(opts.targetDir, opts);
917
1143
  if (opts.packageManager === "yarn") {
918
- await writeFile4(join4(opts.targetDir, "yarn.lock"), "");
1144
+ await writeFile8(join8(opts.targetDir, "yarn.lock"), "");
919
1145
  } else {
920
- await rm2(join4(opts.targetDir, ".yarn"), { recursive: true, force: true });
921
- await rm2(join4(opts.targetDir, ".yarnrc.yml"), { force: true });
1146
+ await rm4(join8(opts.targetDir, ".yarn"), { recursive: true, force: true });
1147
+ await rm4(join8(opts.targetDir, ".yarnrc.yml"), { force: true });
922
1148
  }
923
1149
  if (opts.packageManager === "pnpm") {
924
1150
  await writePnpmWorkspace(opts.targetDir);
@@ -933,9 +1159,98 @@ async function scaffold(opts, templatesDir) {
933
1159
  // src/lib/prompts.ts
934
1160
  import * as p from "@clack/prompts";
935
1161
  import { resolve } from "path";
936
- import { readFile as readFile5 } from "fs/promises";
937
- import { dirname, join as join5 } from "path";
1162
+ import { readFile as readFile8 } from "fs/promises";
1163
+ import { dirname, join as join9 } from "path";
938
1164
  import { fileURLToPath } from "url";
1165
+
1166
+ // src/lib/config.ts
1167
+ import { readFile as readFile7 } from "fs/promises";
1168
+ var ConfigFileError = class extends Error {
1169
+ constructor(message) {
1170
+ super(message);
1171
+ this.name = "ConfigFileError";
1172
+ }
1173
+ };
1174
+ var AUTH_PROVIDERS = ["supabase", "firebase", "mongodb", "none"];
1175
+ var DB_PROVIDERS = ["supabase", "firebase", "mongodb", "none"];
1176
+ var UPLOAD_PROVIDERS = [
1177
+ "supabase",
1178
+ "firebase",
1179
+ "cloudinary",
1180
+ "mongodb",
1181
+ "none"
1182
+ ];
1183
+ var PAYMENT_PROVIDERS = ["paypal", "none"];
1184
+ var JOBS_PROVIDERS = ["bullmq", "none"];
1185
+ var EXAMPLE_MODES = ["notes", "none"];
1186
+ var UI_LIBRARIES = ["shadcn", "antd", "mui"];
1187
+ var MS_TRANSPORTS = ["tcp", "redis", "nats", "mqtt", "rmq", "kafka"];
1188
+ var PACKAGE_MANAGERS = ["yarn", "npm", "pnpm"];
1189
+ function assertEnum(field, value, valid) {
1190
+ if (typeof value !== "string" || !valid.includes(value)) {
1191
+ throw new ConfigFileError(
1192
+ `config field "${field}" got "${String(value)}", expected one of: ${valid.join(", ")}`
1193
+ );
1194
+ }
1195
+ return value;
1196
+ }
1197
+ function assertBoolean(field, value) {
1198
+ if (typeof value !== "boolean") {
1199
+ throw new ConfigFileError(`config field "${field}" must be a boolean, got ${typeof value}`);
1200
+ }
1201
+ return value;
1202
+ }
1203
+ function validateConfig(raw) {
1204
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
1205
+ throw new ConfigFileError("config file must be a JSON object");
1206
+ }
1207
+ const obj = raw;
1208
+ const result = {};
1209
+ if ("projectName" in obj) {
1210
+ const v = obj["projectName"];
1211
+ if (typeof v !== "string" || !/^[a-z0-9-]+$/i.test(v)) {
1212
+ throw new ConfigFileError(
1213
+ `config field "projectName" must match /^[a-z0-9-]+$/i, got "${String(v)}"`
1214
+ );
1215
+ }
1216
+ result.projectName = v;
1217
+ }
1218
+ if ("authProvider" in obj)
1219
+ result.authProvider = assertEnum("authProvider", obj["authProvider"], AUTH_PROVIDERS);
1220
+ if ("dbProvider" in obj)
1221
+ result.dbProvider = assertEnum("dbProvider", obj["dbProvider"], DB_PROVIDERS);
1222
+ if ("upload" in obj) result.upload = assertEnum("upload", obj["upload"], UPLOAD_PROVIDERS);
1223
+ if ("payment" in obj) result.payment = assertEnum("payment", obj["payment"], PAYMENT_PROVIDERS);
1224
+ if ("jobs" in obj) result.jobs = assertEnum("jobs", obj["jobs"], JOBS_PROVIDERS);
1225
+ if ("example" in obj) result.example = assertEnum("example", obj["example"], EXAMPLE_MODES);
1226
+ if ("ui" in obj) result.ui = assertEnum("ui", obj["ui"], UI_LIBRARIES);
1227
+ if ("transport" in obj)
1228
+ result.transport = assertEnum("transport", obj["transport"], MS_TRANSPORTS);
1229
+ if ("packageManager" in obj)
1230
+ result.packageManager = assertEnum("packageManager", obj["packageManager"], PACKAGE_MANAGERS);
1231
+ if ("initGit" in obj) result.initGit = assertBoolean("initGit", obj["initGit"]);
1232
+ if ("install" in obj) result.install = assertBoolean("install", obj["install"]);
1233
+ return result;
1234
+ }
1235
+ async function loadConfig(filePath) {
1236
+ let raw;
1237
+ try {
1238
+ raw = await readFile7(filePath, "utf8");
1239
+ } catch {
1240
+ throw new ConfigFileError(`config file not found: ${filePath}`);
1241
+ }
1242
+ let parsed;
1243
+ try {
1244
+ parsed = JSON.parse(raw);
1245
+ } catch (e) {
1246
+ throw new ConfigFileError(
1247
+ `config file is not valid JSON: ${e instanceof Error ? e.message : String(e)}`
1248
+ );
1249
+ }
1250
+ return validateConfig(parsed);
1251
+ }
1252
+
1253
+ // src/lib/prompts.ts
939
1254
  function detectPackageManager() {
940
1255
  const ua = process.env["npm_config_user_agent"] ?? "";
941
1256
  if (ua.startsWith("yarn/")) return "yarn";
@@ -946,7 +1261,7 @@ function detectPackageManager() {
946
1261
  async function readSelfVersion() {
947
1262
  try {
948
1263
  const here = dirname(fileURLToPath(import.meta.url));
949
- const pkgRaw = await readFile5(join5(here, "..", "package.json"), "utf8");
1264
+ const pkgRaw = await readFile8(join9(here, "..", "package.json"), "utf8");
950
1265
  const pkg = JSON.parse(pkgRaw);
951
1266
  return pkg.version ?? null;
952
1267
  } catch {
@@ -1020,12 +1335,21 @@ function parseFlags(argv) {
1020
1335
  case "no-install":
1021
1336
  out.install = false;
1022
1337
  break;
1338
+ case "config":
1339
+ out._configPath = v;
1340
+ break;
1023
1341
  }
1024
1342
  }
1025
1343
  return out;
1026
1344
  }
1027
1345
  async function collectOptions({ argv, cwd }) {
1028
1346
  const flags = parseFlags(argv);
1347
+ const configPath = flags._configPath;
1348
+ delete flags._configPath;
1349
+ if (configPath) {
1350
+ const configValues = await loadConfig(configPath);
1351
+ Object.assign(flags, { ...configValues, ...flags });
1352
+ }
1029
1353
  const [selfVersion, latestVersion] = await Promise.all([readSelfVersion(), fetchLatestVersion()]);
1030
1354
  const versionTag = selfVersion ? ` v${selfVersion}` : "";
1031
1355
  p.intro(`iCore${versionTag} \u2014 bootstrap a new project`);
@@ -1048,11 +1372,12 @@ Re-run with @latest to refresh:
1048
1372
  options: [
1049
1373
  { value: "supabase", label: "Supabase" },
1050
1374
  { value: "firebase", label: "Firebase" },
1051
- { value: "mongodb", label: "MongoDB (Custom Auth)" }
1375
+ { value: "mongodb", label: "MongoDB (Custom Auth)" },
1376
+ { value: "none", label: "None \u2014 no login, open API (simple SPA)" }
1052
1377
  ]
1053
1378
  });
1054
1379
  if (p.isCancel(authProvider)) throw new Error("cancelled");
1055
- const dbProvider = flags.dbProvider ?? await p.select({
1380
+ const dbProvider = authProvider === "none" ? "none" : flags.dbProvider ?? await p.select({
1056
1381
  message: "Database backend",
1057
1382
  options: [
1058
1383
  { value: "supabase", label: "Supabase Postgres" },
@@ -1091,7 +1416,7 @@ Re-run with @latest to refresh:
1091
1416
  initialValue: "none"
1092
1417
  });
1093
1418
  if (p.isCancel(jobs)) throw new Error("cancelled");
1094
- const example = flags.example ?? await p.select({
1419
+ const example = authProvider === "none" ? "none" : flags.example ?? await p.select({
1095
1420
  message: "Include notes sample feature? (CRUD demo \u2014 remove before production)",
1096
1421
  options: [
1097
1422
  { value: "notes", label: "Yes \u2014 include notes sample" },
@@ -1116,7 +1441,8 @@ Re-run with @latest to refresh:
1116
1441
  initialValue: "shadcn"
1117
1442
  });
1118
1443
  if (p.isCancel(ui)) throw new Error("cancelled");
1119
- const transport = flags.transport ?? await p.select({
1444
+ const noMicroservices = authProvider === "none" && upload === "none" && payment === "none";
1445
+ const transport = flags.transport ?? (noMicroservices ? "tcp" : await p.select({
1120
1446
  message: "Microservice transport",
1121
1447
  options: [
1122
1448
  { value: "tcp", label: "TCP (default, no broker required)" },
@@ -1127,7 +1453,7 @@ Re-run with @latest to refresh:
1127
1453
  { value: "kafka", label: "Kafka" }
1128
1454
  ],
1129
1455
  initialValue: "tcp"
1130
- });
1456
+ }));
1131
1457
  if (p.isCancel(transport)) throw new Error("cancelled");
1132
1458
  const packageManager = flags.packageManager ?? detectPackageManager();
1133
1459
  if (packageManager === "yarn") {
@@ -1157,7 +1483,128 @@ Re-run with @latest to refresh:
1157
1483
  install
1158
1484
  };
1159
1485
  }
1486
+
1487
+ // src/manifest/audit.ts
1488
+ import { readFile as readFile9, readdir as readdir3 } from "fs/promises";
1489
+ import { join as join10 } from "path";
1490
+ var IGNORE_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".nx"]);
1491
+ async function walk(dir, out = []) {
1492
+ const entries = await readdir3(dir, { withFileTypes: true });
1493
+ for (const e of entries) {
1494
+ if (e.isDirectory()) {
1495
+ if (!IGNORE_DIRS.has(e.name)) await walk(join10(dir, e.name), out);
1496
+ } else if (/\.(ts|tsx|mjs)$/.test(e.name)) {
1497
+ out.push(join10(dir, e.name));
1498
+ }
1499
+ }
1500
+ return out;
1501
+ }
1502
+ async function tsconfigAliases(dir) {
1503
+ try {
1504
+ const raw = await readFile9(join10(dir, "tsconfig.base.json"), "utf8");
1505
+ const aliases = /* @__PURE__ */ new Set();
1506
+ for (const m of raw.matchAll(/"(@icore\/[a-z0-9.-]+)"\s*:/g)) {
1507
+ if (m[1]) aliases.add(m[1]);
1508
+ }
1509
+ return aliases;
1510
+ } catch {
1511
+ return /* @__PURE__ */ new Set();
1512
+ }
1513
+ }
1514
+ var PROVIDER_SDKS = {
1515
+ supabase: ["@supabase/supabase-js"],
1516
+ cloudinary: ["cloudinary"],
1517
+ mongodb: ["mongoose"],
1518
+ firebase: ["firebase-admin", "@icore/firebase-admin"]
1519
+ };
1520
+ async function readBlueprint(dir) {
1521
+ try {
1522
+ return JSON.parse(await readFile9(join10(dir, "blueprint.json"), "utf8"));
1523
+ } catch {
1524
+ return null;
1525
+ }
1526
+ }
1527
+ function forbiddenFromBlueprint(bp) {
1528
+ const chosen = new Set(
1529
+ [bp.authProvider, bp.dbProvider, bp.upload].filter((p2) => Boolean(p2))
1530
+ );
1531
+ const forbidden = [];
1532
+ for (const [provider, sdks] of Object.entries(PROVIDER_SDKS)) {
1533
+ if (!chosen.has(provider)) forbidden.push(...sdks);
1534
+ }
1535
+ return forbidden;
1536
+ }
1537
+ async function allPackageJsons(dir) {
1538
+ const out = [];
1539
+ const root = join10(dir, "package.json");
1540
+ out.push(root);
1541
+ async function walk2(d) {
1542
+ let entries;
1543
+ try {
1544
+ entries = await readdir3(d, { withFileTypes: true });
1545
+ } catch {
1546
+ return;
1547
+ }
1548
+ for (const e of entries) {
1549
+ if (e.isDirectory()) {
1550
+ if (!IGNORE_DIRS.has(e.name)) await walk2(join10(d, e.name));
1551
+ } else if (e.name === "package.json") {
1552
+ out.push(join10(d, e.name));
1553
+ }
1554
+ }
1555
+ }
1556
+ await walk2(join10(dir, "apps"));
1557
+ return out;
1558
+ }
1559
+ async function depKeys(pkgPath) {
1560
+ try {
1561
+ const pkg = JSON.parse(await readFile9(pkgPath, "utf8"));
1562
+ return /* @__PURE__ */ new Set([
1563
+ ...Object.keys(pkg.dependencies ?? {}),
1564
+ ...Object.keys(pkg.devDependencies ?? {})
1565
+ ]);
1566
+ } catch {
1567
+ return /* @__PURE__ */ new Set();
1568
+ }
1569
+ }
1570
+ var ICORE_IMPORT = /(?:from|import\()\s*['"](@icore\/[a-z0-9.-]+)/g;
1571
+ async function auditProject(dir, opts = {}) {
1572
+ const violations = [];
1573
+ const aliases = await tsconfigAliases(dir);
1574
+ for (const file of await walk(dir)) {
1575
+ const src = await readFile9(file, "utf8");
1576
+ for (const m of src.matchAll(ICORE_IMPORT)) {
1577
+ const alias = m[1];
1578
+ if (alias && !aliases.has(alias)) {
1579
+ violations.push({
1580
+ kind: "import-of-absent-lib",
1581
+ detail: `${file} imports ${alias} (no tsconfig path \u2192 lib absent)`
1582
+ });
1583
+ }
1584
+ }
1585
+ }
1586
+ const bp = await readBlueprint(dir);
1587
+ const forbidden = /* @__PURE__ */ new Set([
1588
+ ...opts.forbiddenDeps ?? [],
1589
+ ...bp ? forbiddenFromBlueprint(bp) : []
1590
+ ]);
1591
+ if (forbidden.size > 0) {
1592
+ for (const pkgPath of await allPackageJsons(dir)) {
1593
+ const deps = await depKeys(pkgPath);
1594
+ for (const f of forbidden) {
1595
+ if (deps.has(f)) {
1596
+ violations.push({
1597
+ kind: "forbidden-dep",
1598
+ detail: `${pkgPath} keeps forbidden dep ${f}`
1599
+ });
1600
+ }
1601
+ }
1602
+ }
1603
+ }
1604
+ return violations;
1605
+ }
1160
1606
  export {
1607
+ auditProject,
1161
1608
  collectOptions,
1162
1609
  pmRun,
1163
1610
  scaffold