@lastbrain/app 0.1.33 → 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 CHANGED
@@ -11,18 +11,36 @@ Package principal pour créer et gérer des applications Next.js LastBrain.
11
11
  - 🔧 **Scripts utilitaires** - Gestion modules, migrations DB, génération docs
12
12
  - 🎨 **Intégration Tailwind v4** - Configuration optimale out-of-the-box
13
13
 
14
- ## 📦 Installation
14
+ ## 📦 Installation & Démarrage rapide
15
+
16
+ > **Recommandé :** Utilisez directement le CLI pour créer une nouvelle application
15
17
 
16
18
  ```bash
17
- pnpm add @lastbrain/app @lastbrain/core @lastbrain/ui
19
+ # Créer une nouvelle application LastBrain
20
+ pnpx @lastbrain/app@latest init mon-app
21
+
22
+ # Accéder au dossier
23
+ cd mon-app
24
+
25
+ # Initialiser Supabase (optionnel)
26
+ pnpm db:init
27
+
28
+ # Démarrer le serveur
29
+ pnpm dev
18
30
  ```
19
31
 
20
- ## 🚀 Démarrage rapide
32
+ ### Installation manuelle
33
+
34
+ Si vous souhaitez intégrer LastBrain dans un projet existant :
35
+
36
+ ```bash
37
+ pnpm add @lastbrain/app @lastbrain/core @lastbrain/ui
38
+ ```
21
39
 
22
- ### 1. Créer une nouvelle application
40
+ ## 🚀 Ce que fait la commande init
23
41
 
24
42
  ```bash
25
- pnpm lastbrain init
43
+ pnpx @lastbrain/app@latest init mon-app
26
44
  ```
27
45
 
28
46
  Cette commande génère une application Next.js complète avec :
@@ -80,10 +80,29 @@ function parseEnvFile(filePath) {
80
80
  }));
81
81
  }
