@lastbrain/app 0.1.29 → 0.1.31

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 (53) hide show
  1. package/dist/auth/useAuthSession.d.ts.map +1 -1
  2. package/dist/auth/useAuthSession.js +8 -2
  3. package/dist/components/NotificationContainer.d.ts +2 -0
  4. package/dist/components/NotificationContainer.d.ts.map +1 -0
  5. package/dist/components/NotificationContainer.js +8 -0
  6. package/dist/hooks/useNotifications.d.ts +29 -0
  7. package/dist/hooks/useNotifications.d.ts.map +1 -0
  8. package/dist/hooks/useNotifications.js +165 -0
  9. package/dist/hooks/useNotificationsSimple.d.ts +20 -0
  10. package/dist/hooks/useNotificationsSimple.d.ts.map +1 -0
  11. package/dist/hooks/useNotificationsSimple.js +37 -0
  12. package/dist/index.d.ts +4 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +4 -1
  15. package/dist/layouts/AdminLayoutWithSidebar.d.ts.map +1 -1
  16. package/dist/layouts/AdminLayoutWithSidebar.js +15 -3
  17. package/dist/layouts/AppProviders.d.ts +15 -2
  18. package/dist/layouts/AppProviders.d.ts.map +1 -1
  19. package/dist/layouts/AppProviders.js +21 -8
  20. package/dist/layouts/AuthLayoutWithSidebar.d.ts.map +1 -1
  21. package/dist/layouts/AuthLayoutWithSidebar.js +13 -3
  22. package/dist/layouts/PublicLayout.js +1 -1
  23. package/dist/layouts/PublicLayoutWithSidebar.d.ts +8 -0
  24. package/dist/layouts/PublicLayoutWithSidebar.d.ts.map +1 -0
  25. package/dist/layouts/PublicLayoutWithSidebar.js +19 -0
  26. package/dist/layouts/RootLayout.d.ts +3 -1
  27. package/dist/layouts/RootLayout.d.ts.map +1 -1
  28. package/dist/layouts/RootLayout.js +3 -2
  29. package/dist/scripts/init-app.d.ts.map +1 -1
  30. package/dist/scripts/init-app.js +159 -5
  31. package/dist/scripts/module-build.d.ts.map +1 -1
  32. package/dist/scripts/module-build.js +52 -0
  33. package/dist/styles.css +1 -1
  34. package/dist/templates/DefaultDoc.d.ts.map +1 -1
  35. package/dist/templates/DefaultDoc.js +64 -1
  36. package/dist/templates/DocPage.d.ts.map +1 -1
  37. package/dist/templates/DocPage.js +7 -0
  38. package/package.json +1 -2
  39. package/src/auth/useAuthSession.ts +6 -2
  40. package/src/components/NotificationContainer.tsx +20 -0
  41. package/src/hooks/useNotifications.ts +216 -0
  42. package/src/index.ts +4 -1
  43. package/src/layouts/AdminLayoutWithSidebar.tsx +28 -4
  44. package/src/layouts/AppProviders.tsx +50 -13
  45. package/src/layouts/AuthLayoutWithSidebar.tsx +25 -4
  46. package/src/layouts/PublicLayout.tsx +1 -1
  47. package/src/layouts/PublicLayoutWithSidebar.tsx +56 -0
  48. package/src/layouts/RootLayout.tsx +10 -2
  49. package/src/scripts/generate-realtime-config.mjs +183 -0
  50. package/src/scripts/init-app.ts +172 -5
  51. package/src/scripts/module-build.ts +63 -0
  52. package/src/templates/DefaultDoc.tsx +265 -0
  53. package/src/templates/DocPage.tsx +7 -0
