@lastbrain/app 2.0.11 → 2.0.15

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.
@@ -23,6 +23,7 @@ export interface ModuleConfig {
23
23
  moduleName: string;
24
24
  pages: PageConfig[];
25
25
  tables: TableConfig[];
26
+ isPro?: boolean;
26
27
  }
27
28
 
28
29
  // Noms réservés pour éviter les collisions avec le routeur/app
@@ -116,57 +117,84 @@ function getLastBrainPackageVersions(rootDir: string): {
116
117
  function generatePackageJson(
117
118
  moduleName: string,
118
119
  slug: string,
119
- rootDir: string
120
+ rootDir: string,
121
+ isPro: boolean = false
120
122
  ): string {
121
123
  const versions = getLastBrainPackageVersions(rootDir);
122
- const moduleNameOnly = slug.replace("module-", "");
124
+ const moduleNameOnly = slug.replace("module-", "").replace("-pro", "");
123
125
  const buildConfigExport = `./${moduleNameOnly}.build.config`;
124
- return JSON.stringify(
125
- {
126
- name: moduleName,
127
- version: "0.1.0",
128
- private: false,
129
- type: "module",
130
- main: "dist/index.js",
131
- types: "dist/index.d.ts",
132
- files: ["dist", "src", "supabase"],
133
- scripts: {
134
- build: "tsc -p tsconfig.json",
135
- dev: "tsc -p tsconfig.json --watch",
126
+ const packageJson: Record<string, unknown> = {
127
+ name: moduleName,
128
+ version: "0.1.0",
129
+ private: false,
130
+ type: "module",
131
+ main: "dist/index.js",
132
+ types: "dist/index.d.ts",
133
+ files: ["dist", "src", "supabase"],
134
+ scripts: {
135
+ build: "tsc -p tsconfig.json",
136
+ dev: "tsc -p tsconfig.json --watch",
137
+ },
138
+ dependencies: {
139
+ "@lastbrain/core": versions.core,
140
+ "@lastbrain/ui": versions.ui,
141
+ react: "^19.0.0",
142
+ "lucide-react": "^0.554.0",
143
+ "react-dom": "^19.0.0",
144
+ },
145
+ devDependencies: {
146
+ typescript: "^5.4.0",
147
+ },
148
+ exports: {
149
+ ".": {
150
+ types: "./dist/index.d.ts",
151
+ default: "./dist/index.js",
136
152
  },
137
- dependencies: {
138
- "@lastbrain/core": versions.core,
139
- "@lastbrain/ui": versions.ui,
140
- react: "^19.0.0",
141
- "lucide-react": "^0.554.0",
142
- "react-dom": "^19.0.0",
153
+ "./server": {
154
+ types: "./dist/server.d.ts",
155
+ default: "./dist/server.js",
143
156
  },
144
- devDependencies: {
145
- typescript: "^5.4.0",
157
+ [buildConfigExport]: {
158
+ types: `./dist/${moduleNameOnly}.build.config.d.ts`,
159
+ default: `./dist/${moduleNameOnly}.build.config.js`,
146
160
  },
147
- exports: {
148
- ".": {
149
- types: "./dist/index.d.ts",
150
- default: "./dist/index.js",
151
- },
152
- "./server": {
153
- types: "./dist/server.d.ts",
154
- default: "./dist/server.js",
155
- },
156
- [buildConfigExport]: {
157
- types: `./dist/${moduleNameOnly}.build.config.d.ts`,
158
- default: `./dist/${moduleNameOnly}.build.config.js`,
159
- },
160
- "./api/*": {
161
- types: "./dist/api/*.d.ts",
162
- default: "./dist/api/*.js",
163
- },
161
+ "./api/*": {
162
+ types: "./dist/api/*.d.ts",
163
+ default: "./dist/api/*.js",
164
164
  },
165
- sideEffects: false,
166
165
  },
167
- null,
168
- 2
169
- );
166
+ sideEffects: false,
167
+ };
168
+
169
+ // Ajouter les champs spécifiques selon le type (Pro ou Public)
170
+ if (isPro) {
171
+ packageJson.publishConfig = {
172
+ registry: "https://npm.pkg.github.com",
173
+ };
174
+ packageJson.release = {
175
+ type: "pro",
176
+ registry: "https://npm.pkg.github.com",
177
+ };
178
+ packageJson.repository = {
179
+ type: "git",
180
+ url: "https://github.com/Lastbrain-labs/starter",
181
+ directory: `packages/${slug}`,
182
+ };
183
+ } else {
184
+ packageJson.publishConfig = {
185
+ access: "public",
186
+ };
187
+ packageJson.release = {
188
+ type: "public",
189
+ };
190
+ packageJson.repository = {
191
+ type: "git",
192
+ url: "https://github.com/Lastbrain-labs/lb-starter",
193
+ directory: `packages/${slug}`,
194
+ };
195
+ }
196
+
197
+ return JSON.stringify(packageJson, null, 2);
170
198
  }
171
199
 
172
200
  /**
@@ -376,13 +404,31 @@ function toPascalCase(value: string): string {
376
404
  }
377
405
 
378
406
  function generateIndexTs(pages: PageConfig[], moduleNameOnly: string): string {
407
+ // Grouper les pages par nom pour détecter les doublons
408
+ const pagesByName = new Map<string, PageConfig[]>();
409
+ for (const page of pages) {
410
+ const key = page.name;
411
+ const existing = pagesByName.get(key) || [];
412
+ existing.push(page);
413
+ pagesByName.set(key, existing);
414
+ }
415
+
379
416
  const exports = pages.map((page) => {
380
417
  const componentName = toPascalCase(page.name);
381
- const fileName = toPascalCase(page.name);
382
- return `export { ${componentName}Page } from "./web/${page.section}/${fileName}Page";`;
418
+
419
+ // Si le même nom de page existe dans plusieurs sections, ajouter la section au nom du composant ET du fichier
420
+ const duplicates = pagesByName.get(page.name) || [];
421
+ const hasDuplicates = duplicates.length > 1;
422
+ const componentNameWithSection = hasDuplicates
423
+ ? `${componentName}${toPascalCase(page.section)}`
424
+ : componentName;
425
+
426
+ return `export { ${componentNameWithSection}Page } from "./web/${page.section}/${componentNameWithSection}Page";`;
383
427
  });
384
428
 
385
- const moduleAlias = toPascalCase(moduleNameOnly);
429
+ // Nettoyer le nom pour l'alias d'export (enlever -pro si présent)
430
+ const cleanedNameOnly = moduleNameOnly.replace(/-pro$/, "");
431
+ const moduleAlias = toPascalCase(cleanedNameOnly);
386
432
 
387
433
  return `// Client Components
388
434
  ${exports.join("\n")}
@@ -418,22 +464,28 @@ ${exports.join("\n")}
418
464
  /**
419
465
  * Génère le contenu d'une page
420
466
  */
421
- function generatePageComponent(pageName: string, section: string): string {
422
- const componentName = pageName
423
- .split("-")
424
- .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
425
- .join("");
467
+ function generatePageComponent(
468
+ pageName: string,
469
+ section: string,
470
+ componentName?: string
471
+ ): string {
472
+ const finalComponentName =
473
+ componentName ||
474
+ pageName
475
+ .split("-")
476
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
477
+ .join("");
426
478
 
427
479
  return `"use client";
428
480
 
429
481
  import { Card, CardBody, CardHeader } from "@lastbrain/ui";
430
482
 
431
- export function ${componentName}Page() {
483
+ export function ${finalComponentName}Page() {
432
484
  return (
433
485
  <div className="container mx-auto p-6">
434
486
  <Card>
435
487
  <CardHeader>
436
- <h1 className="text-2xl font-bold">${componentName}</h1>
488
+ <h1 className="text-2xl font-bold">${finalComponentName}</h1>
437
489
  </CardHeader>
438
490
  <CardBody>
439
491
  <p className="text-default-600">
@@ -1207,6 +1259,25 @@ async function updateModuleRegistry(config: ModuleConfig, rootDir: string) {
1207
1259
  chalk.yellow(" ⚠️ Fichier de registre non trouvé, création...")
1208
1260
  );
1209
1261
  // Si le fichier n'existe pas, on le crée avec le module actuel
1262
+ const moduleName = config.slug.replace("module-", "").replace("-pro", "");
1263
+ const moduleNameWithSuffix = config.isPro
1264
+ ? `${moduleName}-pro`
1265
+ : moduleName;
1266
+ const moduleEntry = config.isPro
1267
+ ? ` {
1268
+ name: "${moduleNameWithSuffix}",
1269
+ package: "${config.moduleName}",
1270
+ description: "Module ${config.moduleName}",
1271
+ emoji: "📦",
1272
+ isPro: true,
1273
+ },`
1274
+ : ` {
1275
+ name: "${moduleNameWithSuffix}",
1276
+ package: "${config.moduleName}",
1277
+ description: "Module ${config.moduleName}",
1278
+ emoji: "📦",
1279
+ },`;
1280
+
1210
1281
  const content = `/**
1211
1282
  * Configuration centralisée des modules LastBrain
1212
1283
  * Ce fichier est auto-généré et maintenu par les scripts de gestion des modules
@@ -1218,15 +1289,11 @@ export interface ModuleMetadata {
1218
1289
  description: string;
1219
1290
  emoji: string;
1220
1291
  version?: string;
1292
+ isPro?: boolean;
1221
1293
  }
1222
1294
 
1223
1295
  export const AVAILABLE_MODULES: ModuleMetadata[] = [
1224
- {
1225
- name: "${config.slug.replace("module-", "")}",
1226
- package: "@lastbrain/${config.slug}",
1227
- description: "Module ${config.moduleName}",
1228
- emoji: "📦",
1229
- },
1296
+ ${moduleEntry}
1230
1297
  ];
1231
1298
 
1232
1299
  /**
@@ -1258,22 +1325,43 @@ export function getAvailableModuleNames(): string[] {
1258
1325
  try {
1259
1326
  let content = await fs.readFile(moduleRegistryPath, "utf-8");
1260
1327
 
1261
- const moduleName = config.slug.replace("module-", "");
1262
- const moduleEntry = ` {
1263
- name: "${moduleName}",
1264
- package: "@lastbrain/${config.slug}",
1328
+ const moduleName = config.slug.replace("module-", "").replace("-pro", "");
1329
+ const moduleNameWithSuffix = config.isPro
1330
+ ? `${moduleName}-pro`
1331
+ : moduleName;
1332
+ const moduleEntry = config.isPro
1333
+ ? ` {
1334
+ name: "${moduleNameWithSuffix}",
1335
+ package: "${config.moduleName}",
1336
+ description: "Module ${config.moduleName}",
1337
+ emoji: "📦",
1338
+ isPro: true,
1339
+ },`
1340
+ : ` {
1341
+ name: "${moduleNameWithSuffix}",
1342
+ package: "${config.moduleName}",
1265
1343
  description: "Module ${config.moduleName}",
1266
1344
  emoji: "📦",
1267
1345
  },`;
1268
1346
 
1269
1347
  // Vérifier si le module existe déjà
1270
- if (content.includes(`name: "${moduleName}"`)) {
1348
+ if (content.includes(`name: "${moduleNameWithSuffix}"`)) {
1271
1349
  console.log(
1272
- chalk.yellow(` ⚠️ Module ${moduleName} déjà présent dans le registre`)
1350
+ chalk.yellow(
1351
+ ` ⚠️ Module ${moduleNameWithSuffix} déjà présent dans le registre`
1352
+ )
1273
1353
  );
1274
1354
  return;
1275
1355
  }
1276
1356
 
1357
+ // Vérifier si l'interface a le champ isPro
1358
+ if (!content.includes("isPro?: boolean;")) {
1359
+ content = content.replace(
1360
+ /version\?: string;\s*\}/,
1361
+ "version?: string;\n isPro?: boolean;\n}"
1362
+ );
1363
+ }
1364
+
1277
1365
  // Trouver le tableau AVAILABLE_MODULES et ajouter le module
1278
1366
  const arrayMatch = content.match(
1279
1367
  /export const AVAILABLE_MODULES: ModuleMetadata\[\] = \[([\s\S]*?)\];/
@@ -1355,7 +1443,12 @@ export async function createModuleStructure(
1355
1443
  console.log(chalk.yellow(" 📄 package.json"));
1356
1444
  await fs.writeFile(
1357
1445
  path.join(moduleDir, "package.json"),
1358
- generatePackageJson(config.moduleName, config.slug, rootDir)
1446
+ generatePackageJson(
1447
+ config.moduleName,
1448
+ config.slug,
1449
+ rootDir,
1450
+ config.isPro || false
1451
+ )
1359
1452
  );
1360
1453
 
1361
1454
  // Créer tsconfig.json
@@ -1390,20 +1483,38 @@ export async function createModuleStructure(
1390
1483
 
1391
1484
  // Créer les pages
1392
1485
  console.log(chalk.blue("\n📄 Création des pages..."));
1486
+
1487
+ // Grouper les pages par nom pour détecter les doublons
1488
+ const pagesByName = new Map<string, PageConfig[]>();
1489
+ for (const page of config.pages) {
1490
+ const key = page.name;
1491
+ const existing = pagesByName.get(key) || [];
1492
+ existing.push(page);
1493
+ pagesByName.set(key, existing);
1494
+ }
1495
+
1393
1496
  for (const page of config.pages) {
1394
1497
  const pagePath = path.join(moduleDir, "src", "web", page.section);
1395
1498
  await fs.ensureDir(pagePath);
1396
1499
 
1397
- const componentName = page.name
1500
+ const baseName = page.name
1398
1501
  .split("-")
1399
1502
  .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
1400
1503
  .join("");
1401
- const fileName = `${componentName}Page.tsx`;
1504
+
1505
+ // Si le même nom de page existe dans plusieurs sections, ajouter la section au nom du fichier
1506
+ const duplicates = pagesByName.get(page.name) || [];
1507
+ const hasDuplicates = duplicates.length > 1;
1508
+ const componentNameWithSection = hasDuplicates
1509
+ ? `${baseName}${toPascalCase(page.section)}`
1510
+ : baseName;
1511
+
1512
+ const fileName = `${componentNameWithSection}Page.tsx`;
1402
1513
 
1403
1514
  console.log(chalk.yellow(` 📄 src/web/${page.section}/${fileName}`));
1404
1515
  await fs.writeFile(
1405
1516
  path.join(pagePath, fileName),
1406
- generatePageComponent(page.name, page.section)
1517
+ generatePageComponent(page.name, page.section, componentNameWithSection)
1407
1518
  );
1408
1519
  }
1409
1520
 
@@ -1684,10 +1795,59 @@ export async function createModule() {
1684
1795
  });
1685
1796
  }
1686
1797
 
1798
+ // 🛡️ VALIDATION : Détecter et corriger les collisions de noms de pages
1799
+ const pagesByName = new Map<string, PageConfig[]>();
1800
+ for (const page of pages) {
1801
+ const existing = pagesByName.get(page.name) || [];
1802
+ existing.push(page);
1803
+ pagesByName.set(page.name, existing);
1804
+ }
1805
+
1806
+ const correctedPages: PageConfig[] = [];
1807
+ const collisionsDetected = new Set<string>();
1808
+
1809
+ for (const page of pages) {
1810
+ const duplicates = pagesByName.get(page.name) || [];
1811
+ if (duplicates.length > 1) {
1812
+ // Collision détectée ! Ajouter un suffixe pour différencier
1813
+ const correctedPage: PageConfig = {
1814
+ ...page,
1815
+ name: `${page.name}-${page.section}`,
1816
+ path: `${page.path}-${page.section}`,
1817
+ };
1818
+
1819
+ // Afficher l'avertissement une fois par groupe de doublons
1820
+ if (!collisionsDetected.has(page.name)) {
1821
+ collisionsDetected.add(page.name);
1822
+ console.log(
1823
+ chalk.yellow(
1824
+ `\n⚠️ Collision détectée pour la page "${page.name}" (existe dans plusieurs sections)`
1825
+ )
1826
+ );
1827
+ console.log(
1828
+ chalk.gray(
1829
+ ` Les pages seront renommées automatiquement pour éviter les conflits:`
1830
+ )
1831
+ );
1832
+ duplicates.forEach((dup) => {
1833
+ console.log(
1834
+ chalk.gray(
1835
+ ` - [${dup.section}] ${dup.name} → ${dup.name}-${dup.section}`
1836
+ )
1837
+ );
1838
+ });
1839
+ }
1840
+
1841
+ correctedPages.push(correctedPage);
1842
+ } else {
1843
+ correctedPages.push(page);
1844
+ }
1845
+ }
1846
+
1687
1847
  const config: ModuleConfig & { description?: string } = {
1688
1848
  slug,
1689
1849
  moduleName,
1690
- pages,
1850
+ pages: correctedPages,
1691
1851
  tables,
1692
1852
  description,
1693
1853
  };
@@ -1619,7 +1619,7 @@ export function DocUsageCustom() {
1619
1619
  Module LastBrain
1620
1620
  </p>
1621
1621
  <p className="text-xs text-slate-500 dark:text-slate-500">
1622
- <strong>Pages:</strong> 1 auth, 1 admin
1622
+ <strong>Pages:</strong> 1 auth, 2 admin
1623
1623
  </p>
1624
1624
 
1625
1625
  <Snippet symbol="💻" hideSymbol className="text-sm">
@@ -1686,6 +1686,29 @@ export function DocUsageCustom() {
1686
1686
  </span>
1687
1687
  </CardBody>
1688
1688
  </Card>
1689
+ <Card className="hover:shadow-lg transition-shadow border-l-4 border-l-purple-500">
1690
+ <CardBody className="space-y-3">
1691
+ <div className="flex items-start justify-between">
1692
+ <h3 className="text-lg font-semibold">Cj Analyzer Pro</h3>
1693
+ <span className="text-xs bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200 px-2 py-1 rounded">
1694
+ PRO
1695
+ </span>
1696
+ </div>
1697
+ <p className="text-sm text-slate-600 dark:text-slate-400">
1698
+ Module LastBrain
1699
+ </p>
1700
+ <p className="text-xs text-slate-500 dark:text-slate-500">
1701
+ <strong>Pages:</strong> 4 admin
1702
+ </p>
1703
+
1704
+ <Snippet symbol="💻" hideSymbol className="text-sm">
1705
+ {`pnpm lastbrain add-module cj-analyzer-pro`}
1706
+ </Snippet>
1707
+ <span className="text-xs text-slate-500 dark:text-slate-400 inline-flex items-center gap-1">
1708
+ 🔒 Documentation privée
1709
+ </span>
1710
+ </CardBody>
1711
+ </Card>
1689
1712
  <Card className="hover:shadow-lg transition-shadow border-l-4 border-l-purple-500">
1690
1713
  <CardBody className="space-y-3">
1691
1714
  <div className="flex items-start justify-between">
@@ -1698,7 +1721,7 @@ export function DocUsageCustom() {
1698
1721
  Module LastBrain
1699
1722
  </p>
1700
1723
  <p className="text-xs text-slate-500 dark:text-slate-500">
1701
- <strong>Pages:</strong> 2 auth, 1 admin
1724
+ <strong>Pages:</strong> 2 publique(s), 2 auth, 1 admin
1702
1725
  </p>
1703
1726
 
1704
1727
  <Snippet symbol="💻" hideSymbol className="text-sm">
@@ -1877,6 +1900,29 @@ export function DocUsageCustom() {
1877
1900
  </span>
1878
1901
  </CardBody>
1879
1902
  </Card>
1903
+ <Card className="hover:shadow-lg transition-shadow border-l-4 border-l-purple-500">
1904
+ <CardBody className="space-y-3">
1905
+ <div className="flex items-start justify-between">
1906
+ <h3 className="text-lg font-semibold">Shop Pro</h3>
1907
+ <span className="text-xs bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200 px-2 py-1 rounded">
1908
+ PRO
1909
+ </span>
1910
+ </div>
1911
+ <p className="text-sm text-slate-600 dark:text-slate-400">
1912
+ Module LastBrain
1913
+ </p>
1914
+ <p className="text-xs text-slate-500 dark:text-slate-500">
1915
+ <strong>Pages:</strong> 3 admin
1916
+ </p>
1917
+
1918
+ <Snippet symbol="💻" hideSymbol className="text-sm">
1919
+ {`pnpm lastbrain add-module shop-pro`}
1920
+ </Snippet>
1921
+ <span className="text-xs text-slate-500 dark:text-slate-400 inline-flex items-center gap-1">
1922
+ 🔒 Documentation privée
1923
+ </span>
1924
+ </CardBody>
1925
+ </Card>
1880
1926
  <Card className="hover:shadow-lg transition-shadow border-l-4 border-l-blue-500">
1881
1927
  <CardBody className="space-y-3">
1882
1928
  <div className="flex items-start justify-between">