@idevconn/create-icore 0.7.2 → 0.8.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 (86) hide show
  1. package/dist/cli.js +509 -353
  2. package/dist/index.cjs +644 -366
  3. package/dist/index.d.cts +9 -1
  4. package/dist/index.d.ts +9 -1
  5. package/dist/index.js +635 -358
  6. package/dist/manifest/audit.js +122 -0
  7. package/package.json +1 -1
  8. package/templates/apps/api/src/app/app.module.ts +2 -6
  9. package/templates/apps/api/src/app/features.module.ts +9 -0
  10. package/templates/apps/api/src/app/gateway-services.ts +7 -0
  11. package/templates/apps/api/src/main.ts +1 -5
  12. package/templates/apps/microservices/auth/src/app/app.module.ts +4 -93
  13. package/templates/apps/microservices/auth/src/app/auth.provider.ts +9 -0
  14. package/templates/apps/microservices/notes/src/app/app.module.ts +4 -86
  15. package/templates/apps/microservices/notes/src/app/db.provider.ts +9 -0
  16. package/templates/apps/microservices/upload/src/app/app.module.ts +4 -140
  17. package/templates/apps/microservices/upload/src/app/storage.provider.ts +9 -0
  18. package/templates/apps/templates/client-antd/src/components/layout/LayoutSider.tsx +15 -23
  19. package/templates/apps/templates/client-antd/src/nav.config.ts +17 -0
  20. package/templates/apps/templates/client-mui/src/components/layout/LayoutSider.tsx +19 -20
  21. package/templates/apps/templates/client-mui/src/nav.config.ts +17 -0
  22. package/templates/apps/templates/client-shadcn/src/components/layout/LayoutSider.tsx +20 -16
  23. package/templates/apps/templates/client-shadcn/src/nav.config.ts +17 -0
  24. package/templates/libs/auth-strategies/firebase/eslint.config.mjs +1 -0
  25. package/templates/libs/auth-strategies/firebase/package.json +4 -0
  26. package/templates/libs/auth-strategies/firebase/src/index.ts +1 -0
  27. package/templates/libs/auth-strategies/firebase/src/lib/__tests__/firebase-auth.module.unit.test.ts +49 -0
  28. package/templates/libs/auth-strategies/firebase/src/lib/firebase-auth.module.ts +41 -0
  29. package/templates/libs/auth-strategies/firebase/tsconfig.json +2 -0
  30. package/templates/libs/auth-strategies/mongodb/package.json +4 -1
  31. package/templates/libs/auth-strategies/mongodb/src/index.ts +1 -0
  32. package/templates/libs/auth-strategies/mongodb/src/lib/__tests__/mongodb-auth.module.unit.test.ts +16 -0
  33. package/templates/libs/auth-strategies/mongodb/src/lib/mongodb-auth.module.ts +45 -0
  34. package/templates/libs/auth-strategies/mongodb/tsconfig.json +2 -0
  35. package/templates/libs/auth-strategies/supabase/eslint.config.mjs +1 -0
  36. package/templates/libs/auth-strategies/supabase/package.json +3 -0
  37. package/templates/libs/auth-strategies/supabase/src/index.ts +1 -0
  38. package/templates/libs/auth-strategies/supabase/src/lib/__tests__/supabase-auth.module.unit.test.ts +43 -0
  39. package/templates/libs/auth-strategies/supabase/src/lib/supabase-auth.module.ts +41 -0
  40. package/templates/libs/auth-strategies/supabase/tsconfig.json +2 -0
  41. package/templates/libs/db-strategies/firestore/eslint.config.mjs +1 -1
  42. package/templates/libs/db-strategies/firestore/package.json +4 -0
  43. package/templates/libs/db-strategies/firestore/src/index.ts +1 -0
  44. package/templates/libs/db-strategies/firestore/src/lib/__tests__/firestore-db.module.unit.test.ts +37 -0
  45. package/templates/libs/db-strategies/firestore/src/lib/firestore-db.module.ts +41 -0
  46. package/templates/libs/db-strategies/firestore/tsconfig.json +2 -0
  47. package/templates/libs/db-strategies/mongodb/package.json +4 -1
  48. package/templates/libs/db-strategies/mongodb/src/index.ts +1 -0
  49. package/templates/libs/db-strategies/mongodb/src/lib/__tests__/mongodb-db.module.unit.test.ts +14 -0
  50. package/templates/libs/db-strategies/mongodb/src/lib/mongodb-db.module.ts +41 -0
  51. package/templates/libs/db-strategies/mongodb/tsconfig.json +2 -0
  52. package/templates/libs/db-strategies/supabase/eslint.config.mjs +6 -1
  53. package/templates/libs/db-strategies/supabase/package.json +3 -0
  54. package/templates/libs/db-strategies/supabase/src/index.ts +1 -0
  55. package/templates/libs/db-strategies/supabase/src/lib/__tests__/supabase-db.module.unit.test.ts +32 -0
  56. package/templates/libs/db-strategies/supabase/src/lib/supabase-db.module.ts +41 -0
  57. package/templates/libs/db-strategies/supabase/tsconfig.json +2 -0
  58. package/templates/libs/shared/src/strategies/__tests__/provide-strategy.unit.test.ts +73 -0
  59. package/templates/libs/shared/src/strategies/index.ts +1 -0
  60. package/templates/libs/shared/src/strategies/provide-strategy.ts +44 -0
  61. package/templates/libs/storage-strategies/cloudinary/eslint.config.mjs +1 -1
  62. package/templates/libs/storage-strategies/cloudinary/package.json +4 -0
  63. package/templates/libs/storage-strategies/cloudinary/src/index.ts +1 -0
  64. package/templates/libs/storage-strategies/cloudinary/src/lib/__tests__/cloudinary-storage.module.unit.test.ts +40 -0
  65. package/templates/libs/storage-strategies/cloudinary/src/lib/cloudinary-storage.module.ts +85 -0
  66. package/templates/libs/storage-strategies/cloudinary/tsconfig.json +2 -0
  67. package/templates/libs/storage-strategies/firebase/eslint.config.mjs +1 -1
  68. package/templates/libs/storage-strategies/firebase/package.json +4 -0
  69. package/templates/libs/storage-strategies/firebase/src/index.ts +1 -0
  70. package/templates/libs/storage-strategies/firebase/src/lib/__tests__/firebase-storage.module.unit.test.ts +42 -0
  71. package/templates/libs/storage-strategies/firebase/src/lib/firebase-storage.module.ts +46 -0
  72. package/templates/libs/storage-strategies/firebase/tsconfig.json +2 -0
  73. package/templates/libs/storage-strategies/mongodb/package.json +4 -1
  74. package/templates/libs/storage-strategies/mongodb/src/index.ts +1 -0
  75. package/templates/libs/storage-strategies/mongodb/src/lib/__tests__/mongodb-storage.module.unit.test.ts +14 -0
  76. package/templates/libs/storage-strategies/mongodb/src/lib/mongodb-storage.module.ts +41 -0
  77. package/templates/libs/storage-strategies/mongodb/tsconfig.json +2 -0
  78. package/templates/libs/storage-strategies/supabase/eslint.config.mjs +1 -0
  79. package/templates/libs/storage-strategies/supabase/package.json +3 -0
  80. package/templates/libs/storage-strategies/supabase/src/index.ts +1 -0
  81. package/templates/libs/storage-strategies/supabase/src/lib/__tests__/supabase-storage.module.unit.test.ts +46 -0
  82. package/templates/libs/storage-strategies/supabase/src/lib/supabase-storage.module.ts +46 -0
  83. package/templates/libs/storage-strategies/supabase/tsconfig.json +2 -0
  84. package/templates/package.json +1 -1
  85. package/templates/tools/create-icore/_template-shell/package.json +1 -1
  86. 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");