@@ -0,0 +1,56 @@
1
+ "use client";
2
+
3
+ import { PublicLayout } from "./PublicLayout.js";
4
+ import { AppAside, AppAsideSkeleton } from "@lastbrain/ui";
5
+ import { useAuthSession } from "../auth/useAuthSession.js";
6
+
7
+ interface PublicLayoutWithSidebarProps {
8
+ children: React.ReactNode;
9
+ menuConfig?: any;
10
+ className?: string;
11
+ }
12
+
13
+ export function PublicLayoutWithSidebar({
14
+ children,
15
+ menuConfig,
16
+ className = "",
17
+ }: PublicLayoutWithSidebarProps) {
18
+ const { user, loading } = useAuthSession();
19
+
20
+ // Pour la section public, isAdminSection sera false
21
+ const isAdminSection = false;
22
+
23
+ // Vérifier si menuConfig est vraiment disponible et valide
24
+ const hasValidMenuConfig =
25
+ menuConfig &&
26
+ typeof menuConfig === "object" &&
27
+ (menuConfig.admin?.length > 0 ||
28
+ menuConfig.auth?.length > 0 ||
29
+ menuConfig.public?.length > 0);
30
+
31
+ // Afficher le skeleton pendant le chargement de la session ou si pas de menuConfig valide
32
+ const shouldShowSkeleton = loading || !hasValidMenuConfig;
33
+
34
+ return (
35
+ <div className="flex min-h-screen">
36
+ {/* Afficher le skeleton pendant le chargement ou si pas de menuConfig valide */}
37
+ {shouldShowSkeleton ? (
38
+ <AppAsideSkeleton
39
+ className={className}
40
+ isAdminSection={isAdminSection}
41
+ />
42
+ ) : (
43
+ <AppAside
44
+ menuConfig={menuConfig}
45
+ isSuperAdmin={false}
46
+ isAuthenticated={!!user}
47
+ className={className}
48
+ />
49
+ )}
50
+ {/* Contenu principal avec marge pour la sidebar */}
51
+ <div className="flex-1 lg:ml-72">
52
+ <PublicLayout>{children}</PublicLayout>
53
+ </div>
54
+ </div>
55
+ );
56
+ }
@@ -2,9 +2,17 @@
2
2
 
3
3
  import { ThemeProvider } from "next-themes";
4
4
  import { AppProviders } from "./AppProviders.js";
5
+ import type { ModuleRealtimeConfig } from "@lastbrain/core";
5
6
 
6
7
  // Note: L'app Next.js doit importer son propre globals.css dans son layout
