@lastbrain/app 0.1.37 → 0.1.40
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/dist/cli.js +6 -1
- package/dist/layouts/AdminLayoutWithSidebar.d.ts.map +1 -1
- package/dist/layouts/AdminLayoutWithSidebar.js +36 -1
- package/dist/layouts/AuthLayoutWithSidebar.d.ts.map +1 -1
- package/dist/layouts/AuthLayoutWithSidebar.js +36 -1
- package/dist/layouts/PublicLayout.js +1 -1
- package/dist/layouts/PublicLayoutWithSidebar.d.ts.map +1 -1
- package/dist/layouts/PublicLayoutWithSidebar.js +36 -1
- package/dist/scripts/init-app.js +54 -12
- package/dist/scripts/module-add.d.ts.map +1 -1
- package/dist/scripts/module-add.js +54 -6
- package/dist/scripts/module-build.d.ts.map +1 -1
- package/dist/scripts/module-build.js +102 -39
- package/dist/scripts/module-list.d.ts.map +1 -1
- package/dist/scripts/module-list.js +38 -8
- package/dist/styles.css +1 -1
- package/dist/templates/DefaultDoc.d.ts.map +1 -1
- package/dist/templates/DefaultDoc.js +1 -1
- package/package.json +2 -2
- package/src/cli.ts +6 -1
- package/src/layouts/AdminLayoutWithSidebar.tsx +51 -1
- package/src/layouts/AuthLayoutWithSidebar.tsx +51 -1
- package/src/layouts/PublicLayout.tsx +1 -1
- package/src/layouts/PublicLayoutWithSidebar.tsx +51 -1
- package/src/scripts/init-app.ts +56 -12
- package/src/scripts/module-add.ts +86 -7
- package/src/scripts/module-build.ts +111 -41
- package/src/scripts/module-list.ts +45 -8
- package/src/templates/DefaultDoc.tsx +28 -0
|
@@ -19,6 +19,9 @@ const monorepoRoot = projectRoot.includes("/apps/")
|
|
|
19
19
|
: projectRoot;
|
|
20
20
|
const appDirectory = path.join(projectRoot, "app");
|
|
21
21
|
|
|
22
|
+
// Mode debug activé via --debug
|
|
23
|
+
const isDebugMode = process.argv.includes("--debug");
|
|
24
|
+
|
|
22
25
|
// Créer un require dans le contexte de l'application pour résoudre les modules installés dans l'app
|
|
23
26
|
const projectRequire = createRequire(path.join(projectRoot, "package.json"));
|
|
24
27
|
|
|
@@ -161,7 +164,9 @@ function buildPage(moduleConfig: ModuleBuildConfig, page: ModulePageConfig) {
|
|
|
161
164
|
.replace(/^@lastbrain\/module-/, "")
|
|
162
165
|
.toLowerCase();
|
|
163
166
|
|
|
164
|
-
|
|
167
|
+
if (isDebugMode) {
|
|
168
|
+
console.log(`🔄 Building page for module ${modulePrefix}: ${page.path}`);
|
|
169
|
+
}
|
|
165
170
|
|
|
166
171
|
// Ajouter le préfixe du module au path pour les sections admin et auth,
|
|
167
172
|
// MAIS seulement quand la section ne correspond PAS au module lui-même
|
|
@@ -173,11 +178,17 @@ function buildPage(moduleConfig: ModuleBuildConfig, page: ModulePageConfig) {
|
|
|
173
178
|
// Éviter les doublons si le préfixe est déjà présent
|
|
174
179
|
if (!page.path.startsWith(`/${modulePrefix}/`)) {
|
|
175
180
|
effectivePath = `/${modulePrefix}${page.path}`;
|
|
176
|
-
|
|
177
|
-
|
|
181
|
+
if (isDebugMode) {
|
|
182
|
+
console.log(`📂 Added module prefix: ${page.path} -> ${effectivePath}`);
|
|
183
|
+
}
|
|
184
|
+
} else if (isDebugMode) {
|
|
178
185
|
console.log(`✅ Module prefix already present: ${page.path}`);
|
|
179
186
|
}
|
|
180
|
-
} else if (
|
|
187
|
+
} else if (
|
|
188
|
+
page.section === "auth" &&
|
|
189
|
+
modulePrefix === "auth" &&
|
|
190
|
+
isDebugMode
|
|
191
|
+
) {
|
|
181
192
|
console.log(
|
|
182
193
|
`🏠 Auth module in auth section, no prefix needed: ${page.path}`,
|
|
183
194
|
);
|
|
@@ -270,7 +281,9 @@ export default function ${wrapperName}${hasDynamicParams ? "(props: any)" : "()"
|
|
|
270
281
|
}
|
|
271
282
|
|
|
272
283
|
fs.writeFileSync(filePath, content);
|
|
273
|
-
|
|
284
|
+
if (isDebugMode) {
|
|
285
|
+
console.log(`⭐ Generated page: ${filePath}`);
|
|
286
|
+
}
|
|
274
287
|
const entry: MenuEntry = {
|
|
275
288
|
label: `Module:${moduleConfig.moduleName} ${page.componentExport}`,
|
|
276
289
|
path: effectivePath,
|
|
@@ -294,7 +307,9 @@ export const navigation = ${JSON.stringify(navigation, null, 2)};
|
|
|
294
307
|
export const userMenu = ${JSON.stringify(userMenu, null, 2)};
|
|
295
308
|
`;
|
|
296
309
|
fs.writeFileSync(navPath, content);
|
|
297
|
-
|
|
310
|
+
if (isDebugMode) {
|
|
311
|
+
console.log(`🧭 Generated navigation metadata: ${navPath}`);
|
|
312
|
+
}
|
|
298
313
|
}
|
|
299
314
|
|
|
300
315
|
/**
|
|
@@ -369,7 +384,9 @@ export const menuConfig: MenuConfig = {
|
|
|
369
384
|
`;
|
|
370
385
|
|
|
371
386
|
fs.writeFileSync(menuPath, content);
|
|
372
|
-
|
|
387
|
+
if (isDebugMode) {
|
|
388
|
+
console.log(`🍔 Generated menu configuration: ${menuPath}`);
|
|
389
|
+
}
|
|
373
390
|
}
|
|
374
391
|
|
|
375
392
|
function copyModuleMigrations(moduleConfigs: ModuleBuildConfig[]) {
|
|
@@ -557,7 +574,9 @@ ${moduleConfigurations.join(",\n")}
|
|
|
557
574
|
`;
|
|
558
575
|
|
|
559
576
|
fs.writeFileSync(docsPagePath, docsContent);
|
|
560
|
-
|
|
577
|
+
if (isDebugMode) {
|
|
578
|
+
console.log(`📚 Generated docs page: ${docsPagePath}`);
|
|
579
|
+
}
|
|
561
580
|
}
|
|
562
581
|
|
|
563
582
|
function buildGroupedApi(
|
|
@@ -595,7 +614,9 @@ function buildGroupedApi(
|
|
|
595
614
|
${content}`;
|
|
596
615
|
|
|
597
616
|
fs.writeFileSync(filePath, contentWithMarker);
|
|
598
|
-
|
|
617
|
+
if (isDebugMode) {
|
|
618
|
+
console.log(`🔌 Generated API route: ${filePath}`);
|
|
619
|
+
}
|
|
599
620
|
}
|
|
600
621
|
|
|
601
622
|
function getModuleDescription(moduleConfig: ModuleBuildConfig): string {
|
|
@@ -663,7 +684,9 @@ function cleanGeneratedFiles() {
|
|
|
663
684
|
try {
|
|
664
685
|
if (!isProtected(itemPath) && fs.readdirSync(itemPath).length === 0) {
|
|
665
686
|
fs.rmdirSync(itemPath);
|
|
666
|
-
|
|
687
|
+
if (isDebugMode) {
|
|
688
|
+
console.log(`📁 Removed empty directory: ${itemPath}`);
|
|
689
|
+
}
|
|
667
690
|
}
|
|
668
691
|
} catch {
|
|
669
692
|
// Ignorer les erreurs de suppression de fichiers
|
|
@@ -681,12 +704,14 @@ function cleanGeneratedFiles() {
|
|
|
681
704
|
content.includes('} from "@lastbrain/module-'))
|
|
682
705
|
) {
|
|
683
706
|
fs.unlinkSync(itemPath);
|
|
684
|
-
|
|
707
|
+
if (isDebugMode) {
|
|
708
|
+
console.log(`🗑️ Removed: ${itemPath}`);
|
|
709
|
+
}
|
|
685
710
|
}
|
|
686
711
|
} catch {
|
|
687
712
|
// Ignorer les erreurs de lecture/suppression
|
|
688
713
|
}
|
|
689
|
-
} else {
|
|
714
|
+
} else if (isDebugMode) {
|
|
690
715
|
console.log(`🔒 Protected file skipped: ${itemPath}`);
|
|
691
716
|
}
|
|
692
717
|
}
|
|
@@ -708,11 +733,15 @@ function cleanGeneratedFiles() {
|
|
|
708
733
|
const filePath = path.join(appDirectory, file);
|
|
709
734
|
if (fs.existsSync(filePath)) {
|
|
710
735
|
fs.unlinkSync(filePath);
|
|
711
|
-
|
|
736
|
+
if (isDebugMode) {
|
|
737
|
+
console.log(`🗑️ Removed: ${filePath}`);
|
|
738
|
+
}
|
|
712
739
|
}
|
|
713
740
|
});
|
|
714
741
|
|
|
715
|
-
|
|
742
|
+
if (isDebugMode) {
|
|
743
|
+
console.log("🧹 Cleanup completed");
|
|
744
|
+
}
|
|
716
745
|
}
|
|
717
746
|
|
|
718
747
|
function generateAppAside() {
|
|
@@ -720,7 +749,9 @@ function generateAppAside() {
|
|
|
720
749
|
|
|
721
750
|
// Ne pas écraser si le fichier existe déjà
|
|
722
751
|
if (fs.existsSync(targetPath)) {
|
|
723
|
-
|
|
752
|
+
if (isDebugMode) {
|
|
753
|
+
console.log(`⏭️ AppAside already exists, skipping: ${targetPath}`);
|
|
754
|
+
}
|
|
724
755
|
return;
|
|
725
756
|
}
|
|
726
757
|
|
|
@@ -751,7 +782,9 @@ export function AppAside({ className = "", isVisible = true }: AppAsideProps) {
|
|
|
751
782
|
try {
|
|
752
783
|
ensureDirectory(path.dirname(targetPath));
|
|
753
784
|
fs.writeFileSync(targetPath, templateContent, "utf-8");
|
|
754
|
-
|
|
785
|
+
if (isDebugMode) {
|
|
786
|
+
console.log(`✅ Generated AppAside component: ${targetPath}`);
|
|
787
|
+
}
|
|
755
788
|
} catch (error) {
|
|
756
789
|
console.error(`❌ Error generating AppAside component: ${error}`);
|
|
757
790
|
}
|
|
@@ -779,11 +812,13 @@ export default function SectionLayout({
|
|
|
779
812
|
try {
|
|
780
813
|
ensureDirectory(path.dirname(authLayoutPath));
|
|
781
814
|
fs.writeFileSync(authLayoutPath, authLayoutContent, "utf-8");
|
|
782
|
-
|
|
815
|
+
if (isDebugMode) {
|
|
816
|
+
console.log(`✅ Generated auth layout with sidebar: ${authLayoutPath}`);
|
|
817
|
+
}
|
|
783
818
|
} catch (error) {
|
|
784
819
|
console.error(`❌ Error generating auth layout: ${error}`);
|
|
785
820
|
}
|
|
786
|
-
} else {
|
|
821
|
+
} else if (isDebugMode) {
|
|
787
822
|
console.log(`⏭️ Auth layout already exists, skipping: ${authLayoutPath}`);
|
|
788
823
|
}
|
|
789
824
|
|
|
@@ -808,11 +843,15 @@ export default function AdminLayout({
|
|
|
808
843
|
try {
|
|
809
844
|
ensureDirectory(path.dirname(adminLayoutPath));
|
|
810
845
|
fs.writeFileSync(adminLayoutPath, adminLayoutContent, "utf-8");
|
|
811
|
-
|
|
846
|
+
if (isDebugMode) {
|
|
847
|
+
console.log(
|
|
848
|
+
`✅ Generated admin layout with sidebar: ${adminLayoutPath}`,
|
|
849
|
+
);
|
|
850
|
+
}
|
|
812
851
|
} catch (error) {
|
|
813
852
|
console.error(`❌ Error generating admin layout: ${error}`);
|
|
814
853
|
}
|
|
815
|
-
} else {
|
|
854
|
+
} else if (isDebugMode) {
|
|
816
855
|
console.log(`⏭️ Admin layout already exists, skipping: ${adminLayoutPath}`);
|
|
817
856
|
}
|
|
818
857
|
}
|
|
@@ -860,16 +899,20 @@ export default realtimeConfig;
|
|
|
860
899
|
// Écrire le fichier TypeScript
|
|
861
900
|
fs.writeFileSync(outputPath, content);
|
|
862
901
|
|
|
863
|
-
|
|
864
|
-
|
|
902
|
+
if (isDebugMode) {
|
|
903
|
+
console.log(`✅ Generated realtime configuration: ${outputPath}`);
|
|
904
|
+
console.log(`📊 Modules with realtime: ${realtimeConfigs.length}`);
|
|
865
905
|
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
906
|
+
// Afficher un résumé
|
|
907
|
+
realtimeConfigs.forEach((module) => {
|
|
908
|
+
console.log(
|
|
909
|
+
` - ${module.moduleId}: ${module.tables.length} table(s)`,
|
|
910
|
+
);
|
|
911
|
+
module.tables.forEach((table) => {
|
|
912
|
+
console.log(` • ${table.schema}.${table.table} (${table.event})`);
|
|
913
|
+
});
|
|
871
914
|
});
|
|
872
|
-
}
|
|
915
|
+
}
|
|
873
916
|
} catch (error) {
|
|
874
917
|
console.error("❌ Error generating realtime configuration:", error);
|
|
875
918
|
}
|
|
@@ -928,13 +971,15 @@ async function generateUserTabsConfig(moduleConfigs: ModuleBuildConfig[]) {
|
|
|
928
971
|
// Écrire le fichier TypeScript
|
|
929
972
|
fs.writeFileSync(outputPath, appContent);
|
|
930
973
|
|
|
931
|
-
|
|
932
|
-
|
|
974
|
+
if (isDebugMode) {
|
|
975
|
+
console.log(`✅ Generated user tabs configuration: ${outputPath}`);
|
|
976
|
+
console.log(`📊 User tabs count: ${userTabsConfigs.length}`);
|
|
933
977
|
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
978
|
+
// Afficher un résumé
|
|
979
|
+
userTabsConfigs.forEach((tab) => {
|
|
980
|
+
console.log(` - ${tab.title} (${tab.moduleName})`);
|
|
981
|
+
});
|
|
982
|
+
}
|
|
938
983
|
|
|
939
984
|
// Plus de copie vers app/config ni stub core
|
|
940
985
|
} catch (error) {
|
|
@@ -946,21 +991,33 @@ export async function runModuleBuild() {
|
|
|
946
991
|
ensureDirectory(appDirectory);
|
|
947
992
|
|
|
948
993
|
// Nettoyer les fichiers générés précédemment
|
|
949
|
-
|
|
994
|
+
if (isDebugMode) {
|
|
995
|
+
console.log("🧹 Cleaning previously generated files...");
|
|
996
|
+
}
|
|
950
997
|
cleanGeneratedFiles();
|
|
951
998
|
|
|
952
999
|
const moduleConfigs = await loadModuleConfigs();
|
|
953
|
-
|
|
1000
|
+
if (isDebugMode) {
|
|
1001
|
+
console.log(`🔍 Loaded ${moduleConfigs.length} module configurations`);
|
|
1002
|
+
}
|
|
954
1003
|
|
|
955
1004
|
// Générer les pages
|
|
1005
|
+
if (isDebugMode) {
|
|
1006
|
+
console.log("\n📝 Generating pages...");
|
|
1007
|
+
}
|
|
956
1008
|
moduleConfigs.forEach((moduleConfig) => {
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1009
|
+
if (isDebugMode) {
|
|
1010
|
+
console.log(
|
|
1011
|
+
`📦 Processing module: ${moduleConfig.moduleName} with ${moduleConfig.pages.length} pages`,
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
960
1014
|
moduleConfig.pages.forEach((page) => buildPage(moduleConfig, page));
|
|
961
1015
|
});
|
|
962
1016
|
|
|
963
1017
|
// Grouper les APIs par chemin pour éviter les écrasements de fichier
|
|
1018
|
+
if (isDebugMode) {
|
|
1019
|
+
console.log("\n🔌 Generating API routes...");
|
|
1020
|
+
}
|
|
964
1021
|
const apisByPath = new Map<
|
|
965
1022
|
string,
|
|
966
1023
|
Array<{ moduleConfig: ModuleBuildConfig; api: ModuleApiConfig }>
|
|
@@ -988,10 +1045,23 @@ export async function runModuleBuild() {
|
|
|
988
1045
|
copyModuleMigrations(moduleConfigs);
|
|
989
1046
|
|
|
990
1047
|
// Générer la configuration realtime
|
|
991
|
-
|
|
1048
|
+
if (isDebugMode) {
|
|
1049
|
+
console.log("🔄 Generating realtime configuration...");
|
|
1050
|
+
}
|
|
992
1051
|
await generateRealtimeConfig(moduleConfigs);
|
|
993
1052
|
|
|
994
1053
|
// Générer la configuration des user tabs
|
|
995
|
-
|
|
1054
|
+
if (isDebugMode) {
|
|
1055
|
+
console.log("📑 Generating user tabs configuration...");
|
|
1056
|
+
}
|
|
996
1057
|
await generateUserTabsConfig(moduleConfigs);
|
|
1058
|
+
|
|
1059
|
+
// Message de succès final
|
|
1060
|
+
if (!isDebugMode) {
|
|
1061
|
+
console.log("\n✅ Module build completed successfully!");
|
|
1062
|
+
console.log(`📦 ${moduleConfigs.length} module(s) processed`);
|
|
1063
|
+
console.log("💡 Use --debug flag for detailed logs\n");
|
|
1064
|
+
} else {
|
|
1065
|
+
console.log("\n✅ Module build completed (debug mode)\n");
|
|
1066
|
+
}
|
|
997
1067
|
}
|
|
@@ -11,19 +11,40 @@ export async function listModules(targetDir: string) {
|
|
|
11
11
|
|
|
12
12
|
// Lire la config des modules installés
|
|
13
13
|
const modulesConfigPath = path.join(targetDir, ".lastbrain", "modules.json");
|
|
14
|
-
let
|
|
14
|
+
let installedPackages: string[] = [];
|
|
15
|
+
let inactivePackages: string[] = [];
|
|
15
16
|
|
|
16
17
|
if (fs.existsSync(modulesConfigPath)) {
|
|
17
18
|
const modulesConfig = await fs.readJson(modulesConfigPath);
|
|
18
|
-
|
|
19
|
+
const modules = modulesConfig.modules || [];
|
|
20
|
+
|
|
21
|
+
// Supporte deux formats:
|
|
22
|
+
// - Ancien: string[] des noms de packages
|
|
23
|
+
// - Actuel: { package: string; active?: boolean; migrations?: string[] }[]
|
|
24
|
+
if (Array.isArray(modules)) {
|
|
25
|
+
if (modules.length > 0 && typeof modules[0] === "string") {
|
|
26
|
+
installedPackages = modules as string[];
|
|
27
|
+
} else {
|
|
28
|
+
const entries = modules as Array<{ package: string; active?: boolean }>;
|
|
29
|
+
installedPackages = entries
|
|
30
|
+
.filter((m) => m && m.package && m.active !== false)
|
|
31
|
+
.map((m) => m.package);
|
|
32
|
+
inactivePackages = entries
|
|
33
|
+
.filter((m) => m && m.package && m.active === false)
|
|
34
|
+
.map((m) => m.package);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
19
37
|
}
|
|
20
38
|
|
|
21
39
|
// Afficher tous les modules disponibles
|
|
22
40
|
AVAILABLE_MODULES.forEach((module) => {
|
|
23
|
-
const isInstalled =
|
|
41
|
+
const isInstalled = installedPackages.includes(module.package);
|
|
42
|
+
const isInactive = inactivePackages.includes(module.package);
|
|
24
43
|
const status = isInstalled
|
|
25
44
|
? chalk.green("✓ installé")
|
|
26
|
-
:
|
|
45
|
+
: isInactive
|
|
46
|
+
? chalk.yellow("⤳ inactif")
|
|
47
|
+
: chalk.gray(" disponible");
|
|
27
48
|
|
|
28
49
|
console.log(
|
|
29
50
|
chalk.bold(
|
|
@@ -34,11 +55,27 @@ export async function listModules(targetDir: string) {
|
|
|
34
55
|
console.log(chalk.gray(` Package: ${module.package}`));
|
|
35
56
|
console.log(chalk.gray(` Description: ${module.description}`));
|
|
36
57
|
console.log(` Statut: ${status}`);
|
|
58
|
+
|
|
59
|
+
// Afficher la commande appropriée selon le statut
|
|
60
|
+
if (isInstalled) {
|
|
61
|
+
console.log(
|
|
62
|
+
chalk.cyan(` → pnpm lastbrain remove-module ${module.name}`),
|
|
63
|
+
);
|
|
64
|
+
} else if (isInactive || !isInstalled) {
|
|
65
|
+
console.log(chalk.cyan(` → pnpm lastbrain add-module ${module.name}`));
|
|
66
|
+
}
|
|
67
|
+
|
|
37
68
|
console.log();
|
|
38
69
|
});
|
|
39
70
|
|
|
40
|
-
console.log(chalk.gray("
|
|
41
|
-
console.log(chalk.
|
|
42
|
-
console.log(
|
|
43
|
-
|
|
71
|
+
console.log(chalk.gray("─".repeat(50)));
|
|
72
|
+
console.log(chalk.gray("\nCommandes disponibles:"));
|
|
73
|
+
console.log(
|
|
74
|
+
chalk.cyan(" pnpm lastbrain add-module <nom> ") +
|
|
75
|
+
chalk.gray("- Ajouter un module"),
|
|
76
|
+
);
|
|
77
|
+
console.log(
|
|
78
|
+
chalk.cyan(" pnpm lastbrain remove-module <nom> ") +
|
|
79
|
+
chalk.gray("- Supprimer un module\n"),
|
|
80
|
+
);
|
|
44
81
|
}
|
|
@@ -1356,6 +1356,34 @@ export function DocUsageCustom() {
|
|
|
1356
1356
|
</a>
|
|
1357
1357
|
</p>
|
|
1358
1358
|
<p className="text-sm text-slate-600 dark:text-slate-400 mb-2">---</p>
|
|
1359
|
+
<h4 className="font-medium mb-2">
|
|
1360
|
+
[module-recipes](../packages/module-recipes/README.md)
|
|
1361
|
+
</h4>
|
|
1362
|
+
<p className="text-sm text-slate-600 dark:text-slate-400 mb-2">
|
|
1363
|
+
@lastbrain/module-recipes
|
|
1364
|
+
</p>
|
|
1365
|
+
<p className="text-sm text-slate-600 dark:text-slate-400 mb-2">
|
|
1366
|
+
<strong>Pages</strong>: 4 auth
|
|
1367
|
+
</p>
|
|
1368
|
+
<p className="text-sm text-slate-600 dark:text-slate-400 mb-2">
|
|
1369
|
+
<strong>Tables</strong>: recipes, recipe_favorites, recipe_comments,
|
|
1370
|
+
recipe_image_queue, recipe_embeddings
|
|
1371
|
+
</p>
|
|
1372
|
+
<h4 className="font-medium mb-2">Installation du module recipes</h4>
|
|
1373
|
+
<Snippet symbol="" hideSymbol className="text-sm mb-2">
|
|
1374
|
+
{`pnpm lastbrain add-module recipes`}
|
|
1375
|
+
</Snippet>
|
|
1376
|
+
<p className="text-sm text-slate-600 dark:text-slate-400 mb-2">
|
|
1377
|
+
<a
|
|
1378
|
+
href="../packages/module-recipes/README.md"
|
|
1379
|
+
className="text-blue-600 hover:underline"
|
|
1380
|
+
target="_blank"
|
|
1381
|
+
rel="noopener noreferrer"
|
|
1382
|
+
>
|
|
1383
|
+
📖 Documentation complète →
|
|
1384
|
+
</a>
|
|
1385
|
+
</p>
|
|
1386
|
+
<p className="text-sm text-slate-600 dark:text-slate-400 mb-2">---</p>
|
|
1359
1387
|
<h4 className="font-medium mb-2">
|
|
1360
1388
|
[module-tasks](../packages/module-tasks/README.md)
|
|
1361
1389
|
</h4>
|