@idevconn/create-icore 0.4.1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +304 -24
- package/dist/index.cjs +301 -23
- package/dist/index.d.cts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +300 -23
- package/package.json +1 -1
- package/templates/apps/api/.env.example +14 -0
- package/templates/apps/api/src/app/app.module.ts +5 -1
- package/templates/apps/api/src/main.ts +12 -6
- package/templates/apps/microservices/auth/package.json +1 -1
- package/templates/apps/microservices/auth/project.json +2 -1
- package/templates/apps/microservices/auth/src/app/app.module.ts +50 -39
- package/templates/apps/microservices/auth/src/main.ts +6 -23
- package/templates/apps/microservices/jobs/project.json +2 -1
- package/templates/apps/microservices/jobs/src/app/redis-connection.ts +35 -0
- package/templates/apps/microservices/jobs/src/app/workers/cleanup.worker.ts +2 -1
- package/templates/apps/microservices/jobs/src/app/workers/email.worker.ts +2 -1
- package/templates/apps/microservices/jobs/src/app/workers/image-process.worker.ts +2 -1
- package/templates/apps/microservices/notes/project.json +2 -1
- package/templates/apps/microservices/notes/src/app/app.module.ts +52 -38
- package/templates/apps/microservices/notes/src/main.ts +6 -23
- package/templates/apps/microservices/payment/project.json +2 -1
- package/templates/apps/microservices/payment/src/app/app.module.ts +37 -12
- package/templates/apps/microservices/payment/src/main.ts +6 -23
- package/templates/apps/microservices/upload/package.json +1 -1
- package/templates/apps/microservices/upload/project.json +2 -1
- package/templates/apps/microservices/upload/src/app/app.module.ts +50 -42
- package/templates/apps/microservices/upload/src/main.ts +6 -23
- package/templates/apps/templates/client-antd/.env.example +7 -0
- package/templates/apps/templates/client-antd/vite.config.mts +4 -4
- package/templates/apps/templates/client-mui/.env.example +7 -0
- package/templates/apps/templates/client-mui/vite.config.mts +4 -4
- package/templates/apps/templates/client-shadcn/.env.example +6 -1
- package/templates/apps/templates/client-shadcn/vite.config.mts +4 -4
- package/templates/libs/auth-client/src/index.ts +1 -0
- package/templates/libs/auth-client/src/lib/auth-client.module.ts +1 -1
- package/templates/libs/auth-client/src/lib/auth-client.service.ts +1 -1
- package/templates/libs/auth-client/src/lib/auth-client.tokens.ts +4 -0
- package/templates/libs/firebase-admin/README.md +11 -0
- package/templates/libs/firebase-admin/eslint.config.mjs +24 -0
- package/templates/libs/firebase-admin/package.json +12 -0
- package/templates/libs/firebase-admin/project.json +19 -0
- package/templates/libs/firebase-admin/src/index.ts +1 -0
- package/templates/libs/firebase-admin/src/lib/__tests__/firebase-admin.unit.test.ts +105 -0
- package/templates/libs/firebase-admin/src/lib/firebase-admin.ts +70 -0
- package/templates/libs/firebase-admin/tsconfig.json +23 -0
- package/templates/libs/firebase-admin/tsconfig.lib.json +23 -0
- package/templates/libs/firebase-admin/tsconfig.spec.json +22 -0
- package/templates/libs/firebase-admin/vitest.config.mts +21 -0
- package/templates/libs/jobs-client/src/index.ts +1 -0
- package/templates/libs/jobs-client/src/lib/jobs-client.module.ts +1 -1
- package/templates/libs/jobs-client/src/lib/jobs-client.service.ts +15 -3
- package/templates/libs/jobs-client/src/lib/jobs-client.tokens.ts +4 -0
- package/templates/libs/notes-client/src/index.ts +1 -0
- package/templates/libs/notes-client/src/lib/notes-client.module.ts +1 -1
- package/templates/libs/notes-client/src/lib/notes-client.service.ts +1 -1
- package/templates/libs/notes-client/src/lib/notes-client.tokens.ts +4 -0
- package/templates/libs/payment-client/src/index.ts +1 -0
- package/templates/libs/payment-client/src/lib/payment-client.module.ts +1 -1
- package/templates/libs/payment-client/src/lib/payment-client.service.ts +1 -1
- package/templates/libs/payment-client/src/lib/payment-client.tokens.ts +4 -0
- package/templates/libs/shared/src/__tests__/bootstrap.unit.test.ts +92 -0
- package/templates/libs/shared/src/__tests__/transport.unit.test.ts +14 -2
- package/templates/libs/shared/src/bootstrap.ts +79 -0
- package/templates/libs/shared/src/env.ts +88 -0
- package/templates/libs/shared/src/index.ts +2 -0
- package/templates/libs/shared/src/transport.ts +62 -3
- package/templates/libs/upload-client/src/index.ts +1 -0
- package/templates/libs/upload-client/src/lib/upload-client.module.ts +1 -1
- package/templates/libs/upload-client/src/lib/upload-client.service.ts +1 -1
- package/templates/libs/upload-client/src/lib/upload-client.tokens.ts +4 -0
- package/templates/libs/vite-plugins/src/index.d.mts +6 -0
- package/templates/libs/vite-plugins/src/index.mjs +50 -0
- package/templates/package.json +1 -0
- package/templates/tools/create-icore/_template-shell/package.json +1 -0
- package/templates/tsconfig.base.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -201,6 +201,12 @@ Re-run with @latest to refresh:
|
|
|
201
201
|
});
|
|
202
202
|
if (p.isCancel(transport)) throw new Error("cancelled");
|
|
203
203
|
const packageManager = flags.packageManager ?? detectPackageManager();
|
|
204
|
+
if (packageManager === "yarn") {
|
|
205
|
+
p.note(
|
|
206
|
+
"yarn 4.15+ enforces a 24h publish-age gate (npmMinimalAgeGate=1d), so a\n`yarn create @idevconn/icore@latest` run within 24h of a release resolves an\nolder version. If the banner above shows an unexpectedly old version, either:\n \u2022 wait \u2014 the version auto-unlocks 24h after publish, or\n \u2022 bypass once: yarn config set npmMinimalAgeGate 0 (then re-run), or\n \u2022 use npm/pnpm: npm init @idevconn/icore@latest <name> -- [flags]",
|
|
207
|
+
"\u26A0 yarn 24h age-gate"
|
|
208
|
+
);
|
|
209
|
+
}
|
|
204
210
|
const initGit = flags.initGit ?? !await p.confirm({ message: "Initialise git repo?", initialValue: true }) === false;
|
|
205
211
|
const install = flags.install ?? !await p.confirm({
|
|
206
212
|
message: `Run ${packageManager} install?`,
|
|
@@ -228,6 +234,13 @@ import { copyFile, mkdir, readdir, readFile as readFile2, stat, writeFile, rm }
|
|
|
228
234
|
import { readFileSync } from "fs";
|
|
229
235
|
import { join as join2 } from "path";
|
|
230
236
|
import { spawnSync } from "child_process";
|
|
237
|
+
|
|
238
|
+
// src/lib/options.ts
|
|
239
|
+
function pmRun(pm, script) {
|
|
240
|
+
return pm === "npm" ? `npm run ${script}` : `${pm} ${script}`;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// src/lib/scaffold.ts
|
|
231
244
|
var IGNORE_TOP = /* @__PURE__ */ new Set([
|
|
232
245
|
".git",
|
|
233
246
|
"node_modules",
|
|
@@ -260,9 +273,29 @@ async function rewriteRootPackageJson(targetDir, opts) {
|
|
|
260
273
|
pkg["version"] = "0.0.1";
|
|
261
274
|
pkg["private"] = true;
|
|
262
275
|
delete pkg.description;
|
|
276
|
+
if (opts.transport === "nats") {
|
|
277
|
+
const deps = pkg["dependencies"] ??= {};
|
|
278
|
+
deps["nats"] = "^2.29.3";
|
|
279
|
+
}
|
|
263
280
|
if (opts.packageManager !== "yarn") {
|
|
264
281
|
delete pkg.packageManager;
|
|
265
282
|
}
|
|
283
|
+
if (opts.packageManager === "pnpm") {
|
|
284
|
+
pkg["pnpm"] = {
|
|
285
|
+
onlyBuiltDependencies: [
|
|
286
|
+
"@firebase/util",
|
|
287
|
+
"@nestjs/core",
|
|
288
|
+
"@parcel/watcher",
|
|
289
|
+
"@scarf/scarf",
|
|
290
|
+
"@swc/core",
|
|
291
|
+
"less",
|
|
292
|
+
"msgpackr-extract",
|
|
293
|
+
"nx",
|
|
294
|
+
"protobufjs",
|
|
295
|
+
"unrs-resolver"
|
|
296
|
+
]
|
|
297
|
+
};
|
|
298
|
+
}
|
|
266
299
|
await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
267
300
|
}
|
|
268
301
|
async function writeAuthEnv(targetDir, opts) {
|
|
@@ -300,9 +333,9 @@ async function writeNotesEnv(targetDir, opts) {
|
|
|
300
333
|
async function writeGatewayEnv(targetDir, opts) {
|
|
301
334
|
const envExample = join2(targetDir, "apps/api/.env.example");
|
|
302
335
|
const env = await readFile2(envExample, "utf8");
|
|
303
|
-
let next = env.replace(/^AUTH_TRANSPORT=.*$/m, `AUTH_TRANSPORT=${opts.transport}`).replace(/^UPLOAD_TRANSPORT=.*$/m, `UPLOAD_TRANSPORT=${opts.transport}`);
|
|
336
|
+
let next = env.replace(/^AUTH_TRANSPORT=.*$/m, `AUTH_TRANSPORT=${opts.transport}`).replace(/^UPLOAD_TRANSPORT=.*$/m, `UPLOAD_TRANSPORT=${opts.transport}`).replace(/^NOTES_TRANSPORT=.*$/m, `NOTES_TRANSPORT=${opts.transport}`).replace(/^PAYMENT_TRANSPORT=.*$/m, `PAYMENT_TRANSPORT=${opts.transport}`);
|
|
304
337
|
if (opts.transport !== "tcp") {
|
|
305
|
-
next = next.replace(/^# (AUTH_(?:REDIS|NATS)_URL=)/m, "$1").replace(/^# (UPLOAD_(?:REDIS|NATS)_URL=)/m, "$1");
|
|
338
|
+
next = next.replace(/^# (AUTH_(?:REDIS|NATS)_URL=)/m, "$1").replace(/^# (UPLOAD_(?:REDIS|NATS)_URL=)/m, "$1").replace(/^# (NOTES_(?:REDIS|NATS)_URL=)/m, "$1").replace(/^# (PAYMENT_(?:REDIS|NATS)_URL=)/m, "$1");
|
|
306
339
|
}
|
|
307
340
|
await writeFile(join2(targetDir, "apps/api/.env"), next);
|
|
308
341
|
}
|
|
@@ -315,6 +348,25 @@ async function writeRootEnv(targetDir, opts) {
|
|
|
315
348
|
];
|
|
316
349
|
await writeFile(join2(targetDir, ".env"), lines.join("\n"));
|
|
317
350
|
}
|
|
351
|
+
async function stripGatewayTransport(targetDir, prefix) {
|
|
352
|
+
const gatewayEnv = join2(targetDir, "apps/api/.env");
|
|
353
|
+
try {
|
|
354
|
+
const env = await readFile2(gatewayEnv, "utf8");
|
|
355
|
+
const next = env.split("\n").filter(
|
|
356
|
+
(line) => !line.startsWith(`${prefix}_`) && !line.startsWith(`# ${prefix}_`) && !line.includes(`${prefix} MS transport`)
|
|
357
|
+
).join("\n");
|
|
358
|
+
await writeFile(gatewayEnv, next);
|
|
359
|
+
} catch {
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
async function writeClientEnv(targetDir) {
|
|
363
|
+
const envExample = join2(targetDir, "apps/client/.env.example");
|
|
364
|
+
try {
|
|
365
|
+
const env = await readFile2(envExample, "utf8");
|
|
366
|
+
await writeFile(join2(targetDir, "apps/client/.env"), env);
|
|
367
|
+
} catch {
|
|
368
|
+
}
|
|
369
|
+
}
|
|
318
370
|
async function writePaymentEnv(targetDir, opts) {
|
|
319
371
|
if (opts.payment === "none") return;
|
|
320
372
|
const envExample = join2(targetDir, "apps/microservices/payment/.env.example");
|
|
@@ -391,6 +443,7 @@ async function removePaymentStack(targetDir) {
|
|
|
391
443
|
"@icore/payment-client",
|
|
392
444
|
"@idevconn/payment"
|
|
393
445
|
]);
|
|
446
|
+
await stripGatewayTransport(targetDir, "PAYMENT");
|
|
394
447
|
}
|
|
395
448
|
async function removeNotesStack(targetDir) {
|
|
396
449
|
for (const p3 of [
|
|
@@ -412,6 +465,7 @@ async function removeNotesStack(targetDir) {
|
|
|
412
465
|
} catch {
|
|
413
466
|
}
|
|
414
467
|
await stripDeps(join2(targetDir, "apps/api/package.json"), ["@icore/notes-client"]);
|
|
468
|
+
await stripGatewayTransport(targetDir, "NOTES");
|
|
415
469
|
const tsconfigPath = join2(targetDir, "tsconfig.base.json");
|
|
416
470
|
try {
|
|
417
471
|
const src = await readFile2(tsconfigPath, "utf8");
|
|
@@ -468,15 +522,17 @@ async function stripTsconfigPath(targetDir, alias) {
|
|
|
468
522
|
}
|
|
469
523
|
async function removeUnusedAuthStrategies(targetDir, authProvider) {
|
|
470
524
|
const modulePath = join2(targetDir, "apps/microservices/auth/src/app/app.module.ts");
|
|
525
|
+
const AUTH_BRANCH = /if \(provider === 'supabase'\) return makeSupabaseAuth\(cfg\);\n\s*return makeFirebaseAuth\(cfg\);/m;
|
|
471
526
|
if (authProvider === "supabase") {
|
|
472
527
|
await rm(join2(targetDir, "libs/auth-strategies/firebase"), { recursive: true, force: true });
|
|
473
528
|
await stripDeps(join2(targetDir, "apps/microservices/auth/package.json"), [
|
|
474
|
-
"@icore/auth-firebase"
|
|
529
|
+
"@icore/auth-firebase",
|
|
530
|
+
"@icore/firebase-admin"
|
|
475
531
|
]);
|
|
476
532
|
await stripTsconfigPath(targetDir, "@icore/auth-firebase");
|
|
477
533
|
try {
|
|
478
534
|
const src = await readFile2(modulePath, "utf8");
|
|
479
|
-
const next = src.replace(/^import
|
|
535
|
+
const next = src.replace(/^import \{[^}]*\} from '@icore\/firebase-admin';\n/m, "").replace(/^import \{[^}]*FirebaseAuthStrategy[^}]*\} from '@icore\/auth-firebase';\n/m, "").replace(/^ {2}firebase: \[[^\]]*\],\n/m, "").replace(/\nfunction makeFirebaseAuth[\s\S]*?\n}\n/m, "").replace(AUTH_BRANCH, "return makeSupabaseAuth(cfg);");
|
|
480
536
|
await writeFile(modulePath, next);
|
|
481
537
|
} catch {
|
|
482
538
|
}
|
|
@@ -489,10 +545,7 @@ async function removeUnusedAuthStrategies(targetDir, authProvider) {
|
|
|
489
545
|
await stripTsconfigPath(targetDir, "@icore/auth-supabase");
|
|
490
546
|
try {
|
|
491
547
|
const src = await readFile2(modulePath, "utf8");
|
|
492
|
-
const next = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/m, "").replace(/^import \{[^}]*SupabaseAuthStrategy[^}]*\} from '@icore\/auth-supabase';\n/m, "").replace(
|
|
493
|
-
/\n {10}case 'supabase': \{[\s\S]*?return new SupabaseAuthStrategy\(\{ client \}\);\n {10}\}\n/m,
|
|
494
|
-
""
|
|
495
|
-
);
|
|
548
|
+
const next = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/m, "").replace(/^import \{[^}]*SupabaseAuthStrategy[^}]*\} from '@icore\/auth-supabase';\n/m, "").replace(/\nfunction makeSupabaseAuth[\s\S]*?\n}\n/m, "").replace(AUTH_BRANCH, "return makeFirebaseAuth(cfg);");
|
|
496
549
|
await writeFile(modulePath, next);
|
|
497
550
|
} catch {
|
|
498
551
|
}
|
|
@@ -504,7 +557,8 @@ async function removeUnusedStorageStrategies(targetDir, uploadProvider) {
|
|
|
504
557
|
if (uploadProvider !== "firebase") {
|
|
505
558
|
await rm(join2(targetDir, "libs/storage-strategies/firebase"), { recursive: true, force: true });
|
|
506
559
|
await stripDeps(join2(targetDir, "apps/microservices/upload/package.json"), [
|
|
507
|
-
"@icore/storage-firebase"
|
|
560
|
+
"@icore/storage-firebase",
|
|
561
|
+
"@icore/firebase-admin"
|
|
508
562
|
]);
|
|
509
563
|
await stripTsconfigPath(targetDir, "@icore/storage-firebase");
|
|
510
564
|
}
|
|
@@ -528,26 +582,26 @@ async function removeUnusedStorageStrategies(targetDir, uploadProvider) {
|
|
|
528
582
|
try {
|
|
529
583
|
let src = await readFile2(modulePath, "utf8");
|
|
530
584
|
if (uploadProvider !== "firebase") {
|
|
531
|
-
src = src.replace(/^import
|
|
585
|
+
src = src.replace(/^import \{[^}]*\} from '@icore\/firebase-admin';\n/m, "").replace(
|
|
532
586
|
/^import \{[^}]*FirebaseStorageStrategy[^}]*\} from '@icore\/storage-firebase';\n/m,
|
|
533
587
|
""
|
|
534
|
-
).replace(/^
|
|
588
|
+
).replace(/^ {2}firebase: \[[^\]]*\],\n/m, "").replace(/\nfunction makeFirebaseStorage[\s\S]*?\n}\n/m, "");
|
|
535
589
|
}
|
|
536
590
|
if (uploadProvider !== "cloudinary") {
|
|
537
591
|
src = src.replace(/^import \{ v2 as cloudinary \} from 'cloudinary';\n/m, "").replace(
|
|
538
592
|
/^import \{[^}]*CloudinaryStorageStrategy[^}]*\} from '@icore\/storage-cloudinary';\n/m,
|
|
539
593
|
""
|
|
540
|
-
).replace(
|
|
594
|
+
).replace(/\nfunction makeCloudinaryStorage[\s\S]*?\n}\n/m, "");
|
|
541
595
|
}
|
|
542
596
|
if (uploadProvider !== "supabase") {
|
|
543
597
|
src = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/m, "").replace(
|
|
544
598
|
/^import \{[^}]*SupabaseStorageStrategy[^}]*\} from '@icore\/storage-supabase';\n/m,
|
|
545
599
|
""
|
|
546
|
-
).replace(
|
|
547
|
-
/\n {10}case 'supabase': \{[\s\S]*?bucket: requireEnv\(cfg, 'SUPABASE_STORAGE_BUCKET'\),\n {12}\}\);\n {10}\}\n/m,
|
|
548
|
-
""
|
|
549
|
-
);
|
|
600
|
+
).replace(/\nfunction makeSupabaseStorage[\s\S]*?\n}\n/m, "");
|
|
550
601
|
}
|
|
602
|
+
const STORAGE_BRANCH = /if \(provider === 'supabase'\) return makeSupabaseStorage\(cfg\);\n\s*if \(provider === 'firebase'\) return makeFirebaseStorage\(cfg\);\n\s*return makeCloudinaryStorage\(cfg\);/m;
|
|
603
|
+
const chosenReturn = `return make${uploadProvider.charAt(0).toUpperCase() + uploadProvider.slice(1)}Storage(cfg);`;
|
|
604
|
+
src = src.replace(STORAGE_BRANCH, chosenReturn);
|
|
551
605
|
await writeFile(modulePath, src);
|
|
552
606
|
} catch {
|
|
553
607
|
}
|
|
@@ -557,14 +611,15 @@ async function removeUnusedDbStrategies(targetDir, dbProvider) {
|
|
|
557
611
|
if (dbProvider === "supabase") {
|
|
558
612
|
await rm(join2(targetDir, "libs/db-strategies/firestore"), { recursive: true, force: true });
|
|
559
613
|
await stripDeps(join2(targetDir, "apps/microservices/notes/package.json"), [
|
|
560
|
-
"@icore/db-firestore"
|
|
614
|
+
"@icore/db-firestore",
|
|
615
|
+
"@icore/firebase-admin"
|
|
561
616
|
]);
|
|
562
617
|
await stripTsconfigPath(targetDir, "@icore/db-firestore");
|
|
563
618
|
try {
|
|
564
619
|
const src = await readFile2(modulePath, "utf8");
|
|
565
|
-
const next = src.replace(/^import
|
|
566
|
-
|
|
567
|
-
""
|
|
620
|
+
const next = src.replace(/^import \{[^}]*\} from '@icore\/firebase-admin';\n/m, "").replace(/^import \{[^}]*FirestoreDBStrategy[^}]*\} from '@icore\/db-firestore';\n/m, "").replace(/^ {2}firestore: \[[^\]]*\],\n/m, "").replace(/^ {2}firebase: \[[^\]]*\],\n/m, "").replace(/\nfunction makeFirestoreDB[\s\S]*?\n}\n/m, "").replace(
|
|
621
|
+
/if \(provider === 'supabase'\) return makeSupabaseDB\(cfg\);\n\s*return makeFirestoreDB\(cfg\);/m,
|
|
622
|
+
"return makeSupabaseDB(cfg);"
|
|
568
623
|
);
|
|
569
624
|
await writeFile(modulePath, next);
|
|
570
625
|
} catch {
|
|
@@ -578,15 +633,19 @@ async function removeUnusedDbStrategies(targetDir, dbProvider) {
|
|
|
578
633
|
await stripTsconfigPath(targetDir, "@icore/db-supabase");
|
|
579
634
|
try {
|
|
580
635
|
const src = await readFile2(modulePath, "utf8");
|
|
581
|
-
const next = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/m, "").replace(/^import \{[^}]*SupabaseDBStrategy[^}]*\} from '@icore\/db-supabase';\n/m, "").replace(
|
|
582
|
-
|
|
583
|
-
""
|
|
636
|
+
const next = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/m, "").replace(/^import \{[^}]*SupabaseDBStrategy[^}]*\} from '@icore\/db-supabase';\n/m, "").replace(/\nfunction makeSupabaseDB[\s\S]*?\n}\n/m, "").replace(
|
|
637
|
+
/if \(provider === 'supabase'\) return makeSupabaseDB\(cfg\);\n\s*return makeFirestoreDB\(cfg\);/m,
|
|
638
|
+
"return makeFirestoreDB(cfg);"
|
|
584
639
|
);
|
|
585
640
|
await writeFile(modulePath, next);
|
|
586
641
|
} catch {
|
|
587
642
|
}
|
|
588
643
|
}
|
|
589
644
|
}
|
|
645
|
+
async function removeFirebaseAdminLib(targetDir) {
|
|
646
|
+
await rm(join2(targetDir, "libs/firebase-admin"), { recursive: true, force: true });
|
|
647
|
+
await stripTsconfigPath(targetDir, "@icore/firebase-admin");
|
|
648
|
+
}
|
|
590
649
|
async function removeUploadStack(targetDir) {
|
|
591
650
|
const paths = [
|
|
592
651
|
"apps/microservices/upload",
|
|
@@ -691,6 +750,7 @@ async function scaffold(opts, templatesDir2) {
|
|
|
691
750
|
await writeGatewayEnv(opts.targetDir, opts);
|
|
692
751
|
await writeRootEnv(opts.targetDir, opts);
|
|
693
752
|
await selectClientTemplate(opts.targetDir, opts);
|
|
753
|
+
await writeClientEnv(opts.targetDir);
|
|
694
754
|
if (opts.upload === "none") await removeUploadStack(opts.targetDir);
|
|
695
755
|
if (opts.payment === "none") await removePaymentStack(opts.targetDir);
|
|
696
756
|
if (opts.jobs === "none") await removeJobsStack(opts.targetDir);
|
|
@@ -698,12 +758,230 @@ async function scaffold(opts, templatesDir2) {
|
|
|
698
758
|
await removeUnusedAuthStrategies(opts.targetDir, opts.authProvider);
|
|
699
759
|
await removeUnusedStorageStrategies(opts.targetDir, opts.upload);
|
|
700
760
|
await removeUnusedDbStrategies(opts.targetDir, opts.dbProvider);
|
|
761
|
+
const firebaseUsed = opts.authProvider === "firebase" || opts.dbProvider === "firebase" || opts.upload === "firebase";
|
|
762
|
+
if (!firebaseUsed) await removeFirebaseAdminLib(opts.targetDir);
|
|
701
763
|
if (opts.packageManager === "yarn") {
|
|
702
764
|
await writeFile(join2(opts.targetDir, "yarn.lock"), "");
|
|
765
|
+
} else {
|
|
766
|
+
await rm(join2(opts.targetDir, ".yarn"), { recursive: true, force: true });
|
|
767
|
+
await rm(join2(opts.targetDir, ".yarnrc.yml"), { force: true });
|
|
703
768
|
}
|
|
769
|
+
await patchGitignoreForPm(opts.targetDir, opts.packageManager);
|
|
770
|
+
await writeAiFiles(opts.targetDir, opts);
|
|
704
771
|
if (opts.install) runInstall(opts.targetDir, opts.packageManager);
|
|
705
772
|
if (opts.initGit) gitInit(opts.targetDir, opts.projectName);
|
|
706
773
|
}
|
|
774
|
+
async function patchGitignoreForPm(targetDir, pm) {
|
|
775
|
+
const giPath = join2(targetDir, ".gitignore");
|
|
776
|
+
try {
|
|
777
|
+
let src = await readFile2(giPath, "utf8");
|
|
778
|
+
src = src.replace(/^# Build artifacts.*\ntools\/create-icore\/templates\/\s*\n/m, "");
|
|
779
|
+
if (pm !== "yarn") {
|
|
780
|
+
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, "");
|
|
781
|
+
}
|
|
782
|
+
if (pm === "pnpm") {
|
|
783
|
+
if (!src.includes(".pnpm-debug.log")) {
|
|
784
|
+
src += "\n# pnpm\n.pnpm-debug.log*\n";
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
if (pm === "npm") {
|
|
788
|
+
if (!src.includes("npm-debug.log")) {
|
|
789
|
+
src += "\n# npm\nnpm-debug.log*\n";
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
await writeFile(giPath, src);
|
|
793
|
+
} catch {
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
async function writeAiFiles(targetDir, opts) {
|
|
797
|
+
const pm = opts.packageManager;
|
|
798
|
+
const nx = pm === "npm" ? "npx nx" : `${pm} nx`;
|
|
799
|
+
const devCmd = pmRun(pm, "dev");
|
|
800
|
+
const activeMSes = ["auth (port 4001)"];
|
|
801
|
+
if (opts.upload !== "none") activeMSes.push(`upload (port 4002)`);
|
|
802
|
+
if (opts.payment !== "none") activeMSes.push(`payment (port 4003)`);
|
|
803
|
+
if (opts.example !== "none") activeMSes.push(`notes (port 4004)`);
|
|
804
|
+
if (opts.jobs !== "none") activeMSes.push(`jobs (standalone)`);
|
|
805
|
+
const usesSupabase = opts.authProvider === "supabase" || opts.dbProvider === "supabase" || opts.upload === "supabase";
|
|
806
|
+
const usesFirebase = opts.authProvider === "firebase" || opts.dbProvider === "firebase" || opts.upload === "firebase";
|
|
807
|
+
await writeFile(join2(targetDir, "CLAUDE.md"), "@AGENTS.md\n");
|
|
808
|
+
const uiLabel = { shadcn: "shadcn/ui + Tailwind", antd: "Ant Design 6", mui: "MUI 6" }[opts.ui];
|
|
809
|
+
const readme = `# ${opts.projectName}
|
|
810
|
+
|
|
811
|
+
> Scaffolded with [iCore](https://github.com/iDEVconn/create-icore) \u2014 Nx + NestJS + React full-stack template.
|
|
812
|
+
|
|
813
|
+
## Stack
|
|
814
|
+
|
|
815
|
+
| Layer | Technology |
|
|
816
|
+
|-------|-----------|
|
|
817
|
+
| Monorepo | Nx + ${pm} |
|
|
818
|
+
| Gateway | NestJS 11 + Swagger |
|
|
819
|
+
| Auth | ${opts.authProvider} |
|
|
820
|
+
| Database | ${opts.dbProvider} |
|
|
821
|
+
| Upload | ${opts.upload === "none" ? "\u2014" : opts.upload} |
|
|
822
|
+
| UI | ${uiLabel} + TanStack Router + Query |
|
|
823
|
+
| i18n | i18next (en / ru / he) |
|
|
824
|
+
|
|
825
|
+
## Quick start
|
|
826
|
+
|
|
827
|
+
\`\`\`bash
|
|
828
|
+
# 1. Fill in provider credentials
|
|
829
|
+
# apps/microservices/auth/.env
|
|
830
|
+
# apps/microservices/upload/.env (if upload is enabled)
|
|
831
|
+
# apps/client/.env (VITE_API_URL \u2014 already defaults to /api)
|
|
832
|
+
|
|
833
|
+
# 2. Start everything
|
|
834
|
+
${devCmd}
|
|
835
|
+
# \u2192 http://localhost:4200 client
|
|
836
|
+
# \u2192 http://localhost:3001/api/docs Swagger
|
|
837
|
+
\`\`\`
|
|
838
|
+
|
|
839
|
+
## Commands
|
|
840
|
+
|
|
841
|
+
\`\`\`bash
|
|
842
|
+
${nx} run <project>:serve # start a single service
|
|
843
|
+
${nx} test <project> # unit tests
|
|
844
|
+
${nx} lint <project> # lint
|
|
845
|
+
${nx} build <project> # production build
|
|
846
|
+
${pm === "yarn" ? "yarn remove-notes" : pm === "pnpm" ? "pnpm remove-notes" : "npm run remove-notes"} # strip the notes sample feature
|
|
847
|
+
\`\`\`
|
|
848
|
+
|
|
849
|
+
## Scaffolded by
|
|
850
|
+
|
|
851
|
+
[iCore](https://github.com/iDEVconn/create-icore) \u2014 [@idevconn/create-icore](https://www.npmjs.com/package/@idevconn/create-icore)
|
|
852
|
+
|
|
853
|
+
## License
|
|
854
|
+
|
|
855
|
+
Apache-2.0
|
|
856
|
+
`;
|
|
857
|
+
await writeFile(join2(targetDir, "README.md"), readme);
|
|
858
|
+
const agents = `# ${opts.projectName} \u2014 Agent Instructions
|
|
859
|
+
|
|
860
|
+
## Stack snapshot
|
|
861
|
+
|
|
862
|
+
| Dimension | Choice |
|
|
863
|
+
|------------|--------|
|
|
864
|
+
| Auth | ${opts.authProvider} |
|
|
865
|
+
| Database | ${opts.dbProvider} |
|
|
866
|
+
| Upload | ${opts.upload} |
|
|
867
|
+
| Payment | ${opts.payment} |
|
|
868
|
+
| Jobs | ${opts.jobs} |
|
|
869
|
+
| UI | ${opts.ui} |
|
|
870
|
+
| Transport | ${opts.transport} |
|
|
871
|
+
| PM | ${pm} |
|
|
872
|
+
|
|
873
|
+
## \u{1F680} Mandatory Workflow
|
|
874
|
+
|
|
875
|
+
- **Branch strategy**: \`dev\` is default. Cut \`feature/<name>\` or \`bug/<name>\` from dev. PRs only target dev. Never push directly to main.
|
|
876
|
+
- **No code without approval**: Propose changes first, wait for go-ahead.
|
|
877
|
+
- **\u0417\u0410\u041A\u041E\u041D \u2014 no crash on missing .env**: MS factories must catch config errors, print a boxed banner with ALL missing vars, and return a Fake strategy in dev. In prod (\`NODE_ENV=production\`) throw the same banner. The \`formatEnvBanner\` + \`missingEnv\` helpers from \`@icore/shared\` handle this.
|
|
878
|
+
- **Post-coding routine**: \`npx prettier --write <files>\` \u2192 \`${nx} lint <project>\` \u2192 \`${nx} build <project>\` \u2014 all green before committing.
|
|
879
|
+
- **Nx generators only**: never hand-write \`project.json\` / tsconfig stacks. Use \`${nx} g @nx/<plugin>:<schematic>\`.
|
|
880
|
+
|
|
881
|
+
## Architecture
|
|
882
|
+
|
|
883
|
+
\`\`\`
|
|
884
|
+
apps/
|
|
885
|
+
\u251C\u2500\u2500 api/ NestJS gateway \u2014 all client traffic enters here (:3001)
|
|
886
|
+
\u251C\u2500\u2500 microservices/
|
|
887
|
+
${activeMSes.map((s) => `\u2502 \u251C\u2500\u2500 ${s.split(" ")[0]}/`).join("\n")}
|
|
888
|
+
\u2514\u2500\u2500 client/ Vite + React 19 + ${opts.ui} (:4200)
|
|
889
|
+
libs/
|
|
890
|
+
\u251C\u2500\u2500 shared/ contracts, CASL, transport helpers, env banner utils
|
|
891
|
+
\u251C\u2500\u2500 auth-strategies/${opts.authProvider}/
|
|
892
|
+
${opts.upload !== "none" ? `\u251C\u2500\u2500 storage-strategies/${opts.upload}/
|
|
893
|
+
` : ""}\u251C\u2500\u2500 db-strategies/${opts.dbProvider === "firebase" ? "firestore" : opts.dbProvider}/
|
|
894
|
+
\u251C\u2500\u2500 auth-client/ gateway \u2192 auth MS (TCP/Redis/NATS)
|
|
895
|
+
${opts.upload !== "none" ? `\u251C\u2500\u2500 upload-client/ gateway \u2192 upload MS
|
|
896
|
+
` : ""}\u2514\u2500\u2500 template-shared/ browser-safe React foundation (stores, i18n, CASL)
|
|
897
|
+
\`\`\`
|
|
898
|
+
|
|
899
|
+
## Key patterns
|
|
900
|
+
|
|
901
|
+
**Strategy swap** \u2014 provider is chosen at runtime via env. Never import a concrete strategy in app code; always inject via the factory token (\`AuthStrategy\`, \`StorageStrategy\`, \`DBStrategy\`).
|
|
902
|
+
|
|
903
|
+
**Transport** \u2014 \`buildTransport(prefix)\` reads \`${opts.transport.toUpperCase()}*\` vars. Same helper on gateway client-modules and each MS \`main.ts\`. Supports tcp / redis / nats \u2014 change by flipping \`*_TRANSPORT\` in \`.env\`.
|
|
904
|
+
|
|
905
|
+
**Env layering**:
|
|
906
|
+
1. Root \`.env\` \u2014 \`DB_PROVIDER\`
|
|
907
|
+
2. \`apps/api/.env\` \u2014 gateway transport endpoints
|
|
908
|
+
3. \`apps/microservices/<name>/.env\` \u2014 each MS provider + transport
|
|
909
|
+
4. \`apps/client/.env\` \u2014 \`VITE_API_URL\`
|
|
910
|
+
|
|
911
|
+
## Commands
|
|
912
|
+
|
|
913
|
+
\`\`\`bash
|
|
914
|
+
${devCmd} # start all services
|
|
915
|
+
${nx} run api:serve # gateway only
|
|
916
|
+
${nx} run auth:serve # auth MS only
|
|
917
|
+
${nx} test <project> # run tests
|
|
918
|
+
${nx} lint <project> # lint
|
|
919
|
+
${nx} build <project> # build
|
|
920
|
+
${nx} g @nx/nest:resource # generate NestJS resource
|
|
921
|
+
\`\`\`
|
|
922
|
+
|
|
923
|
+
## .env files to configure
|
|
924
|
+
|
|
925
|
+
| File | Key vars |
|
|
926
|
+
|------|----------|
|
|
927
|
+
| \`apps/microservices/auth/.env\` | \`AUTH_PROVIDER=${opts.authProvider}\`, ${opts.authProvider === "supabase" ? "`SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`" : "`FB_ADMIN_*`, `FIREBASE_WEB_API_KEY`"} |
|
|
928
|
+
${opts.upload !== "none" ? `| \`apps/microservices/upload/.env\` | \`STORAGE_PROVIDER=${opts.upload}\`, provider creds |
|
|
929
|
+
` : ""}| \`apps/microservices/notes/.env\` | \`DB_PROVIDER=${opts.dbProvider}\`, DB creds |
|
|
930
|
+
| \`apps/client/.env\` | \`VITE_API_URL=/api\` (proxied to :3001 in dev) |
|
|
931
|
+
|
|
932
|
+
## Testing
|
|
933
|
+
|
|
934
|
+
- Unit tests: Vitest, files named \`*.unit.test.ts(x)\` in \`__tests__/\` next to source.
|
|
935
|
+
- Test behaviour, not implementation. Fake strategies from \`@icore/shared\` (FakeAuthStrategy etc.) serve as test doubles.
|
|
936
|
+
- Run: \`${nx} test <project>\`
|
|
937
|
+
`;
|
|
938
|
+
await writeFile(join2(targetDir, "AGENTS.md"), agents);
|
|
939
|
+
await mkdir(join2(targetDir, ".claude"), { recursive: true });
|
|
940
|
+
const mcpServers = {
|
|
941
|
+
nx: {
|
|
942
|
+
command: "npx",
|
|
943
|
+
args: ["-y", "@nx/mcp@latest", "--directory", "."],
|
|
944
|
+
type: "stdio"
|
|
945
|
+
}
|
|
946
|
+
};
|
|
947
|
+
if (usesSupabase) {
|
|
948
|
+
mcpServers["supabase"] = {
|
|
949
|
+
command: "npx",
|
|
950
|
+
args: [
|
|
951
|
+
"-y",
|
|
952
|
+
"@supabase/mcp-server-supabase@latest",
|
|
953
|
+
"--access-token",
|
|
954
|
+
"<SUPABASE_PERSONAL_ACCESS_TOKEN>"
|
|
955
|
+
],
|
|
956
|
+
type: "stdio"
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
if (usesFirebase) {
|
|
960
|
+
mcpServers["firebase"] = {
|
|
961
|
+
command: "npx",
|
|
962
|
+
args: ["-y", "firebase-tools@latest", "experimental:mcp"],
|
|
963
|
+
type: "stdio"
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
const nxCmds = [`Bash(${nx} *)`, `Bash(${devCmd})`];
|
|
967
|
+
if (pm !== "npm") nxCmds.push(`Bash(npx nx *)`);
|
|
968
|
+
const settings = {
|
|
969
|
+
mcpServers,
|
|
970
|
+
permissions: {
|
|
971
|
+
allow: [
|
|
972
|
+
...nxCmds,
|
|
973
|
+
"Bash(npx prettier *)",
|
|
974
|
+
"Bash(git status)",
|
|
975
|
+
"Bash(git diff *)",
|
|
976
|
+
"Bash(git log *)"
|
|
977
|
+
]
|
|
978
|
+
}
|
|
979
|
+
};
|
|
980
|
+
await writeFile(
|
|
981
|
+
join2(targetDir, ".claude", "settings.json"),
|
|
982
|
+
JSON.stringify(settings, null, 2) + "\n"
|
|
983
|
+
);
|
|
984
|
+
}
|
|
707
985
|
|
|
708
986
|
// src/cli.ts
|
|
709
987
|
var [nodeMajor] = process.versions.node.split(".").map(Number);
|
|
@@ -735,7 +1013,9 @@ async function main() {
|
|
|
735
1013
|
p2.log.info(`Next:`);
|
|
736
1014
|
p2.log.info(` cd ${opts.projectName}`);
|
|
737
1015
|
if (!opts.install) p2.log.info(` ${opts.packageManager} install`);
|
|
738
|
-
p2.log.info(
|
|
1016
|
+
p2.log.info(
|
|
1017
|
+
` ${pmRun(opts.packageManager, "dev")} # gateway + auth MS + upload MS + client`
|
|
1018
|
+
);
|
|
739
1019
|
p2.log.info(` open http://localhost:4200`);
|
|
740
1020
|
p2.log.info(` edit apps/microservices/auth/.env to plug in real ${opts.authProvider} creds`);
|
|
741
1021
|
}
|