7
- export function RootLayout({ children }: { children: React.ReactNode }) {
8
+ // Note: La configuration realtime doit être fournie par l'app qui utilise ce layout
9
+ export function RootLayout({
10
+ children,
11
+ realtimeConfig = [],
12
+ }: {
13
+ children: React.ReactNode;
14
+ realtimeConfig?: ModuleRealtimeConfig[];
15
+ }) {
8
16
  return (
9
17
  <html lang="fr" suppressHydrationWarning>
10
18
  <body className="min-h-screen">
@@ -14,7 +22,7 @@ export function RootLayout({ children }: { children: React.ReactNode }) {
14
22
  enableSystem={false}
15
23
  storageKey="lastbrain-theme"
16
24
  >
17
- <AppProviders>
25
+ <AppProviders realtimeConfig={realtimeConfig}>
18
26
  <div className=" min-h-screen bg-slate-50 text-slate-900 dark:bg-slate-950 dark:text-white">
19
27
  {children}
20
28
  </div>
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env node
2
+ /* global console, process */
3
+
4
+ import fs from "fs";
5
+ import path from "path";
6
+ import { fileURLToPath } from "url";
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ /**
12
+ * Script de génération de configuration realtime globale
13
+ * Lit les build.config de tous les modules et génère un fichier TypeScript de configuration
14
+ * pour le RealtimeProvider
15
+ */
16
+
17
+ async function loadModuleBuildConfig(modulePath) {
18
+ try {
19
+ // Charger directement le build.config depuis la version compilée
20
+ const configPath = path.join(modulePath, 'dist');
21
+ if (!fs.existsSync(configPath)) {
22
+ console.warn(`⚠️ Module non compilé: ${modulePath}/dist`);
23
+ return null;
24
+ }
25
+
26
+ // Chercher le fichier principal exporté
27
+ const packageJsonPath = path.join(modulePath, 'package.json');
28
+ if (!fs.existsSync(packageJsonPath)) {
29
+ return null;
30
+ }
31
+
32
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
33
+ const main = packageJson.main || 'dist/index.js';
34
+ const mainPath = path.resolve(modulePath, main);
35
+
36
+ if (!fs.existsSync(mainPath)) {
37
+ console.warn(`⚠️ Fichier principal non trouvé: ${mainPath}`);
38
+ return null;
39
+ }
40
+
41
+ // Charger le module
42
+ const moduleUrl = `file://${mainPath}`;
43
+ const moduleExports = await import(moduleUrl);
44
+ const buildConfig = moduleExports.default || moduleExports;
45
+
46
+ if (buildConfig && typeof buildConfig === 'object' && buildConfig.realtime) {
47
+ console.log(`✅ Configuration realtime trouvée pour ${buildConfig.moduleName}`);
48
+ return buildConfig;
49
+ }
50
+
51
+ return null;
52
+ } catch (error) {
53
+ console.warn(`⚠️ Erreur lors du chargement de ${modulePath}:`, error.message);
54
+ return null;
55
+ }
56
+ }
57
+
58
+ async function findModules(rootPath) {
59
+ const packagesPath = path.join(rootPath, 'packages');
60
+ const modules = [];
61
+
62
+ if (!fs.existsSync(packagesPath)) {
63
+ console.error('❌ Dossier packages non trouvé');
64
+ return modules;
65
+ }
66
+
67
+ const entries = fs.readdirSync(packagesPath);
68
+
69
+ for (const entry of entries) {
70
+ const entryPath = path.join(packagesPath, entry);
71
+ const stat = fs.statSync(entryPath);
72
+
73
+ if (stat.isDirectory() && entry.startsWith('module-')) {
74
+ const packageJsonPath = path.join(entryPath, 'package.json');
75
+
76
+ if (fs.existsSync(packageJsonPath)) {
77
+ modules.push(entryPath);
78
+ }
79
+ }
80
+ }
81
+
82
+ return modules;
83
+ }
84
+
85
+ function generateTypeScriptConfig(realtimeConfigs, targetPath) {
86
+ const imports = `import type { ModuleRealtimeConfig } from "@lastbrain/core";`;
87
+
88
+ const configData = {
89
+ modules: realtimeConfigs,
90
+ generatedAt: new Date().toISOString(),
91
+ version: "1.0.0"
92
+ };
93
+
94
+ const content = `${imports}
95
+
96
+ // GENERATED FILE - DO NOT EDIT MANUALLY
97
+ // Generated at: ${configData.generatedAt}
98
+
99
+ export const realtimeConfig: ModuleRealtimeConfig[] = ${JSON.stringify(configData.modules, null, 2)};
100
+
101
+ export default realtimeConfig;
102
+ `;
103
+
104
+ // Créer le dossier config s'il n'existe pas
105
+ const configDir = path.dirname(targetPath);
106
+ if (!fs.existsSync(configDir)) {
107
+ fs.mkdirSync(configDir, { recursive: true });
108
+ }
109
+
110
+ // Écrire le fichier TypeScript
111
+ fs.writeFileSync(targetPath, content);
112
+
113
+ return configData;
114
+ }
115
+
116
+ async function generateRealtimeConfig(targetAppPath) {
117
+ console.log('🔄 Génération de la configuration realtime...');
118
+
119
+ // Trouver le dossier racine du projet
120
+ const rootPath = path.resolve(__dirname, '../../../..');
121
+ console.log(`📁 Répertoire racine: ${rootPath}`);
122
+
123
+ // Trouver tous les modules
124
+ const modulePaths = await findModules(rootPath);
125
+ console.log(`📦 Modules trouvés: ${modulePaths.length}`);
126
+
127
+ const realtimeConfigs = [];
128
+
129
+ // Charger les configurations de chaque module
130
+ for (const modulePath of modulePaths) {
131
+ console.log(`🔍 Analyse du module: ${path.basename(modulePath)}`);
132
+
133
+ const buildConfig = await loadModuleBuildConfig(modulePath);
134
+
135
+ if (buildConfig?.realtime) {
136
+ console.log(`✅ Configuration realtime trouvée pour ${buildConfig.moduleName}`);
137
+ realtimeConfigs.push(buildConfig.realtime);
138
+ } else {
139
+ console.log(`ℹ️ Pas de configuration realtime pour ${path.basename(modulePath)}`);
140
+ }
141
+ }
142
+
143
+ // Générer le fichier TypeScript de configuration
144
+ const outputPath = path.join(targetAppPath, 'config', 'realtime.ts');
145
+ const configData = generateTypeScriptConfig(realtimeConfigs, outputPath);
146
+
147
+ console.log('✅ Configuration realtime générée avec succès!');
148
+ console.log(`📄 Fichier: ${outputPath}`);
149
+ console.log(`📊 Modules avec realtime: ${realtimeConfigs.length}`);
150
+
151
+ // Afficher un résumé
152
+ realtimeConfigs.forEach(module => {
153
+ console.log(` - ${module.moduleId}: ${module.tables.length} table(s)`);
154
+ module.tables.forEach(table => {
155
+ console.log(` • ${table.schema}.${table.table} (${table.event})`);
156
+ });
157
+ });
158
+
159
+ return configData;
160
+ }
161
+
162
+ // Exécution si le script est appelé directement
163
+ if (import.meta.url === `file://${process.argv[1]}`) {
164
+ const targetAppPath = process.argv[2];
165
+
166
+ if (!targetAppPath) {
167
+ console.error('❌ Chemin de l\'app cible requis');
168
+ console.log('Usage: node generate-realtime-config.mjs <target-app-path>');
169
+ process.exit(1);
170
+ }
171
+
172
+ generateRealtimeConfig(targetAppPath)
173
+ .then(() => {
174
+ console.log('🎉 Génération terminée!');
175
+ process.exit(0);
176
+ })
177
+ .catch((error) => {
178
+ console.error('❌ Erreur lors de la génération:', error);
179
+ process.exit(1);
180
+ });
181
+ }
182
+
183
+ export { generateRealtimeConfig };
@@ -90,6 +90,7 @@ export async function initApp(options: InitAppOptions) {
90
90
  // 6. Créer .gitignore et .env.local.example
91
91
  await createGitIgnore(targetDir, force);
92
92
  await createEnvExample(targetDir, force);
93
+ await createEnvLocal(targetDir, force);
93
94
 
94
95
  // 7. Créer la structure Supabase avec migrations
95
96
  await createSupabaseStructure(targetDir, force);
@@ -328,7 +329,7 @@ async function addDependencies(
328
329
  "@lastbrain/core": versions.core,
329
330
  "@lastbrain/ui": versions.ui,
330
331
  "next-themes": "^0.4.6",
331
- "pino": "^10.1.0",
332
+ pino: "^10.1.0",
332
333
  "pino-pretty": "^13.1.2",
333
334
  };
334
335
 
@@ -443,7 +444,7 @@ import { HeroUIProvider } from "@heroui/system";
443
444
 
444
445
  import { ThemeProvider } from "next-themes";
445
446
  import { useRouter } from "next/navigation";
446
- import { AppProviders } from "@lastbrain/app";
447
+ import { AppProviders } from "../components/AppProviders";
447
448
  import type { PropsWithChildren } from "react";
448
449
  import { AppHeader } from "../components/AppHeader";
449
450
 
@@ -481,7 +482,7 @@ export default function RootLayout({ children }: PropsWithChildren<{}>) {
481
482
 
482
483
  import "../styles/globals.css";
483
484
  import { ThemeProvider } from "next-themes";
484
- import { AppProviders } from "@lastbrain/app";
485
+ import { AppProviders } from "../components/AppProviders";
485
486
  import type { PropsWithChildren } from "react";
486
487
  import { AppHeader } from "../components/AppHeader";
487
488
 
@@ -591,6 +592,9 @@ export default function NotFound() {
591
592
 
592
593
  // Créer le composant AppAside
593
594
  await createAppAside(targetDir, force);
595
+
596
+ // Créer le wrapper AppProviders avec configuration realtime
597
+ await createAppProvidersWrapper(targetDir, force);
594
598
  }
595
599
 
596
600
  async function createRoute(
@@ -723,11 +727,13 @@ import { Header } from "@lastbrain/ui";
723
727
  import { menuConfig } from "../config/menu";
724
728
  import { supabaseBrowserClient } from "@lastbrain/core";
725
729
  import { useRouter } from "next/navigation";
726
- import { useAuthSession } from "@lastbrain/app";
730
+ import { useAuthSession, useNotificationsContext } from "@lastbrain/app";
727
731
 
728
732
  export function AppHeader() {
729
733
  const router = useRouter();
730
734
  const { user, isSuperAdmin } = useAuthSession();
735
+ const { data, loading, markAsRead, markAllAsRead, deleteNotification } =
736
+ useNotificationsContext();
731
737
 
732
738
  const handleLogout = async () => {
733
739
  await supabaseBrowserClient.auth.signOut();
@@ -743,6 +749,12 @@ export function AppHeader() {
743
749
  brandName="LastBrain App"
744
750
  brandHref="/"
745
751
  isSuperAdmin={isSuperAdmin}
752
+ notifications={data.notifications}
753
+ unreadCount={data.unreadCount}
754
+ notificationsLoading={loading}
755
+ onMarkAsRead={markAsRead}
756
+ onMarkAllAsRead={markAllAsRead}
757
+ onDeleteNotification={deleteNotification}
746
758
  />
747
759
  );
748
760
  }
@@ -798,6 +810,36 @@ export function AppAside({ className = "", isVisible = true }: AppAsideProps) {
798
810
  }
799
811
  }
800
812
 
813
+ async function createAppProvidersWrapper(targetDir: string, force: boolean) {
814
+ const componentsDir = path.join(targetDir, "components");
815
+ await fs.ensureDir(componentsDir);
816
+
817
+ const providersPath = path.join(componentsDir, "AppProviders.tsx");
818
+ if (!fs.existsSync(providersPath) || force) {
819
+ const providersContent = `"use client";
820
+
821
+ import { AppProviders as BaseAppProviders } from "@lastbrain/app";
822
+ import { realtimeConfig } from "../config/realtime";
823
+ import type { ReactNode } from "react";
824
+
825
+ export function AppProviders({ children }: { children: ReactNode }) {
826
+ return (
827
+ <BaseAppProviders realtimeConfig={realtimeConfig}>
828
+ {children}
829
+ </BaseAppProviders>
830
+ );
831
+ }`;
832
+ await fs.writeFile(providersPath, providersContent);
833
+ console.log(chalk.green("✓ components/AppProviders.tsx créé"));
834
+ } else {
835
+ console.log(
836
+ chalk.gray(
837
+ " components/AppProviders.tsx existe déjà (utilisez --force pour écraser)",
838
+ ),
839
+ );
840
+ }
841
+ }
842
+
801
843
  async function createConfigFiles(
802
844
  targetDir: string,
803
845
  force: boolean,
@@ -984,6 +1026,7 @@ export default config;
984
1026
  const tsconfigPath = path.join(targetDir, "tsconfig.json");
985
1027
  if (!fs.existsSync(tsconfigPath) || force) {
986
1028
  const tsconfig = {
1029
+ extends: "../../tsconfig.base.json",
987
1030
  compilerOptions: {
988
1031
  target: "ES2020",
989
1032
  lib: ["ES2020", "DOM", "DOM.Iterable"],
@@ -1003,9 +1046,10 @@ export default config;
1003
1046
  paths: {
1004
1047
  "@/*": ["./*"],
1005
1048
  },
1049
+ types: [],
1006
1050
  },
1007
1051
  include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
1008
- exclude: ["node_modules"],
1052
+ exclude: ["node_modules", "dist", ".next", "out"],
1009
1053
  };
1010
1054
  await fs.writeJson(tsconfigPath, tsconfig, { spaces: 2 });
1011
1055
  console.log(chalk.green("✓ tsconfig.json créé"));
@@ -1036,6 +1080,107 @@ export const menuConfig: MenuConfig = {
1036
1080
  await fs.writeFile(menuConfigPath, menuConfig);
1037
1081
  console.log(chalk.green("✓ config/menu.ts créé"));
1038
1082
  }
1083
+
1084
+ // Créer les hooks
1085
+ await createHooksDirectory(targetDir, force);
1086
+ }
1087
+
1088
+ async function createHooksDirectory(targetDir: string, force: boolean) {
1089
+ const hooksDir = path.join(targetDir, "hooks");
1090
+ await fs.ensureDir(hooksDir);
1091
+
1092
+ // Créer le hook useNotifications
1093
+ const useNotificationsPath = path.join(hooksDir, "useNotifications.ts");
1094
+ if (!fs.existsSync(useNotificationsPath) || force) {
1095
+ const hookContent = `"use client";
1096
+
1097
+ import { useEffect, useState } from "react";
1098
+ import { useNotificationRealtime } from "@lastbrain/core";
1099
+ import { supabaseBrowserClient } from "@lastbrain/core";
1100
+ import { useAuth } from "@lastbrain/app";
1101
+
1102
+ export interface Notification {
1103
+ id: string;
1104
+ title: string;
1105
+ message: string;
1106
+ read: boolean;
1107
+ created_at: string;
1108
+ owner_id: string;
1109
+ }
1110
+
1111
+ export function useNotifications() {
1112
+ const { user } = useAuth();
1113
+ const [notifications, setNotifications] = useState<Notification[]>([]);
1114
+ const [loading, setLoading] = useState(true);
1115
+ const [error, setError] = useState<Error | null>(null);
1116
+
1117
+ // Écouter les changements en temps réel
1118
+ const realtimeSignal = useNotificationRealtime();
1119
+
1120
+ // Charger les notifications initiales
1121
+ const fetchNotifications = async () => {
1122
+ if (!user?.id) return;
1123
+
1124
+ try {
1125
+ setLoading(true);
1126
+ const { data, error } = await supabaseBrowserClient
1127
+ .from("user_notifications")
1128
+ .select("*")
1129
+ .eq("owner_id", user.id)
1130
+ .order("created_at", { ascending: false });
1131
+
1132
+ if (error) throw error;
1133
+ setNotifications(data || []);
1134
+ setError(null);
1135
+ } catch (err) {
1136
+ setError(err as Error);
1137
+ } finally {
1138
+ setLoading(false);
1139
+ }
1140
+ };
1141
+
1142
+ // Charger au montage et quand l'utilisateur change
1143
+ useEffect(() => {
1144
+ fetchNotifications();
1145
+ }, [user?.id]);
1146
+
1147
+ // Recharger quand les données changent en temps réel
1148
+ useEffect(() => {
1149
+ if (realtimeSignal.hasUpdates && user?.id) {
1150
+ console.log("🔔 Rechargement des notifications suite à un changement");
1151
+ fetchNotifications();
1152
+ }
1153
+ }, [realtimeSignal.tick, user?.id]);
1154
+
1155
+ return {
1156
+ notifications,
1157
+ loading,
1158
+ error,
1159
+ refetch: fetchNotifications,
1160
+ hasUpdates: realtimeSignal.hasUpdates,
1161
+ };
1162
+ }
1163
+ `;
1164
+ await fs.writeFile(useNotificationsPath, hookContent);
1165
+ console.log(chalk.green("✓ hooks/useNotifications.ts créé"));
1166
+ }
1167
+
1168
+ // Créer un placeholder pour realtime.ts qui sera généré par build:modules
1169
+ const realtimePath = path.join(targetDir, "config", "realtime.ts");
1170
+ if (!fs.existsSync(realtimePath) || force) {
1171
+ const realtimeContent = `import type { ModuleRealtimeConfig } from "@lastbrain/core";
1172
+
1173
+ // GENERATED FILE - DO NOT EDIT MANUALLY
1174
+ // Ce fichier sera automatiquement généré lors du build:modules
1175
+ // Exécutez "pnpm build:modules" pour créer la configuration realtime
1176
+
1177
+ export const realtimeConfig: ModuleRealtimeConfig[] = [];
1178
+
1179
+ export default realtimeConfig;
1180
+ `;
1181
+ await fs.writeFile(realtimePath, realtimeContent);
1182
+ console.log(chalk.green("✓ config/realtime.ts placeholder créé"));
1183
+ }
1039
1184
  }
1040
1185
 
1041
1186
  async function createGitIgnore(targetDir: string, force: boolean) {
@@ -1145,6 +1290,28 @@ SUPABASE_SERVICE_ROLE_KEY=YOUR_LOCAL_SERVICE_ROLE_KEY
1145
1290
  }
1146
1291
  }
1147
1292
 
1293
+ async function createEnvLocal(targetDir: string, force: boolean) {
1294
+ const envLocalPath = path.join(targetDir, ".env.local");
1295
+
1296
+ if (!fs.existsSync(envLocalPath) || force) {
1297
+ console.log(chalk.yellow("\n🔐 Création de .env.local..."));
1298
+
1299
+ const envContent = `# Supabase Configuration
1300
+ # Valeurs par défaut pour le développement local
1301
+
1302
+ # Supabase Local (par défaut)
1303
+ NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321
1304
+ NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGc...
1305
+ SUPABASE_SERVICE_ROLE_KEY=eyJhbGc...
1306
+
1307
+ # OpenAI Configuration (clé factice pour éviter les erreurs de build)
1308
+ OPENAI_API_KEY=sk-fake-openai-key-for-development-replace-with-real-key
1309
+ `;
1310
+ await fs.writeFile(envLocalPath, envContent);
1311
+ console.log(chalk.green("✓ .env.local créé"));
1312
+ }
1313
+ }
1314
+
1148
1315
  async function createSupabaseStructure(targetDir: string, force: boolean) {
1149
1316
  console.log(chalk.yellow("\n🗄️ Création de la structure Supabase..."));
1150
1317
 
@@ -8,6 +8,7 @@ import type {
8
8
  ModuleMenuItemConfig,
9
9
  ModulePageConfig,
10
10
  ModuleSection,
11
+ ModuleRealtimeConfig,
11
12
  } from "@lastbrain/core";
12
13
 
13
14
  // Utiliser PROJECT_ROOT si défini (pour pnpm --filter), sinon process.cwd()
@@ -779,6 +780,64 @@ export default function AdminLayout({
779
780
  }
780
781
  }
781
782
 
783
+ async function generateRealtimeConfig(moduleConfigs: ModuleBuildConfig[]) {
784
+ try {
785
+ // Extraire les configurations realtime des modules
786
+ const realtimeConfigs: ModuleRealtimeConfig[] = moduleConfigs
787
+ .filter((config) => config.realtime)
788
+ .map((config) => config.realtime!);
789
+
790
+ if (realtimeConfigs.length === 0) {
791
+ console.log("⏭️ No realtime configuration found in modules");
792
+ return;
793
+ }
794
+
795
+ // Générer le contenu TypeScript
796
+ const imports = `import type { ModuleRealtimeConfig } from "@lastbrain/core";`;
797
+
798
+ const configData = {
799
+ modules: realtimeConfigs,
800
+ generatedAt: new Date().toISOString(),
801
+ version: "1.0.0",
802
+ };
803
+
804
+ const content = `${imports}
805
+
806
+ // GENERATED FILE - DO NOT EDIT MANUALLY
807
+ // Generated at: ${configData.generatedAt}
808
+
809
+ export const realtimeConfig: ModuleRealtimeConfig[] = ${JSON.stringify(configData.modules, null, 2)};
810
+
811
+ export default realtimeConfig;
812
+ `;
813
+
814
+ // Créer le fichier de configuration
815
+ const outputPath = path.join(projectRoot, "config", "realtime.ts");
816
+ const configDir = path.dirname(outputPath);
817
+
818
+ // Créer le dossier config s'il n'existe pas
819
+ if (!fs.existsSync(configDir)) {
820
+ fs.mkdirSync(configDir, { recursive: true });
821
+ }
822
+
823
+ // Écrire le fichier TypeScript
824
+ fs.writeFileSync(outputPath, content);
825
+
826
+ console.log(`✅ Generated realtime configuration: ${outputPath}`);
827
+ console.log(`📊 Modules with realtime: ${realtimeConfigs.length}`);
828
+
829
+ // Afficher un résumé
830
+ realtimeConfigs.forEach((module) => {
831
+ console.log(` - ${module.moduleId}: ${module.tables.length} table(s)`);
832
+ module.tables.forEach((table) => {
833
+ console.log(` • ${table.schema}.${table.table} (${table.event})`);
834
+ });
835
+ });
836
+ } catch (error) {
837
+ console.error("❌ Error generating realtime configuration:", error);
838
+ }
839
+ }
840
+
782
841
  export async function runModuleBuild() {
783
842
  ensureDirectory(appDirectory);
784
843
 
@@ -823,4 +882,8 @@ export async function runModuleBuild() {
823
882
  generateAppAside();
824
883
  generateLayouts();
825
884
  copyModuleMigrations(moduleConfigs);
885
+
886
+ // Générer la configuration realtime
887
+ console.log("🔄 Generating realtime configuration...");
888
+ await generateRealtimeConfig(moduleConfigs);
826
889
  }