From ab369e44f197401bd9da13f67cfa52792d67336c Mon Sep 17 00:00:00 2001 From: Damien Romito Castro Date: Thu, 18 Jun 2026 09:48:51 +0200 Subject: [PATCH 1/2] feat:#ENABLING-764 add presences repo structure --- src/core/apps/AppDiscovery.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/core/apps/AppDiscovery.ts b/src/core/apps/AppDiscovery.ts index 89267f5..51a8bb2 100644 --- a/src/core/apps/AppDiscovery.ts +++ b/src/core/apps/AppDiscovery.ts @@ -51,6 +51,34 @@ export async function discoverApps(appsRoot: string): Promise { }); } + // Schéma racine direct : {application}/.env + package.json (sans sous-dossier frontend) + for (const name of entries) { + const appPath = path.join(appsRoot, name); + const stat = await fs.stat(appPath).catch(() => null); + if (!stat?.isDirectory()) continue; + + // Ignorer si déjà détecté via le schéma frontend + if (apps.some((a) => a.id === name)) continue; + + const rootEnvPath = path.join(appPath, '.env'); + const rootEnvStat = await fs.stat(rootEnvPath).catch(() => null); + if (!rootEnvStat?.isFile()) continue; + + const packageJsonStat = await fs.stat(path.join(appPath, 'package.json')).catch(() => null); + if (!packageJsonStat?.isFile()) continue; + + const frontendPath = path.join(appPath, 'frontend'); + const frontendStat = await fs.stat(frontendPath).catch(() => null); + if (frontendStat?.isDirectory()) continue; + + apps.push({ + id: name, + name, + path: appPath, + envPath: rootEnvPath, + }); + } + // Schéma entcore : entcore/{application}/frontend (id = "entcore/{application}") const entcorePath = path.join(appsRoot, 'entcore'); const entcoreStat = await fs.stat(entcorePath).catch(() => null); From 0c0c351b97c91b76be56ad1271752edbe501a0a4 Mon Sep 17 00:00:00 2001 From: Damien Romito Castro Date: Fri, 19 Jun 2026 10:53:19 +0200 Subject: [PATCH 2/2] add entcore admin structure and refacto --- src/core/apps/AppDiscovery.ts | 118 ++++++++++++++------------- tests/core/apps/AppDiscovery.test.ts | 40 ++++++++- 2 files changed, 99 insertions(+), 59 deletions(-) diff --git a/src/core/apps/AppDiscovery.ts b/src/core/apps/AppDiscovery.ts index 51a8bb2..547cf4e 100644 --- a/src/core/apps/AppDiscovery.ts +++ b/src/core/apps/AppDiscovery.ts @@ -10,11 +10,21 @@ export interface AppSummary { envPath: string; } +async function isDirectory(p: string): Promise { + return (await fs.stat(p).catch(() => null))?.isDirectory() ?? false; +} + +async function isFile(p: string): Promise { + return (await fs.stat(p).catch(() => null))?.isFile() ?? false; +} + /** - * Parcourt appsRoot et détecte les applications ayant un dossier frontend. - * Deux schémas supportés : - * - À la racine : {application}/frontend - * - Sous entcore : entcore/{application}/frontend (id = "entcore/{application}") + * Parcourt appsRoot et détecte les applications frontend. + * Quatre schémas supportés (par ordre de priorité dans chaque zone) : + * - Racine : {application}/frontend + * - Racine direct : {application}/.env + package.json (sans sous-dossier frontend) + * - Entcore : entcore/{application}/frontend (id = "entcore/{application}") + * - Entcore TS : entcore/{application}/src/main/ts (id = "entcore/{application}") */ export async function discoverApps(appsRoot: string): Promise { let entries: string[]; @@ -35,77 +45,69 @@ export async function discoverApps(appsRoot: string): Promise { // Schéma racine : {application}/frontend for (const name of entries) { const appPath = path.join(appsRoot, name); - const stat = await fs.stat(appPath).catch(() => null); - if (!stat?.isDirectory()) continue; + if (!(await isDirectory(appPath))) continue; - const frontendPath = path.join(appPath, 'frontend'); - const frontendStat = await fs.stat(frontendPath).catch(() => null); - if (!frontendStat?.isDirectory()) continue; + const frontendDir = path.join(appPath, 'frontend'); + if (!(await isDirectory(frontendDir))) continue; - const envPath = path.join(frontendPath, '.env'); - apps.push({ - id: name, - name, - path: appPath, - envPath, - }); + apps.push({ id: name, name, path: appPath, envPath: path.join(frontendDir, '.env') }); } // Schéma racine direct : {application}/.env + package.json (sans sous-dossier frontend) for (const name of entries) { + if (apps.some((a) => a.id === name)) continue; + const appPath = path.join(appsRoot, name); - const stat = await fs.stat(appPath).catch(() => null); - if (!stat?.isDirectory()) continue; + if (!(await isDirectory(appPath))) continue; + if (!(await isFile(path.join(appPath, '.env')))) continue; + if (!(await isFile(path.join(appPath, 'package.json')))) continue; + if (await isDirectory(path.join(appPath, 'frontend'))) continue; - // Ignorer si déjà détecté via le schéma frontend - if (apps.some((a) => a.id === name)) continue; + apps.push({ id: name, name, path: appPath, envPath: path.join(appPath, '.env') }); + } + + const entcorePath = path.join(appsRoot, 'entcore'); + if (!(await isDirectory(entcorePath))) return apps.sort((a, b) => a.name.localeCompare(b.name)); - const rootEnvPath = path.join(appPath, '.env'); - const rootEnvStat = await fs.stat(rootEnvPath).catch(() => null); - if (!rootEnvStat?.isFile()) continue; + let entcoreEntries: string[]; + try { + entcoreEntries = await fs.readdir(entcorePath); + } catch { + entcoreEntries = []; + } - const packageJsonStat = await fs.stat(path.join(appPath, 'package.json')).catch(() => null); - if (!packageJsonStat?.isFile()) continue; + // Schéma entcore : entcore/{application}/frontend (id = "entcore/{application}") + for (const appName of entcoreEntries) { + const appPath = path.join(entcorePath, appName); + if (!(await isDirectory(appPath))) continue; - const frontendPath = path.join(appPath, 'frontend'); - const frontendStat = await fs.stat(frontendPath).catch(() => null); - if (frontendStat?.isDirectory()) continue; + const frontendDir = path.join(appPath, 'frontend'); + if (!(await isDirectory(frontendDir))) continue; apps.push({ - id: name, - name, + id: `entcore/${appName}`, + name: appName, path: appPath, - envPath: rootEnvPath, + envPath: path.join(frontendDir, '.env'), }); } - // Schéma entcore : entcore/{application}/frontend (id = "entcore/{application}") - const entcorePath = path.join(appsRoot, 'entcore'); - const entcoreStat = await fs.stat(entcorePath).catch(() => null); - if (entcoreStat?.isDirectory()) { - let entcoreEntries: string[]; - try { - entcoreEntries = await fs.readdir(entcorePath); - } catch { - entcoreEntries = []; - } - for (const appName of entcoreEntries) { - const appPath = path.join(entcorePath, appName); - const stat = await fs.stat(appPath).catch(() => null); - if (!stat?.isDirectory()) continue; - - const frontendPath = path.join(appPath, 'frontend'); - const frontendStat = await fs.stat(frontendPath).catch(() => null); - if (!frontendStat?.isDirectory()) continue; - - const envPath = path.join(frontendPath, '.env'); - apps.push({ - id: `entcore/${appName}`, - name: appName, - path: appPath, - envPath, - }); - } + // Schéma entcore TS : entcore/{application}/src/main/ts (id = "entcore/{application}") + for (const appName of entcoreEntries) { + if (apps.some((a) => a.id === `entcore/${appName}`)) continue; + + const appPath = path.join(entcorePath, appName); + if (!(await isDirectory(appPath))) continue; + + const tsDir = path.join(appPath, 'src', 'main', 'ts'); + if (!(await isDirectory(tsDir))) continue; + + apps.push({ + id: `entcore/${appName}`, + name: appName, + path: appPath, + envPath: path.join(tsDir, '.env'), + }); } return apps.sort((a, b) => a.name.localeCompare(b.name)); diff --git a/tests/core/apps/AppDiscovery.test.ts b/tests/core/apps/AppDiscovery.test.ts index 787f17e..8a0d6ff 100644 --- a/tests/core/apps/AppDiscovery.test.ts +++ b/tests/core/apps/AppDiscovery.test.ts @@ -1,4 +1,4 @@ -import { mkdtemp, mkdir, rm } from 'fs/promises'; +import { mkdtemp, mkdir, rm, writeFile } from 'fs/promises'; import { tmpdir } from 'os'; import { join } from 'path'; @@ -69,4 +69,42 @@ describe('AppDiscovery', () => { expect(rootApp!.path).not.toContain('entcore'); expect(entcoreApp!.path).toContain('entcore'); }); + + it('détecte une app racine directe avec .env + package.json (sans dossier frontend)', async () => { + await mkdir(join(tempRoot, 'app1'), { recursive: true }); + await writeFile(join(tempRoot, 'app1', '.env'), ''); + await writeFile(join(tempRoot, 'app1', 'package.json'), '{}'); + const apps = await discoverApps(tempRoot); + expect(apps).toHaveLength(1); + expect(apps[0].id).toBe('app1'); + expect(apps[0].envPath).toBe(join(tempRoot, 'app1', '.env')); + }); + + it('ignore le schéma racine direct si un dossier frontend existe (priorité frontend)', async () => { + await mkdir(join(tempRoot, 'app1', 'frontend'), { recursive: true }); + await writeFile(join(tempRoot, 'app1', '.env'), ''); + await writeFile(join(tempRoot, 'app1', 'package.json'), '{}'); + const apps = await discoverApps(tempRoot); + expect(apps).toHaveLength(1); + expect(apps[0].envPath).toContain('frontend'); + expect(apps[0].envPath).toContain('.env'); + }); + + it('détecte une app entcore TS via src/main/ts', async () => { + await mkdir(join(tempRoot, 'entcore', 'app1', 'src', 'main', 'ts'), { recursive: true }); + const apps = await discoverApps(tempRoot); + expect(apps).toHaveLength(1); + expect(apps[0].id).toBe('entcore/app1'); + expect(apps[0].name).toBe('app1'); + expect(apps[0].envPath).toBe(join(tempRoot, 'entcore', 'app1', 'src', 'main', 'ts', '.env')); + }); + + it('ignore le schéma entcore TS si un dossier frontend existe (priorité frontend)', async () => { + await mkdir(join(tempRoot, 'entcore', 'app1', 'frontend'), { recursive: true }); + await mkdir(join(tempRoot, 'entcore', 'app1', 'src', 'main', 'ts'), { recursive: true }); + const apps = await discoverApps(tempRoot); + expect(apps).toHaveLength(1); + expect(apps[0].envPath).toContain('frontend'); + expect(apps[0].envPath).toContain('.env'); + }); });