@@ -186,42 +211,26 @@ async function stripTsconfigPath(targetDir, alias) {
186
211
  } catch {
187
212
  }
188
213
  }
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"
214
+ async function removeFirebaseAdminLib(targetDir) {
215
+ await rm(join2(targetDir, "libs/firebase-admin"), { recursive: true, force: true });
216
+ await stripTsconfigPath(targetDir, "@icore/firebase-admin");
217
+ await stripDeps(join2(targetDir, "apps/microservices/auth/package.json"), [
218
+ "@icore/firebase-admin"
219
+ ]);
220
+ await stripDeps(join2(targetDir, "apps/microservices/upload/package.json"), [
221
+ "@icore/firebase-admin"
222
+ ]);
223
+ await stripDeps(join2(targetDir, "apps/microservices/notes/package.json"), [
224
+ "@icore/firebase-admin"
210
225
  ]);
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
226
  }
219
- async function removePaymentStack(targetDir) {
227
+ async function removeUploadStack(targetDir) {
220
228
  const paths = [
221
- "apps/microservices/payment",
222
- "apps/microservices/payment-e2e",
223
- "libs/payment-client",
224
- "apps/api/src/app/payment"
229
+ "apps/microservices/upload",
230
+ "apps/microservices/upload-e2e",
231
+ "libs/storage-strategies",
232
+ "libs/upload-client",
233
+ "apps/api/src/app/storage"
225
234
  ];
226
235
  for (const p2 of paths) {
227
236
  await rm(join2(targetDir, p2), { recursive: true, force: true });
@@ -229,322 +238,459 @@ async function removePaymentStack(targetDir) {
229
238
  const appModulePath = join2(targetDir, "apps/api/src/app/app.module.ts");
230
239
  try {
231
240
  const appModule = await readFile2(appModulePath, "utf8");
232
- const next = appModule.replace(/^import \{ PaymentModule \} from '\.\/payment\/payment\.module';\n/m, "").replace(/,\s*PaymentModule/g, "");
241
+ const next = appModule.replace(/^import \{ StorageModule \} from '\.\/storage\/storage\.module';\n/m, "").replace(/,\s*StorageModule/g, "");
233
242
  await writeFile2(appModulePath, next);
234
243
  } catch {
235
244
  }
236
- await stripDeps(join2(targetDir, "apps/api/package.json"), [
237
- "@icore/payment-client",
238
- "@idevconn/payment"
239
- ]);
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
- }
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
- ]) {
257
- await rm(join2(targetDir, p2), { recursive: true, force: true });
258
- }
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
- const appModulePath = join2(targetDir, "apps/api/src/app/app.module.ts");
245
+ const gatewayEnv = join2(targetDir, "apps/api/.env");
262
246
  try {
263
- const src = await readFile2(appModulePath, "utf8");
264
- const next = src.replace(/^import \{ NotesModule \} from '\.\/notes\/notes\.module';\n/m, "").replace(/,\s*NotesModule/g, "");
265
- await writeFile2(appModulePath, next);
247
+ const env = await readFile2(gatewayEnv, "utf8");
248
+ const next = env.split("\n").filter(
249
+ (line) => !line.startsWith("UPLOAD_") && !line.startsWith("# UPLOAD_") && !line.startsWith("MAX_FILE_SIZE_KB")
250
+ ).join("\n");
251
+ await writeFile2(gatewayEnv, next);
266
252
  } catch {
267
253
  }
268
254
  await stripDeps(join2(targetDir, "apps/api/package.json"), [
269
- "@icore/notes-client",
270
- "@casl/ability"
255
+ "@icore/upload-client",
256
+ "@types/multer"
271
257
  ]);
272
- await stripGatewayTransport(targetDir, "NOTES");
273
- const mainTsPath = join2(targetDir, "apps/api/src/main.ts");
274
- try {
275
- const src = await readFile2(mainTsPath, "utf8");
276
- const next = src.replace(/\n\s*\{ name: 'notes', prefix: 'NOTES' \},/, "");
277
- await writeFile2(mainTsPath, next);
278
- } catch {
279
- }
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 {
258
+ }
259
+
260
+ // src/manifest/wire-features.ts
261
+ import { readFile as readFile4, writeFile as writeFile4, rm as rm3 } from "fs/promises";
262
+ import { join as join4 } from "path";
263
+
264
+ // src/manifest/index.ts
265
+ var EMPTY = { libDirs: [], deps: {}, tsPaths: {} };
266
+ var MANIFEST = {
267
+ auth: {
268
+ supabase: {
269
+ libDirs: ["libs/auth-strategies/supabase"],
270
+ deps: { "@supabase/supabase-js": "^2.106.2" },
271
+ tsPaths: { "@icore/auth-supabase": ["libs/auth-strategies/supabase/src/index.ts"] },
272
+ nestModule: {
273
+ importFrom: "@icore/auth-supabase",
274
+ symbol: "SupabaseAuthModule",
275
+ into: "auth"
276
+ },
277
+ appTests: [
278
+ "apps/microservices/auth/src/app/__tests__/auth.controller.supabase.integration.unit.test.ts"
279
+ ]
280
+ },
281
+ firebase: {
282
+ libDirs: ["libs/auth-strategies/firebase"],
283
+ deps: {},
284
+ tsPaths: { "@icore/auth-firebase": ["libs/auth-strategies/firebase/src/index.ts"] },
285
+ nestModule: {
286
+ importFrom: "@icore/auth-firebase",
287
+ symbol: "FirebaseAuthModule",
288
+ into: "auth"
289
+ },
290
+ appTests: [
291
+ "apps/microservices/auth/src/app/__tests__/auth.controller.firebase.integration.unit.test.ts"
292
+ ]
293
+ },
294
+ mongodb: {
295
+ libDirs: ["libs/auth-strategies/mongodb"],
296
+ deps: { mongoose: "^9.6.3" },
297
+ tsPaths: { "@icore/auth-mongodb": ["libs/auth-strategies/mongodb/src/index.ts"] },
298
+ nestModule: { importFrom: "@icore/auth-mongodb", symbol: "MongoDbAuthModule", into: "auth" }
299
+ }
300
+ },
301
+ storage: {
302
+ supabase: {
303
+ libDirs: ["libs/storage-strategies/supabase"],
304
+ deps: { "@supabase/supabase-js": "^2.106.2" },
305
+ tsPaths: { "@icore/storage-supabase": ["libs/storage-strategies/supabase/src/index.ts"] },
306
+ nestModule: {
307
+ importFrom: "@icore/storage-supabase",
308
+ symbol: "SupabaseStorageModule",
309
+ into: "upload"
310
+ }
311
+ },
312
+ firebase: {
313
+ libDirs: ["libs/storage-strategies/firebase"],
314
+ deps: {},
315
+ tsPaths: { "@icore/storage-firebase": ["libs/storage-strategies/firebase/src/index.ts"] },
316
+ nestModule: {
317
+ importFrom: "@icore/storage-firebase",
318
+ symbol: "FirebaseStorageModule",
319
+ into: "upload"
320
+ }
321
+ },
322
+ cloudinary: {
323
+ libDirs: ["libs/storage-strategies/cloudinary"],
324
+ deps: { cloudinary: "^2.10.0" },
325
+ tsPaths: { "@icore/storage-cloudinary": ["libs/storage-strategies/cloudinary/src/index.ts"] },
326
+ nestModule: {
327
+ importFrom: "@icore/storage-cloudinary",
328
+ symbol: "CloudinaryStorageModule",
329
+ into: "upload"
330
+ }
331
+ },
332
+ mongodb: {
333
+ libDirs: ["libs/storage-strategies/mongodb"],
334
+ deps: { mongoose: "^9.6.3" },
335
+ tsPaths: { "@icore/storage-mongodb": ["libs/storage-strategies/mongodb/src/index.ts"] },
336
+ nestModule: {
337
+ importFrom: "@icore/storage-mongodb",
338
+ symbol: "MongoDbStorageModule",
339
+ into: "upload"
340
+ }
341
+ }
342
+ },
343
+ db: {
344
+ supabase: {
345
+ libDirs: ["libs/db-strategies/supabase"],
346
+ deps: { "@supabase/supabase-js": "^2.106.2" },
347
+ tsPaths: { "@icore/db-supabase": ["libs/db-strategies/supabase/src/index.ts"] },
348
+ nestModule: { importFrom: "@icore/db-supabase", symbol: "SupabaseDbModule", into: "notes" }
349
+ },
350
+ firebase: {
351
+ libDirs: ["libs/db-strategies/firestore"],
352
+ deps: {},
353
+ tsPaths: { "@icore/db-firestore": ["libs/db-strategies/firestore/src/index.ts"] },
354
+ nestModule: { importFrom: "@icore/db-firestore", symbol: "FirestoreDbModule", into: "notes" }
355
+ },
356
+ mongodb: {
357
+ libDirs: ["libs/db-strategies/mongodb"],
358
+ deps: { mongoose: "^9.6.3" },
359
+ tsPaths: { "@icore/db-mongodb": ["libs/db-strategies/mongodb/src/index.ts"] },
360
+ nestModule: { importFrom: "@icore/db-mongodb", symbol: "MongoDbDbModule", into: "notes" }
361
+ }
362
+ },
363
+ feature: {
364
+ notes: {
365
+ libDirs: [
366
+ "apps/microservices/notes",
367
+ "apps/microservices/notes-e2e",
368
+ "libs/notes-client",
369
+ "libs/db-strategies",
370
+ "apps/api/src/app/notes",
371
+ "apps/client/src/components/notes",
372
+ "apps/client/src/routes/_dashboard/notes.tsx",
373
+ "apps/client/src/queries/notes.ts"
374
+ ],
375
+ deps: { "@icore/notes-client": "*", "@casl/ability": "^7.0.0" },
376
+ tsPaths: { "@icore/notes-client": ["libs/notes-client/src/index.ts"] },
377
+ gatewayModule: { importFrom: "./notes/notes.module", symbol: "NotesModule" },
378
+ gatewayService: { name: "notes", prefix: "NOTES" },
379
+ clientNav: { route: "/notes", labelKey: "nav.notes", iconName: "notes" }
380
+ },
381
+ payment: {
382
+ libDirs: [
383
+ "apps/microservices/payment",
384
+ "apps/microservices/payment-e2e",
385
+ "libs/payment-client",
386
+ "apps/api/src/app/payment"
387
+ ],
388
+ deps: { "@icore/payment-client": "*", "@idevconn/payment": "^1.2.0" },
389
+ tsPaths: { "@icore/payment-client": ["libs/payment-client/src/index.ts"] },
390
+ gatewayModule: { importFrom: "./payment/payment.module", symbol: "PaymentModule" },
391
+ gatewayService: { name: "payment", prefix: "PAYMENT" }
392
+ },
393
+ jobs: {
394
+ libDirs: [
395
+ "apps/microservices/jobs",
396
+ "libs/jobs-client",
397
+ "apps/api/src/app/admin",
398
+ "Dockerfile.ms-jobs"
399
+ ],
400
+ deps: {
401
+ "@icore/jobs-client": "*",
402
+ "@bull-board/api": "^7.1.5",
403
+ "@bull-board/express": "^7.1.5"
404
+ },
405
+ tsPaths: { "@icore/jobs-client": ["libs/jobs-client/src/index.ts"] },
406
+ gatewayModule: { importFrom: "./admin/admin.module", symbol: "AdminModule" },
407
+ dockerService: "jobs"
408
+ }
409
+ },
410
+ ui: { shadcn: EMPTY, antd: EMPTY, mui: EMPTY },
411
+ transport: { tcp: EMPTY, redis: EMPTY, nats: EMPTY, mqtt: EMPTY, rmq: EMPTY, kafka: EMPTY },
412
+ shared: {
413
+ firebaseAdmin: {
414
+ libDirs: ["libs/firebase-admin"],
415
+ deps: { "firebase-admin": "^13.10.0" },
416
+ tsPaths: { "@icore/firebase-admin": ["libs/firebase-admin/src/index.ts"] }
417
+ }
286
418
  }
287
- const siderPath = join2(targetDir, "apps/client/src/components/layout/LayoutSider.tsx");
419
+ };
420
+
421
+ // src/manifest/wire-provider.ts
422
+ import { readFile as readFile3, writeFile as writeFile3, rm as rm2 } from "fs/promises";
423
+ import { join as join3 } from "path";
424
+ async function writeProvider(targetDir, axis, provider) {
425
+ const nestModule = axis.section[provider]?.nestModule;
426
+ if (!nestModule) throw new Error(`provider "${provider}" has no nestModule in the manifest`);
427
+ const { importFrom, symbol } = nestModule;
428
+ const content = `import { ${symbol} } from '${importFrom}';
429
+
430
+ const ENV_PATH = '${axis.envPath}';
431
+
432
+ export const ${axis.exportConst} = ${symbol}.forRoot(ENV_PATH);
433
+ `;
434
+ await writeFile3(join3(targetDir, axis.providerFile), content);
435
+ }
436
+ async function stripJsonKeys(path, drop) {
288
437
  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);
438
+ const pkg = JSON.parse(await readFile3(path, "utf8"));
439
+ for (const field of ["dependencies", "devDependencies"]) {
440
+ const deps = pkg[field];
441
+ if (!deps) continue;
442
+ for (const k of Object.keys(deps)) if (drop(k)) delete deps[k];
443
+ }
444
+ await writeFile3(path, JSON.stringify(pkg, null, 2) + "\n");
301
445
  } catch {
302
446
  }
