@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.
- package/README.md +23 -5
- package/dist/scripts/module-create.d.ts.map +1 -1
- package/dist/scripts/module-create.js +452 -0
- package/dist/styles.css +1 -1
- package/dist/templates/DefaultDoc.d.ts.map +1 -1
- package/dist/templates/DefaultDoc.js +47 -30
- package/dist/templates/DocPage.d.ts.map +1 -1
- package/dist/templates/DocPage.js +1 -1
- package/dist/templates/migrations/20201010100000_app_base.sql +23 -24
- package/package.json +1 -1
- package/src/scripts/db-init.ts +2 -2
- package/src/scripts/module-create.ts +499 -0
- package/src/templates/DefaultDoc.tsx +452 -739
- package/src/templates/DocPage.tsx +2 -1
- package/src/templates/migrations/20201010100000_app_base.sql +23 -24
|
@@ -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: [
|
|
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
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
AND
|
|
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
|
|
196
|
-
FROM
|
|
197
|
-
JOIN
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
AND
|
|
203
|
-
|
|
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
package/src/scripts/db-init.ts
CHANGED
|
@@ -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:"));
|