@idevconn/create-icore 0.10.0 → 0.10.2

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 (24) hide show
  1. package/dist/cli.js +131 -8
  2. package/dist/index.cjs +131 -8
  3. package/dist/index.js +131 -8
  4. package/package.json +1 -1
  5. package/templates/apps/templates/client-antd/package.json +1 -1
  6. package/templates/apps/templates/client-antd/src/components/layout/LayoutHeader.tsx +2 -2
  7. package/templates/apps/templates/client-mui/src/components/layout/LayoutHeader.tsx +1 -1
  8. package/templates/apps/templates/client-shadcn/src/components/layout/LayoutHeader.tsx +2 -2
  9. package/templates/apps/templates/client-shadcn/src/components/layout/LayoutSider.tsx +7 -3
  10. package/templates/apps/templates/client-shadcn/src/routes/index.tsx +2 -2
  11. package/templates/apps/templates/client-shadcn/vite.config.mts +18 -1
  12. package/templates/libs/auth-strategies/firebase/package.json +1 -0
  13. package/templates/libs/auth-strategies/firebase/src/lib/firebase-auth.module.ts +2 -1
  14. package/templates/libs/auth-strategies/mongodb/package.json +1 -1
  15. package/templates/libs/db-strategies/firestore/package.json +1 -0
  16. package/templates/libs/db-strategies/firestore/src/lib/firestore-db.module.ts +2 -1
  17. package/templates/libs/db-strategies/mongodb/package.json +1 -1
  18. package/templates/libs/firebase-admin/package.json +1 -1
  19. package/templates/libs/firebase-admin/src/lib/__tests__/firebase-admin.unit.test.ts +11 -11
  20. package/templates/libs/firebase-admin/src/lib/firebase-admin.ts +6 -6
  21. package/templates/libs/storage-strategies/firebase/package.json +1 -0
  22. package/templates/libs/storage-strategies/firebase/src/lib/firebase-storage.module.ts +4 -1
  23. package/templates/libs/storage-strategies/mongodb/package.json +1 -1
  24. package/templates/libs/template-shared/package.json +1 -1
