@idevconn/create-icore 0.3.0 → 0.4.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.
- package/dist/cli.js +193 -7
- package/dist/index.cjs +182 -5
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +182 -5
- package/package.json +1 -1
- package/templates/apps/microservices/auth/src/app/app.module.ts +33 -19
- package/templates/apps/microservices/notes/src/app/app.module.ts +39 -23
- package/templates/apps/microservices/upload/src/app/app.module.ts +41 -25
- package/templates/apps/templates/client-antd/vite.config.mts +16 -48
- package/templates/apps/templates/client-mui/vite.config.mts +16 -48
- package/templates/apps/templates/client-shadcn/vite.config.mts +16 -48
- package/templates/libs/shared/package.json +10 -0
- package/templates/libs/shared/src/__tests__/cross-boundary.unit.test.ts +121 -0
- package/templates/libs/shared/src/client.ts +5 -0
- package/templates/libs/shared/src/strategies/fakes/fake-auth.ts +8 -9
- package/templates/libs/shared/src/strategies/fakes/fake-storage.ts +1 -2
- package/templates/libs/template-shared/src/lib/abilities/ability-provider.tsx +1 -1
- package/templates/libs/vite-plugins/README.md +7 -0
- package/templates/libs/vite-plugins/eslint.config.mjs +19 -0
- package/templates/libs/vite-plugins/package.json +18 -0
- package/templates/libs/vite-plugins/project.json +19 -0
- package/templates/libs/vite-plugins/src/index.d.mts +21 -0
- package/templates/libs/vite-plugins/src/index.mjs +106 -0
- package/templates/libs/vite-plugins/tsconfig.json +20 -0
- package/templates/libs/vite-plugins/tsconfig.lib.json +9 -0
- package/templates/tsconfig.base.json +3 -1
package/dist/index.js
CHANGED
|
@@ -34,6 +34,9 @@ async function rewriteRootPackageJson(targetDir, opts) {
|
|
|
34
34
|
pkg["version"] = "0.0.1";
|
|
35
35
|
pkg["private"] = true;
|
|
36
36
|
delete pkg.description;
|
|
37
|
+
if (opts.packageManager !== "yarn") {
|
|
38
|
+
delete pkg.packageManager;
|
|
39
|
+
}
|
|
37
40
|
await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
38
41
|
}
|
|
39
42
|
async function writeAuthEnv(targetDir, opts) {
|
|
@@ -55,6 +58,19 @@ async function writeUploadEnv(targetDir, opts) {
|
|
|
55
58
|
}
|
|
56
59
|
await writeFile(join(targetDir, "apps/microservices/upload/.env"), next);
|
|
57
60
|
}
|
|
61
|
+
async function writeNotesEnv(targetDir, opts) {
|
|
62
|
+
if (opts.example === "none") return;
|
|
63
|
+
const envExample = join(targetDir, "apps/microservices/notes/.env.example");
|
|
64
|
+
try {
|
|
65
|
+
const env = await readFile(envExample, "utf8");
|
|
66
|
+
let next = env.replace(/^NOTES_TRANSPORT=.*$/m, `NOTES_TRANSPORT=${opts.transport}`);
|
|
67
|
+
if (opts.transport !== "tcp") {
|
|
68
|
+
next = next.replace(/^# (NOTES_(?:REDIS|NATS)_URL=)/m, "$1");
|
|
69
|
+
}
|
|
70
|
+
await writeFile(join(targetDir, "apps/microservices/notes/.env"), next);
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
}
|
|
58
74
|
async function writeGatewayEnv(targetDir, opts) {
|
|
59
75
|
const envExample = join(targetDir, "apps/api/.env.example");
|
|
60
76
|
const env = await readFile(envExample, "utf8");
|
|
@@ -206,6 +222,145 @@ async function removeNotesStack(targetDir) {
|
|
|
206
222
|
} catch {
|
|
207
223
|
}
|
|
208
224
|
}
|
|
225
|
+
async function stripTsconfigPath(targetDir, alias) {
|
|
226
|
+
const tsconfigPath = join(targetDir, "tsconfig.base.json");
|
|
227
|
+
try {
|
|
228
|
+
const src = await readFile(tsconfigPath, "utf8");
|
|
229
|
+
const escaped = alias.replace(/[@/]/g, (c) => c === "@" ? "@" : "\\/");
|
|
230
|
+
const pretty = src.replace(new RegExp(`^\\s*"${escaped}": \\[[^\\]]*\\],?\\n`, "m"), "");
|
|
231
|
+
if (pretty !== src) {
|
|
232
|
+
await writeFile(tsconfigPath, pretty);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const parsed = JSON.parse(src);
|
|
236
|
+
if (parsed.compilerOptions?.paths) {
|
|
237
|
+
delete parsed.compilerOptions.paths[alias];
|
|
238
|
+
}
|
|
239
|
+
await writeFile(tsconfigPath, JSON.stringify(parsed));
|
|
240
|
+
} catch {
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async function removeUnusedAuthStrategies(targetDir, authProvider) {
|
|
244
|
+
const modulePath = join(targetDir, "apps/microservices/auth/src/app/app.module.ts");
|
|
245
|
+
if (authProvider === "supabase") {
|
|
246
|
+
await rm(join(targetDir, "libs/auth-strategies/firebase"), { recursive: true, force: true });
|
|
247
|
+
await stripDeps(join(targetDir, "apps/microservices/auth/package.json"), [
|
|
248
|
+
"@icore/auth-firebase"
|
|
249
|
+
]);
|
|
250
|
+
await stripTsconfigPath(targetDir, "@icore/auth-firebase");
|
|
251
|
+
try {
|
|
252
|
+
const src = await readFile(modulePath, "utf8");
|
|
253
|
+
const next = src.replace(/^import \* as admin from 'firebase-admin';\n/m, "").replace(/^import \{[^}]*FirebaseAuthStrategy[^}]*\} from '@icore\/auth-firebase';\n/m, "").replace(/^function makeFirebaseStrategy\b[\s\S]*?\n^}\n/m, "").replace(/(?<=\n) *case 'firebase':\n *return makeFirebaseStrategy\(cfg\);\n/, "");
|
|
254
|
+
await writeFile(modulePath, next);
|
|
255
|
+
} catch {
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (authProvider === "firebase") {
|
|
259
|
+
await rm(join(targetDir, "libs/auth-strategies/supabase"), { recursive: true, force: true });
|
|
260
|
+
await stripDeps(join(targetDir, "apps/microservices/auth/package.json"), [
|
|
261
|
+
"@icore/auth-supabase"
|
|
262
|
+
]);
|
|
263
|
+
await stripTsconfigPath(targetDir, "@icore/auth-supabase");
|
|
264
|
+
try {
|
|
265
|
+
const src = await readFile(modulePath, "utf8");
|
|
266
|
+
const next = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/m, "").replace(/^import \{[^}]*SupabaseAuthStrategy[^}]*\} from '@icore\/auth-supabase';\n/m, "").replace(
|
|
267
|
+
/\n {10}case 'supabase': \{[\s\S]*?return new SupabaseAuthStrategy\(\{ client \}\);\n {10}\}\n/m,
|
|
268
|
+
""
|
|
269
|
+
);
|
|
270
|
+
await writeFile(modulePath, next);
|
|
271
|
+
} catch {
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
async function removeUnusedStorageStrategies(targetDir, uploadProvider) {
|
|
276
|
+
if (uploadProvider === "none") return;
|
|
277
|
+
const modulePath = join(targetDir, "apps/microservices/upload/src/app/app.module.ts");
|
|
278
|
+
if (uploadProvider !== "firebase") {
|
|
279
|
+
await rm(join(targetDir, "libs/storage-strategies/firebase"), { recursive: true, force: true });
|
|
280
|
+
await stripDeps(join(targetDir, "apps/microservices/upload/package.json"), [
|
|
281
|
+
"@icore/storage-firebase"
|
|
282
|
+
]);
|
|
283
|
+
await stripTsconfigPath(targetDir, "@icore/storage-firebase");
|
|
284
|
+
}
|
|
285
|
+
if (uploadProvider !== "cloudinary") {
|
|
286
|
+
await rm(join(targetDir, "libs/storage-strategies/cloudinary"), {
|
|
287
|
+
recursive: true,
|
|
288
|
+
force: true
|
|
289
|
+
});
|
|
290
|
+
await stripDeps(join(targetDir, "apps/microservices/upload/package.json"), [
|
|
291
|
+
"@icore/storage-cloudinary"
|
|
292
|
+
]);
|
|
293
|
+
await stripTsconfigPath(targetDir, "@icore/storage-cloudinary");
|
|
294
|
+
}
|
|
295
|
+
if (uploadProvider !== "supabase") {
|
|
296
|
+
await rm(join(targetDir, "libs/storage-strategies/supabase"), { recursive: true, force: true });
|
|
297
|
+
await stripDeps(join(targetDir, "apps/microservices/upload/package.json"), [
|
|
298
|
+
"@icore/storage-supabase"
|
|
299
|
+
]);
|
|
300
|
+
await stripTsconfigPath(targetDir, "@icore/storage-supabase");
|
|
301
|
+
}
|
|
302
|
+
try {
|
|
303
|
+
let src = await readFile(modulePath, "utf8");
|
|
304
|
+
if (uploadProvider !== "firebase") {
|
|
305
|
+
src = src.replace(/^import \* as admin from 'firebase-admin';\n/m, "").replace(
|
|
306
|
+
/^import \{[^}]*FirebaseStorageStrategy[^}]*\} from '@icore\/storage-firebase';\n/m,
|
|
307
|
+
""
|
|
308
|
+
).replace(/^function makeFirebaseStorage\b[\s\S]*?\n^}\n/m, "").replace(/(?<=\n) *case 'firebase':\n *return makeFirebaseStorage\(cfg\);\n/, "");
|
|
309
|
+
}
|
|
310
|
+
if (uploadProvider !== "cloudinary") {
|
|
311
|
+
src = src.replace(/^import \{ v2 as cloudinary \} from 'cloudinary';\n/m, "").replace(
|
|
312
|
+
/^import \{[^}]*CloudinaryStorageStrategy[^}]*\} from '@icore\/storage-cloudinary';\n/m,
|
|
313
|
+
""
|
|
314
|
+
).replace(/^function makeCloudinaryStorage\b[\s\S]*?\n^}\n/m, "").replace(/(?<=\n) *case 'cloudinary':\n *return makeCloudinaryStorage\(cfg\);\n/, "");
|
|
315
|
+
}
|
|
316
|
+
if (uploadProvider !== "supabase") {
|
|
317
|
+
src = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/m, "").replace(
|
|
318
|
+
/^import \{[^}]*SupabaseStorageStrategy[^}]*\} from '@icore\/storage-supabase';\n/m,
|
|
319
|
+
""
|
|
320
|
+
).replace(
|
|
321
|
+
/\n {10}case 'supabase': \{[\s\S]*?bucket: requireEnv\(cfg, 'SUPABASE_STORAGE_BUCKET'\),\n {12}\}\);\n {10}\}\n/m,
|
|
322
|
+
""
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
await writeFile(modulePath, src);
|
|
326
|
+
} catch {
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
async function removeUnusedDbStrategies(targetDir, dbProvider) {
|
|
330
|
+
const modulePath = join(targetDir, "apps/microservices/notes/src/app/app.module.ts");
|
|
331
|
+
if (dbProvider === "supabase") {
|
|
332
|
+
await rm(join(targetDir, "libs/db-strategies/firestore"), { recursive: true, force: true });
|
|
333
|
+
await stripDeps(join(targetDir, "apps/microservices/notes/package.json"), [
|
|
334
|
+
"@icore/db-firestore"
|
|
335
|
+
]);
|
|
336
|
+
await stripTsconfigPath(targetDir, "@icore/db-firestore");
|
|
337
|
+
try {
|
|
338
|
+
const src = await readFile(modulePath, "utf8");
|
|
339
|
+
const next = src.replace(/^import \* as admin from 'firebase-admin';\n/m, "").replace(/^import \{[^}]*FirestoreDBStrategy[^}]*\} from '@icore\/db-firestore';\n/m, "").replace(
|
|
340
|
+
/\n {8}if \(provider === 'firestore'[\s\S]*?return new FirestoreDBStrategy\(\{[\s\S]*?\}\);\n {8}\}\n/m,
|
|
341
|
+
""
|
|
342
|
+
);
|
|
343
|
+
await writeFile(modulePath, next);
|
|
344
|
+
} catch {
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (dbProvider === "firebase") {
|
|
348
|
+
await rm(join(targetDir, "libs/db-strategies/supabase"), { recursive: true, force: true });
|
|
349
|
+
await stripDeps(join(targetDir, "apps/microservices/notes/package.json"), [
|
|
350
|
+
"@icore/db-supabase"
|
|
351
|
+
]);
|
|
352
|
+
await stripTsconfigPath(targetDir, "@icore/db-supabase");
|
|
353
|
+
try {
|
|
354
|
+
const src = await readFile(modulePath, "utf8");
|
|
355
|
+
const next = src.replace(/^import \{ createClient \} from '@supabase\/supabase-js';\n/m, "").replace(/^import \{[^}]*SupabaseDBStrategy[^}]*\} from '@icore\/db-supabase';\n/m, "").replace(
|
|
356
|
+
/\n {8}if \(provider === 'supabase'\) \{[\s\S]*?return new SupabaseDBStrategy\(\{ client \}\);\n {8}\}\n/m,
|
|
357
|
+
""
|
|
358
|
+
);
|
|
359
|
+
await writeFile(modulePath, next);
|
|
360
|
+
} catch {
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
209
364
|
async function removeUploadStack(targetDir) {
|
|
210
365
|
const paths = [
|
|
211
366
|
"apps/microservices/upload",
|
|
@@ -282,14 +437,16 @@ function gitInit(cwd, projectName) {
|
|
|
282
437
|
{ cwd, stdio: "inherit" }
|
|
283
438
|
);
|
|
284
439
|
}
|
|
285
|
-
function
|
|
286
|
-
|
|
440
|
+
function runInstall(cwd, pm) {
|
|
441
|
+
const [cmd, ...args] = pm === "npm" ? ["npm", "install"] : pm === "pnpm" ? ["pnpm", "install"] : ["yarn", "install"];
|
|
442
|
+
spawnSync(cmd, args, { cwd, stdio: "inherit" });
|
|
287
443
|
}
|
|
288
444
|
async function scaffold(opts, templatesDir) {
|
|
289
445
|
await copyTree(templatesDir, opts.targetDir);
|
|
290
446
|
await rewriteRootPackageJson(opts.targetDir, opts);
|
|
291
447
|
await writeAuthEnv(opts.targetDir, opts);
|
|
292
448
|
await writeUploadEnv(opts.targetDir, opts);
|
|
449
|
+
await writeNotesEnv(opts.targetDir, opts);
|
|
293
450
|
await writePaymentEnv(opts.targetDir, opts);
|
|
294
451
|
await writeGatewayEnv(opts.targetDir, opts);
|
|
295
452
|
await writeRootEnv(opts.targetDir, opts);
|
|
@@ -298,8 +455,13 @@ async function scaffold(opts, templatesDir) {
|
|
|
298
455
|
if (opts.payment === "none") await removePaymentStack(opts.targetDir);
|
|
299
456
|
if (opts.jobs === "none") await removeJobsStack(opts.targetDir);
|
|
300
457
|
if (opts.example === "none") await removeNotesStack(opts.targetDir);
|
|
301
|
-
await
|
|
302
|
-
|
|
458
|
+
await removeUnusedAuthStrategies(opts.targetDir, opts.authProvider);
|
|
459
|
+
await removeUnusedStorageStrategies(opts.targetDir, opts.upload);
|
|
460
|
+
await removeUnusedDbStrategies(opts.targetDir, opts.dbProvider);
|
|
461
|
+
if (opts.packageManager === "yarn") {
|
|
462
|
+
await writeFile(join(opts.targetDir, "yarn.lock"), "");
|
|
463
|
+
}
|
|
464
|
+
if (opts.install) runInstall(opts.targetDir, opts.packageManager);
|
|
303
465
|
if (opts.initGit) gitInit(opts.targetDir, opts.projectName);
|
|
304
466
|
}
|
|
305
467
|
|
|
@@ -309,6 +471,13 @@ import { resolve } from "path";
|
|
|
309
471
|
import { readFile as readFile2 } from "fs/promises";
|
|
310
472
|
import { dirname, join as join2 } from "path";
|
|
311
473
|
import { fileURLToPath } from "url";
|
|
474
|
+
function detectPackageManager() {
|
|
475
|
+
const ua = process.env["npm_config_user_agent"] ?? "";
|
|
476
|
+
if (ua.startsWith("yarn/")) return "yarn";
|
|
477
|
+
if (ua.startsWith("pnpm/")) return "pnpm";
|
|
478
|
+
if (ua.startsWith("npm/")) return "npm";
|
|
479
|
+
return "yarn";
|
|
480
|
+
}
|
|
312
481
|
async function readSelfVersion() {
|
|
313
482
|
try {
|
|
314
483
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
@@ -377,6 +546,9 @@ function parseFlags(argv) {
|
|
|
377
546
|
case "transport":
|
|
378
547
|
out.transport = v;
|
|
379
548
|
break;
|
|
549
|
+
case "package-manager":
|
|
550
|
+
out.packageManager = v;
|
|
551
|
+
break;
|
|
380
552
|
case "no-git":
|
|
381
553
|
out.initGit = false;
|
|
382
554
|
break;
|
|
@@ -486,8 +658,12 @@ Re-run with @latest to refresh:
|
|
|
486
658
|
initialValue: "tcp"
|
|
487
659
|
});
|
|
488
660
|
if (p.isCancel(transport)) throw new Error("cancelled");
|
|
661
|
+
const packageManager = flags.packageManager ?? detectPackageManager();
|
|
489
662
|
const initGit = flags.initGit ?? !await p.confirm({ message: "Initialise git repo?", initialValue: true }) === false;
|
|
490
|
-
const install = flags.install ?? !await p.confirm({
|
|
663
|
+
const install = flags.install ?? !await p.confirm({
|
|
664
|
+
message: `Run ${packageManager} install?`,
|
|
665
|
+
initialValue: true
|
|
666
|
+
}) === false;
|
|
491
667
|
return {
|
|
492
668
|
projectName,
|
|
493
669
|
targetDir: resolve(cwd, projectName),
|
|
@@ -499,6 +675,7 @@ Re-run with @latest to refresh:
|
|
|
499
675
|
example,
|
|
500
676
|
ui,
|
|
501
677
|
transport,
|
|
678
|
+
packageManager,
|
|
502
679
|
initGit,
|
|
503
680
|
install
|
|
504
681
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idevconn/create-icore",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Bootstrap a new project from the iCore scaffold (Nx + NestJS + React + Vite + shadcn/Tailwind, swappable auth + storage providers).",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "iDEVconn",
|
|
@@ -5,23 +5,29 @@ import { createClient } from '@supabase/supabase-js';
|
|
|
5
5
|
import * as admin from 'firebase-admin';
|
|
6
6
|
import { SupabaseAuthStrategy } from '@icore/auth-supabase';
|
|
7
7
|
import { FirebaseAuthStrategy, HttpIdentityToolkitClient } from '@icore/auth-firebase';
|
|
8
|
+
import { FakeAuthStrategy } from '@icore/shared';
|
|
8
9
|
import type { AuthStrategy } from '@icore/shared';
|
|
10
|
+
import { Logger } from '@nestjs/common';
|
|
9
11
|
import { AuthController } from './auth.controller';
|
|
10
12
|
|
|
13
|
+
function requireEnv(cfg: ConfigService, key: string): string {
|
|
14
|
+
const val = cfg.getOrThrow<string>(key);
|
|
15
|
+
if (!val) throw new Error(`${key} is not set — check apps/microservices/auth/.env`);
|
|
16
|
+
return val;
|
|
17
|
+
}
|
|
18
|
+
|
|
11
19
|
function makeFirebaseStrategy(cfg: ConfigService): AuthStrategy {
|
|
12
|
-
const projectId = cfg
|
|
20
|
+
const projectId = requireEnv(cfg, 'FB_ADMIN_PROJECT_ID');
|
|
13
21
|
if (admin.apps.length === 0) {
|
|
14
22
|
admin.initializeApp({
|
|
15
23
|
credential: admin.credential.cert({
|
|
16
24
|
projectId,
|
|
17
|
-
clientEmail: cfg
|
|
18
|
-
privateKey: cfg
|
|
25
|
+
clientEmail: requireEnv(cfg, 'FB_ADMIN_CLIENT_EMAIL'),
|
|
26
|
+
privateKey: requireEnv(cfg, 'FB_ADMIN_PRIVATE_KEY').replace(/\\n/g, '\n'),
|
|
19
27
|
}),
|
|
20
28
|
});
|
|
21
29
|
}
|
|
22
|
-
const identityToolkit = new HttpIdentityToolkitClient(
|
|
23
|
-
cfg.getOrThrow<string>('FIREBASE_WEB_API_KEY'),
|
|
24
|
-
);
|
|
30
|
+
const identityToolkit = new HttpIdentityToolkitClient(requireEnv(cfg, 'FIREBASE_WEB_API_KEY'));
|
|
25
31
|
return new FirebaseAuthStrategy({
|
|
26
32
|
identityToolkit,
|
|
27
33
|
adminAuth: admin.auth(),
|
|
@@ -43,20 +49,28 @@ function makeFirebaseStrategy(cfg: ConfigService): AuthStrategy {
|
|
|
43
49
|
{
|
|
44
50
|
provide: 'AuthStrategy',
|
|
45
51
|
useFactory: (cfg: ConfigService): AuthStrategy => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
try {
|
|
53
|
+
const provider = requireEnv(cfg, 'AUTH_PROVIDER');
|
|
54
|
+
switch (provider) {
|
|
55
|
+
case 'supabase': {
|
|
56
|
+
const client = createClient(
|
|
57
|
+
requireEnv(cfg, 'SUPABASE_URL'),
|
|
58
|
+
requireEnv(cfg, 'SUPABASE_SERVICE_ROLE_KEY'),
|
|
59
|
+
{ auth: { autoRefreshToken: false, persistSession: false } },
|
|
60
|
+
);
|
|
61
|
+
return new SupabaseAuthStrategy({ client });
|
|
62
|
+
}
|
|
63
|
+
case 'firebase':
|
|
64
|
+
return makeFirebaseStrategy(cfg);
|
|
65
|
+
default:
|
|
66
|
+
throw new Error(`Unsupported AUTH_PROVIDER: ${provider}`);
|
|
55
67
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
68
|
+
} catch (err) {
|
|
69
|
+
new Logger('AuthStrategy').warn(
|
|
70
|
+
`Not configured: ${err instanceof Error ? err.message : String(err)}. ` +
|
|
71
|
+
`Requests will fail until apps/microservices/auth/.env is set.`,
|
|
72
|
+
);
|
|
73
|
+
return new FakeAuthStrategy();
|
|
60
74
|
}
|
|
61
75
|
},
|
|
62
76
|
inject: [ConfigService],
|
|
@@ -5,9 +5,17 @@ import { createClient } from '@supabase/supabase-js';
|
|
|
5
5
|
import * as admin from 'firebase-admin';
|
|
6
6
|
import { SupabaseDBStrategy } from '@icore/db-supabase';
|
|
7
7
|
import { FirestoreDBStrategy } from '@icore/db-firestore';
|
|
8
|
+
import { FakeDBStrategy } from '@icore/shared';
|
|
8
9
|
import type { DBStrategy } from '@icore/shared';
|
|
10
|
+
import { Logger } from '@nestjs/common';
|
|
9
11
|
import { NotesController } from './notes.controller';
|
|
10
12
|
|
|
13
|
+
function requireEnv(cfg: ConfigService, key: string): string {
|
|
14
|
+
const val = cfg.getOrThrow<string>(key);
|
|
15
|
+
if (!val) throw new Error(`${key} is not set — check apps/microservices/notes/.env`);
|
|
16
|
+
return val;
|
|
17
|
+
}
|
|
18
|
+
|
|
11
19
|
@Module({
|
|
12
20
|
imports: [
|
|
13
21
|
ConfigModule.forRoot({
|
|
@@ -23,32 +31,40 @@ import { NotesController } from './notes.controller';
|
|
|
23
31
|
{
|
|
24
32
|
provide: 'DBStrategy',
|
|
25
33
|
useFactory: (cfg: ConfigService): DBStrategy => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (
|
|
37
|
-
admin.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
try {
|
|
35
|
+
const provider = requireEnv(cfg, 'DB_PROVIDER');
|
|
36
|
+
if (provider === 'supabase') {
|
|
37
|
+
const client = createClient(
|
|
38
|
+
requireEnv(cfg, 'SUPABASE_URL'),
|
|
39
|
+
requireEnv(cfg, 'SUPABASE_SERVICE_ROLE_KEY'),
|
|
40
|
+
{ auth: { autoRefreshToken: false, persistSession: false } },
|
|
41
|
+
);
|
|
42
|
+
return new SupabaseDBStrategy({ client });
|
|
43
|
+
}
|
|
44
|
+
if (provider === 'firestore' || provider === 'firebase') {
|
|
45
|
+
if (admin.apps.length === 0) {
|
|
46
|
+
admin.initializeApp({
|
|
47
|
+
credential: admin.credential.cert({
|
|
48
|
+
projectId: requireEnv(cfg, 'FB_ADMIN_PROJECT_ID'),
|
|
49
|
+
clientEmail: requireEnv(cfg, 'FB_ADMIN_CLIENT_EMAIL'),
|
|
50
|
+
privateKey: requireEnv(cfg, 'FB_ADMIN_PRIVATE_KEY').replace(/\\n/g, '\n'),
|
|
51
|
+
}),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return new FirestoreDBStrategy({
|
|
55
|
+
db: admin.firestore() as unknown as ConstructorParameters<
|
|
56
|
+
typeof FirestoreDBStrategy
|
|
57
|
+
>[0]['db'],
|
|
43
58
|
});
|
|
44
59
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
60
|
+
throw new Error(`Unsupported DB_PROVIDER: ${provider}`);
|
|
61
|
+
} catch (err) {
|
|
62
|
+
new Logger('DBStrategy').warn(
|
|
63
|
+
`Not configured: ${err instanceof Error ? err.message : String(err)}. ` +
|
|
64
|
+
`Requests will fail until apps/microservices/notes/.env is set.`,
|
|
65
|
+
);
|
|
66
|
+
return new FakeDBStrategy();
|
|
50
67
|
}
|
|
51
|
-
throw new Error(`Unsupported DB_PROVIDER: ${provider}`);
|
|
52
68
|
},
|
|
53
69
|
inject: [ConfigService],
|
|
54
70
|
},
|
|
@@ -7,17 +7,25 @@ import { v2 as cloudinary } from 'cloudinary';
|
|
|
7
7
|
import { SupabaseStorageStrategy } from '@icore/storage-supabase';
|
|
8
8
|
import { FirebaseStorageStrategy } from '@icore/storage-firebase';
|
|
9
9
|
import { CloudinaryStorageStrategy, type CloudinaryApiLike } from '@icore/storage-cloudinary';
|
|
10
|
+
import { FakeStorageStrategy } from '@icore/shared';
|
|
10
11
|
import type { StorageStrategy } from '@icore/shared';
|
|
12
|
+
import { Logger } from '@nestjs/common';
|
|
11
13
|
import { StorageController } from './storage.controller';
|
|
12
14
|
|
|
15
|
+
function requireEnv(cfg: ConfigService, key: string): string {
|
|
16
|
+
const val = cfg.getOrThrow<string>(key);
|
|
17
|
+
if (!val) throw new Error(`${key} is not set — check apps/microservices/upload/.env`);
|
|
18
|
+
return val;
|
|
19
|
+
}
|
|
20
|
+
|
|
13
21
|
function makeFirebaseStorage(cfg: ConfigService): StorageStrategy {
|
|
14
|
-
const bucketName = cfg
|
|
22
|
+
const bucketName = requireEnv(cfg, 'FIREBASE_STORAGE_BUCKET');
|
|
15
23
|
if (admin.apps.length === 0) {
|
|
16
24
|
admin.initializeApp({
|
|
17
25
|
credential: admin.credential.cert({
|
|
18
|
-
projectId: cfg
|
|
19
|
-
clientEmail: cfg
|
|
20
|
-
privateKey: cfg
|
|
26
|
+
projectId: requireEnv(cfg, 'FB_ADMIN_PROJECT_ID'),
|
|
27
|
+
clientEmail: requireEnv(cfg, 'FB_ADMIN_CLIENT_EMAIL'),
|
|
28
|
+
privateKey: requireEnv(cfg, 'FB_ADMIN_PRIVATE_KEY').replace(/\\n/g, '\n'),
|
|
21
29
|
}),
|
|
22
30
|
});
|
|
23
31
|
}
|
|
@@ -30,9 +38,9 @@ function makeFirebaseStorage(cfg: ConfigService): StorageStrategy {
|
|
|
30
38
|
|
|
31
39
|
function makeCloudinaryStorage(cfg: ConfigService): StorageStrategy {
|
|
32
40
|
cloudinary.config({
|
|
33
|
-
cloud_name: cfg
|
|
34
|
-
api_key: cfg
|
|
35
|
-
api_secret: cfg
|
|
41
|
+
cloud_name: requireEnv(cfg, 'CLOUDINARY_CLOUD_NAME'),
|
|
42
|
+
api_key: requireEnv(cfg, 'CLOUDINARY_API_KEY'),
|
|
43
|
+
api_secret: requireEnv(cfg, 'CLOUDINARY_API_SECRET'),
|
|
36
44
|
secure: true,
|
|
37
45
|
});
|
|
38
46
|
|
|
@@ -89,25 +97,33 @@ function makeCloudinaryStorage(cfg: ConfigService): StorageStrategy {
|
|
|
89
97
|
{
|
|
90
98
|
provide: 'StorageStrategy',
|
|
91
99
|
useFactory: (cfg: ConfigService): StorageStrategy => {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
try {
|
|
101
|
+
const provider = requireEnv(cfg, 'STORAGE_PROVIDER');
|
|
102
|
+
switch (provider) {
|
|
103
|
+
case 'supabase': {
|
|
104
|
+
const client = createClient(
|
|
105
|
+
requireEnv(cfg, 'SUPABASE_URL'),
|
|
106
|
+
requireEnv(cfg, 'SUPABASE_SERVICE_ROLE_KEY'),
|
|
107
|
+
{ auth: { autoRefreshToken: false, persistSession: false } },
|
|
108
|
+
);
|
|
109
|
+
return new SupabaseStorageStrategy({
|
|
110
|
+
client,
|
|
111
|
+
bucket: requireEnv(cfg, 'SUPABASE_STORAGE_BUCKET'),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
case 'firebase':
|
|
115
|
+
return makeFirebaseStorage(cfg);
|
|
116
|
+
case 'cloudinary':
|
|
117
|
+
return makeCloudinaryStorage(cfg);
|
|
118
|
+
default:
|
|
119
|
+
throw new Error(`Unsupported STORAGE_PROVIDER: ${provider}`);
|
|
104
120
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
121
|
+
} catch (err) {
|
|
122
|
+
new Logger('StorageStrategy').warn(
|
|
123
|
+
`Not configured: ${err instanceof Error ? err.message : String(err)}. ` +
|
|
124
|
+
`Requests will fail until apps/microservices/upload/.env is set.`,
|
|
125
|
+
);
|
|
126
|
+
return new FakeStorageStrategy();
|
|
111
127
|
}
|
|
112
128
|
},
|
|
113
129
|
inject: [ConfigService],
|
|
@@ -2,9 +2,16 @@
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import { defineConfig } from 'vite';
|
|
4
4
|
import react from '@vitejs/plugin-react';
|
|
5
|
-
import {
|
|
5
|
+
import { tanstackRouter } from '@tanstack/router-plugin/vite';
|
|
6
6
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
|
7
7
|
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
|
|
8
|
+
import {
|
|
9
|
+
commonDefines,
|
|
10
|
+
commonManualChunks,
|
|
11
|
+
commonTestConfig,
|
|
12
|
+
injectAppVersionPlugin,
|
|
13
|
+
noServerModulesPlugin,
|
|
14
|
+
} from '@icore/vite-plugins';
|
|
8
15
|
|
|
9
16
|
const rootPackageJsonPath = new URL('../../../package.json', import.meta.url);
|
|
10
17
|
const rootPackageJson = JSON.parse(fs.readFileSync(rootPackageJsonPath, 'utf-8')) as {
|
|
@@ -14,11 +21,7 @@ const rootPackageJson = JSON.parse(fs.readFileSync(rootPackageJsonPath, 'utf-8')
|
|
|
14
21
|
};
|
|
15
22
|
|
|
16
23
|
function depVersion(name: string): string {
|
|
17
|
-
return
|
|
18
|
-
rootPackageJson.dependencies?.[name] ??
|
|
19
|
-
rootPackageJson.devDependencies?.[name] ??
|
|
20
|
-
'?'
|
|
21
|
-
);
|
|
24
|
+
return rootPackageJson.dependencies?.[name] ?? rootPackageJson.devDependencies?.[name] ?? '?';
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
export default defineConfig(() => ({
|
|
@@ -33,22 +36,11 @@ export default defineConfig(() => ({
|
|
|
33
36
|
host: 'localhost',
|
|
34
37
|
},
|
|
35
38
|
define: {
|
|
36
|
-
|
|
37
|
-
// Dep versions injected at build time so routes don't need JSON imports
|
|
38
|
-
'import.meta.env.VITE_DEP_REACT': JSON.stringify(depVersion('react')),
|
|
39
|
+
...commonDefines(rootPackageJson),
|
|
39
40
|
'import.meta.env.VITE_DEP_ANTD': JSON.stringify(depVersion('antd')),
|
|
40
|
-
'import.meta.env.VITE_DEP_VITE': JSON.stringify(depVersion('vite')),
|
|
41
|
-
'import.meta.env.VITE_DEP_TANSTACK_ROUTER': JSON.stringify(
|
|
42
|
-
depVersion('@tanstack/react-router'),
|
|
43
|
-
),
|
|
44
|
-
'import.meta.env.VITE_DEP_TANSTACK_QUERY': JSON.stringify(
|
|
45
|
-
depVersion('@tanstack/react-query'),
|
|
46
|
-
),
|
|
47
|
-
'import.meta.env.VITE_DEP_ZUSTAND': JSON.stringify(depVersion('zustand')),
|
|
48
|
-
'import.meta.env.VITE_DEP_CASL': JSON.stringify(depVersion('@casl/ability')),
|
|
49
41
|
},
|
|
50
42
|
plugins: [
|
|
51
|
-
|
|
43
|
+
tanstackRouter({
|
|
52
44
|
target: 'react',
|
|
53
45
|
autoCodeSplitting: true,
|
|
54
46
|
routeFileIgnorePattern: '(__tests__|\\.test\\.(t|j)sx?$)',
|
|
@@ -56,12 +48,8 @@ export default defineConfig(() => ({
|
|
|
56
48
|
react(),
|
|
57
49
|
nxViteTsPaths(),
|
|
58
50
|
nxCopyAssetsPlugin(['*.md']),
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
transformIndexHtml(html: string) {
|
|
62
|
-
return html.replace('%APP_VERSION%', rootPackageJson.version);
|
|
63
|
-
},
|
|
64
|
-
},
|
|
51
|
+
noServerModulesPlugin(),
|
|
52
|
+
injectAppVersionPlugin(rootPackageJson),
|
|
65
53
|
],
|
|
66
54
|
// Uncomment this if you are using workers.
|
|
67
55
|
// worker: {
|
|
@@ -76,32 +64,12 @@ export default defineConfig(() => ({
|
|
|
76
64
|
},
|
|
77
65
|
rolldownOptions: {
|
|
78
66
|
output: {
|
|
79
|
-
manualChunks: (id
|
|
80
|
-
if (!id.includes('node_modules')) return;
|
|
81
|
-
if (id.includes('/react/') || id.includes('/react-dom/') || id.includes('scheduler'))
|
|
82
|
-
return 'vendor-react';
|
|
83
|
-
if (id.includes('@tanstack')) return 'vendor-tanstack';
|
|
84
|
-
if (id.includes('i18next') || id.includes('react-i18next')) return 'vendor-i18n';
|
|
85
|
-
if (id.includes('@casl')) return 'vendor-casl';
|
|
67
|
+
manualChunks: commonManualChunks((id) => {
|
|
86
68
|
if (id.includes('/antd/') || id.includes('@ant-design') || id.includes('rc-'))
|
|
87
69
|
return 'vendor-antd';
|
|
88
|
-
|
|
89
|
-
if (id.includes('@idevconn')) return 'vendor-idevconn';
|
|
90
|
-
return 'vendor-core';
|
|
91
|
-
},
|
|
70
|
+
}),
|
|
92
71
|
},
|
|
93
72
|
},
|
|
94
73
|
},
|
|
95
|
-
test:
|
|
96
|
-
name: 'client-antd',
|
|
97
|
-
watch: false,
|
|
98
|
-
globals: true,
|
|
99
|
-
environment: 'jsdom',
|
|
100
|
-
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
|
101
|
-
reporters: ['default'],
|
|
102
|
-
coverage: {
|
|
103
|
-
reportsDirectory: '../../../coverage/apps/templates/client-antd',
|
|
104
|
-
provider: 'v8' as const,
|
|
105
|
-
},
|
|
106
|
-
},
|
|
74
|
+
test: commonTestConfig('client-antd', '../../../coverage/apps/templates/client-antd'),
|
|
107
75
|
}));
|