303
- const keysPath = join2(targetDir, "libs/template-shared/src/lib/i18n/keys.ts");
447
+ }
448
+ async function stripTsconfigKeys(targetDir, aliases) {
449
+ const path = join3(targetDir, "tsconfig.base.json");
304
450
  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);
451
+ const parsed = JSON.parse(await readFile3(path, "utf8"));
452
+ const paths = parsed.compilerOptions?.paths;
453
+ if (paths) for (const a of aliases) delete paths[a];
454
+ await writeFile3(path, JSON.stringify(parsed, null, 2) + "\n");
308
455
  } catch {
309
456
  }
310
457
  }
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
- }
458
+ async function cleanupUnusedAxis(targetDir, axis, chosen) {
459
+ for (const provider of Object.keys(axis.section)) {
460
+ if (provider === chosen) continue;
461
+ const unit = axis.section[provider];
462
+ for (const dir of unit.libDirs)
463
+ await rm2(join3(targetDir, dir), { recursive: true, force: true });
464
+ for (const t of unit.appTests ?? []) await rm2(join3(targetDir, t), { force: true });
465
+ const dropKeys = /* @__PURE__ */ new Set([...Object.keys(unit.tsPaths), ...Object.keys(unit.deps)]);
466
+ await stripJsonKeys(join3(targetDir, axis.msPackageJson), (k) => dropKeys.has(k));
467
+ await stripTsconfigKeys(targetDir, Object.keys(unit.tsPaths));
369
468
  }
370
469
  }
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");
470
+
471
+ // src/manifest/wire-features.ts
472
+ var FEATURES_MODULE = "apps/api/src/app/features.module.ts";
473
+ var GATEWAY_SERVICES = "apps/api/src/app/gateway-services.ts";
474
+ var API_PKG = "apps/api/package.json";
475
+ var FEATURES = MANIFEST.feature;
476
+ function selectedFeatures(opts) {
477
+ const out = [];
478
+ if (opts.example === "notes") out.push("notes");
479
+ if (opts.payment !== "none") out.push("payment");
480
+ if (opts.jobs !== "none") out.push("jobs");
481
+ return out;
482
+ }
483
+ async function writeFeaturesWiring(targetDir, opts) {
484
+ const chosen = selectedFeatures(opts);
485
+ const mods = chosen.map((k) => FEATURES[k].gatewayModule).filter((m) => !!m);
486
+ const imports = mods.map((m) => `import { ${m.symbol} } from '${m.importFrom}';`).join("\n");
487
+ const symbols = mods.map((m) => m.symbol).join(", ");
488
+ const featuresModule = `import { Module } from '@nestjs/common';
489
+ ` + (imports ? imports + "\n" : "") + `
490
+ @Module({
491
+ imports: [${symbols}],
492
+ })
493
+ export class FeaturesModule {}
494
+ `;
495
+ await writeFile4(join4(targetDir, FEATURES_MODULE), featuresModule);
496
+ const services = [{ name: "auth", prefix: "AUTH" }];
497
+ if (opts.upload !== "none") services.push({ name: "upload", prefix: "UPLOAD" });
498
+ for (const k of chosen) {
499
+ const svc = FEATURES[k].gatewayService;
500
+ if (svc) services.push(svc);
406
501
  }
502
+ const entries = services.map((s) => ` { name: '${s.name}', prefix: '${s.prefix}' },`).join("\n");
503
+ const gatewayServices = `/** Microservices the gateway proxies. Generated by create-icore. */
504
+ export const GATEWAY_SERVICES = [
505
+ ${entries}
506
+ ];
507
+ `;
508
+ await writeFile4(join4(targetDir, GATEWAY_SERVICES), gatewayServices);
509
+ }
510
+ async function stripJobsDockerCompose(targetDir) {
511
+ const composePath = join4(targetDir, "docker-compose.yml");
407
512
  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);
