@lastbrain/app 0.1.34 → 0.1.36

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.
@@ -172,7 +172,7 @@ export function DocPage({ modules = [], defaultContent }) {
172
172
  ];
173
173
  return (_jsx("div", { className: "w-full pt-8 md:pt-12 pb-24 lg:pb-8", children: _jsxs("div", { className: "container mx-auto md:px-4 py-8", children: [_jsx("div", { className: "fixed w-full h-16 left-0 bottom-0 bg-background/20 backdrop-blur-lg z-50 lg:hidden p-2", children: _jsx("div", { className: "flex justify-center", children: _jsx(Button, { isIconOnly: true, variant: "solid", onPress: () => setIsDrawerOpen(true), children: _jsx(Menu, { size: 24 }) }) }) }), _jsx(Drawer, { isOpen: isDrawerOpen, onOpenChange: setIsDrawerOpen, placement: "left", children: _jsxs(DrawerContent, { children: [_jsx(DrawerHeader, { children: _jsx("h2", { className: "text-xl font-semibold", children: "Navigation" }) }), _jsx(DrawerBody, { children: _jsx(NavigationListbox, { navigationItems: navigationItems, selectedModule: selectedModule, scrollToSection: scrollToSection, setSelectedModule: setSelectedModule }) })] }) }), _jsxs("div", { className: "flex gap-8", children: [_jsx("aside", { className: "hidden lg:block w-64 shrink-0 sticky top-18 self-start", children: _jsxs(Card, { children: [_jsx(CardHeader, { className: "pb-2", children: _jsx("h2", { className: "text-xl font-semibold", children: "Navigation" }) }), _jsx(CardBody, { children: _jsx(NavigationListbox, { navigationItems: navigationItems, selectedModule: selectedModule, scrollToSection: scrollToSection, setSelectedModule: setSelectedModule }) })] }) }), _jsxs("main", { className: "flex-1 w-full min-w-0 space-y-8", children: [defaultContent ? (_jsx("div", { children: defaultContent })) : (_jsx(DefaultDocumentation, {})), modules.length > 0 && (_jsxs("div", { className: "space-y-6", children: [_jsxs(Card, { id: "section-modules", className: "scroll-mt-32", children: [_jsx(CardHeader, { children: _jsx("h2", { className: "text-2xl font-semibold", children: "Modules disponibles" }) }), _jsxs(CardBody, { children: [_jsx("p", { className: "text-slate-600 dark:text-slate-400 mb-4", children: "Voici la liste de tous les modules disponibles dans LastBrain. Les modules actifs sont utilis\u00E9s dans votre application." }), _jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: modules.map((module) => (_jsx(Card, { isPressable: module.available, onPress: () => module.available && scrollToSection(module.id), className: `${module.available
174
174
  ? "cursor-pointer hover:shadow-lg"
175
- : "opacity-70"} transition-shadow`, children: _jsxs(CardBody, { children: [_jsxs("div", { className: "flex items-start justify-between mb-2", children: [_jsx("h3", { className: "text-lg font-semibold", children: module.name }), _jsx(Chip, { size: "sm", color: module.available ? "success" : "warning", variant: "flat", children: module.available ? "Actif" : "Inactif" })] }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: module.description }), !module.available && (_jsxs("div", { className: "flex justify-between items-center text-xs text-default-700 mt-2", children: [_jsx("span", { children: "Pour activer : " }), _jsx(Snippet, { hideSymbol: true, color: "primary", children: `pnpm lastbrain add-module ${module.id}` })] }))] }) }, module.id))) })] })] }), modules
175
+ : "opacity-70"} transition-shadow`, children: _jsxs(CardBody, { children: [_jsxs("div", { className: "flex items-start justify-between mb-2", children: [_jsxs("h3", { className: "text-lg font-semibold flex flex-inline items-center gap-2", children: [_jsx(Blocks, { size: 20, className: "shrink-0" }), module.name] }), _jsx(Chip, { size: "sm", color: module.available ? "success" : "warning", variant: "flat", children: module.available ? "Actif" : "Inactif" })] }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: module.description }), !module.available && (_jsxs("div", { className: "flex justify-between items-center text-xs text-default-700 mt-2", children: [_jsx("span", { children: "Pour activer : " }), _jsx(Snippet, { hideSymbol: true, color: "primary", children: `pnpm lastbrain add-module ${module.id}` })] }))] }) }, module.id))) })] })] }), modules
176
176
  .filter((m) => m.available)
177
177
  .map((module) => (_jsx("div", { id: `module-${module.id}`, className: "scroll-mt-32", children: module.content }, module.id)))] }))] })] })] }) }));
178
178
  }
@@ -177,33 +177,32 @@ BEGIN
177
177
  ), false
178
178
  ) as is_foreign_key,
179
179
  (
180
- SELECT ccu.table_name::text
181
- FROM information_schema.table_constraints tc
182
- JOIN information_schema.key_column_usage kcu
183
- ON tc.constraint_name = kcu.constraint_name
184
- AND tc.table_schema = kcu.table_schema
185
- JOIN information_schema.constraint_column_usage ccu
186
- ON ccu.constraint_name = tc.constraint_name
187
- AND ccu.table_schema = tc.table_schema
188
- WHERE tc.constraint_type = 'FOREIGN KEY'
189
- AND tc.table_schema = 'public'
190
- AND tc.table_name = p_table_name
191
- AND kcu.column_name = c.column_name
180
+ SELECT
181
+ CASE
182
+ WHEN nsp.nspname = 'public' THEN ref_table.relname::text
183
+ ELSE nsp.nspname::text || '.' || ref_table.relname::text
184
+ END
185
+ FROM pg_constraint con
186
+ JOIN pg_attribute att ON att.attrelid = con.conrelid AND att.attnum = ANY(con.conkey)
187
+ JOIN pg_class tbl ON tbl.oid = con.conrelid
188
+ JOIN pg_class ref_table ON ref_table.oid = con.confrelid
189
+ JOIN pg_namespace nsp ON nsp.oid = ref_table.relnamespace
190
+ WHERE con.contype = 'f'
191
+ AND tbl.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')
192
+ AND tbl.relname = p_table_name
193
+ AND att.attname = c.column_name
192
194
  LIMIT 1
193
195
  ) as foreign_table,
194
196
  (
195
- SELECT ccu.column_name::text
196
- FROM information_schema.table_constraints tc
197
- JOIN information_schema.key_column_usage kcu
198
- ON tc.constraint_name = kcu.constraint_name
199
- AND tc.table_schema = kcu.table_schema
200
- JOIN information_schema.constraint_column_usage ccu
201
- ON ccu.constraint_name = tc.constraint_name
202
- AND ccu.table_schema = tc.table_schema
203
- WHERE tc.constraint_type = 'FOREIGN KEY'
204
- AND tc.table_schema = 'public'
205
- AND tc.table_name = p_table_name
206
- AND kcu.column_name = c.column_name
197
+ SELECT ref_att.attname::text
198
+ FROM pg_constraint con
199
+ JOIN pg_attribute att ON att.attrelid = con.conrelid AND att.attnum = ANY(con.conkey)
200
+ JOIN pg_class tbl ON tbl.oid = con.conrelid
201
+ JOIN pg_attribute ref_att ON ref_att.attrelid = con.confrelid AND ref_att.attnum = ANY(con.confkey)
202
+ WHERE con.contype = 'f'
203
+ AND tbl.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')
204
+ AND tbl.relname = p_table_name
205
+ AND att.attname = c.column_name
207
206
  LIMIT 1
208
207
  ) as foreign_column,
209
208
  pgd.description::text
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lastbrain/app",
3
- "version": "0.1.34",
3
+ "version": "0.1.36",
4
4
  "description": "Framework modulaire Next.js avec CLI et système de modules",
5
5
  "private": false,
6
6
  "type": "module",
@@ -117,7 +117,7 @@ function parseEnvFile(filePath: string) {
117
117
  function ensureEnvFile(values: Record<string, string>) {
118
118
  envTargets.forEach((target) => {
119
119
  let existingVars: Record<string, string> = {};
120
-
120
+
121
121
  // Lire le fichier .env.local existant pour préserver les variables personnalisées
122
122
  if (fs.existsSync(target)) {
123
123
  try {
@@ -138,7 +138,7 @@ function ensureEnvFile(values: Record<string, string>) {
138
138
 
139
139
  // Fusionner les nouvelles valeurs avec les existantes (nouvelles valeurs prioritaires)
140
140
  const mergedValues = { ...existingVars, ...values };
141
-
141
+
142
142
  const content = Object.entries(mergedValues)
143
143
  .map(([key, value]) => `${key}=${value}`)
144
144
  .join("\n");
@@ -312,6 +312,9 @@ function generateIndexTs(pages: PageConfig[]): string {
312
312
  return `// Client Components
313
313
  ${exports.join("\n")}
314
314
 
315
+ // Documentation Component
316
+ export { Doc } from "./components/Doc.js";
317
+
315
318
  // Configuration de build
316
319
  export { default as buildConfig } from "./build.config.js";
317
320
  `;
@@ -605,6 +608,490 @@ ${tablesSQL}
605
608
  `;
606
609
  }
607
610
 
611
+ /**
612
+ * Generate Doc.tsx component for the module
613
+ */
614
+ function generateDocComponent(config: ModuleConfig): string {
615
+ const moduleNameClean = config.slug.replace("module-", "");
616
+
617
+ // Generate pages sections
618
+ const publicPages = config.pages.filter((p) => p.section === "public");
619
+ const authPages = config.pages.filter((p) => p.section === "auth");
620
+ const adminPages = config.pages.filter((p) => p.section === "admin");
621
+
622
+ let pagesSection = "";
623
+ if (config.pages.length > 0) {
624
+ pagesSection = `
625
+ <Card>
626
+ <CardHeader>
627
+ <h2 className="text-2xl font-semibold flex items-center gap-2">
628
+ <FileText size={24} />
629
+ Pages Disponibles
630
+ </h2>
631
+ </CardHeader>
632
+ <CardBody className="space-y-4">`;
633
+
634
+ if (publicPages.length > 0) {
635
+ pagesSection += `
636
+ <div>
637
+ <h3 className="text-lg font-semibold mb-2">Pages Publiques</h3>
638
+ <div className="space-y-2">`;
639
+ for (const page of publicPages) {
640
+ const componentName = page.name
641
+ .split("-")
642
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
643
+ .join("");
644
+ pagesSection += `
645
+ <div className="flex items-start gap-2">
646
+ <Chip size="sm" color="success" variant="flat">GET</Chip>
647
+ <code className="text-sm">${page.path}</code>
648
+ <span className="text-sm text-slate-600 dark:text-slate-400">- ${componentName}</span>
649
+ </div>`;
650
+ }
651
+ pagesSection += `
652
+ </div>
653
+ </div>`;
654
+ }
655
+
656
+ if (authPages.length > 0) {
657
+ pagesSection += `
658
+ <div>
659
+ <h3 className="text-lg font-semibold mb-2">Pages Protégées (Auth)</h3>
660
+ <div className="space-y-2">`;
661
+ for (const page of authPages) {
662
+ const componentName = page.name
663
+ .split("-")
664
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
665
+ .join("");
666
+ pagesSection += `
667
+ <div className="flex items-start gap-2">
668
+ <Chip size="sm" color="primary" variant="flat">GET</Chip>
669
+ <code className="text-sm">${page.path}</code>
670
+ <span className="text-sm text-slate-600 dark:text-slate-400">- ${componentName}</span>
671
+ </div>`;
672
+ }
673
+ pagesSection += `
674
+ </div>
675
+ </div>`;
676
+ }
677
+
678
+ if (adminPages.length > 0) {
679
+ pagesSection += `
680
+ <div>
681
+ <h3 className="text-lg font-semibold mb-2">Pages Admin</h3>
682
+ <div className="space-y-2">`;
683
+ for (const page of adminPages) {
684
+ const componentName = page.name
685
+ .split("-")
686
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
687
+ .join("");
688
+ pagesSection += `
689
+ <div className="flex items-start gap-2">
690
+ <Chip size="sm" color="secondary" variant="flat">GET</Chip>
691
+ <code className="text-sm">${page.path}</code>
692
+ <span className="text-sm text-slate-600 dark:text-slate-400">- ${componentName}</span>
693
+ </div>`;
694
+ }
695
+ pagesSection += `
696
+ </div>
697
+ </div>`;
698
+ }
699
+
700
+ pagesSection += `
701
+ </CardBody>
702
+ </Card>
703
+ `;
704
+ }
705
+
706
+ // Generate APIs section
707
+ let apisSection = "";
708
+ if (config.tables.length > 0) {
709
+ apisSection = `
710
+ <Card>
711
+ <CardHeader>
712
+ <h2 className="text-2xl font-semibold flex items-center gap-2">
713
+ <Zap size={24} />
714
+ API Routes
715
+ </h2>
716
+ </CardHeader>
717
+ <CardBody className="space-y-4">`;
718
+
719
+ for (const table of config.tables) {
720
+ for (const section of table.sections) {
721
+ apisSection += `
722
+ <div>
723
+ <h3 className="text-lg font-semibold mb-2">
724
+ <code>/api/${section}/${table.name}</code>
725
+ </h3>
726
+ <div className="flex gap-2">
727
+ <Chip size="sm" color="success" variant="flat">GET</Chip>
728
+ <Chip size="sm" color="primary" variant="flat">POST</Chip>
729
+ <Chip size="sm" color="warning" variant="flat">PUT</Chip>
730
+ <Chip size="sm" color="danger" variant="flat">DELETE</Chip>
731
+ </div>
732
+ </div>`;
733
+ }
734
+ }
735
+
736
+ apisSection += `
737
+ </CardBody>
738
+ </Card>
739
+ `;
740
+ }
741
+
742
+ // Generate tables section
743
+ let tablesSection = "";
744
+ if (config.tables.length > 0) {
745
+ tablesSection = `
746
+ <Card>
747
+ <CardHeader>
748
+ <h2 className="text-2xl font-semibold flex items-center gap-2">
749
+ <Database size={24} />
750
+ Base de Données
751
+ </h2>
752
+ </CardHeader>
753
+ <CardBody className="space-y-6">`;
754
+
755
+ for (const table of config.tables) {
756
+ tablesSection += `
757
+ <TableStructure
758
+ tableName="${table.name}"
759
+ title="${table.name}"
760
+ description="Table ${table.name} du module ${moduleNameClean}"
761
+ />`;
762
+ }
763
+
764
+ tablesSection += `
765
+ </CardBody>
766
+ </Card>
767
+ `;
768
+ }
769
+
770
+ // Installation commands
771
+ const installSection = `
772
+ <Card>
773
+ <CardHeader>
774
+ <h2 className="text-2xl font-semibold flex items-center gap-2">
775
+ <Package size={24} />
776
+ Installation
777
+ </h2>
778
+ </CardHeader>
779
+ <CardBody className="space-y-4">
780
+ <div>
781
+ <h3 className="text-lg font-semibold mb-2">Ajouter le module</h3>
782
+ <Snippet symbol="" hideSymbol className="text-sm mb-2">
783
+ pnpm lastbrain add-module ${moduleNameClean}
784
+ </Snippet>
785
+ <Snippet symbol="" hideSymbol className="text-sm mb-2">
786
+ pnpm build:modules
787
+ </Snippet>
788
+ </div>
789
+
790
+ <div>
791
+ <h3 className="text-lg font-semibold mb-2">Appliquer les migrations</h3>
792
+ <Snippet symbol="" hideSymbol className="text-sm mb-2">
793
+ cd apps/votre-app
794
+ </Snippet>
795
+ <Snippet symbol="" hideSymbol className="text-sm mb-2">
796
+ supabase migration up
797
+ </Snippet>
798
+ </div>
799
+ </CardBody>
800
+ </Card>
801
+ `;
802
+
803
+ // Usage section with placeholder
804
+ const usageSection = `
805
+ <Card>
806
+ <CardHeader>
807
+ <h2 className="text-2xl font-semibold flex items-center gap-2">
808
+ <BookOpen size={24} />
809
+ Utilisation
810
+ </h2>
811
+ </CardHeader>
812
+ <CardBody className="space-y-4">
813
+ <Alert color="default" className="mb-4">
814
+ <p className="text-sm">
815
+ 📝 <strong>Section à compléter par l'auteur du module</strong>
816
+ </p>
817
+ <p className="text-sm text-slate-600 dark:text-slate-400 mt-2">
818
+ Ajoutez ici des exemples d'utilisation, des configurations spécifiques,
819
+ et toute information utile pour les développeurs utilisant ce module.
820
+ </p>
821
+ </Alert>
822
+
823
+ <div>
824
+ <h3 className="text-lg font-semibold mb-2">Exemple d'utilisation</h3>
825
+ <Alert color="primary" className="p-4 mb-4">
826
+ <pre className="whitespace-pre-wrap">{\`// Importez les composants depuis le module
827
+ import { ${
828
+ config.pages.length > 0
829
+ ? config.pages[0].name
830
+ .split("-")
831
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
832
+ .join("") + "Page"
833
+ : "Component"
834
+ } } from "${config.moduleName}";
835
+
836
+ // Utilisez-les dans votre application
837
+ <${
838
+ config.pages.length > 0
839
+ ? config.pages[0].name
840
+ .split("-")
841
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
842
+ .join("") + "Page"
843
+ : "Component"
844
+ } />\`}</pre>
845
+ </Alert>
846
+ </div>
847
+ </CardBody>
848
+ </Card>
849
+ `;
850
+
851
+ // Danger zone
852
+ const dangerSection = `
853
+ <Card>
854
+ <CardHeader>
855
+ <h2 className="text-2xl font-semibold flex items-center gap-2 text-danger">
856
+ <AlertTriangle size={24} />
857
+ Danger Zone
858
+ </h2>
859
+ </CardHeader>
860
+ <CardBody className="space-y-4">
861
+ <Alert color="danger" className="mb-4">
862
+ <p className="text-sm font-semibold">
863
+ ⚠️ Cette action est irréversible
864
+ </p>
865
+ <p className="text-sm mt-2">
866
+ La suppression du module supprimera toutes les pages, routes API et migrations associées.
867
+ </p>
868
+ </Alert>
869
+
870
+ <div>
871
+ <h3 className="text-lg font-semibold mb-2">Supprimer le module</h3>
872
+ <Snippet symbol="" hideSymbol color="danger" className="text-sm mb-2">
873
+ pnpm lastbrain remove-module ${moduleNameClean}
874
+ </Snippet>
875
+ <Snippet symbol="" hideSymbol color="danger" className="text-sm mb-2">
876
+ pnpm build:modules
877
+ </Snippet>
878
+ </div>
879
+ </CardBody>
880
+ </Card>
881
+ `;
882
+
883
+ return `"use client";
884
+
885
+ import { Card, CardBody, CardHeader } from "@lastbrain/ui";
886
+ import { Chip } from "@lastbrain/ui";
887
+ import { Snippet } from "@lastbrain/ui";
888
+ import { Alert } from "@lastbrain/ui";
889
+ import { TableStructure } from "@lastbrain/ui";
890
+ import {
891
+ FileText,
892
+ Zap,
893
+ Database,
894
+ Package,
895
+ BookOpen,
896
+ AlertTriangle
897
+ } from "lucide-react";
898
+
899
+ /**
900
+ * Documentation component for ${config.moduleName}
901
+ * Auto-generated from ${config.slug}.build.config.ts
902
+ *
903
+ * To regenerate this file, run:
904
+ * pnpm generate:module-docs
905
+ */
906
+ export function Doc() {
907
+ return (
908
+ <div className="container mx-auto p-6 space-y-6">
909
+ <Card>
910
+ <CardHeader>
911
+ <div>
912
+ <h1 className="text-3xl font-bold mb-2">📦 Module ${moduleNameClean}</h1>
913
+ <p className="text-slate-600 dark:text-slate-400">${config.moduleName}</p>
914
+ </div>
915
+ </CardHeader>
916
+ <CardBody>
917
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
918
+ <div>
919
+ <p className="text-sm text-slate-600 dark:text-slate-400">Package</p>
920
+ <code className="text-sm font-semibold">${config.moduleName}</code>
921
+ </div>
922
+ <div>
923
+ <p className="text-sm text-slate-600 dark:text-slate-400">Slug</p>
924
+ <code className="text-sm font-semibold">${config.slug}</code>
925
+ </div>
926
+ <div>
927
+ <p className="text-sm text-slate-600 dark:text-slate-400">Type</p>
928
+ <code className="text-sm font-semibold">Module LastBrain</code>
929
+ </div>
930
+ </div>
931
+ </CardBody>
932
+ </Card>
933
+ ${pagesSection}${apisSection}${tablesSection}${installSection}${usageSection}${dangerSection}
934
+ </div>
935
+ );
936
+ }
937
+ `;
938
+ }
939
+
940
+ /**
941
+ * Generate README.md for the module
942
+ */
943
+ async function generateModuleReadme(config: ModuleConfig, moduleDir: string) {
944
+ const moduleNameClean = config.slug.replace("module-", "");
945
+
946
+ let md = `# 📦 Module ${moduleNameClean}\n\n`;
947
+ md += `> ${config.moduleName}\n\n`;
948
+
949
+ // Information section
950
+ md += `## 📋 Informations\n\n`;
951
+ md += `- **Nom du package**: \`${config.moduleName}\`\n`;
952
+ md += `- **Slug**: \`${config.slug}\`\n`;
953
+ md += `- **Type**: Module LastBrain\n\n`;
954
+
955
+ // Pages section
956
+ if (config.pages.length > 0) {
957
+ md += `## 📄 Pages Disponibles\n\n`;
958
+
959
+ const publicPages = config.pages.filter((p) => p.section === "public");
960
+ const authPages = config.pages.filter((p) => p.section === "auth");
961
+ const adminPages = config.pages.filter((p) => p.section === "admin");
962
+
963
+ if (publicPages.length > 0) {
964
+ md += `### Pages Publiques\n\n`;
965
+ for (const page of publicPages) {
966
+ const componentName = page.name
967
+ .split("-")
968
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
969
+ .join("");
970
+ md += `- **GET** \`${page.path}\` - ${componentName}\n`;
971
+ }
972
+ md += `\n`;
973
+ }
974
+
975
+ if (authPages.length > 0) {
976
+ md += `### Pages Protégées (Auth)\n\n`;
977
+ for (const page of authPages) {
978
+ const componentName = page.name
979
+ .split("-")
980
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
981
+ .join("");
982
+ md += `- **GET** \`${page.path}\` - ${componentName}\n`;
983
+ }
984
+ md += `\n`;
985
+ }
986
+
987
+ if (adminPages.length > 0) {
988
+ md += `### Pages Admin\n\n`;
989
+ for (const page of adminPages) {
990
+ const componentName = page.name
991
+ .split("-")
992
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
993
+ .join("");
994
+ md += `- **GET** \`${page.path}\` - ${componentName}\n`;
995
+ }
996
+ md += `\n`;
997
+ }
998
+ }
999
+
1000
+ // APIs section
1001
+ if (config.tables.length > 0) {
1002
+ md += `## 🔌 API Routes\n\n`;
1003
+
1004
+ for (const table of config.tables) {
1005
+ for (const section of table.sections) {
1006
+ md += `### \`/api/${section}/${table.name}\`\n\n`;
1007
+ md += `**Méthodes supportées**: GET, POST, PUT, DELETE\n\n`;
1008
+ }
1009
+ }
1010
+ }
1011
+
1012
+ // Database section
1013
+ if (config.tables.length > 0) {
1014
+ md += `## 🗄️ Base de Données\n\n`;
1015
+ md += `### Tables\n\n`;
1016
+
1017
+ for (const table of config.tables) {
1018
+ md += `#### \`${table.name}\`\n\n`;
1019
+ md += `\`\`\`tsx\n`;
1020
+ md += `<TableStructure\n`;
1021
+ md += ` tableName="${table.name}"\n`;
1022
+ md += ` title="${table.name}"\n`;
1023
+ md += ` description="Table ${table.name} du module ${moduleNameClean}"\n`;
1024
+ md += `/>\n`;
1025
+ md += `\`\`\`\n\n`;
1026
+ }
1027
+
1028
+ // Get migration files
1029
+ const migrationsPath = path.join(moduleDir, "supabase", "migrations");
1030
+ if (fs.existsSync(migrationsPath)) {
1031
+ const migrationFiles = fs
1032
+ .readdirSync(migrationsPath)
1033
+ .filter((f) => f.endsWith(".sql"))
1034
+ .sort();
1035
+
1036
+ if (migrationFiles.length > 0) {
1037
+ md += `### Migrations\n\n`;
1038
+ for (const migration of migrationFiles) {
1039
+ md += `- \`${migration}\`\n`;
1040
+ }
1041
+ md += `\n`;
1042
+ }
1043
+ }
1044
+ }
1045
+
1046
+ // Installation section
1047
+ md += `## 📦 Installation\n\n`;
1048
+ md += `\`\`\`bash\n`;
1049
+ md += `pnpm lastbrain add-module ${moduleNameClean}\n`;
1050
+ md += `pnpm build:modules\n`;
1051
+ md += `\`\`\`\n\n`;
1052
+
1053
+ md += `### Appliquer les migrations\n\n`;
1054
+ md += `\`\`\`bash\n`;
1055
+ md += `cd apps/votre-app\n`;
1056
+ md += `supabase migration up\n`;
1057
+ md += `\`\`\`\n\n`;
1058
+
1059
+ // Usage section
1060
+ md += `## 💡 Utilisation\n\n`;
1061
+ md += `<!-- 📝 Section à compléter par l'auteur du module -->\n\n`;
1062
+ md += `### Exemple d'utilisation\n\n`;
1063
+ md += `\`\`\`tsx\n`;
1064
+ md += `// Importez les composants depuis le module\n`;
1065
+ if (config.pages.length > 0) {
1066
+ const firstPage = config.pages[0];
1067
+ const componentName = firstPage.name
1068
+ .split("-")
1069
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
1070
+ .join("");
1071
+ md += `import { ${componentName}Page } from "${config.moduleName}";\n\n`;
1072
+ md += `// Utilisez-les dans votre application\n`;
1073
+ md += `<${componentName}Page />\n`;
1074
+ }
1075
+ md += `\`\`\`\n\n`;
1076
+
1077
+ md += `### Configuration\n\n`;
1078
+ md += `<!-- Ajoutez ici les détails de configuration spécifiques -->\n\n`;
1079
+
1080
+ // Danger zone
1081
+ md += `## ⚠️ Danger Zone\n\n`;
1082
+ md += `La suppression du module supprimera toutes les pages, routes API et migrations associées. **Cette action est irréversible.**\n\n`;
1083
+ md += `\`\`\`bash\n`;
1084
+ md += `pnpm lastbrain remove-module ${moduleNameClean}\n`;
1085
+ md += `pnpm build:modules\n`;
1086
+ md += `\`\`\`\n\n`;
1087
+
1088
+ // Write README.md
1089
+ const readmePath = path.join(moduleDir, "README.md");
1090
+ await fs.writeFile(readmePath, md);
1091
+
1092
+ console.log(chalk.yellow(" 📄 README.md"));
1093
+ }
1094
+
608
1095
  /**
609
1096
  * Crée la structure du module
610
1097
  */
@@ -618,6 +1105,7 @@ async function createModuleStructure(config: ModuleConfig, rootDir: string) {
618
1105
  await fs.ensureDir(path.join(moduleDir, "src"));
619
1106
  await fs.ensureDir(path.join(moduleDir, "src", "web"));
620
1107
  await fs.ensureDir(path.join(moduleDir, "src", "api"));
1108
+ await fs.ensureDir(path.join(moduleDir, "src", "components"));
621
1109
  await fs.ensureDir(path.join(moduleDir, "supabase", "migrations"));
622
1110
 
623
1111
  // Créer package.json
@@ -653,6 +1141,13 @@ async function createModuleStructure(config: ModuleConfig, rootDir: string) {
653
1141
  generateServerTs(config.tables),
654
1142
  );
655
1143
 
1144
+ // Créer Doc.tsx
1145
+ console.log(chalk.yellow(" 📄 src/components/Doc.tsx"));
1146
+ await fs.writeFile(
1147
+ path.join(moduleDir, "src", "components", "Doc.tsx"),
1148
+ generateDocComponent(config),
1149
+ );
1150
+
656
1151
  // Créer les pages
657
1152
  console.log(chalk.blue("\n📄 Création des pages..."));
658
1153
  for (const page of config.pages) {
@@ -707,6 +1202,10 @@ async function createModuleStructure(config: ModuleConfig, rootDir: string) {
707
1202
  );
708
1203
  }
709
1204
 
1205
+ // Générer la documentation du module
1206
+ console.log(chalk.blue("\n📝 Génération de la documentation..."));
1207
+ await generateModuleReadme(config, moduleDir);
1208
+
710
1209
  console.log(chalk.green(`\n✅ Module ${config.slug} créé avec succès!\n`));
711
1210
  console.log(chalk.gray(`📂 Emplacement: ${moduleDir}\n`));
712
1211
  console.log(chalk.blue("Prochaines étapes:"));