@lastbrain/app 0.1.34 → 0.1.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +23 -5
  2. package/dist/__tests__/module-registry.test.js +5 -16
  3. package/dist/scripts/init-app.d.ts.map +1 -1
  4. package/dist/scripts/init-app.js +2 -2
  5. package/dist/scripts/module-add.d.ts +0 -11
  6. package/dist/scripts/module-add.d.ts.map +1 -1
  7. package/dist/scripts/module-add.js +45 -22
  8. package/dist/scripts/module-build.d.ts.map +1 -1
  9. package/dist/scripts/module-build.js +90 -1
  10. package/dist/scripts/module-create.d.ts +23 -0
  11. package/dist/scripts/module-create.d.ts.map +1 -1
  12. package/dist/scripts/module-create.js +737 -52
  13. package/dist/scripts/module-delete.d.ts +6 -0
  14. package/dist/scripts/module-delete.d.ts.map +1 -0
  15. package/dist/scripts/module-delete.js +143 -0
  16. package/dist/scripts/module-list.d.ts.map +1 -1
  17. package/dist/scripts/module-list.js +2 -2
  18. package/dist/scripts/module-remove.d.ts.map +1 -1
  19. package/dist/scripts/module-remove.js +20 -4
  20. package/dist/styles.css +1 -1
  21. package/dist/templates/DefaultDoc.d.ts.map +1 -1
  22. package/dist/templates/DefaultDoc.js +170 -30
  23. package/dist/templates/DocPage.d.ts.map +1 -1
  24. package/dist/templates/DocPage.js +25 -8
  25. package/dist/templates/migrations/20201010100000_app_base.sql +23 -24
  26. package/package.json +4 -4
  27. package/src/__tests__/module-registry.test.ts +5 -17
  28. package/src/scripts/db-init.ts +2 -2
  29. package/src/scripts/init-app.ts +5 -2
  30. package/src/scripts/module-add.ts +55 -23
  31. package/src/scripts/module-build.ts +109 -1
  32. package/src/scripts/module-create.ts +885 -63
  33. package/src/scripts/module-delete.ts +202 -0
  34. package/src/scripts/module-list.ts +9 -2
  35. package/src/scripts/module-remove.ts +36 -4
  36. package/src/templates/DefaultDoc.tsx +1163 -753
  37. package/src/templates/DocPage.tsx +28 -11
  38. package/src/templates/migrations/20201010100000_app_base.sql +23 -24
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 :
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import { AVAILABLE_MODULES } from "../scripts/module-add";
2
+ import { AVAILABLE_MODULES } from "@lastbrain/core/config/modules";
3
3
  describe("Module Registry", () => {
4
4
  describe("AVAILABLE_MODULES", () => {
5
5
  it("should have at least one module defined", () => {
@@ -9,29 +9,18 @@ describe("Module Registry", () => {
9
9
  const authModule = AVAILABLE_MODULES.find((m) => m.name === "auth");
10
10
  expect(authModule).toBeDefined();
11
11
  expect(authModule?.package).toBe("@lastbrain/module-auth");
12
- expect(authModule?.hasMigrations).toBe(true);
13
12
  });
14
13
  it("should have ai module defined", () => {
15
14
  const aiModule = AVAILABLE_MODULES.find((m) => m.name === "ai");
16
15
  expect(aiModule).toBeDefined();
17
16
  expect(aiModule?.package).toBe("@lastbrain/module-ai");
18
- expect(aiModule?.hasMigrations).toBe(true);
19
17
  });
20
18
  it("should have all required properties for each module", () => {
21
19
  AVAILABLE_MODULES.forEach((module) => {
22
20
  expect(module.name).toBeDefined();
23
21
  expect(module.package).toBeDefined();
24
- expect(module.displayName).toBeDefined();
22
+ expect(module.emoji).toBeDefined();
25
23
  expect(module.description).toBeDefined();
26
- expect(typeof module.hasMigrations).toBe("boolean");
27
- });
28
- });
29
- it("should have migrations paths when hasMigrations is true", () => {
30
- AVAILABLE_MODULES.forEach((module) => {
31
- if (module.hasMigrations) {
32
- expect(module.migrationsPath).toBeDefined();
33
- expect(module.migrationsDownPath).toBeDefined();
34
- }
35
24
  });
36
25
  });
37
26
  it("should have unique module names", () => {
@@ -44,10 +33,10 @@ describe("Module Registry", () => {
44
33
  const uniquePackages = new Set(packages);
45
34
  expect(packages.length).toBe(uniquePackages.size);
46
35
  });
47
- it("should have display names with emoji", () => {
36
+ it("should have emoji", () => {
48
37
  AVAILABLE_MODULES.forEach((module) => {
49
- // Check if displayName contains at least one emoji (basic check)
50
- expect(module.displayName).toMatch(/[\u{1F300}-\u{1F9FF}]/u);
38
+ // Check if emoji contains at least one emoji (basic check)
39
+ expect(module.emoji).toMatch(/[\u{1F300}-\u{1F9FF}]/u);
51
40
  });
52
41
  });
53
42
  it("should have non-empty descriptions", () => {
@@ -1 +1 @@
1
- {"version":3,"file":"init-app.d.ts","sourceRoot":"","sources":["../../src/scripts/init-app.ts"],"names":[],"mappings":"AAWA,UAAU,cAAc;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAsB,OAAO,CAAC,OAAO,EAAE,cAAc,iBAyMpD"}
1
+ {"version":3,"file":"init-app.d.ts","sourceRoot":"","sources":["../../src/scripts/init-app.ts"],"names":[],"mappings":"AAcA,UAAU,cAAc;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAsB,OAAO,CAAC,OAAO,EAAE,cAAc,iBAyMpD"}
@@ -4,7 +4,7 @@ import { fileURLToPath } from "url";
4
4
  import chalk from "chalk";
5
5
  import inquirer from "inquirer";
6
6
  import { execSync } from "child_process";
7
- import { AVAILABLE_MODULES } from "./module-add.js";
7
+ import { AVAILABLE_MODULES, } from "@lastbrain/core/config/modules";
8
8
  const __filename = fileURLToPath(import.meta.url);
9
9
  const __dirname = path.dirname(__filename);
10
10
  export async function initApp(options) {
@@ -28,7 +28,7 @@ export async function initApp(options) {
28
28
  name: "modules",
29
29
  message: "Quels modules voulez-vous installer ?",
30
30
  choices: AVAILABLE_MODULES.map((module) => ({
31
- name: `${module.displayName} - ${module.description}`,
31
+ name: `${module.emoji} ${module.name.charAt(0).toUpperCase() + module.name.slice(1)} - ${module.description}`,
32
32
  value: module.name,
33
33
  checked: false,
34
34
  })),
@@ -1,13 +1,2 @@
1
- interface ModuleDefinition {
2
- name: string;
3
- package: string;
4
- displayName: string;
5
- description: string;
6
- hasMigrations: boolean;
7
- migrationsPath?: string;
8
- migrationsDownPath?: string;
9
- }
10
- export declare const AVAILABLE_MODULES: ModuleDefinition[];
11
1
  export declare function addModule(moduleName: string, targetDir: string): Promise<void>;
12
- export {};
13
2
  //# sourceMappingURL=module-add.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"module-add.d.ts","sourceRoot":"","sources":["../../src/scripts/module-add.ts"],"names":[],"mappings":"AAMA,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,OAAO,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAGD,eAAO,MAAM,iBAAiB,EAAE,gBAAgB,EAqB/C,CAAC;AAEF,wBAAsB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,iBAuOpE"}
1
+ {"version":3,"file":"module-add.d.ts","sourceRoot":"","sources":["../../src/scripts/module-add.ts"],"names":[],"mappings":"AAiCA,wBAAsB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,iBA8QpE"}
@@ -3,38 +3,30 @@ import path from "path";
3
3
  import chalk from "chalk";
4
4
  import { execSync } from "child_process";
5
5
  import inquirer from "inquirer";
6
- // Registre des modules disponibles
7
- export const AVAILABLE_MODULES = [
8
- {
9
- name: "auth",
10
- package: "@lastbrain/module-auth",
11
- displayName: "🔐 Authentication",
12
- description: "Système d'authentification complet (signin, signup, sessions)",
6
+ import { AVAILABLE_MODULES, } from "@lastbrain/core/config/modules";
7
+ // Convert core module metadata to local module definition
8
+ function toModuleDefinition(meta) {
9
+ return {
10
+ name: meta.name,
11
+ package: meta.package,
12
+ displayName: `${meta.emoji} ${meta.name.charAt(0).toUpperCase() + meta.name.slice(1)}`,
13
+ description: meta.description,
13
14
  hasMigrations: true,
14
15
  migrationsPath: "supabase/migrations",
15
16
  migrationsDownPath: "supabase/migrations-down",
16
- },
17
- {
18
- name: "ai",
19
- package: "@lastbrain/module-ai",
20
- displayName: "🤖 AI Generation",
21
- description: "Génération de texte et d'images avec gestion de tokens",
22
- hasMigrations: true,
23
- migrationsPath: "supabase/migrations",
24
- migrationsDownPath: "supabase/migrations-down",
25
- },
26
- // Ajouter d'autres modules ici au fur et à mesure
27
- ];
17
+ };
18
+ }
28
19
  export async function addModule(moduleName, targetDir) {
29
20
  console.log(chalk.blue(`\n🔧 Ajout du module: ${moduleName}\n`));
30
- const module = AVAILABLE_MODULES.find((m) => m.name === moduleName);
31
- if (!module) {
21
+ const moduleMeta = AVAILABLE_MODULES.find((m) => m.name === moduleName);
22
+ if (!moduleMeta) {
32
23
  console.error(chalk.red(`❌ Module "${moduleName}" non trouvé. Modules disponibles:`));
33
24
  AVAILABLE_MODULES.forEach((m) => {
34
- console.log(chalk.gray(` - ${m.name}: ${m.description}`));
25
+ console.log(chalk.gray(` - ${m.emoji} ${m.name}: ${m.description}`));
35
26
  });
36
27
  process.exit(1);
37
28
  }
29
+ const module = toModuleDefinition(moduleMeta);
38
30
  // 1. Vérifier qu'on est dans un projet LastBrain
39
31
  const pkgPath = path.join(targetDir, "package.json");
40
32
  if (!fs.existsSync(pkgPath)) {
@@ -62,6 +54,37 @@ export async function addModule(moduleName, targetDir) {
62
54
  console.error(chalk.red("❌ Erreur lors de l'installation"));
63
55
  process.exit(1);
64
56
  }
57
+ // 3.1 Build du module ajouté (pour générer dist/ et éviter les erreurs d'import)
58
+ console.log(chalk.yellow(`\n🏗️ Build du module ${module.package}...`));
59
+ try {
60
+ // Trouver la racine du monorepo (chercher pnpm-workspace.yaml en remontant)
61
+ let workspaceRoot = targetDir;
62
+ let attempts = 0;
63
+ const maxAttempts = 6;
64
+ while (attempts < maxAttempts) {
65
+ if (fs.existsSync(path.join(workspaceRoot, "pnpm-workspace.yaml"))) {
66
+ break;
67
+ }
68
+ const parent = path.resolve(workspaceRoot, "..");
69
+ if (parent === workspaceRoot)
70
+ break;
71
+ workspaceRoot = parent;
72
+ attempts++;
73
+ }
74
+ if (!fs.existsSync(path.join(workspaceRoot, "pnpm-workspace.yaml"))) {
75
+ console.log(chalk.gray(" ℹ️ Impossible de localiser la racine du monorepo, build ignoré"));
76
+ }
77
+ else {
78
+ execSync(`pnpm --filter ${module.package} build`, {
79
+ cwd: workspaceRoot,
80
+ stdio: "inherit",
81
+ });
82
+ console.log(chalk.green(" ✓ Module compilé"));
83
+ }
84
+ }
85
+ catch {
86
+ console.log(chalk.yellow(" ⚠️ Build du module échoué, essayez: pnpm --filter"), module.package, "build");
87
+ }
65
88
  // 5. Copier les migrations du module
66
89
  const copiedMigrationFiles = [];
67
90
  if (module.hasMigrations) {
@@ -1 +1 @@
1
- {"version":3,"file":"module-build.d.ts","sourceRoot":"","sources":["../../src/scripts/module-build.ts"],"names":[],"mappings":"AAw0BA,wBAAsB,cAAc,kBAgDnC"}
1
+ {"version":3,"file":"module-build.d.ts","sourceRoot":"","sources":["../../src/scripts/module-build.ts"],"names":[],"mappings":"AAg7BA,wBAAsB,cAAc,kBAoDnC"}
@@ -3,6 +3,10 @@ import path from "node:path";
3
3
  import { createRequire } from "node:module";
4
4
  // Utiliser PROJECT_ROOT si défini (pour pnpm --filter), sinon process.cwd()
5
5
  const projectRoot = process.env.PROJECT_ROOT || process.cwd();
6
+ // Si on est dans une app, monter jusqu'à la racine du monorepo
7
+ const monorepoRoot = projectRoot.includes("/apps/")
8
+ ? path.resolve(projectRoot, "..", "..")
9
+ : projectRoot;
6
10
  const appDirectory = path.join(projectRoot, "app");
7
11
  // Créer un require dans le contexte de l'application pour résoudre les modules installés dans l'app
8
12
  const projectRequire = createRequire(path.join(projectRoot, "package.json"));
@@ -154,8 +158,38 @@ function buildPage(moduleConfig, page) {
154
158
  (page.path.includes("signin") ||
155
159
  page.path.includes("signup") ||
156
160
  page.path.includes("reset-password"));
161
+ // Détecter si c'est la page de détail utilisateur qui a besoin des user tabs
162
+ const isUserDetailPage = page.section === "admin" &&
163
+ page.path.includes("users/[id]") &&
164
+ page.componentExport === "UserPage";
157
165
  let content;
158
- if (isPublicAuthPage) {
166
+ if (isUserDetailPage) {
167
+ // Page spéciale SSR avec injection des user tabs
168
+ // On importe directement depuis app/config au lieu de passer via props
169
+ content = `// GENERATED BY LASTBRAIN MODULE BUILD
170
+ import { UserDetailPage } from "${moduleConfig.moduleName}";
171
+
172
+ interface UserPageProps { params: Promise<{ id: string }> }
173
+
174
+ async function getModuleUserTabs() {
175
+ try {
176
+ // Depuis /app/admin/auth/users/[id]/ vers /apps/test-01/config/user-tabs
177
+ const { moduleUserTabs } = await import("../../../../../config/user-tabs");
178
+ return moduleUserTabs || [];
179
+ } catch (e) {
180
+ console.warn("[user-detail-wrapper] erreur chargement user-tabs", e);
181
+ return [];
182
+ }
183
+ }
184
+
185
+ export default async function ${wrapperName}(props: UserPageProps) {
186
+ const { id } = await props.params;
187
+ const moduleUserTabs = await getModuleUserTabs();
188
+ return <UserDetailPage userId={id} moduleUserTabs={moduleUserTabs} />;
189
+ }
190
+ `;
191
+ }
192
+ else if (isPublicAuthPage) {
159
193
  content = `// GENERATED BY LASTBRAIN MODULE BUILD
160
194
  "use client";
161
195
 
@@ -252,6 +286,8 @@ export interface MenuItem {
252
286
  icon?: string;
253
287
  path: string;
254
288
  order?: number;
289
+ shortcut?: string;
290
+ shortcutDisplay?: string;
255
291
  }
256
292
 
257
293
  export interface MenuConfig {
@@ -685,6 +721,56 @@ export default realtimeConfig;
685
721
  console.error("❌ Error generating realtime configuration:", error);
686
722
  }
687
723
  }
724
+ async function generateUserTabsConfig(moduleConfigs) {
725
+ try {
726
+ // Extraire les configurations user tabs des modules
727
+ const userTabsConfigs = moduleConfigs
728
+ .filter((config) => config.userTabs && config.userTabs.length > 0)
729
+ .flatMap((config) => config.userTabs.map((tab) => ({
730
+ ...tab,
731
+ moduleName: config.moduleName,
732
+ })))
733
+ .sort((a, b) => (a.order || 0) - (b.order || 0));
734
+ if (userTabsConfigs.length === 0) {
735
+ console.log("⏭️ No user tabs configuration found in modules");
736
+ return;
737
+ }
738
+ // Générer les imports statiques (Next/dynamic pour chaque composant)
739
+ const importsForApp = userTabsConfigs
740
+ .map((tab) => `const ${tab.componentExport} = dynamic(() => import("${tab.moduleName}").then(mod => ({ default: mod.${tab.componentExport} })), { ssr: true });`)
741
+ .join("\n");
742
+ // Générer le tableau des tabs
743
+ const tabsArray = userTabsConfigs
744
+ .map((tab) => ` {
745
+ key: "${tab.key}",
746
+ title: "${tab.title}",
747
+ icon: "${tab.icon || ""}",
748
+ component: ${tab.componentExport},
749
+ }`)
750
+ .join(",\n");
751
+ const timestamp = new Date().toISOString();
752
+ const appContent = `// GENERATED FILE - DO NOT EDIT MANUALLY\n// User tabs configuration\n// Generated at: ${timestamp}\n\n"use client";\n\nimport dynamic from "next/dynamic";\nimport type React from "react";\n\n${importsForApp}\n\nexport interface ModuleUserTab {\n key: string;\n title: string;\n icon?: string;\n component: React.ComponentType<{ userId: string }>;\n}\n\nexport const moduleUserTabs: ModuleUserTab[] = [\n${tabsArray}\n];\n\nexport default moduleUserTabs;\n`;
753
+ // Créer le fichier de configuration (uniquement dans /config)
754
+ const outputPath = path.join(projectRoot, "config", "user-tabs.ts");
755
+ const configDir = path.dirname(outputPath);
756
+ // Créer le dossier config s'il n'existe pas
757
+ if (!fs.existsSync(configDir)) {
758
+ fs.mkdirSync(configDir, { recursive: true });
759
+ }
760
+ // Écrire le fichier TypeScript
761
+ fs.writeFileSync(outputPath, appContent);
762
+ console.log(`✅ Generated user tabs configuration: ${outputPath}`);
763
+ console.log(`📊 User tabs count: ${userTabsConfigs.length}`);
764
+ // Afficher un résumé
765
+ userTabsConfigs.forEach((tab) => {
766
+ console.log(` - ${tab.title} (${tab.moduleName})`);
767
+ });
768
+ // Plus de copie vers app/config ni stub core
769
+ }
770
+ catch (error) {
771
+ console.error("❌ Error generating user tabs configuration:", error);
772
+ }
773
+ }
688
774
  export async function runModuleBuild() {
689
775
  ensureDirectory(appDirectory);
690
776
  // Nettoyer les fichiers générés précédemment
@@ -720,4 +806,7 @@ export async function runModuleBuild() {
720
806
  // Générer la configuration realtime
721
807
  console.log("🔄 Generating realtime configuration...");
722
808
  await generateRealtimeConfig(moduleConfigs);
809
+ // Générer la configuration des user tabs
810
+ console.log("📑 Generating user tabs configuration...");
811
+ await generateUserTabsConfig(moduleConfigs);
723
812
  }
@@ -1,3 +1,26 @@
1
+ export interface PageConfig {
2
+ section: "public" | "auth" | "admin";
3
+ path: string;
4
+ name: string;
5
+ }
6
+ export interface TableConfig {
7
+ name: string;
8
+ sections: ("public" | "auth" | "admin")[];
9
+ }
10
+ export interface ModuleConfig {
11
+ slug: string;
12
+ moduleName: string;
13
+ pages: PageConfig[];
14
+ tables: TableConfig[];
15
+ }
16
+ /**
17
+ * Trouve le répertoire racine du workspace (avec pnpm-workspace.yaml)
18
+ */
19
+ export declare function findWorkspaceRoot(): string;
20
+ /**
21
+ * Crée la structure du module
22
+ */
23
+ export declare function createModuleStructure(config: ModuleConfig, rootDir: string): Promise<void>;
1
24
  /**
2
25
  * Point d'entrée du script
3
26
  */
@@ -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":"AASA,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,CAAC,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC;CAC3C;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB;AA+vCD;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAiB1C;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,MAAM,iBAoLhB;AAED;;GAEG;AACH,wBAAsB,YAAY,kBAwKjC"}