513
+ const compose = await readFile4(composePath, "utf8");
514
+ 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, "");
515
+ await writeFile4(composePath, next);
442
516
  } catch {
443
517
  }
444
518
  }
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
- }
519
+ async function cleanupUnusedFeatures(targetDir, opts) {
520
+ const chosen = new Set(selectedFeatures(opts));
521
+ for (const key of ["notes", "payment", "jobs"]) {
522
+ if (chosen.has(key)) continue;
523
+ const unit = FEATURES[key];
524
+ for (const dir of unit.libDirs)
525
+ await rm3(join4(targetDir, dir), { recursive: true, force: true });
526
+ const dropKeys = /* @__PURE__ */ new Set([...Object.keys(unit.tsPaths), ...Object.keys(unit.deps)]);
527
+ await stripJsonKeys(join4(targetDir, API_PKG), (k) => dropKeys.has(k));
528
+ await stripTsconfigKeys(targetDir, Object.keys(unit.tsPaths));
529
+ if (unit.gatewayService) await stripGatewayTransport(targetDir, unit.gatewayService.prefix);
530
+ if (unit.dockerService === "jobs") await stripJobsDockerCompose(targetDir);
486
531
  }
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
- }
532
+ }
533
+
534
+ // src/manifest/wire-client.ts
535
+ import { writeFile as writeFile5 } from "fs/promises";
536
+ import { join as join5 } from "path";
537
+ var NAV_CONFIG_FILE = "apps/client/src/nav.config.ts";
538
+ var BASE_NAV = [
539
+ { to: "/dashboard", labelKey: "nav.dashboard", iconName: "dashboard", exact: true }
540
+ ];
541
+ var PROFILE_NAV = { to: "/profile", labelKey: "nav.profile", iconName: "profile" };
542
+ function renderEntry(n) {
543
+ const parts = [`to: '${n.to}'`, `labelKey: '${n.labelKey}'`, `iconName: '${n.iconName}'`];
544
+ if (n.exact) parts.push("exact: true");
545
+ return ` { ${parts.join(", ")} },`;
546
+ }
547
+ async function writeNavConfig(targetDir, opts) {
548
+ const entries = [...BASE_NAV];
549
+ const notesNav = MANIFEST.feature.notes.clientNav;
550
+ if (opts.example === "notes" && notesNav) {
551
+ entries.push({
552
+ to: notesNav.route,
553
+ labelKey: notesNav.labelKey,
554
+ iconName: notesNav.iconName,
555
+ exact: notesNav.exact
556
+ });
503
557
  }
558
+ entries.push(PROFILE_NAV);
559
+ const content = `/**
560
+ * Sidebar navigation. UI-agnostic: each LayoutSider maps \`iconName\` to its own
561
+ * icon library. Generated by create-icore.
562
+ */
563
+ export interface NavItem {
564
+ to: string;
565
+ labelKey: string;
566
+ iconName: 'dashboard' | 'notes' | 'profile';
567
+ exact?: boolean;
504
568
  }
505
- async function removeFirebaseAdminLib(targetDir) {
506
- await rm(join2(targetDir, "libs/firebase-admin"), { recursive: true, force: true });
507
- await stripTsconfigPath(targetDir, "@icore/firebase-admin");
569
+
570
+ export const NAV_CONFIG: NavItem[] = [
571
+ ` + entries.map(renderEntry).join("\n") + `
572
+ ];
573
+ `;
574
+ await writeFile5(join5(targetDir, NAV_CONFIG_FILE), content);
508
575
  }