82
82
  function ensureEnvFile(values) {
83
- const content = Object.entries(values)
84
- .map(([key, value]) => `${key}=${value}`)
85
- .join("\n");
86
83
  envTargets.forEach((target) => {
84
+ let existingVars = {};
85
+ // Lire le fichier .env.local existant pour préserver les variables personnalisées
86
+ if (fs.existsSync(target)) {
87
+ try {
88
+ const existingContent = fs.readFileSync(target, "utf-8");
89
+ existingVars = Object.fromEntries(existingContent
90
+ .split("\n")
91
+ .filter((line) => line.includes("=") && !line.startsWith("#"))
92
+ .map((line) => {
93
+ const [key, ...value] = line.split("=");
94
+ return [key, value.join("=")];
95
+ }));
96
+ }
97
+ catch (error) {
98
+ console.warn(`⚠️ Erreur lors de la lecture de ${target}:`, error);
99
+ }
100
+ }
101
+ // Fusionner les nouvelles valeurs avec les existantes (nouvelles valeurs prioritaires)
102
+ const mergedValues = { ...existingVars, ...values };
103
+ const content = Object.entries(mergedValues)
104
+ .map(([key, value]) => `${key}=${value}`)
105
+ .join("\n");
87
106
  fs.writeFileSync(target, content);
88
107
  console.log(`📝 Wrote env to ${target}`);
89
108
  });
@@ -1 +1 @@
1
- {"version":3,"file":"module-create.d.ts","sourceRoot":"","sources":["../../src/scripts/module-create.ts"],"names":[],"mappings":"AAktBA;;GAEG;AACH,wBAAsB,YAAY,kBAyIjC"}
1
+ {"version":3,"file":"module-create.d.ts","sourceRoot":"","sources":["../../src/scripts/module-create.ts"],"names":[],"mappings":"AAqsCA;;GAEG;AACH,wBAAsB,YAAY,kBAyIjC"}
@@ -248,6 +248,9 @@ function generateIndexTs(pages) {
248
248
  return `// Client Components
249
249
  ${exports.join("\n")}
250
250
 
251
+ // Documentation Component
252
+ export { Doc } from "./components/Doc.js";
253
+
251
254
  // Configuration de build
252
255
  export { default as buildConfig } from "./build.config.js";
253
256
  `;
@@ -520,6 +523,448 @@ $$;
520
523
  ${tablesSQL}
521
524
  `;
522
525
  }
526
+ /**
527
+ * Generate Doc.tsx component for the module
528
+ */
529
+ function generateDocComponent(config) {
530
+ const moduleNameClean = config.slug.replace("module-", "");
531
+ // Generate pages sections
532
+ const publicPages = config.pages.filter((p) => p.section === "public");
533
+ const authPages = config.pages.filter((p) => p.section === "auth");
534
+ const adminPages = config.pages.filter((p) => p.section === "admin");
535
+ let pagesSection = "";
536
+ if (config.pages.length > 0) {
537
+ pagesSection = `
538
+ <Card>
539
+ <CardHeader>
540
+ <h2 className="text-2xl font-semibold flex items-center gap-2">
541
+ <FileText size={24} />
542
+ Pages Disponibles
543
+ </h2>
544
+ </CardHeader>
545
+ <CardBody className="space-y-4">`;
546
+ if (publicPages.length > 0) {
547
+ pagesSection += `
548
+ <div>
549
+ <h3 className="text-lg font-semibold mb-2">Pages Publiques</h3>
550
+ <div className="space-y-2">`;
551
+ for (const page of publicPages) {
552
+ const componentName = page.name
553
+ .split("-")
554
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
555
+ .join("");
556
+ pagesSection += `
557
+ <div className="flex items-start gap-2">
558
+ <Chip size="sm" color="success" variant="flat">GET</Chip>
559
+ <code className="text-sm">${page.path}</code>
560
+ <span className="text-sm text-slate-600 dark:text-slate-400">- ${componentName}</span>
561
+ </div>`;
562
+ }
563
+ pagesSection += `
564
+ </div>
565
+ </div>`;
566
+ }
567
+ if (authPages.length > 0) {
568
+ pagesSection += `
569
+ <div>
570
+ <h3 className="text-lg font-semibold mb-2">Pages Protégées (Auth)</h3>
571
+ <div className="space-y-2">`;
572
+ for (const page of authPages) {
573
+ const componentName = page.name
574
+ .split("-")
575
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
576
+ .join("");
577
+ pagesSection += `
578
+ <div className="flex items-start gap-2">
579
+ <Chip size="sm" color="primary" variant="flat">GET</Chip>
580
+ <code className="text-sm">${page.path}</code>
581
+ <span className="text-sm text-slate-600 dark:text-slate-400">- ${componentName}</span>
582
+ </div>`;
583
+ }
584
+ pagesSection += `
585
+ </div>
586
+ </div>`;
587
+ }
588
+ if (adminPages.length > 0) {
589
+ pagesSection += `
590
+ <div>
591
+ <h3 className="text-lg font-semibold mb-2">Pages Admin</h3>
592
+ <div className="space-y-2">`;
593
+ for (const page of adminPages) {
594
+ const componentName = page.name
595
+ .split("-")
596
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
597
+ .join("");
598
+ pagesSection += `
599
+ <div className="flex items-start gap-2">
600
+ <Chip size="sm" color="secondary" variant="flat">GET</Chip>
601
+ <code className="text-sm">${page.path}</code>
602
+ <span className="text-sm text-slate-600 dark:text-slate-400">- ${componentName}</span>
603
+ </div>`;
604
+ }
605
+ pagesSection += `
606
+ </div>
607
+ </div>`;
608
+ }
609
+ pagesSection += `
610
+ </CardBody>
611
+ </Card>
612
+ `;
613
+ }
614
+ // Generate APIs section
615
+ let apisSection = "";
616
+ if (config.tables.length > 0) {
617
+ apisSection = `
618
+ <Card>
619
+ <CardHeader>
620
+ <h2 className="text-2xl font-semibold flex items-center gap-2">
621
+ <Zap size={24} />
622
+ API Routes
623
+ </h2>
624
+ </CardHeader>
625
+ <CardBody className="space-y-4">`;
626
+ for (const table of config.tables) {
627
+ for (const section of table.sections) {
628
+ apisSection += `
629
+ <div>
630
+ <h3 className="text-lg font-semibold mb-2">
631
+ <code>/api/${section}/${table.name}</code>
632
+ </h3>
633
+ <div className="flex gap-2">
634
+ <Chip size="sm" color="success" variant="flat">GET</Chip>
635
+ <Chip size="sm" color="primary" variant="flat">POST</Chip>
636
+ <Chip size="sm" color="warning" variant="flat">PUT</Chip>
637
+ <Chip size="sm" color="danger" variant="flat">DELETE</Chip>
638
+ </div>
639
+ </div>`;
640
+ }
641
+ }
642
+ apisSection += `
643
+ </CardBody>
644
+ </Card>
645
+ `;
646
+ }
647
+ // Generate tables section
648
+ let tablesSection = "";
649
+ if (config.tables.length > 0) {
650
+ tablesSection = `
651
+ <Card>
652
+ <CardHeader>
653
+ <h2 className="text-2xl font-semibold flex items-center gap-2">
654
+ <Database size={24} />
655
+ Base de Données
656
+ </h2>
657
+ </CardHeader>
658
+ <CardBody className="space-y-6">`;
659
+ for (const table of config.tables) {
660
+ tablesSection += `
661
+ <TableStructure
662
+ tableName="${table.name}"
663
+ title="${table.name}"
664
+ description="Table ${table.name} du module ${moduleNameClean}"
665
+ />`;
666
+ }
667
+ tablesSection += `
668
+ </CardBody>
669
+ </Card>
670
+ `;
671
+ }
672
+ // Installation commands
673
+ const installSection = `
674
+ <Card>
675
+ <CardHeader>
676
+ <h2 className="text-2xl font-semibold flex items-center gap-2">
677
+ <Package size={24} />
678
+ Installation
679
+ </h2>
680
+ </CardHeader>
681
+ <CardBody className="space-y-4">
682
+ <div>
683
+ <h3 className="text-lg font-semibold mb-2">Ajouter le module</h3>
684
+ <Snippet symbol="" hideSymbol className="text-sm mb-2">
685
+ pnpm lastbrain add-module ${moduleNameClean}
686
+ </Snippet>
687
+ <Snippet symbol="" hideSymbol className="text-sm mb-2">
688
+ pnpm build:modules
689
+ </Snippet>
690
+ </div>
691
+
692
+ <div>
693
+ <h3 className="text-lg font-semibold mb-2">Appliquer les migrations</h3>
694
+ <Snippet symbol="" hideSymbol className="text-sm mb-2">
695
+ cd apps/votre-app
696
+ </Snippet>
697
+ <Snippet symbol="" hideSymbol className="text-sm mb-2">
698
+ supabase migration up
699
+ </Snippet>
700
+ </div>
701
+ </CardBody>
702
+ </Card>
703
+ `;
704
+ // Usage section with placeholder
705
+ const usageSection = `
706
+ <Card>
707
+ <CardHeader>
708
+ <h2 className="text-2xl font-semibold flex items-center gap-2">
709
+ <BookOpen size={24} />
710
+ Utilisation
711
+ </h2>
712
+ </CardHeader>
713
+ <CardBody className="space-y-4">
714
+ <Alert color="default" className="mb-4">
715
+ <p className="text-sm">
716
+ 📝 <strong>Section à compléter par l'auteur du module</strong>
717
+ </p>
718
+ <p className="text-sm text-slate-600 dark:text-slate-400 mt-2">
719
+ Ajoutez ici des exemples d'utilisation, des configurations spécifiques,
720
+ et toute information utile pour les développeurs utilisant ce module.
721
+ </p>
722
+ </Alert>
723
+
724
+ <div>
725
+ <h3 className="text-lg font-semibold mb-2">Exemple d'utilisation</h3>
726
+ <Alert color="primary" className="p-4 mb-4">
727
+ <pre className="whitespace-pre-wrap">{\`// Importez les composants depuis le module
728
+ import { ${config.pages.length > 0
729
+ ? config.pages[0].name
730
+ .split("-")
731
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
732
+ .join("") + "Page"
733
+ : "Component"} } from "${config.moduleName}";
734
+
735
+ // Utilisez-les dans votre application
736
+ <${config.pages.length > 0
737
+ ? config.pages[0].name
738
+ .split("-")
739
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
740
+ .join("") + "Page"
741
+ : "Component"} />\`}</pre>
742
+ </Alert>
743
+ </div>
744
+ </CardBody>
745
+ </Card>
746
+ `;
747
+ // Danger zone
748
+ const dangerSection = `
749
+ <Card>
750
+ <CardHeader>
751
+ <h2 className="text-2xl font-semibold flex items-center gap-2 text-danger">
752
+ <AlertTriangle size={24} />
753
+ Danger Zone
754
+ </h2>
755
+ </CardHeader>
756
+ <CardBody className="space-y-4">
757
+ <Alert color="danger" className="mb-4">
758
+ <p className="text-sm font-semibold">
759
+ ⚠️ Cette action est irréversible
760
+ </p>
761
+ <p className="text-sm mt-2">
762
+ La suppression du module supprimera toutes les pages, routes API et migrations associées.
763
+ </p>
764
+ </Alert>
765
+
766
+ <div>
767
+ <h3 className="text-lg font-semibold mb-2">Supprimer le module</h3>
768
+ <Snippet symbol="" hideSymbol color="danger" className="text-sm mb-2">
769
+ pnpm lastbrain remove-module ${moduleNameClean}
770
+ </Snippet>
771
+ <Snippet symbol="" hideSymbol color="danger" className="text-sm mb-2">
772
+ pnpm build:modules
773
+ </Snippet>
774
+ </div>
775
+ </CardBody>
776
+ </Card>
777
+ `;
778
+ return `"use client";
779
+
780
+ import { Card, CardBody, CardHeader } from "@lastbrain/ui";
781
+ import { Chip } from "@lastbrain/ui";
782
+ import { Snippet } from "@lastbrain/ui";
783
+ import { Alert } from "@lastbrain/ui";
784
+ import { TableStructure } from "@lastbrain/ui";
785
+ import {
786
+ FileText,
787
+ Zap,
788
+ Database,
789
+ Package,
790
+ BookOpen,
791
+ AlertTriangle
792
+ } from "lucide-react";
793
+
794
+ /**
795
+ * Documentation component for ${config.moduleName}
796
+ * Auto-generated from ${config.slug}.build.config.ts
797
+ *
798
+ * To regenerate this file, run:
799
+ * pnpm generate:module-docs
800
+ */
801
+ export function Doc() {
802
+ return (
803
+ <div className="container mx-auto p-6 space-y-6">
804
+ <Card>
805
+ <CardHeader>
806
+ <div>
807
+ <h1 className="text-3xl font-bold mb-2">📦 Module ${moduleNameClean}</h1>
808
+ <p className="text-slate-600 dark:text-slate-400">${config.moduleName}</p>
809
+ </div>
810
+ </CardHeader>
811
+ <CardBody>
812
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
813
+ <div>
814
+ <p className="text-sm text-slate-600 dark:text-slate-400">Package</p>
815
+ <code className="text-sm font-semibold">${config.moduleName}</code>
816
+ </div>
817
+ <div>
818
+ <p className="text-sm text-slate-600 dark:text-slate-400">Slug</p>
819
+ <code className="text-sm font-semibold">${config.slug}</code>
820
+ </div>
821
+ <div>
822
+ <p className="text-sm text-slate-600 dark:text-slate-400">Type</p>
823
+ <code className="text-sm font-semibold">Module LastBrain</code>
824
+ </div>
825
+ </div>
826
+ </CardBody>
827
+ </Card>
828
+ ${pagesSection}${apisSection}${tablesSection}${installSection}${usageSection}${dangerSection}
829
+ </div>
830
+ );
831
+ }
832
+ `;
833
+ }
834
+ /**
835
+ * Generate README.md for the module
836
+ */
837
+ async function generateModuleReadme(config, moduleDir) {
838
+ const moduleNameClean = config.slug.replace("module-", "");
839
+ let md = `# 📦 Module ${moduleNameClean}\n\n`;
840
+ md += `> ${config.moduleName}\n\n`;
841
+ // Information section
842
+ md += `## 📋 Informations\n\n`;
843
+ md += `- **Nom du package**: \`${config.moduleName}\`\n`;
844
+ md += `- **Slug**: \`${config.slug}\`\n`;
845
+ md += `- **Type**: Module LastBrain\n\n`;
846
+ // Pages section
847
+ if (config.pages.length > 0) {
848
+ md += `## 📄 Pages Disponibles\n\n`;
849
+ const publicPages = config.pages.filter((p) => p.section === "public");
850
+ const authPages = config.pages.filter((p) => p.section === "auth");
851
+ const adminPages = config.pages.filter((p) => p.section === "admin");
852
+ if (publicPages.length > 0) {
853
+ md += `### Pages Publiques\n\n`;
854
+ for (const page of publicPages) {
855
+ const componentName = page.name
856
+ .split("-")
857
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
858
+ .join("");
859
+ md += `- **GET** \`${page.path}\` - ${componentName}\n`;
860
+ }
861
+ md += `\n`;
862
+ }
863
+ if (authPages.length > 0) {
864
+ md += `### Pages Protégées (Auth)\n\n`;
865
+ for (const page of authPages) {
866
+ const componentName = page.name
867
+ .split("-")
868
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
869
+ .join("");
870
+ md += `- **GET** \`${page.path}\` - ${componentName}\n`;
871
+ }
872
+ md += `\n`;
873
+ }
874
+ if (adminPages.length > 0) {
875
+ md += `### Pages Admin\n\n`;
876
+ for (const page of adminPages) {
877
+ const componentName = page.name
878
+ .split("-")
879
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
880
+ .join("");
881
+ md += `- **GET** \`${page.path}\` - ${componentName}\n`;
882
+ }
883
+ md += `\n`;
884
+ }
885
+ }
886
+ // APIs section
887
+ if (config.tables.length > 0) {
888
+ md += `## 🔌 API Routes\n\n`;
889
+ for (const table of config.tables) {
890
+ for (const section of table.sections) {
891
+ md += `### \`/api/${section}/${table.name}\`\n\n`;
892
+ md += `**Méthodes supportées**: GET, POST, PUT, DELETE\n\n`;
893
+ }
894
+ }
895
+ }
896
+ // Database section
897
+ if (config.tables.length > 0) {
898
+ md += `## 🗄️ Base de Données\n\n`;
899
+ md += `### Tables\n\n`;
900
+ for (const table of config.tables) {
901
+ md += `#### \`${table.name}\`\n\n`;
902
+ md += `\`\`\`tsx\n`;
903
+ md += `<TableStructure\n`;
904
+ md += ` tableName="${table.name}"\n`;
905
+ md += ` title="${table.name}"\n`;
906
+ md += ` description="Table ${table.name} du module ${moduleNameClean}"\n`;
907
+ md += `/>\n`;
908
+ md += `\`\`\`\n\n`;
909
+ }
910
+ // Get migration files
911
+ const migrationsPath = path.join(moduleDir, "supabase", "migrations");
912
+ if (fs.existsSync(migrationsPath)) {
913
+ const migrationFiles = fs
914
+ .readdirSync(migrationsPath)
915
+ .filter((f) => f.endsWith(".sql"))
916
+ .sort();
917
+ if (migrationFiles.length > 0) {
918
+ md += `### Migrations\n\n`;
919
+ for (const migration of migrationFiles) {
920
+ md += `- \`${migration}\`\n`;
921
+ }
922
+ md += `\n`;
923
+ }
924
+ }
925
+ }
926
+ // Installation section
927
+ md += `## 📦 Installation\n\n`;
928
+ md += `\`\`\`bash\n`;
929
+ md += `pnpm lastbrain add-module ${moduleNameClean}\n`;
930
+ md += `pnpm build:modules\n`;
931
+ md += `\`\`\`\n\n`;
932
+ md += `### Appliquer les migrations\n\n`;
933
+ md += `\`\`\`bash\n`;
934
+ md += `cd apps/votre-app\n`;
935
+ md += `supabase migration up\n`;
936
+ md += `\`\`\`\n\n`;
937
+ // Usage section
938
+ md += `## 💡 Utilisation\n\n`;
939
+ md += `<!-- 📝 Section à compléter par l'auteur du module -->\n\n`;
940
+ md += `### Exemple d'utilisation\n\n`;
941
+ md += `\`\`\`tsx\n`;
942
+ md += `// Importez les composants depuis le module\n`;
943
+ if (config.pages.length > 0) {
944
+ const firstPage = config.pages[0];
945
+ const componentName = firstPage.name
946
+ .split("-")
947
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
948
+ .join("");
949
+ md += `import { ${componentName}Page } from "${config.moduleName}";\n\n`;
950
+ md += `// Utilisez-les dans votre application\n`;
951
+ md += `<${componentName}Page />\n`;
952
+ }
953
+ md += `\`\`\`\n\n`;
954
+ md += `### Configuration\n\n`;
955
+ md += `<!-- Ajoutez ici les détails de configuration spécifiques -->\n\n`;
956
+ // Danger zone
957
+ md += `## ⚠️ Danger Zone\n\n`;
958
+ md += `La suppression du module supprimera toutes les pages, routes API et migrations associées. **Cette action est irréversible.**\n\n`;
959
+ md += `\`\`\`bash\n`;
960
+ md += `pnpm lastbrain remove-module ${moduleNameClean}\n`;
961
+ md += `pnpm build:modules\n`;
962
+ md += `\`\`\`\n\n`;
963
+ // Write README.md
964
+ const readmePath = path.join(moduleDir, "README.md");
965
+ await fs.writeFile(readmePath, md);
966
+ console.log(chalk.yellow(" 📄 README.md"));
967
+ }
523
968
  /**
524
969
  * Crée la structure du module
525
970
  */
@@ -531,6 +976,7 @@ async function createModuleStructure(config, rootDir) {
531
976
  await fs.ensureDir(path.join(moduleDir, "src"));
532
977
  await fs.ensureDir(path.join(moduleDir, "src", "web"));
533
978
  await fs.ensureDir(path.join(moduleDir, "src", "api"));
979
+ await fs.ensureDir(path.join(moduleDir, "src", "components"));
534
980
  await fs.ensureDir(path.join(moduleDir, "supabase", "migrations"));
535
981
  // Créer package.json
536
982
  console.log(chalk.yellow(" 📄 package.json"));
@@ -548,6 +994,9 @@ async function createModuleStructure(config, rootDir) {
548
994
  // Créer server.ts
549
995
  console.log(chalk.yellow(" 📄 src/server.ts"));
550
996
  await fs.writeFile(path.join(moduleDir, "src", "server.ts"), generateServerTs(config.tables));
997
+ // Créer Doc.tsx
998
+ console.log(chalk.yellow(" 📄 src/components/Doc.tsx"));
999
+ await fs.writeFile(path.join(moduleDir, "src", "components", "Doc.tsx"), generateDocComponent(config));
551
1000
  // Créer les pages
552
1001
  console.log(chalk.blue("\n📄 Création des pages..."));
553
1002
  for (const page of config.pages) {
@@ -586,6 +1035,9 @@ async function createModuleStructure(config, rootDir) {
586
1035
  console.log(chalk.yellow(` 📄 supabase/migrations/${migrationFileName}`));
587
1036
  await fs.writeFile(path.join(moduleDir, "supabase", "migrations", migrationFileName), generateMigration(config.tables, config.slug));
588
1037
  }
1038
+ // Générer la documentation du module
1039
+ console.log(chalk.blue("\n📝 Génération de la documentation..."));
1040
+ await generateModuleReadme(config, moduleDir);
589
1041
  console.log(chalk.green(`\n✅ Module ${config.slug} créé avec succès!\n`));
590
1042
  console.log(chalk.gray(`📂 Emplacement: ${moduleDir}\n`));
591
1043
  console.log(chalk.blue("Prochaines étapes:"));