package/dist/cli.js CHANGED
@@ -410,6 +410,7 @@ async function rewriteRootPackageJson(targetDir, opts) {
410
410
  const pkg = JSON.parse(raw);
411
411
  pkg["name"] = opts.projectName;
412
412
  pkg["version"] = "0.0.1";
413
+ pkg["icoreVersion"] = true ? "0.10.2" : "unknown";
413
414
  pkg["private"] = true;
414
415
  delete pkg.description;
415
416
  const transportDeps = TRANSPORT_DEPS[opts.transport];
@@ -677,6 +678,7 @@ var AUTH_ONLY_PATHS = [
677
678
  "apps/api/src/app/abilities",
678
679
  "libs/shared/src/abilities",
679
680
  "apps/client/src/components/auth",
681
+ "apps/client/src/components/AccessDeniedPage.tsx",
680
682
  "apps/client/src/routes/login.tsx",
681
683
  "apps/client/src/routes/auth.callback.tsx",
682
684
  "apps/client/src/routes/auth.oauth.callback.tsx",
@@ -813,6 +815,121 @@ export * from './lib/draft/index.js';
813
815
  export * from './lib/landing/LandingPage.js';
814
816
  export * from './lib/stores/theme.store.js';
815
817
  `;
818
+ var SHADCN_PAGE_LAYOUT_TSX = `import type { ReactNode } from 'react';
819
+ import { useTranslation } from 'react-i18next';
820
+ import { useDraft, useLoading } from '@icore/template-shared';
821
+
822
+ interface PageLayoutProps {
823
+ title: string;
824
+ description?: string;
825
+ actions?: ReactNode;
826
+ children: ReactNode;
827
+ }
828
+
829
+ export function PageLayout({ title, description, actions, children }: PageLayoutProps) {
830
+ const { t } = useTranslation();
831
+ const isLoading = useLoading();
832
+
833
+ useDraft(false);
834
+
835
+ return (
836
+ <div className="p-4 md:p-6 space-y-4">
837
+ <div className="flex items-start justify-between gap-3">
838
+ <div>
839
+ <h1 className="text-xl font-semibold text-foreground">{title}</h1>
840
+ {description && <p className="text-sm text-muted-foreground mt-1">{description}</p>}
841
+ </div>
842
+ {actions && <div>{actions}</div>}
843
+ </div>
844
+
845
+ {isLoading && (
846
+ <div
847
+ role="status"
848
+ aria-label={t('common.loading')}
849
+ className="fixed inset-0 z-50 flex items-center justify-center bg-background/60 backdrop-blur-sm"
850
+ >
851
+ <div className="h-8 w-8 animate-spin rounded-full border-2 border-primary border-t-transparent" />
852
+ </div>
853
+ )}
854
+
855
+ {children}
856
+ </div>
857
+ );
858
+ }
859
+ `;
860
+ var ANTD_PAGE_LAYOUT_TSX = `import type { ReactNode } from 'react';
861
+ import { Descriptions, Spin } from 'antd';
862
+ import { useDraft, useLoading } from '@icore/template-shared';
863
+
864
+ export interface PageLayoutProps {
865
+ title: ReactNode;
866
+ description?: ReactNode;
867
+ extra?: ReactNode;
868
+ children?: ReactNode;
869
+ }
870
+
871
+ export function PageLayout({ title, description, extra, children }: PageLayoutProps) {
872
+ useDraft(false);
873
+ const loading = useLoading();
874
+
875
+ return (
876
+ <div style={{ padding: 24 }}>
877
+ <Descriptions title={title} extra={extra} style={{ marginBottom: 16 }}>
878
+ {description ? <Descriptions.Item>{description}</Descriptions.Item> : null}
879
+ </Descriptions>
880
+ <Spin spinning={loading}>
881
+ <div>{children}</div>
882
+ </Spin>
883
+ </div>
884
+ );
885
+ }
886
+ `;
887
+ var MUI_PAGE_LAYOUT_TSX = `import type { ReactNode } from 'react';
888
+ import { Box, LinearProgress, Stack, Typography } from '@mui/material';
889
+ import { useDraft, useLoading } from '@icore/template-shared';
890
+
891
+ export interface PageLayoutProps {
892
+ title: ReactNode;
893
+ description?: ReactNode;
894
+ extra?: ReactNode;
895
+ children?: ReactNode;
896
+ }
897
+
898
+ export function PageLayout({ title, description, extra, children }: PageLayoutProps) {
899
+ useDraft(false);
900
+ const loading = useLoading();
901
+
902
+ return (
903
+ <Box sx={{ p: 3 }}>
904
+ <Stack
905
+ direction="row"
906
+ justifyContent="space-between"
907
+ alignItems="flex-start"
908
+ spacing={2}
909
+ mb={3}
910
+ >
911
+ <Box>
912
+ <Typography variant="h4" component="h1">
913
+ {title}
914
+ </Typography>
915
+ {description ? (
916
+ <Typography variant="body2" color="text.secondary" mt={0.5}>
917
+ {description}
918
+ </Typography>
919
+ ) : null}
920
+ </Box>
921
+ {extra ? (
922
+ <Stack direction="row" spacing={1}>
923
+ {extra}
924
+ </Stack>
925
+ ) : null}
926
+ </Stack>
927
+ {loading ? <LinearProgress sx={{ mb: 2 }} /> : null}
928
+ <Box>{children}</Box>
929
+ </Box>
930
+ );
931
+ }
932
+ `;
816
933
  var SHADCN_MAIN_TSX = `import './globals.css';
817
934
  import { StrictMode } from 'react';
818
935
  import { createRoot } from 'react-dom/client';
@@ -913,7 +1030,7 @@ export const Route = createFileRoute('/')({
913
1030
  ),
914
1031
  });
915
1032
  `;
916
- var SHADCN_LAYOUT_HEADER_TSX = `import { setStoredLocale, type IcoreLocale } from '@icore/template-shared';
1033
+ var SHADCN_LAYOUT_HEADER_TSX = `import { setStoredLocale, type IcoreLocale, i18next } from '@icore/template-shared';
917
1034
  import { ThemeToggle } from '../ThemeToggle';
918
1035
 
919
1036
  const LOCALES: { code: IcoreLocale; label: string }[] = [
@@ -925,7 +1042,7 @@ const LOCALES: { code: IcoreLocale; label: string }[] = [
925
1042
  export function LayoutHeader() {
926
1043
  function handleLocale(code: IcoreLocale) {
927
1044
  setStoredLocale(code);
928
- window.location.reload();
1045
+ void i18next.changeLanguage(code);
929
1046
  }
930
1047
 
931
1048
  return (
@@ -1059,7 +1176,7 @@ export const Route = createFileRoute('/')({
1059
1176
  });
1060
1177
  `;
1061
1178
  var ANTD_LAYOUT_HEADER_TSX = `import { Button, Layout, Space } from 'antd';
1062
- import { setStoredLocale, type IcoreLocale } from '@icore/template-shared';
1179
+ import { setStoredLocale, type IcoreLocale, i18next } from '@icore/template-shared';
1063
1180
  import { ThemeToggle } from '../ThemeToggle';
1064
1181
 
1065
1182
  const APP_VERSION = (import.meta.env.VITE_APP_VERSION as string | undefined) ?? '0.0.0-dev';
@@ -1073,7 +1190,7 @@ const LOCALES: { code: IcoreLocale; label: string }[] = [
1073
1190
  export function LayoutHeader() {
1074
1191
  function handleLocale(code: IcoreLocale) {
1075
1192
  setStoredLocale(code);
1076
- window.location.reload();
1193
+ void i18next.changeLanguage(code);
1077
1194
  }
1078
1195
 
1079
1196
  return (
@@ -1245,7 +1362,7 @@ export function LayoutHeader() {
1245
1362
 
1246
1363
  function handleLocale(code: IcoreLocale) {
1247
1364
  setStoredLocale(code);
1248
- window.location.reload();
1365
+ void i18n.changeLanguage(code);
1249
1366
  }
1250
1367
 
1251
1368
  return (
@@ -1289,19 +1406,22 @@ var UI_VARIANTS = {
1289
1406
  "apps/client/src/main.tsx": SHADCN_MAIN_TSX,
1290
1407
  "apps/client/src/routes/_dashboard.tsx": SHADCN_DASHBOARD_TSX,
1291
1408
  "apps/client/src/routes/index.tsx": SHADCN_INDEX_TSX,
1292
- "apps/client/src/components/layout/LayoutHeader.tsx": SHADCN_LAYOUT_HEADER_TSX
1409
+ "apps/client/src/components/layout/LayoutHeader.tsx": SHADCN_LAYOUT_HEADER_TSX,
1410
+ "apps/client/src/components/PageLayout.tsx": SHADCN_PAGE_LAYOUT_TSX
1293
1411
  },
1294
1412
  antd: {
1295
1413
  "apps/client/src/main.tsx": ANTD_MAIN_TSX,
1296
1414
  "apps/client/src/routes/_dashboard.tsx": ANTD_DASHBOARD_TSX,
1297
1415
  "apps/client/src/routes/index.tsx": ANTD_INDEX_TSX,
1298
- "apps/client/src/components/layout/LayoutHeader.tsx": ANTD_LAYOUT_HEADER_TSX
1416
+ "apps/client/src/components/layout/LayoutHeader.tsx": ANTD_LAYOUT_HEADER_TSX,
1417
+ "apps/client/src/components/PageLayout.tsx": ANTD_PAGE_LAYOUT_TSX
1299
1418
  },
1300
1419
  mui: {
1301
1420
  "apps/client/src/main.tsx": MUI_MAIN_TSX,
1302
1421
  "apps/client/src/routes/_dashboard.tsx": MUI_DASHBOARD_TSX,
1303
1422
  "apps/client/src/routes/index.tsx": MUI_INDEX_TSX,
1304
- "apps/client/src/components/layout/LayoutHeader.tsx": MUI_LAYOUT_HEADER_TSX
1423
+ "apps/client/src/components/layout/LayoutHeader.tsx": MUI_LAYOUT_HEADER_TSX,
1424
+ "apps/client/src/components/PageLayout.tsx": MUI_PAGE_LAYOUT_TSX
1305
1425
  }
1306
1426
  };
1307
1427
  async function applyAuthNoneVariants(targetDir, ui) {
@@ -2188,6 +2308,9 @@ async function scaffold(rawOpts, templatesDir2) {
2188
2308
  await writePnpmWorkspace(opts.targetDir);
2189
2309
  await rewritePnpmWorkspaceDeps(opts.targetDir);
2190
2310
  }
2311
+ if (opts.packageManager === "npm") {
2312
+ await writeFile9(join10(opts.targetDir, ".npmrc"), "legacy-peer-deps=true\n");
2313
+ }
2191
2314
  await patchGitignoreForPm(opts.targetDir, opts.packageManager);
2192
2315
  await writeAiFiles(opts.targetDir, opts);
2193
2316
  if (opts.install) runInstall(opts.targetDir, opts.packageManager);
package/dist/index.cjs CHANGED
@@ -121,6 +121,7 @@ async function rewriteRootPackageJson(targetDir, opts) {
121
121
  const pkg = JSON.parse(raw);
122
122
  pkg["name"] = opts.projectName;
123
123
  pkg["version"] = "0.0.1";
124
+ pkg["icoreVersion"] = typeof ICORE_OWN_VERSION !== "undefined" ? ICORE_OWN_VERSION : "unknown";
124
125
  pkg["private"] = true;
125
126
  delete pkg.description;
126
127
  const transportDeps = TRANSPORT_DEPS[opts.transport];
@@ -388,6 +389,7 @@ var AUTH_ONLY_PATHS = [
388
389
  "apps/api/src/app/abilities",
389
390
  "libs/shared/src/abilities",
390
391
  "apps/client/src/components/auth",
392
+ "apps/client/src/components/AccessDeniedPage.tsx",
391
393
  "apps/client/src/routes/login.tsx",
392
394
  "apps/client/src/routes/auth.callback.tsx",
393
395
  "apps/client/src/routes/auth.oauth.callback.tsx",
@@ -524,6 +526,121 @@ export * from './lib/draft/index.js';
524
526
  export * from './lib/landing/LandingPage.js';
525
527
  export * from './lib/stores/theme.store.js';
526
528
  `;
529
+ var SHADCN_PAGE_LAYOUT_TSX = `import type { ReactNode } from 'react';
530
+ import { useTranslation } from 'react-i18next';
531
+ import { useDraft, useLoading } from '@icore/template-shared';
532
+
533
+ interface PageLayoutProps {
534
+ title: string;
535
+ description?: string;
536
+ actions?: ReactNode;
537
+ children: ReactNode;
538
+ }
539
+
540
+ export function PageLayout({ title, description, actions, children }: PageLayoutProps) {
541
+ const { t } = useTranslation();
542
+ const isLoading = useLoading();
543
+
544
+ useDraft(false);
545
+
546
+ return (
547
+ <div className="p-4 md:p-6 space-y-4">
548
+ <div className="flex items-start justify-between gap-3">
549
+ <div>
550
+ <h1 className="text-xl font-semibold text-foreground">{title}</h1>
551
+ {description && <p className="text-sm text-muted-foreground mt-1">{description}</p>}
552
+ </div>
553
+ {actions && <div>{actions}</div>}
554
+ </div>
555
+
556
+ {isLoading && (
557
+ <div
558
+ role="status"
559
+ aria-label={t('common.loading')}
560
+ className="fixed inset-0 z-50 flex items-center justify-center bg-background/60 backdrop-blur-sm"
561
+ >
562
+ <div className="h-8 w-8 animate-spin rounded-full border-2 border-primary border-t-transparent" />
563
+ </div>
564
+ )}
565
+
566
+ {children}
567
+ </div>
568
+ );
569
+ }
570
+ `;
571
+ var ANTD_PAGE_LAYOUT_TSX = `import type { ReactNode } from 'react';
572
+ import { Descriptions, Spin } from 'antd';
573
+ import { useDraft, useLoading } from '@icore/template-shared';
574
+
575
+ export interface PageLayoutProps {
576
+ title: ReactNode;
577
+ description?: ReactNode;
578
+ extra?: ReactNode;
579
+ children?: ReactNode;
580
+ }
581
+
582
+ export function PageLayout({ title, description, extra, children }: PageLayoutProps) {
583
+ useDraft(false);
584
+ const loading = useLoading();
585
+
586
+ return (
587
+ <div style={{ padding: 24 }}>
588
+ <Descriptions title={title} extra={extra} style={{ marginBottom: 16 }}>
589
+ {description ? <Descriptions.Item>{description}</Descriptions.Item> : null}
590
+ </Descriptions>
591
+ <Spin spinning={loading}>
592
+ <div>{children}</div>
593
+ </Spin>
594
+ </div>
595
+ );
596
+ }
597
+ `;
598
+ var MUI_PAGE_LAYOUT_TSX = `import type { ReactNode } from 'react';
599
+ import { Box, LinearProgress, Stack, Typography } from '@mui/material';
600
+ import { useDraft, useLoading } from '@icore/template-shared';
601
+
602
+ export interface PageLayoutProps {
603
+ title: ReactNode;
604
+ description?: ReactNode;
605
+ extra?: ReactNode;
606
+ children?: ReactNode;
607
+ }
608
+
609
+ export function PageLayout({ title, description, extra, children }: PageLayoutProps) {
610
+ useDraft(false);
611
+ const loading = useLoading();
612
+
613
+ return (
614
+ <Box sx={{ p: 3 }}>
615
+ <Stack
616
+ direction="row"
617
+ justifyContent="space-between"
618
+ alignItems="flex-start"
619
+ spacing={2}
620
+ mb={3}
621
+ >
622
+ <Box>
623
+ <Typography variant="h4" component="h1">
624
+ {title}
625
+ </Typography>
626
+ {description ? (
627
+ <Typography variant="body2" color="text.secondary" mt={0.5}>
628
+ {description}
629
+ </Typography>
630
+ ) : null}
631
+ </Box>
632
+ {extra ? (
633
+ <Stack direction="row" spacing={1}>
634
+ {extra}
635
+ </Stack>
636
+ ) : null}
637
+ </Stack>
638
+ {loading ? <LinearProgress sx={{ mb: 2 }} /> : null}
639
+ <Box>{children}</Box>
640
+ </Box>
641
+ );
642
+ }
643
+ `;
527
644
  var SHADCN_MAIN_TSX = `import './globals.css';
528
645
  import { StrictMode } from 'react';
529
646
  import { createRoot } from 'react-dom/client';
@@ -624,7 +741,7 @@ export const Route = createFileRoute('/')({
624
741
  ),
625
742
  });
626
743
  `;
627
- var SHADCN_LAYOUT_HEADER_TSX = `import { setStoredLocale, type IcoreLocale } from '@icore/template-shared';
744
+ var SHADCN_LAYOUT_HEADER_TSX = `import { setStoredLocale, type IcoreLocale, i18next } from '@icore/template-shared';
628
745
  import { ThemeToggle } from '../ThemeToggle';
629
746
 
630
747
  const LOCALES: { code: IcoreLocale; label: string }[] = [
@@ -636,7 +753,7 @@ const LOCALES: { code: IcoreLocale; label: string }[] = [
636
753
  export function LayoutHeader() {
637
754
  function handleLocale(code: IcoreLocale) {
638
755
  setStoredLocale(code);
639
- window.location.reload();
756
+ void i18next.changeLanguage(code);
640
757
  }
641
758
 
642
759
  return (
@@ -770,7 +887,7 @@ export const Route = createFileRoute('/')({
770
887
  });
771
888
  `;
772
889
  var ANTD_LAYOUT_HEADER_TSX = `import { Button, Layout, Space } from 'antd';
773
- import { setStoredLocale, type IcoreLocale } from '@icore/template-shared';
890
+ import { setStoredLocale, type IcoreLocale, i18next } from '@icore/template-shared';
774
891
  import { ThemeToggle } from '../ThemeToggle';
775
892
 
776
893
  const APP_VERSION = (import.meta.env.VITE_APP_VERSION as string | undefined) ?? '0.0.0-dev';
@@ -784,7 +901,7 @@ const LOCALES: { code: IcoreLocale; label: string }[] = [
784
901
  export function LayoutHeader() {
785
902
  function handleLocale(code: IcoreLocale) {
786
903
  setStoredLocale(code);
787
- window.location.reload();
904
+ void i18next.changeLanguage(code);
788
905
  }
789
906
 
790
907
  return (
@@ -956,7 +1073,7 @@ export function LayoutHeader() {
956
1073
 
957
1074
  function handleLocale(code: IcoreLocale) {
958
1075
  setStoredLocale(code);
959
- window.location.reload();
1076
+ void i18n.changeLanguage(code);
960
1077
  }
961
1078
 
962
1079
  return (
@@ -1000,19 +1117,22 @@ var UI_VARIANTS = {
1000
1117
  "apps/client/src/main.tsx": SHADCN_MAIN_TSX,
1001
1118
  "apps/client/src/routes/_dashboard.tsx": SHADCN_DASHBOARD_TSX,
1002
1119
  "apps/client/src/routes/index.tsx": SHADCN_INDEX_TSX,
1003
- "apps/client/src/components/layout/LayoutHeader.tsx": SHADCN_LAYOUT_HEADER_TSX
1120
+ "apps/client/src/components/layout/LayoutHeader.tsx": SHADCN_LAYOUT_HEADER_TSX,
1121
+ "apps/client/src/components/PageLayout.tsx": SHADCN_PAGE_LAYOUT_TSX
1004
1122
  },
1005
1123
  antd: {
1006
1124
  "apps/client/src/main.tsx": ANTD_MAIN_TSX,
1007
1125
  "apps/client/src/routes/_dashboard.tsx": ANTD_DASHBOARD_TSX,
1008
1126
  "apps/client/src/routes/index.tsx": ANTD_INDEX_TSX,
1009
- "apps/client/src/components/layout/LayoutHeader.tsx": ANTD_LAYOUT_HEADER_TSX
1127
+ "apps/client/src/components/layout/LayoutHeader.tsx": ANTD_LAYOUT_HEADER_TSX,
1128
+ "apps/client/src/components/PageLayout.tsx": ANTD_PAGE_LAYOUT_TSX
1010
1129
  },
1011
1130
  mui: {
1012
1131
  "apps/client/src/main.tsx": MUI_MAIN_TSX,
1013
1132
  "apps/client/src/routes/_dashboard.tsx": MUI_DASHBOARD_TSX,
1014
1133
  "apps/client/src/routes/index.tsx": MUI_INDEX_TSX,
1015
- "apps/client/src/components/layout/LayoutHeader.tsx": MUI_LAYOUT_HEADER_TSX
1134
+ "apps/client/src/components/layout/LayoutHeader.tsx": MUI_LAYOUT_HEADER_TSX,
1135
+ "apps/client/src/components/PageLayout.tsx": MUI_PAGE_LAYOUT_TSX
1016
1136
  }
1017
1137
  };
1018
1138
  async function applyAuthNoneVariants(targetDir, ui) {
@@ -1892,6 +2012,9 @@ async function scaffold(rawOpts, templatesDir) {
1892
2012
  await writePnpmWorkspace(opts.targetDir);
1893
2013
  await rewritePnpmWorkspaceDeps(opts.targetDir);
1894
2014
  }
2015
+ if (opts.packageManager === "npm") {
2016
+ await (0, import_promises9.writeFile)((0, import_node_path9.join)(opts.targetDir, ".npmrc"), "legacy-peer-deps=true\n");
2017
+ }
1895
2018
  await patchGitignoreForPm(opts.targetDir, opts.packageManager);
1896
2019
  await writeAiFiles(opts.targetDir, opts);
1897
2020
  if (opts.install) runInstall(opts.targetDir, opts.packageManager);
package/dist/index.js CHANGED
@@ -78,6 +78,7 @@ async function rewriteRootPackageJson(targetDir, opts) {
78
78
  const pkg = JSON.parse(raw);
79
79
  pkg["name"] = opts.projectName;
80
80
  pkg["version"] = "0.0.1";
81
+ pkg["icoreVersion"] = typeof ICORE_OWN_VERSION !== "undefined" ? ICORE_OWN_VERSION : "unknown";
81
82
  pkg["private"] = true;
82
83
  delete pkg.description;
83
84
  const transportDeps = TRANSPORT_DEPS[opts.transport];
@@ -345,6 +346,7 @@ var AUTH_ONLY_PATHS = [
345
346
  "apps/api/src/app/abilities",
346
347
  "libs/shared/src/abilities",
347
348
  "apps/client/src/components/auth",
349
+ "apps/client/src/components/AccessDeniedPage.tsx",
348
350
  "apps/client/src/routes/login.tsx",
349
351
  "apps/client/src/routes/auth.callback.tsx",
350
352
  "apps/client/src/routes/auth.oauth.callback.tsx",
@@ -481,6 +483,121 @@ export * from './lib/draft/index.js';
481
483
  export * from './lib/landing/LandingPage.js';
482
484
  export * from './lib/stores/theme.store.js';
483
485
  `;
486
+ var SHADCN_PAGE_LAYOUT_TSX = `import type { ReactNode } from 'react';
487
+ import { useTranslation } from 'react-i18next';
488
+ import { useDraft, useLoading } from '@icore/template-shared';
489
+
490
+ interface PageLayoutProps {
491
+ title: string;
492
+ description?: string;
493
+ actions?: ReactNode;
494
+ children: ReactNode;
495
+ }
496
+
497
+ export function PageLayout({ title, description, actions, children }: PageLayoutProps) {
498
+ const { t } = useTranslation();
499
+ const isLoading = useLoading();
500
+
501
+ useDraft(false);
502
+
503
+ return (
504
+ <div className="p-4 md:p-6 space-y-4">
505
+ <div className="flex items-start justify-between gap-3">
506
+ <div>
507
+ <h1 className="text-xl font-semibold text-foreground">{title}</h1>
508
+ {description && <p className="text-sm text-muted-foreground mt-1">{description}</p>}
509
+ </div>
510
+ {actions && <div>{actions}</div>}
511
+ </div>
512
+
513
+ {isLoading && (
514
+ <div
515
+ role="status"
516
+ aria-label={t('common.loading')}
517
+ className="fixed inset-0 z-50 flex items-center justify-center bg-background/60 backdrop-blur-sm"
518
+ >
519
+ <div className="h-8 w-8 animate-spin rounded-full border-2 border-primary border-t-transparent" />
520
+ </div>
521
+ )}
522
+
523
+ {children}
524
+ </div>
525
+ );
526
+ }
527
+ `;
528
+ var ANTD_PAGE_LAYOUT_TSX = `import type { ReactNode } from 'react';
529
+ import { Descriptions, Spin } from 'antd';
530
+ import { useDraft, useLoading } from '@icore/template-shared';
531
+
532
+ export interface PageLayoutProps {
533
+ title: ReactNode;
534
+ description?: ReactNode;
535
+ extra?: ReactNode;
536
+ children?: ReactNode;
537
+ }
538
+
539
+ export function PageLayout({ title, description, extra, children }: PageLayoutProps) {
540
+ useDraft(false);
541
+ const loading = useLoading();
542
+
543
+ return (
544
+ <div style={{ padding: 24 }}>
545
+ <Descriptions title={title} extra={extra} style={{ marginBottom: 16 }}>
546
+ {description ? <Descriptions.Item>{description}</Descriptions.Item> : null}
547
+ </Descriptions>
548
+ <Spin spinning={loading}>
549
+ <div>{children}</div>
550
+ </Spin>
551
+ </div>
552
+ );
553
+ }
554
+ `;
555
+ var MUI_PAGE_LAYOUT_TSX = `import type { ReactNode } from 'react';
556
+ import { Box, LinearProgress, Stack, Typography } from '@mui/material';
557
+ import { useDraft, useLoading } from '@icore/template-shared';
558
+
559
+ export interface PageLayoutProps {
560
+ title: ReactNode;
561
+ description?: ReactNode;
562
+ extra?: ReactNode;
563
+ children?: ReactNode;
564
+ }
565
+
566
+ export function PageLayout({ title, description, extra, children }: PageLayoutProps) {
567
+ useDraft(false);
568
+ const loading = useLoading();
569
+
570
+ return (
571
+ <Box sx={{ p: 3 }}>
572
+ <Stack
573
+ direction="row"
574
+ justifyContent="space-between"
575
+ alignItems="flex-start"
576
+ spacing={2}
577
+ mb={3}
578
+ >
579
+ <Box>
580
+ <Typography variant="h4" component="h1">
581
+ {title}
582
+ </Typography>
583
+ {description ? (
584
+ <Typography variant="body2" color="text.secondary" mt={0.5}>
585
+ {description}
586
+ </Typography>
587
+ ) : null}
588
+ </Box>
589
+ {extra ? (
590
+ <Stack direction="row" spacing={1}>
591
+ {extra}
592
+ </Stack>
593
+ ) : null}
594
+ </Stack>
595
+ {loading ? <LinearProgress sx={{ mb: 2 }} /> : null}
596
+ <Box>{children}</Box>
597
+ </Box>
598
+ );
599
+ }
600
+ `;
484
601
  var SHADCN_MAIN_TSX = `import './globals.css';
485
602
  import { StrictMode } from 'react';
486
603
  import { createRoot } from 'react-dom/client';
@@ -581,7 +698,7 @@ export const Route = createFileRoute('/')({
581
698
  ),
582
699
  });
583
700
  `;
584
- var SHADCN_LAYOUT_HEADER_TSX = `import { setStoredLocale, type IcoreLocale } from '@icore/template-shared';
701
+ var SHADCN_LAYOUT_HEADER_TSX = `import { setStoredLocale, type IcoreLocale, i18next } from '@icore/template-shared';
585
702
  import { ThemeToggle } from '../ThemeToggle';
586
703
 
587
704
  const LOCALES: { code: IcoreLocale; label: string }[] = [
@@ -593,7 +710,7 @@ const LOCALES: { code: IcoreLocale; label: string }[] = [
593
710
  export function LayoutHeader() {
594
711
  function handleLocale(code: IcoreLocale) {
595
712
  setStoredLocale(code);
596
- window.location.reload();
713
+ void i18next.changeLanguage(code);
597
714
  }
598
715
 
599
716
  return (
@@ -727,7 +844,7 @@ export const Route = createFileRoute('/')({
727
844
  });
728
845
  `;
729
846
  var ANTD_LAYOUT_HEADER_TSX = `import { Button, Layout, Space } from 'antd';
730
- import { setStoredLocale, type IcoreLocale } from '@icore/template-shared';
847
+ import { setStoredLocale, type IcoreLocale, i18next } from '@icore/template-shared';
731
848
  import { ThemeToggle } from '../ThemeToggle';
732
849
 
733
850
  const APP_VERSION = (import.meta.env.VITE_APP_VERSION as string | undefined) ?? '0.0.0-dev';
@@ -741,7 +858,7 @@ const LOCALES: { code: IcoreLocale; label: string }[] = [
741
858
  export function LayoutHeader() {
742
859
  function handleLocale(code: IcoreLocale) {
743
860
  setStoredLocale(code);
744
- window.location.reload();
861
+ void i18next.changeLanguage(code);
745
862
  }
746
863
 
747
864
  return (
@@ -913,7 +1030,7 @@ export function LayoutHeader() {
913
1030
 
914
1031
  function handleLocale(code: IcoreLocale) {
915
1032
  setStoredLocale(code);
916
- window.location.reload();
1033
+ void i18n.changeLanguage(code);
917
1034
  }
918
1035
 
919
1036
  return (
@@ -957,19 +1074,22 @@ var UI_VARIANTS = {
957
1074
  "apps/client/src/main.tsx": SHADCN_MAIN_TSX,
958
1075
  "apps/client/src/routes/_dashboard.tsx": SHADCN_DASHBOARD_TSX,
959
1076
  "apps/client/src/routes/index.tsx": SHADCN_INDEX_TSX,
960
- "apps/client/src/components/layout/LayoutHeader.tsx": SHADCN_LAYOUT_HEADER_TSX
1077
+ "apps/client/src/components/layout/LayoutHeader.tsx": SHADCN_LAYOUT_HEADER_TSX,
1078
+ "apps/client/src/components/PageLayout.tsx": SHADCN_PAGE_LAYOUT_TSX
961
1079
  },
962
1080
  antd: {
963
1081
  "apps/client/src/main.tsx": ANTD_MAIN_TSX,
964
1082
  "apps/client/src/routes/_dashboard.tsx": ANTD_DASHBOARD_TSX,
965
1083
  "apps/client/src/routes/index.tsx": ANTD_INDEX_TSX,
966
- "apps/client/src/components/layout/LayoutHeader.tsx": ANTD_LAYOUT_HEADER_TSX
1084
+ "apps/client/src/components/layout/LayoutHeader.tsx": ANTD_LAYOUT_HEADER_TSX,
1085
+ "apps/client/src/components/PageLayout.tsx": ANTD_PAGE_LAYOUT_TSX
967
1086
  },
968
1087
  mui: {
969
1088
  "apps/client/src/main.tsx": MUI_MAIN_TSX,
970
1089
  "apps/client/src/routes/_dashboard.tsx": MUI_DASHBOARD_TSX,
971
1090
  "apps/client/src/routes/index.tsx": MUI_INDEX_TSX,
972
- "apps/client/src/components/layout/LayoutHeader.tsx": MUI_LAYOUT_HEADER_TSX
1091
+ "apps/client/src/components/layout/LayoutHeader.tsx": MUI_LAYOUT_HEADER_TSX,
1092
+ "apps/client/src/components/PageLayout.tsx": MUI_PAGE_LAYOUT_TSX
973
1093
  }
974
1094
  };
975
1095
  async function applyAuthNoneVariants(targetDir, ui) {
@@ -1849,6 +1969,9 @@ async function scaffold(rawOpts, templatesDir) {
1849
1969
  await writePnpmWorkspace(opts.targetDir);
1850
1970
  await rewritePnpmWorkspaceDeps(opts.targetDir);
1851
1971
  }
1972
+ if (opts.packageManager === "npm") {
1973
+ await writeFile9(join9(opts.targetDir, ".npmrc"), "legacy-peer-deps=true\n");
1974
+ }
1852
1975
  await patchGitignoreForPm(opts.targetDir, opts.packageManager);
1853
1976
  await writeAiFiles(opts.targetDir, opts);
1854
1977
  if (opts.install) runInstall(opts.targetDir, opts.packageManager);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idevconn/create-icore",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
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",
@@ -4,6 +4,6 @@
4
4
  "private": true,
5
5
  "dependencies": {
6
6
  "@ant-design/icons": "^6.2.5",
7
- "antd": "^6.4.4"
7
+ "antd": "^6.4.5"
8
8
  }
9
9
  }
@@ -13,13 +13,13 @@ const LOCALES: { code: IcoreLocale; label: string }[] = [
13
13
  ];
14
14
 
15
15
  export function LayoutHeader() {
16
- const { t } = useTranslation();
16
+ const { t, i18n } = useTranslation();
17
17
  const navigate = useNavigate();
18
18
  const user = useAuthStore((s) => s.user);
19
19
 
20
20
  function handleLocale(code: IcoreLocale) {
21
21
  setStoredLocale(code);
22
- window.location.reload();
22
+ void i18n.changeLanguage(code);
23
23
  }
24
24
 
25
25
  function handleLogout() {
@@ -32,7 +32,7 @@ export function LayoutHeader() {
32
32
 
33
33
  function handleLocale(code: IcoreLocale) {
34
34
  setStoredLocale(code);
35
- window.location.reload();
35
+ void i18n.changeLanguage(code);
36
36
  }
37
37
 
38
38
  function handleMenuOpen(event: React.MouseEvent<HTMLElement>) {
@@ -12,14 +12,14 @@ const LOCALES: { code: IcoreLocale; label: string }[] = [
12
12
  ];
13
13
 
14
14
  export function LayoutHeader() {
15
- const { t } = useTranslation();
15
+ const { t, i18n } = useTranslation();
16
16
  const navigate = useNavigate();
17
17
  const user = useAuthStore((s) => s.user);
18
18
  const logout = useAuthStore((s) => s.logout);
19
19
 
20
20
  function handleLocale(code: IcoreLocale) {
21
21
  setStoredLocale(code);
22
- window.location.reload();
22
+ void i18n.changeLanguage(code);
23
23
  }
24
24
 
25
25
  function handleLogout() {
@@ -16,7 +16,7 @@ export function LayoutSider() {
16
16
 
17
17
  return (
18
18
  <aside
19
- className={`relative flex flex-col border-r border-[--color-border] bg-[--color-card] transition-all duration-200 ${
19
+ className={`relative flex flex-col border-e border-[--color-border] bg-[--color-card] transition-all duration-200 ${
20
20
  collapsed ? 'w-14' : 'w-52'
21
21
  }`}
22
22
  >
@@ -41,9 +41,13 @@ export function LayoutSider() {
41
41
  type="button"
42
42
  onClick={() => setCollapsed((c) => !c)}
43
43
  aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
44
- className="absolute -right-3 top-5 z-10 flex h-6 w-6 cursor-pointer items-center justify-center rounded-full border border-[--color-border] bg-[--color-card] text-[--color-muted-foreground] shadow-sm hover:text-[--color-foreground] transition-colors"
44
+ className="absolute -end-3 top-5 z-10 flex h-6 w-6 cursor-pointer items-center justify-center rounded-full border border-[--color-border] bg-[--color-card] text-[--color-muted-foreground] shadow-sm hover:text-[--color-foreground] transition-colors"
45
45
  >
46
- {collapsed ? <ChevronRight size={12} /> : <ChevronLeft size={12} />}
46
+ {collapsed ? (
47
+ <ChevronRight size={12} className="rtl:scale-x-[-1]" />
48
+ ) : (
49
+ <ChevronLeft size={12} className="rtl:scale-x-[-1]" />
50
+ )}
47
51
  </button>
48
52
  </aside>
49
53
  );
@@ -4,12 +4,12 @@ import { LandingPage } from '@icore/template-shared';
4
4
  // All version strings are injected at build time by vite.config.mts
5
5
  // (reads root package.json via fs.readFileSync so they stay accurate
6
6
  // even when workspace packages are bumped independently).
7
- const APP_VERSION = (import.meta.env.VITE_APP_VERSION as string | undefined) ?? '0.0.0-dev';
7
+ const ICORE_VERSION = (import.meta.env.VITE_ICORE_VERSION as string | undefined) ?? '0.0.0-dev';
8
8
 
9
9
  export const Route = createFileRoute('/')({
10
10
  component: () => (
11
11
  <LandingPage
12
- coreVersion={APP_VERSION}
12
+ coreVersion={ICORE_VERSION}
13
13
  uiLibrary="shadcn"
14
14
  deps={[
15
15
  { name: 'react', version: (import.meta.env.VITE_DEP_REACT as string) ?? '?' },
@@ -19,12 +19,26 @@ import {
19
19
  const rootPackageJsonPath = new URL('../../../package.json', import.meta.url);
20
20
  const rootPackageJson = JSON.parse(fs.readFileSync(rootPackageJsonPath, 'utf-8')) as {
21
21
  version: string;
22
+ icoreVersion?: string;
23
+ dependencies?: Record<string, string>;
24
+ devDependencies?: Record<string, string>;
25
+ };
26
+
27
+ const selfPackageJson = JSON.parse(
28
+ fs.readFileSync(new URL('./package.json', import.meta.url), 'utf-8'),
29
+ ) as {
22
30
  dependencies?: Record<string, string>;
23
31
  devDependencies?: Record<string, string>;
24
32
  };
25
33
 
26
34
  function depVersion(name: string): string {
27
- return rootPackageJson.dependencies?.[name] ?? rootPackageJson.devDependencies?.[name] ?? '?';
35
+ return (
36
+ rootPackageJson.dependencies?.[name] ??
37
+ rootPackageJson.devDependencies?.[name] ??
38
+ selfPackageJson.dependencies?.[name] ??
39
+ selfPackageJson.devDependencies?.[name] ??
40
+ '?'
41
+ );
28
42
  }
29
43
 
30
44
  export default defineConfig(() => ({
@@ -37,6 +51,9 @@ export default defineConfig(() => ({
37
51
  },
38
52
  define: {
39
53
  ...commonDefines(rootPackageJson),
54
+ 'import.meta.env.VITE_ICORE_VERSION': JSON.stringify(
55
+ rootPackageJson.icoreVersion ?? rootPackageJson.version,
56
+ ),
40
57
  'import.meta.env.VITE_DEP_TAILWINDCSS': JSON.stringify(depVersion('tailwindcss')),
41
58
  },
42
59
  plugins: [
@@ -10,6 +10,7 @@
10
10
  "@icore/shared": "*",
11
11
  "@nestjs/common": "^11.1.27",
12
12
  "@nestjs/config": "^4.0.4",
13
+ "firebase-admin": "^14.1.0",
13
14
  "tslib": "^2.8.1"
14
15
  },
15
16
  "devDependencies": {
@@ -1,6 +1,7 @@
1
1
  import { Module, DynamicModule } from '@nestjs/common';
2
2
  import { ConfigService } from '@nestjs/config';
3
3
  import { getFirebaseAdmin, FIREBASE_ADMIN_REQUIRED_ENV } from '@icore/firebase-admin';
4
+ import { getAuth } from 'firebase-admin/auth';
4
5
  import { buildStrategyWithFallback, FakeAuthStrategy } from '@icore/shared';
5
6
  import type { AuthStrategy } from '@icore/shared';
6
7
  import { FirebaseAuthStrategy } from './firebase-auth.strategy';
@@ -28,7 +29,7 @@ export class FirebaseAuthModule {
28
29
  const identityToolkit = new HttpIdentityToolkitClient(
29
30
  cfg.getOrThrow<string>('FIREBASE_WEB_API_KEY'),
30
31
  );
31
- return new FirebaseAuthStrategy({ identityToolkit, adminAuth: app.auth() });
32
+ return new FirebaseAuthStrategy({ identityToolkit, adminAuth: getAuth(app) });
32
33
  },
33
34
  fake: () => new FakeAuthStrategy(),
34
35
  }),
@@ -17,7 +17,7 @@
17
17
  "bcrypt": "^6.0.0",
18
18
  "jsonwebtoken": "^9.0.3",
19
19
  "mongodb-memory-server": "^11.2.0",
20
- "mongoose": "^9.7.1",
20
+ "mongoose": "^9.7.2",
21
21
  "tslib": "^2.8.1"
22
22
  }
23
23
  }
@@ -10,6 +10,7 @@
10
10
  "@icore/shared": "*",
11
11
  "@nestjs/common": "^11.1.27",
12
12
  "@nestjs/config": "^4.0.4",
13
+ "firebase-admin": "^14.1.0",
13
14
  "tslib": "^2.8.1"
14
15
  },
15
16
  "devDependencies": {
@@ -1,6 +1,7 @@
1
1
  import { Module, DynamicModule } from '@nestjs/common';
2
2
  import { ConfigService } from '@nestjs/config';
3
3
  import { getFirebaseAdmin, FIREBASE_ADMIN_REQUIRED_ENV } from '@icore/firebase-admin';
4
+ import { getFirestore } from 'firebase-admin/firestore';
4
5
  import { buildStrategyWithFallback, FakeDBStrategy } from '@icore/shared';
5
6
  import type { DBStrategy } from '@icore/shared';
6
7
  import { FirestoreDBStrategy } from './firestore-db.strategy';
@@ -25,7 +26,7 @@ export class FirestoreDbModule {
25
26
  build: () => {
26
27
  const app = getFirebaseAdmin(cfg);
27
28
  return new FirestoreDBStrategy({
28
- db: app.firestore() as unknown as ConstructorParameters<
29
+ db: getFirestore(app) as unknown as ConstructorParameters<
29
30
  typeof FirestoreDBStrategy
30
31
  >[0]['db'],
31
32
  });
@@ -11,7 +11,7 @@
11
11
  "@nestjs/config": "^4.0.4",
12
12
  "@nestjs/mongoose": "^11.0.4",
13
13
  "mongodb-memory-server": "^11.2.0",
14
- "mongoose": "^9.7.1",
14
+ "mongoose": "^9.7.2",
15
15
  "tslib": "^2.8.1"
16
16
  }
17
17
  }
@@ -6,7 +6,7 @@
6
6
  "main": "./src/index.js",
7
7
  "types": "./src/index.ts",
8
8
  "dependencies": {
9
- "firebase-admin": "^13.10.0",
9
+ "firebase-admin": "^14.1.0",
10
10
  "tslib": "^2.8.1"
11
11
  }
12
12
  }
@@ -1,6 +1,6 @@
1
1
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
2
 
3
- const { apps, cert, initializeApp, app } = vi.hoisted(() => {
3
+ const { apps, cert, initializeApp, getApp, getApps } = vi.hoisted(() => {
4
4
  const apps: unknown[] = [];
5
5
  return {
6
6
  apps,
@@ -10,17 +10,16 @@ const { apps, cert, initializeApp, app } = vi.hoisted(() => {
10
10
  apps.push(created);
11
11
  return created;
12
12
  }),
13
- app: vi.fn(() => apps[0]),
13
+ getApp: vi.fn(() => apps[0]),
14
+ getApps: vi.fn(() => apps),
14
15
  };
15
16
  });
16
17
 
17
- vi.mock('firebase-admin', () => ({
18
- get apps() {
19
- return apps;
20
- },
21
- app,
18
+ vi.mock('firebase-admin/app', () => ({
19
+ cert,
22
20
  initializeApp,
23
- credential: { cert },
21
+ getApp,
22
+ getApps,
24
23
  }));
25
24
 
26
25
  import { FIREBASE_ADMIN_REQUIRED_ENV, getFirebaseAdmin } from '../firebase-admin';
@@ -59,7 +58,8 @@ describe('getFirebaseAdmin', () => {
59
58
  apps.length = 0;
60
59
  cert.mockClear();
61
60
  initializeApp.mockClear();
62
- app.mockClear();
61
+ getApp.mockClear();
62
+ getApps.mockClear();
63
63
  });
64
64
 
65
65
  afterEach(() => vi.restoreAllMocks());
@@ -94,12 +94,12 @@ describe('getFirebaseAdmin', () => {
94
94
  expect(sa['private_key']).not.toContain('\\n');
95
95
  });
96
96
 
97
- it('initializes the default app only once (guards on admin.apps)', () => {
97
+ it('initializes the default app only once (guards on getApps)', () => {
98
98
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
99
99
  const cfg = makeCfg() as any;
100
100
  getFirebaseAdmin(cfg);
101
101
  getFirebaseAdmin(cfg);
102
102
  expect(initializeApp).toHaveBeenCalledTimes(1);
103
- expect(app).toHaveBeenCalledTimes(1); // second call returns the existing app
103
+ expect(getApp).toHaveBeenCalledTimes(1); // second call returns the existing app
104
104
  });
105
105
  });
@@ -1,4 +1,4 @@
1
- import * as admin from 'firebase-admin';
1
+ import { initializeApp, getApps, getApp, cert, App, ServiceAccount } from 'firebase-admin/app';
2
2
 
3
3
  /**
4
4
  * Minimal structural view of NestJS's `ConfigService` — just the two readers
@@ -46,11 +46,11 @@ export const FIREBASE_ADMIN_REQUIRED_ENV = [
46
46
  * tokens from project_id/client_email/private_key, but feeding the complete
47
47
  * downloaded JSON keeps the code aligned with the documented FB_ADMIN_* env.
48
48
  */
49
- export function getFirebaseAdmin(cfg: FirebaseConfigReader): admin.app.App {
50
- if (admin.apps.length > 0) return admin.app();
49
+ export function getFirebaseAdmin(cfg: FirebaseConfigReader): App {
50
+ if (getApps().length > 0) return getApp();
51
51
 
52
- return admin.initializeApp({
53
- credential: admin.credential.cert({
52
+ return initializeApp({
53
+ credential: cert({
54
54
  type: cfg.getOrThrow<string>('FB_ADMIN_TYPE'),
55
55
  project_id: cfg.getOrThrow<string>('FB_ADMIN_PROJECT_ID'),
56
56
  private_key_id: cfg.getOrThrow<string>('FB_ADMIN_PRIVATE_KEY_ID'),
@@ -62,7 +62,7 @@ export function getFirebaseAdmin(cfg: FirebaseConfigReader): admin.app.App {
62
62
  auth_provider_x509_cert_url: cfg.getOrThrow<string>('FB_ADMIN_AUTH_PROVIDER_X509_CERT_URL'),
63
63
  client_x509_cert_url: cfg.getOrThrow<string>('FB_ADMIN_CLIENT_X509_CERT_URL'),
64
64
  universe_domain: cfg.getOrThrow<string>('FB_ADMIN_UNIVERSE_DOMAIN'),
65
- } as unknown as admin.ServiceAccount),
65
+ } as ServiceAccount),
66
66
  // Optional: the storage MS also passes the bucket name explicitly to
67
67
  // .bucket(); set here too so admin.storage().bucket() (no arg) works.
68
68
  storageBucket: cfg.get<string>('FIREBASE_STORAGE_BUCKET'),
@@ -10,6 +10,7 @@
10
10
  "@icore/shared": "*",
11
11
  "@nestjs/common": "^11.1.27",
12
12
  "@nestjs/config": "^4.0.4",
13
+ "firebase-admin": "^14.1.0",
13
14
  "tslib": "^2.8.1"
14
15
  },
15
16
  "devDependencies": {
@@ -1,6 +1,7 @@
1
1
  import { Module, DynamicModule } from '@nestjs/common';
2
2
  import { ConfigService } from '@nestjs/config';
3
3
  import { getFirebaseAdmin, FIREBASE_ADMIN_REQUIRED_ENV } from '@icore/firebase-admin';
4
+ import { getStorage } from 'firebase-admin/storage';
4
5
  import { buildStrategyWithFallback, FakeStorageStrategy } from '@icore/shared';
5
6
  import type { StorageStrategy } from '@icore/shared';
6
7
  import {
@@ -32,7 +33,9 @@ export class FirebaseStorageModule {
32
33
  const bucketName = cfg.getOrThrow<string>('FIREBASE_STORAGE_BUCKET');
33
34
  const app = getFirebaseAdmin(cfg);
34
35
  return new FirebaseStorageStrategy({
35
- bucket: app.storage().bucket(bucketName) as unknown as FirebaseStorageBucketLike,
36
+ bucket: getStorage(app).bucket(
37
+ bucketName,
38
+ ) as unknown as FirebaseStorageBucketLike,
36
39
  });
37
40
  },
38
41
  fake: () => new FakeStorageStrategy(),
@@ -11,7 +11,7 @@
11
11
  "@nestjs/config": "^4.0.4",
12
12
  "@nestjs/mongoose": "^11.0.4",
13
13
  "mongodb-memory-server": "^11.2.0",
14
- "mongoose": "^9.7.1",
14
+ "mongoose": "^9.7.2",
15
15
  "tslib": "^2.8.1"
16
16
  }
17
17
  }
@@ -10,7 +10,7 @@
10
10
  "@icore/shared": "*",
11
11
  "@idevconn/api-client": "^0.3.0",
12
12
  "@idevconn/use-draft": "^0.2.0",
13
- "i18next": "^26.3.1",
13
+ "i18next": "^26.3.2",
14
14
  "react-i18next": "^17.0.8",
15
15
  "tslib": "^2.8.1",
16
16
  "zustand": "^5.0.14"