509
- async function removeUploadStack(targetDir) {
510
- const paths = [
511
- "apps/microservices/upload",
512
- "apps/microservices/upload-e2e",
513
- "libs/storage-strategies",
514
- "libs/upload-client",
515
- "apps/api/src/app/storage"
516
- ];
517
- for (const p2 of paths) {
518
- await rm(join2(targetDir, p2), { recursive: true, force: true });
576
+
577
+ // src/manifest/blueprint.ts
578
+ import { writeFile as writeFile6 } from "fs/promises";
579
+ import { join as join6 } from "path";
580
+ async function writeBlueprintJson(targetDir, opts) {
581
+ const blueprint = {
582
+ schemaVersion: 1,
583
+ projectName: opts.projectName,
584
+ authProvider: opts.authProvider,
585
+ dbProvider: opts.dbProvider,
586
+ upload: opts.upload,
587
+ payment: opts.payment,
588
+ jobs: opts.jobs,
589
+ example: opts.example,
590
+ ui: opts.ui,
591
+ transport: opts.transport,
592
+ packageManager: opts.packageManager
593
+ };
594
+ await writeFile6(join6(targetDir, "blueprint.json"), JSON.stringify(blueprint, null, 2) + "\n");
595
+ }
596
+ async function writeJson(targetDir, rel, data) {
597
+ await writeFile6(join6(targetDir, rel, "blueprint.json"), JSON.stringify(data, null, 2) + "\n");
598
+ }
599
+ async function writeServiceBlueprints(targetDir, opts) {
600
+ const t = opts.transport;
601
+ await writeJson(targetDir, "apps/microservices/auth", {
602
+ schemaVersion: 1,
603
+ service: "auth",
604
+ authProvider: opts.authProvider,
605
+ transport: t
606
+ });
607
+ if (opts.upload !== "none") {
608
+ await writeJson(targetDir, "apps/microservices/upload", {
609
+ schemaVersion: 1,
610
+ service: "upload",
611
+ storageProvider: opts.upload,
612
+ transport: t
613
+ });
519
614
  }
520
- const appModulePath = join2(targetDir, "apps/api/src/app/app.module.ts");
521
- try {
522
- const appModule = await readFile2(appModulePath, "utf8");
523
- const next = appModule.replace(/^import \{ StorageModule \} from '\.\/storage\/storage\.module';\n/m, "").replace(/,\s*StorageModule/g, "");
524
- await writeFile2(appModulePath, next);
525
- } catch {
615
+ if (opts.example !== "none") {
616
+ await writeJson(targetDir, "apps/microservices/notes", {
617
+ schemaVersion: 1,
618
+ service: "notes",
619
+ dbProvider: opts.dbProvider,
620
+ transport: t
621
+ });
526
622
  }
527
- const gatewayEnv = join2(targetDir, "apps/api/.env");
528
- try {
529
- const env = await readFile2(gatewayEnv, "utf8");
530
- const next = env.split("\n").filter(
531
- (line) => !line.startsWith("UPLOAD_") && !line.startsWith("# UPLOAD_") && !line.startsWith("MAX_FILE_SIZE_KB")
532
- ).join("\n");
533
- await writeFile2(gatewayEnv, next);
534
- } catch {
623
+ if (opts.payment !== "none") {
624
+ await writeJson(targetDir, "apps/microservices/payment", {
625
+ schemaVersion: 1,
626
+ service: "payment",
627
+ paymentProvider: opts.payment,
628
+ transport: t
629
+ });
535
630
  }
536
- await stripDeps(join2(targetDir, "apps/api/package.json"), [
537
- "@icore/upload-client",
538
- "@types/multer"
539
- ]);
631
+ if (opts.jobs !== "none") {
632
+ await writeJson(targetDir, "apps/microservices/jobs", {
633
+ schemaVersion: 1,
634
+ service: "jobs",
635
+ jobsProvider: opts.jobs
636
+ });
637
+ }
638
+ const features = [];
639
+ if (opts.example !== "none") features.push("notes");
640
+ if (opts.payment !== "none") features.push("payment");
641
+ if (opts.jobs !== "none") features.push("jobs");
642
+ await writeJson(targetDir, "apps/api", {
643
+ schemaVersion: 1,
644
+ service: "api",
645
+ features,
646
+ transport: t
647
+ });
648
+ await writeJson(targetDir, "apps/client", {
649
+ schemaVersion: 1,
650
+ service: "client",
651
+ ui: opts.ui
652
+ });
540
653
  }
541
654
 
655
+ // src/manifest/wire-auth.ts
656
+ var AUTH = {
657
+ section: MANIFEST.auth,
658
+ providerFile: "apps/microservices/auth/src/app/auth.provider.ts",
659
+ exportConst: "AuthProviderModule",
660
+ msPackageJson: "apps/microservices/auth/package.json",
661
+ envPath: "apps/microservices/auth/.env"
662
+ };
663
+ var writeAuthProvider = (targetDir, provider) => writeProvider(targetDir, AUTH, provider);
664
+ var cleanupUnusedAuth = (targetDir, chosen) => cleanupUnusedAxis(targetDir, AUTH, chosen);
665
+
666
+ // src/manifest/wire-storage.ts
667
+ var STORAGE = {
668
+ section: MANIFEST.storage,
669
+ providerFile: "apps/microservices/upload/src/app/storage.provider.ts",
670
+ exportConst: "StorageProviderModule",
671
+ msPackageJson: "apps/microservices/upload/package.json",
672
+ envPath: "apps/microservices/upload/.env"
673
+ };
674
+ var writeStorageProvider = (targetDir, provider) => writeProvider(targetDir, STORAGE, provider);
675
+ var cleanupUnusedStorage = (targetDir, chosen) => cleanupUnusedAxis(targetDir, STORAGE, chosen);
676
+
677
+ // src/manifest/wire-db.ts
678
+ var DB = {
679
+ section: MANIFEST.db,
680
+ providerFile: "apps/microservices/notes/src/app/db.provider.ts",
681
+ exportConst: "DbProviderModule",
682
+ msPackageJson: "apps/microservices/notes/package.json",
683
+ envPath: "apps/microservices/notes/.env"
684
+ };
685
+ var writeDbProvider = (targetDir, provider) => writeProvider(targetDir, DB, provider);
686
+ var cleanupUnusedDb = (targetDir, chosen) => cleanupUnusedAxis(targetDir, DB, chosen);
687
+
542
688
  // src/lib/scaffold-pkg.ts
543
- import { readFile as readFile3, writeFile as writeFile3, mkdir, readdir } from "fs/promises";
544
- import { join as join3 } from "path";
689
+ import { readFile as readFile5, writeFile as writeFile7, mkdir, readdir } from "fs/promises";
690
+ import { join as join7 } from "path";
545
691
  async function writePnpmWorkspace(targetDir) {
546
- const pkgPath = join3(targetDir, "package.json");
547
- const pkg = JSON.parse(await readFile3(pkgPath, "utf8"));
692
+ const pkgPath = join7(targetDir, "package.json");
693
+ const pkg = JSON.parse(await readFile5(pkgPath, "utf8"));
548
694
  const workspaces = pkg.workspaces ?? [];
549
695
  const packagesBlock = workspaces.map((p2) => ` - '${p2}'`).join("\n");
550
696
  const allowBuilds = [
@@ -565,10 +711,10 @@ ${packagesBlock}
565
711
  allowBuilds:
566
712
  ${allowBuilds}
567
713
  `;
568
- await writeFile3(join3(targetDir, "pnpm-workspace.yaml"), content);
714
+ await writeFile7(join7(targetDir, "pnpm-workspace.yaml"), content);
569
715
  }
570
716
  async function rewritePnpmWorkspaceDeps(targetDir) {
571
- async function walk(dir) {
717
+ async function walk2(dir) {
572
718
  const found = [];
573
719
  let entries;
574
720
  try {
@@ -578,27 +724,27 @@ async function rewritePnpmWorkspaceDeps(targetDir) {
578
724
  }
579
725
  for (const e of entries) {
580
726
  if (e.isDirectory() && e.name !== "node_modules") {
581
- found.push(...await walk(join3(dir, e.name)));
727
+ found.push(...await walk2(join7(dir, e.name)));
582
728
  } else if (e.isFile() && e.name === "package.json") {
583
- found.push(join3(dir, e.name));
729
+ found.push(join7(dir, e.name));
584
730
  }
585
731
  }
586
732
  return found;
587
733
  }
588
- const pkgFiles = await walk(targetDir);
734
+ const pkgFiles = await walk2(targetDir);
589
735
  for (const f of pkgFiles) {
590
736
  try {
591
- const raw = await readFile3(f, "utf8");
737
+ const raw = await readFile5(f, "utf8");
592
738
  const next = raw.replace(/"(@icore\/[^"]+)":\s*"\*"/g, '"$1": "workspace:*"');
593
- if (next !== raw) await writeFile3(f, next);
739
+ if (next !== raw) await writeFile7(f, next);
594
740
  } catch {
595
741
  }
596
742
  }
597
743
  }
598
744
  async function patchGitignoreForPm(targetDir, pm) {
599
- const giPath = join3(targetDir, ".gitignore");
745
+ const giPath = join7(targetDir, ".gitignore");
600
746
  try {
601
- let src = await readFile3(giPath, "utf8");
747
+ let src = await readFile5(giPath, "utf8");
602
748
  src = src.replace(/^# Build artifacts.*\ntools\/create-icore\/templates\/\s*\n/m, "");
603
749
  if (pm !== "yarn") {
604
750
  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 +759,7 @@ async function patchGitignoreForPm(targetDir, pm) {
613
759
  src += "\n# npm\nnpm-debug.log*\n";
614
760
  }
615
761
  }
616
- await writeFile3(giPath, src);
762
+ await writeFile7(giPath, src);
617
763
  } catch {
618
764
  }
619
765
  }
@@ -628,7 +774,7 @@ async function writeAiFiles(targetDir, opts) {
628
774
  if (opts.jobs !== "none") activeMSes.push(`jobs (standalone)`);
629
775
  const usesSupabase = opts.authProvider === "supabase" || opts.dbProvider === "supabase" || opts.upload === "supabase";
630
776
  const usesFirebase = opts.authProvider === "firebase" || opts.dbProvider === "firebase" || opts.upload === "firebase";
631
- await writeFile3(join3(targetDir, "CLAUDE.md"), "@AGENTS.md\n");
777
+ await writeFile7(join7(targetDir, "CLAUDE.md"), "@AGENTS.md\n");
632
778
  const uiLabel = { shadcn: "shadcn/ui + Tailwind", antd: "Ant Design 6", mui: "MUI 6" }[opts.ui];
633
779
  const readme = `# ${opts.projectName}
634
780
 
@@ -678,7 +824,7 @@ ${pm === "yarn" ? "yarn remove-notes" : pm === "pnpm" ? "pnpm remove-notes" : "n
678
824
 
679
825
  Apache-2.0
680
826
  `;
681
- await writeFile3(join3(targetDir, "README.md"), readme);
827
+ await writeFile7(join7(targetDir, "README.md"), readme);
682
828
  const agents = `# ${opts.projectName} \u2014 Agent Instructions
683
829
 
684
830
  ## Stack snapshot
@@ -759,8 +905,8 @@ ${opts.upload !== "none" ? `| \`apps/microservices/upload/.env\` | \`STORAGE_PRO
759
905
  - Test behaviour, not implementation. Fake strategies from \`@icore/shared\` (FakeAuthStrategy etc.) serve as test doubles.
760
906
  - Run: \`${nx} test <project>\`
761
907
  `;
762
- await writeFile3(join3(targetDir, "AGENTS.md"), agents);
763
- await mkdir(join3(targetDir, ".claude"), { recursive: true });
908
+ await writeFile7(join7(targetDir, "AGENTS.md"), agents);
909
+ await mkdir(join7(targetDir, ".claude"), { recursive: true });
764
910
  const mcpServers = {
765
911
  nx: {
766
912
  command: "npx",
@@ -801,8 +947,8 @@ ${opts.upload !== "none" ? `| \`apps/microservices/upload/.env\` | \`STORAGE_PRO
801
947
  ]
802
948
  }
803
949
  };
804
- await writeFile3(
805
- join3(targetDir, ".claude", "settings.json"),
950
+ await writeFile7(
951
+ join7(targetDir, ".claude", "settings.json"),
806
952
  JSON.stringify(settings, null, 2) + "\n"
807
953
  );
808
954
  }
@@ -826,16 +972,16 @@ async function copyTree(src, dest) {
826
972
  const entries = await readdir2(src, { withFileTypes: true });
827
973
  for (const entry of entries) {
828
974
  if (IGNORE_TOP.has(entry.name)) continue;
829
- const s = join4(src, entry.name);
830
- const d = join4(dest, entry.name);
975
+ const s = join8(src, entry.name);
976
+ const d = join8(dest, entry.name);
831
977
  if (entry.isDirectory()) await copyTree(s, d);
832
978
  else if (entry.isFile()) await copyFile(s, d);
833
979
  }
834
980
  }
835
981
  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");
982
+ const templatesRoot = join8(targetDir, "apps/templates");
983
+ const chosen = join8(templatesRoot, `client-${opts.ui}`);
984
+ const destClient = join8(targetDir, "apps/client");
839
985
  let chosenUi = opts.ui;
840
986
  try {
841
987
  const s = await stat(chosen);
@@ -843,9 +989,9 @@ async function selectClientTemplate(targetDir, opts) {
843
989
  await copyTree(chosen, destClient);
844
990
  } catch {
845
991
  chosenUi = "shadcn";
846
- await copyTree(join4(templatesRoot, "client-shadcn"), destClient);
992
+ await copyTree(join8(templatesRoot, "client-shadcn"), destClient);
847
993
  }
848
- await rm2(templatesRoot, { recursive: true, force: true });
994
+ await rm4(templatesRoot, { recursive: true, force: true });
849
995
  await rewriteClientPaths(destClient, chosenUi);
850
996
  }
851
997
  async function rewriteClientPaths(clientDir, ui) {
@@ -858,11 +1004,11 @@ async function rewriteClientPaths(clientDir, ui) {
858
1004
  "eslint.config.mjs"
859
1005
  ];
860
1006
  for (const rel of candidates) {
861
- const path = join4(clientDir, rel);
1007
+ const path = join8(clientDir, rel);
862
1008
  try {
863
- const raw = await readFile4(path, "utf8");
1009
+ const raw = await readFile6(path, "utf8");
864
1010
  const next = raw.replaceAll("../../../", "../../").replaceAll(`apps/templates/client-${ui}`, "apps/client").replaceAll(`client-${ui}`, "client");
865
- if (next !== raw) await writeFile4(path, next);
1011
+ if (next !== raw) await writeFile8(path, next);
866
1012
  } catch {
867
1013
  }
868
1014
  }
@@ -878,12 +1024,12 @@ function gitInit(cwd, projectName) {
878
1024
  }
879
1025
  function resolveYarnBin(cwd) {
880
1026
  try {
881
- const yarnrc = readFileSync(join4(cwd, ".yarnrc.yml"), "utf8");
1027
+ const yarnrc = readFileSync(join8(cwd, ".yarnrc.yml"), "utf8");
882
1028
  const match = yarnrc.match(/^yarnPath:\s*(.+)$/m);
883
- if (match?.[1]) return join4(cwd, match[1].trim());
1029
+ if (match?.[1]) return join8(cwd, match[1].trim());
884
1030
  } catch {
885
1031
  }
886
- return join4(cwd, ".yarn", "releases", "yarn-4.5.0.cjs");
1032
+ return join8(cwd, ".yarn", "releases", "yarn-4.5.0.cjs");
887
1033
  }
888
1034
  function runInstall(cwd, pm) {
889
1035
  if (pm === "yarn") {
@@ -906,19 +1052,29 @@ async function scaffold(opts, templatesDir) {
906
1052
  await selectClientTemplate(opts.targetDir, opts);
907
1053
  await writeClientEnv(opts.targetDir);
908
1054
  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);
1055
+ await cleanupUnusedFeatures(opts.targetDir, opts);
1056
+ await writeFeaturesWiring(opts.targetDir, opts);
1057
+ await writeNavConfig(opts.targetDir, opts);
1058
+ await cleanupUnusedAuth(opts.targetDir, opts.authProvider);
1059
+ await writeAuthProvider(opts.targetDir, opts.authProvider);
1060
+ if (opts.upload !== "none") {
1061
+ await cleanupUnusedStorage(opts.targetDir, opts.upload);
1062
+ await writeStorageProvider(opts.targetDir, opts.upload);
1063
+ }
1064
+ if (opts.example !== "none") {
1065
+ await cleanupUnusedDb(opts.targetDir, opts.dbProvider);
1066
+ await writeDbProvider(opts.targetDir, opts.dbProvider);
1067
+ }
915
1068
  const firebaseUsed = opts.authProvider === "firebase" || opts.dbProvider === "firebase" || opts.upload === "firebase";
916
1069
  if (!firebaseUsed) await removeFirebaseAdminLib(opts.targetDir);
1070
+ await pruneRootProviderDeps(opts.targetDir, opts);
1071
+ await writeBlueprintJson(opts.targetDir, opts);
1072
+ await writeServiceBlueprints(opts.targetDir, opts);
917
1073
  if (opts.packageManager === "yarn") {
918
- await writeFile4(join4(opts.targetDir, "yarn.lock"), "");
1074
+ await writeFile8(join8(opts.targetDir, "yarn.lock"), "");
919
1075
  } else {
920
- await rm2(join4(opts.targetDir, ".yarn"), { recursive: true, force: true });
921
- await rm2(join4(opts.targetDir, ".yarnrc.yml"), { force: true });
1076
+ await rm4(join8(opts.targetDir, ".yarn"), { recursive: true, force: true });
1077
+ await rm4(join8(opts.targetDir, ".yarnrc.yml"), { force: true });
922
1078
  }
923
1079
  if (opts.packageManager === "pnpm") {
924
1080
  await writePnpmWorkspace(opts.targetDir);
@@ -933,8 +1089,8 @@ async function scaffold(opts, templatesDir) {
933
1089
  // src/lib/prompts.ts
934
1090
  import * as p from "@clack/prompts";
935
1091
  import { resolve } from "path";
936
- import { readFile as readFile5 } from "fs/promises";
937
- import { dirname, join as join5 } from "path";
1092
+ import { readFile as readFile7 } from "fs/promises";
1093
+ import { dirname, join as join9 } from "path";
938
1094
  import { fileURLToPath } from "url";
939
1095
  function detectPackageManager() {
940
1096
  const ua = process.env["npm_config_user_agent"] ?? "";
@@ -946,7 +1102,7 @@ function detectPackageManager() {
946
1102
  async function readSelfVersion() {
947
1103
  try {
948
1104
  const here = dirname(fileURLToPath(import.meta.url));
949
- const pkgRaw = await readFile5(join5(here, "..", "package.json"), "utf8");
1105
+ const pkgRaw = await readFile7(join9(here, "..", "package.json"), "utf8");
950
1106
  const pkg = JSON.parse(pkgRaw);
951
1107
  return pkg.version ?? null;
952
1108
  } catch {
@@ -1157,7 +1313,128 @@ Re-run with @latest to refresh:
1157
1313
  install
1158
1314
  };
1159
1315
  }
1316
+
1317
+ // src/manifest/audit.ts
1318
+ import { readFile as readFile8, readdir as readdir3 } from "fs/promises";
1319
+ import { join as join10 } from "path";
1320
+ var IGNORE_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".nx"]);
1321
+ async function walk(dir, out = []) {
1322
+ const entries = await readdir3(dir, { withFileTypes: true });
1323
+ for (const e of entries) {
1324
+ if (e.isDirectory()) {
1325
+ if (!IGNORE_DIRS.has(e.name)) await walk(join10(dir, e.name), out);
1326
+ } else if (/\.(ts|tsx|mjs)$/.test(e.name)) {
1327
+ out.push(join10(dir, e.name));
1328
+ }
1329
+ }
1330
+ return out;
1331
+ }
1332
+ async function tsconfigAliases(dir) {
1333
+ try {
1334
+ const raw = await readFile8(join10(dir, "tsconfig.base.json"), "utf8");
1335
+ const aliases = /* @__PURE__ */ new Set();
1336
+ for (const m of raw.matchAll(/"(@icore\/[a-z0-9.-]+)"\s*:/g)) {
1337
+ if (m[1]) aliases.add(m[1]);
1338
+ }
1339
+ return aliases;
1340
+ } catch {
1341
+ return /* @__PURE__ */ new Set();
1342
+ }
1343
+ }
1344
+ var PROVIDER_SDKS = {
1345
+ supabase: ["@supabase/supabase-js"],
1346
+ cloudinary: ["cloudinary"],
1347
+ mongodb: ["mongoose"],
1348
+ firebase: ["firebase-admin", "@icore/firebase-admin"]
1349
+ };
1350
+ async function readBlueprint(dir) {
1351
+ try {
1352
+ return JSON.parse(await readFile8(join10(dir, "blueprint.json"), "utf8"));
1353
+ } catch {
1354
+ return null;
1355
+ }
1356
+ }
1357
+ function forbiddenFromBlueprint(bp) {
1358
+ const chosen = new Set(
1359
+ [bp.authProvider, bp.dbProvider, bp.upload].filter((p2) => Boolean(p2))
1360
+ );
1361
+ const forbidden = [];
1362
+ for (const [provider, sdks] of Object.entries(PROVIDER_SDKS)) {
1363
+ if (!chosen.has(provider)) forbidden.push(...sdks);
1364
+ }
1365
+ return forbidden;
1366
+ }
1367
+ async function allPackageJsons(dir) {
1368
+ const out = [];
1369
+ const root = join10(dir, "package.json");
1370
+ out.push(root);
1371
+ async function walk2(d) {
1372
+ let entries;
1373
+ try {
1374
+ entries = await readdir3(d, { withFileTypes: true });
1375
+ } catch {
1376
+ return;
1377
+ }
1378
+ for (const e of entries) {
1379
+ if (e.isDirectory()) {
1380
+ if (!IGNORE_DIRS.has(e.name)) await walk2(join10(d, e.name));
1381
+ } else if (e.name === "package.json") {
1382
+ out.push(join10(d, e.name));
1383
+ }
1384
+ }
1385
+ }
1386
+ await walk2(join10(dir, "apps"));
1387
+ return out;
1388
+ }
1389
+ async function depKeys(pkgPath) {
1390
+ try {
1391
+ const pkg = JSON.parse(await readFile8(pkgPath, "utf8"));
1392
+ return /* @__PURE__ */ new Set([
1393
+ ...Object.keys(pkg.dependencies ?? {}),
1394
+ ...Object.keys(pkg.devDependencies ?? {})
1395
+ ]);
1396
+ } catch {
1397
+ return /* @__PURE__ */ new Set();
1398
+ }
1399
+ }
1400
+ var ICORE_IMPORT = /(?:from|import\()\s*['"](@icore\/[a-z0-9.-]+)/g;
1401
+ async function auditProject(dir, opts = {}) {
1402
+ const violations = [];
1403
+ const aliases = await tsconfigAliases(dir);
1404
+ for (const file of await walk(dir)) {
1405
+ const src = await readFile8(file, "utf8");
1406
+ for (const m of src.matchAll(ICORE_IMPORT)) {
1407
+ const alias = m[1];
1408
+ if (alias && !aliases.has(alias)) {
1409
+ violations.push({
1410
+ kind: "import-of-absent-lib",
1411
+ detail: `${file} imports ${alias} (no tsconfig path \u2192 lib absent)`
1412
+ });
1413
+ }
1414
+ }
1415
+ }
1416
+ const bp = await readBlueprint(dir);
1417
+ const forbidden = /* @__PURE__ */ new Set([
1418
+ ...opts.forbiddenDeps ?? [],
1419
+ ...bp ? forbiddenFromBlueprint(bp) : []
1420
+ ]);
1421
+ if (forbidden.size > 0) {
1422
+ for (const pkgPath of await allPackageJsons(dir)) {
1423
+ const deps = await depKeys(pkgPath);
1424
+ for (const f of forbidden) {
1425
+ if (deps.has(f)) {
1426
+ violations.push({
1427
+ kind: "forbidden-dep",
1428
+ detail: `${pkgPath} keeps forbidden dep ${f}`
1429
+ });
1430
+ }
1431
+ }
1432
+ }
1433
+ }
1434
+ return violations;
1435
+ }
1160
1436
  export {
1437
+ auditProject,
1161
1438
  collectOptions,
1162
1439
  pmRun,
1163
1440
  scaffold