@mostajs/setup 1.5.1 → 2.0.1

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.
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Creates a POST handler that creates a database.
3
+ * Delegates entirely to @mostajs/orm createDatabase().
4
+ */
5
+ export declare function createCreateDbHandler(): {
6
+ POST: (req: Request) => Promise<Response>;
7
+ };
@@ -0,0 +1,29 @@
1
+ // @mosta/setup — Create Database route (delegates to @mostajs/orm)
2
+ // Author: Dr Hamid MADANI drmdh@msn.com
3
+ //
4
+ // Copy to: src/app/api/setup/create-db/route.ts
5
+ import { composeDbUri } from '../lib/compose-uri.js';
6
+ /**
7
+ * Creates a POST handler that creates a database.
8
+ * Delegates entirely to @mostajs/orm createDatabase().
9
+ */
10
+ export function createCreateDbHandler() {
11
+ async function POST(req) {
12
+ try {
13
+ const body = await req.json();
14
+ const { dialect, host, port, name, user, password } = body;
15
+ if (!dialect || !name) {
16
+ return Response.json({ ok: false, error: 'dialect et name requis' }, { status: 400 });
17
+ }
18
+ const uri = composeDbUri(dialect, { host, port, name, user, password });
19
+ const { createDatabase } = await import('@mostajs/orm');
20
+ const result = await createDatabase(dialect, uri, name);
21
+ return Response.json(result);
22
+ }
23
+ catch (err) {
24
+ const message = err instanceof Error ? err.message : 'Erreur création base';
25
+ return Response.json({ ok: false, error: message });
26
+ }
27
+ }
28
+ return { POST };
29
+ }
@@ -1,6 +1,6 @@
1
1
  // @mosta/setup — Detect modules API route factory
2
2
  // Author: Dr Hamid MADANI drmdh@msn.com
3
- import { discoverNpmModules } from '../lib/discover-modules';
3
+ import { discoverNpmModules } from '../lib/discover-modules.js';
4
4
  export function createDetectModulesHandler() {
5
5
  async function GET() {
6
6
  const result = await discoverNpmModules();
@@ -4,7 +4,7 @@ import { exec } from 'child_process';
4
4
  import { promisify } from 'util';
5
5
  import fs from 'fs';
6
6
  import path from 'path';
7
- import { MODULES, resolveModuleDependencies } from '../data/module-definitions';
7
+ import { MODULES, resolveModuleDependencies } from '../data/module-definitions.js';
8
8
  const execAsync = promisify(exec);
9
9
  /**
10
10
  * Factory for POST /api/setup/install-modules
@@ -1,4 +1,4 @@
1
- import type { MostaSetupConfig } from '../types/index';
1
+ import type { MostaSetupConfig } from '../types/index.js';
2
2
  type NeedsSetupFn = () => Promise<boolean>;
3
3
  /**
4
4
  * Creates a POST handler for running the installation.
@@ -2,7 +2,7 @@
2
2
  // Author: Dr Hamid MADANI drmdh@msn.com
3
3
  //
4
4
  // Copy to: src/app/api/setup/install/route.ts
5
- import { runInstall } from '../lib/setup';
5
+ import { runInstall } from '../lib/setup.js';
6
6
  /**
7
7
  * Creates a POST handler for running the installation.
8
8
  */
@@ -0,0 +1,13 @@
1
+ export interface PreflightCheck {
2
+ key: string;
3
+ label: string;
4
+ status: 'ok' | 'warn' | 'fail';
5
+ detail: string;
6
+ }
7
+ /**
8
+ * Creates a GET handler for preflight environment checks.
9
+ * Returns an array of checks: env file, dialect, URI, setup.json, DB connection, users count, Node.js version.
10
+ */
11
+ export declare function createPreflightHandler(): {
12
+ GET: () => Promise<Response>;
13
+ };
@@ -0,0 +1,136 @@
1
+ // @mosta/setup — Preflight checks: env, DB connection, DB exists, users
2
+ // Author: Dr Hamid MADANI drmdh@msn.com
3
+ //
4
+ // Copy to: src/app/api/setup/preflight/route.ts
5
+ import { existsSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+ /**
8
+ * Creates a GET handler for preflight environment checks.
9
+ * Returns an array of checks: env file, dialect, URI, setup.json, DB connection, users count, Node.js version.
10
+ */
11
+ export function createPreflightHandler() {
12
+ async function GET() {
13
+ const checks = [];
14
+ const cwd = process.cwd();
15
+ // 1. .env.local
16
+ const envExists = existsSync(join(cwd, '.env.local'));
17
+ checks.push({
18
+ key: 'envFile',
19
+ label: '.env.local',
20
+ status: envExists ? 'ok' : 'warn',
21
+ detail: envExists ? 'Fichier trouvé' : 'Fichier absent — sera créé lors de l\'installation',
22
+ });
23
+ // 2. DB_DIALECT
24
+ const dialect = process.env.DB_DIALECT;
25
+ checks.push({
26
+ key: 'dialect',
27
+ label: 'DB_DIALECT',
28
+ status: dialect ? 'ok' : 'warn',
29
+ detail: dialect ? `Dialect configuré : ${dialect}` : 'Non défini — à configurer',
30
+ });
31
+ // 3. SGBD_URI (masquer le mot de passe)
32
+ const uri = process.env.SGBD_URI;
33
+ checks.push({
34
+ key: 'uri',
35
+ label: 'SGBD_URI',
36
+ status: uri ? 'ok' : 'warn',
37
+ detail: uri
38
+ ? `URI : ${uri.replace(/\/\/([^:]+):([^@]+)@/, '//$1:***@')}`
39
+ : 'Non définie — à configurer',
40
+ });
41
+ // 4. setup.json
42
+ const setupJsonExists = existsSync(join(cwd, 'setup.json'));
43
+ checks.push({
44
+ key: 'setupJson',
45
+ label: 'setup.json',
46
+ status: setupJsonExists ? 'ok' : 'warn',
47
+ detail: setupJsonExists ? 'Fichier de configuration trouvé' : 'Fichier absent',
48
+ });
49
+ // 5. DB connection + users
50
+ if (dialect && uri) {
51
+ try {
52
+ const { getDialect, BaseRepository, registerSchemas } = await import('@mostajs/orm');
53
+ const d = await getDialect();
54
+ if (d) {
55
+ checks.push({
56
+ key: 'dbConnection',
57
+ label: 'Connexion DB',
58
+ status: 'ok',
59
+ detail: `Connecté (${d.constructor.name})`,
60
+ });
61
+ // Count users
62
+ try {
63
+ const UserSchema = {
64
+ name: 'User', collection: 'users', timestamps: true,
65
+ fields: { email: { type: 'string' } }, relations: {}, indexes: [],
66
+ };
67
+ registerSchemas([UserSchema]);
68
+ const repo = new BaseRepository(UserSchema, d);
69
+ const count = await repo.count();
70
+ checks.push({
71
+ key: 'users',
72
+ label: 'Utilisateurs',
73
+ status: count > 0 ? 'ok' : 'warn',
74
+ detail: count > 0
75
+ ? `${count} utilisateur(s) trouvé(s)`
76
+ : 'Aucun utilisateur — installation requise',
77
+ });
78
+ }
79
+ catch {
80
+ checks.push({
81
+ key: 'users',
82
+ label: 'Utilisateurs',
83
+ status: 'warn',
84
+ detail: 'Table users inexistante — installation requise',
85
+ });
86
+ }
87
+ }
88
+ else {
89
+ checks.push({
90
+ key: 'dbConnection',
91
+ label: 'Connexion DB',
92
+ status: 'fail',
93
+ detail: 'Connexion échouée (dialect null)',
94
+ });
95
+ }
96
+ }
97
+ catch (err) {
98
+ const msg = err instanceof Error ? err.message : 'Erreur inconnue';
99
+ const dbNotExist = msg.includes('does not exist') || msg.includes('Unknown database');
100
+ checks.push({
101
+ key: 'dbConnection',
102
+ label: 'Connexion DB',
103
+ status: 'fail',
104
+ detail: dbNotExist
105
+ ? 'Base de données introuvable — à créer'
106
+ : `Erreur : ${msg}`,
107
+ });
108
+ if (dbNotExist) {
109
+ checks.push({
110
+ key: 'dbExists',
111
+ label: 'Base de données',
112
+ status: 'fail',
113
+ detail: 'La base n\'existe pas — utilisez l\'étape Connexion pour la créer',
114
+ });
115
+ }
116
+ }
117
+ }
118
+ else {
119
+ checks.push({
120
+ key: 'dbConnection',
121
+ label: 'Connexion DB',
122
+ status: 'warn',
123
+ detail: 'Pas de configuration DB — à configurer',
124
+ });
125
+ }
126
+ // 6. Node.js
127
+ checks.push({
128
+ key: 'node',
129
+ label: 'Node.js',
130
+ status: 'ok',
131
+ detail: `Version ${process.version}`,
132
+ });
133
+ return Response.json({ checks });
134
+ }
135
+ return { GET };
136
+ }
@@ -1,8 +1,8 @@
1
1
  // @mostajs/setup — API Route factory for reconfiguration
2
2
  // Author: Dr Hamid MADANI drmdh@msn.com
3
- import { testDbConnection } from '../lib/db-test';
4
- import { writeEnvLocal } from '../lib/env-writer';
5
- import { composeDbUri } from '../lib/compose-uri';
3
+ import { testDbConnection } from '../lib/db-test.js';
4
+ import { writeEnvLocal } from '../lib/env-writer.js';
5
+ import { composeDbUri } from '../lib/compose-uri.js';
6
6
  /**
7
7
  * Creates handlers for the reconfiguration API.
8
8
  *
@@ -2,7 +2,7 @@
2
2
  // Author: Dr Hamid MADANI drmdh@msn.com
3
3
  //
4
4
  // Copy to: src/app/api/setup/test-db/route.ts
5
- import { testDbConnection } from '../lib/db-test';
5
+ import { testDbConnection } from '../lib/db-test.js';
6
6
  /**
7
7
  * Creates a POST handler for testing DB connections.
8
8
  */
@@ -376,7 +376,7 @@ export function createUploadJarHandlers() {
376
376
  if (!dialect) {
377
377
  return Response.json({ ok: false, error: 'dialect requis' }, { status: 400 });
378
378
  }
379
- const { composeDbUri } = await import('../lib/compose-uri');
379
+ const { composeDbUri } = await import('../lib/compose-uri.js');
380
380
  const uri = composeDbUri(dialect, {
381
381
  host: host || 'localhost',
382
382
  port: port || 0,
@@ -25,8 +25,10 @@ export function createSetupJsonHandler(needsSetup) {
25
25
  exists: true,
26
26
  config: {
27
27
  appName: json.app?.name,
28
+ dbNamePrefix: json.app?.dbNamePrefix,
28
29
  hasRbac: !!(json.rbac?.roles?.length || json.rbac?.permissions?.length),
29
30
  seedCount: json.seeds?.length ?? 0,
31
+ modules: json.modules ?? [],
30
32
  },
31
33
  });
32
34
  }
@@ -13,10 +13,34 @@ export interface SetupWizardProps {
13
13
  wireModule?: string;
14
14
  /** Seed endpoint — runs module seeds from the runtime registry */
15
15
  seed?: string;
16
+ /** Preflight checks endpoint */
17
+ preflight?: string;
18
+ /** Create database endpoint */
19
+ createDb?: string;
16
20
  };
17
21
  /** Default database name prefix (e.g. 'secuaccessdb') */
18
22
  dbNamePrefix?: string;
19
23
  /** Whether to persist wizard state in sessionStorage (default: true) */
20
24
  persistState?: boolean;
25
+ /**
26
+ * Show the modules selection step even without a detectModules endpoint.
27
+ * When true and no detectModules endpoint, uses the built-in module list.
28
+ * Default: true
29
+ */
30
+ showModules?: boolean;
31
+ /**
32
+ * Full module definitions from setup.json (section "modules").
33
+ * When provided, the wizard shows these modules directly
34
+ * instead of calling the detectModules endpoint.
35
+ */
36
+ declaredModules?: {
37
+ key: string;
38
+ packageName?: string;
39
+ label?: string;
40
+ description?: string;
41
+ icon?: string;
42
+ required?: boolean;
43
+ dependsOn?: string[];
44
+ }[];
21
45
  }
22
- export default function SetupWizard({ t: tProp, onComplete, endpoints, dbNamePrefix, persistState, }: SetupWizardProps): import("react/jsx-runtime").JSX.Element;
46
+ export default function SetupWizard({ t: tProp, onComplete, endpoints, dbNamePrefix, persistState, showModules, declaredModules, }: SetupWizardProps): import("react/jsx-runtime").JSX.Element;
@@ -344,10 +344,10 @@ function JarUploadInline({ dialect, jarEndpoint, dbConfig }) {
344
344
  return (_jsxs("div", { style: S.jarBox, children: [_jsxs("div", { style: S.flex(8), children: [_jsx("span", { style: S.jarTitle, children: "Driver JDBC" }), jarStatus?.hasJar ? (_jsx("span", { style: { ...S.badge('installed'), marginLeft: 0 }, children: jarStatus.jarFile })) : (_jsx("span", { style: { fontSize: 12, color: '#6b7280' }, children: "Aucun JAR installe" }))] }), _jsxs("div", { style: { marginTop: 8 }, children: [_jsxs("label", { style: { ...S.btn('primary', uploading), cursor: uploading ? 'wait' : 'pointer', fontSize: 12, padding: '6px 12px' }, children: [uploading ? 'Upload...' : 'Uploader un .jar', _jsx("input", { type: "file", accept: ".jar", onChange: handleUpload, disabled: uploading, style: { display: 'none' } })] }), _jsx("span", { style: { fontSize: 11, color: '#9ca3af', marginLeft: 8 }, children: "Ex: hsqldb*.jar, ojdbc*.jar" })] }), isHsqldb && jarStatus?.hasJar && (_jsxs("div", { style: { marginTop: 10, padding: '10px 12px', backgroundColor: '#fef9c3', borderRadius: 6, border: '1px solid #fde68a' }, children: [_jsxs("div", { style: { fontSize: 12, fontWeight: 600, color: '#92400e', marginBottom: 6 }, children: ["Serveur HSQLDB", _jsxs("span", { style: { fontWeight: 400, color: '#6b7280', marginLeft: 4 }, children: ["(port SGBD : ", dbConfig.port || 9001, ")"] }), serverInfo?.running && (_jsxs("span", { style: { fontWeight: 400, color: '#059669', marginLeft: 8 }, children: ["En marche sur port ", serverInfo.port, serverInfo.pid > 0 ? ` — PID ${serverInfo.pid}` : ''] })), !serverInfo?.running && (_jsx("span", { style: { fontWeight: 400, color: '#dc2626', marginLeft: 8 }, children: "Arrete" }))] }), _jsxs("div", { style: { display: 'flex', gap: 8 }, children: [_jsx("button", { style: btnSmall('#059669', loading === 'start-server' || serverInfo?.running), onClick: () => patchAction({ action: 'start-server', dialect, name: dbConfig.name, host: dbConfig.host, port: dbConfig.port || 9001 }, 'start-server'), disabled: loading === 'start-server' || !!serverInfo?.running, children: loading === 'start-server' ? 'Demarrage...' : `Demarrer le serveur (port ${dbConfig.port || 9001})` }), _jsx("button", { style: btnSmall('#dc2626', loading === 'stop-server' || !serverInfo?.running), onClick: () => patchAction({ action: 'stop-server', port: serverInfo?.port || dbConfig.port || 9001 }, 'stop-server'), disabled: loading === 'stop-server' || !serverInfo?.running, children: loading === 'stop-server' ? 'Arret...' : 'Arreter le serveur' })] })] })), jarStatus?.hasJar && (_jsxs("div", { style: { marginTop: 10, padding: '10px 12px', backgroundColor: '#f0fdf4', borderRadius: 6, border: '1px solid #bbf7d0' }, children: [_jsxs("div", { style: { fontSize: 12, fontWeight: 600, color: '#166534', marginBottom: 6 }, children: ["Bridge JDBC", _jsxs("span", { style: { fontWeight: 400, color: '#6b7280', marginLeft: 4 }, children: ["(port bridge : ", bridgePort || 8765, ")"] }), bridgePort && (_jsxs("span", { style: { fontWeight: 400, color: '#059669', marginLeft: 8 }, children: ["Actif sur port ", bridgePort] })), !bridgePort && bridges.length === 0 && (_jsx("span", { style: { fontWeight: 400, color: '#dc2626', marginLeft: 8 }, children: "Inactif" }))] }), _jsx("div", { style: { display: 'flex', gap: 8, marginBottom: bridges.length > 0 ? 8 : 0 }, children: _jsx("button", { style: btnSmall('#059669', loading === 'start-bridge' || bridges.length > 0), onClick: () => patchAction({ action: 'start', dialect, ...dbConfig }, 'start-bridge'), disabled: loading === 'start-bridge' || bridges.length > 0, children: loading === 'start-bridge' ? 'Lancement...' : `Lancer le bridge (SGBD ${dbConfig.host || 'localhost'}:${dbConfig.port || 9001})` }) }), bridges.map(b => (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, padding: '4px 0', fontSize: 12 }, children: [_jsx("span", { style: { width: 8, height: 8, borderRadius: '50%', backgroundColor: b.status === 'active' ? '#22c55e' : '#f59e0b', flexShrink: 0 } }), _jsxs("span", { style: { color: '#374151', fontFamily: 'monospace', fontSize: 11 }, children: ["Bridge port :", b.port, " ", b.pid > 0 ? `(PID ${b.pid})` : '', " ", b.jdbcUrl ? `— ${b.jdbcUrl}` : ''] }), _jsx("button", { style: { ...btnSmall('#dc2626', loading === `kill-${b.port}`), fontSize: 11, padding: '2px 8px', marginLeft: 'auto' }, onClick: () => patchAction({ action: 'stop', port: b.port, pid: b.pid }, `kill-${b.port}`), disabled: loading === `kill-${b.port}`, children: loading === `kill-${b.port}` ? '...' : 'Kill' })] }, b.port)))] })), message && (_jsx("p", { style: { fontSize: 12, color: message.ok ? '#059669' : '#dc2626', marginTop: 8 }, children: message.text }))] }));
345
345
  }
346
346
  // ── Main Component ───────────────────────────────────────────
347
- export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNamePrefix = 'mydb', persistState = true, }) {
347
+ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNamePrefix = 'mydb', persistState = true, showModules = true, declaredModules, }) {
348
348
  const t = tProp || ((k) => k);
349
- // Modules step is only shown if detectModules endpoint is explicitly provided
350
- const hasModulesStep = !!endpoints.detectModules;
349
+ // Modules step is shown unless explicitly disabled
350
+ const hasModulesStep = showModules !== false;
351
351
  const STEPS = hasModulesStep
352
352
  ? ALL_STEPS
353
353
  : ALL_STEPS.filter(s => s !== 'modules');
@@ -359,6 +359,8 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
359
359
  uploadJar: endpoints.uploadJar || '/api/setup/upload-jar',
360
360
  wireModule: endpoints.wireModule || '',
361
361
  seed: endpoints.seed || '',
362
+ preflight: endpoints.preflight || '/api/setup/preflight',
363
+ createDb: endpoints.createDb || '/api/setup/create-db',
362
364
  };
363
365
  // --- State ---
364
366
  const [currentStep, setCurrentStep] = useState(0);
@@ -380,6 +382,11 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
380
382
  const [wireLoading, setWireLoading] = useState(false);
381
383
  const [wireBusy, setWireBusy] = useState(null);
382
384
  const [wireMessage, setWireMessage] = useState(null);
385
+ const [preflightChecks, setPreflightChecks] = useState([]);
386
+ const [preflightLoading, setPreflightLoading] = useState(false);
387
+ // Create DB
388
+ const [creatingDb, setCreatingDb] = useState(false);
389
+ const [createDbResult, setCreateDbResult] = useState(null);
383
390
  const step = STEPS[currentStep];
384
391
  // --- Persist / Restore ---
385
392
  useEffect(() => {
@@ -416,12 +423,30 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
416
423
  }
417
424
  catch { }
418
425
  }, [hydrated, persistState, currentStep, dialect, dbConfig, adminConfig, seedOptions, selectedModules]);
419
- // --- Detect modules (only if endpoint is provided) ---
426
+ // --- Detect modules ---
420
427
  useEffect(() => {
428
+ // From setup.json (passed as prop)
429
+ if (declaredModules && declaredModules.length > 0) {
430
+ const mods = declaredModules.map(m => ({
431
+ key: m.key,
432
+ label: m.label ?? m.key,
433
+ description: m.description ?? '',
434
+ icon: m.icon ?? '📦',
435
+ required: m.required,
436
+ default: true,
437
+ dependsOn: m.dependsOn,
438
+ }));
439
+ setAvailableModules(mods);
440
+ setSelectedModules(declaredModules.map(m => m.key));
441
+ setModulesDetected(true);
442
+ return;
443
+ }
444
+ // No modules and no endpoint → skip
421
445
  if (!ep.detectModules) {
422
446
  setModulesDetected(true);
423
447
  return;
424
448
  }
449
+ // From API endpoint
425
450
  fetch(ep.detectModules)
426
451
  .then(r => r.json())
427
452
  .then((data) => {
@@ -531,6 +556,47 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
531
556
  setDbConfig({ ...defaults, name: defaults.name === 'mydb' ? dbNamePrefix : defaults.name === 'mydb_prod' ? `${dbNamePrefix}_prod` : defaults.name.replace('mydb', dbNamePrefix) });
532
557
  setDbTestResult(null);
533
558
  }
559
+ // --- Preflight checks ---
560
+ async function runPreflight() {
561
+ setPreflightLoading(true);
562
+ try {
563
+ const res = await fetch(ep.preflight);
564
+ const data = await res.json();
565
+ setPreflightChecks(data.checks || []);
566
+ }
567
+ catch {
568
+ setPreflightChecks([{ key: 'error', label: 'Preflight', status: 'fail', detail: 'Erreur réseau' }]);
569
+ }
570
+ setPreflightLoading(false);
571
+ }
572
+ // Auto-run preflight on welcome step
573
+ useEffect(() => {
574
+ if (step === 'welcome' && preflightChecks.length === 0) {
575
+ runPreflight();
576
+ }
577
+ }, [step]); // eslint-disable-line react-hooks/exhaustive-deps
578
+ // --- Create Database ---
579
+ async function createDatabase() {
580
+ setCreatingDb(true);
581
+ setCreateDbResult(null);
582
+ try {
583
+ const res = await fetch(ep.createDb, {
584
+ method: 'POST',
585
+ headers: { 'Content-Type': 'application/json' },
586
+ body: JSON.stringify({ dialect, ...dbConfig }),
587
+ });
588
+ const data = await res.json();
589
+ setCreateDbResult(data);
590
+ if (data.ok) {
591
+ // Auto-run test after creation
592
+ setDbTestResult(null);
593
+ }
594
+ }
595
+ catch (err) {
596
+ setCreateDbResult({ ok: false, error: err instanceof Error ? err.message : 'Erreur réseau' });
597
+ }
598
+ setCreatingDb(false);
599
+ }
534
600
  // --- Test DB ---
535
601
  async function testDb() {
536
602
  setDbTesting(true);
@@ -634,7 +700,12 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
634
700
  onComplete?.();
635
701
  }
636
702
  // ── Render ─────────────────────────────────────────────────
637
- return (_jsx("div", { style: S.wrapper, children: _jsxs("div", { style: S.container, children: [_jsx("div", { style: S.stepperRow, children: STEPS.map((s, i) => (_jsxs("div", { style: S.flex(8), children: [_jsx("div", { style: S.stepCircle(i < currentStep ? 'done' : i === currentStep ? 'current' : 'future'), children: i < currentStep ? '✓' : i + 1 }), _jsx("span", { style: S.stepLabel(i === currentStep), children: t(`setup.steps.${s}`) }), i < STEPS.length - 1 && _jsx("div", { style: S.stepLine(i < currentStep) })] }, s))) }), _jsxs("div", { style: S.card, children: [step === 'welcome' && (_jsxs("div", { style: S.center, children: [_jsx("div", { style: { width: 80, height: 80, borderRadius: '50%', backgroundColor: '#e0f2fe', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 24px', fontSize: 40 }, children: "\uD83D\uDEE1\uFE0F" }), _jsx("h2", { style: { fontSize: 24, fontWeight: 700, color: '#111827', marginBottom: 8 }, children: t('setup.welcome.title') }), _jsx("p", { style: { color: '#6b7280', marginBottom: 24 }, children: t('setup.welcome.description') }), _jsxs("button", { style: S.btn('lg'), onClick: goNext, children: [t('setup.welcome.start'), " \u2192"] })] })), step === 'modules' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\uD83D\uDCE6" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.modules.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.modules.description') })] })] }), _jsx("div", { style: S.grid2, children: availableModules.map(mod => {
703
+ return (_jsx("div", { style: S.wrapper, children: _jsxs("div", { style: S.container, children: [_jsx("div", { style: S.stepperRow, children: STEPS.map((s, i) => (_jsxs("div", { style: S.flex(8), children: [_jsx("div", { style: S.stepCircle(i < currentStep ? 'done' : i === currentStep ? 'current' : 'future'), children: i < currentStep ? '✓' : i + 1 }), _jsx("span", { style: S.stepLabel(i === currentStep), children: t(`setup.steps.${s}`) }), i < STEPS.length - 1 && _jsx("div", { style: S.stepLine(i < currentStep) })] }, s))) }), _jsxs("div", { style: S.card, children: [step === 'welcome' && (_jsxs("div", { children: [_jsxs("div", { style: S.center, children: [_jsx("div", { style: { width: 80, height: 80, borderRadius: '50%', backgroundColor: '#e0f2fe', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 24px', fontSize: 40 }, children: "\uD83D\uDEE1\uFE0F" }), _jsx("h2", { style: { fontSize: 24, fontWeight: 700, color: '#111827', marginBottom: 8 }, children: t('setup.welcome.title') }), _jsx("p", { style: { color: '#6b7280', marginBottom: 16 }, children: t('setup.welcome.description') })] }), _jsxs("div", { style: { margin: '16px 0 24px', padding: 16, backgroundColor: '#f9fafb', border: '1px solid #e5e7eb', borderRadius: 10 }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }, children: [_jsxs("div", { style: { fontSize: 14, fontWeight: 600, color: '#374151' }, children: ["\uD83D\uDD0D ", t('setup.welcome.checks')] }), _jsxs("button", { style: { ...S.btn('outline'), fontSize: 12, padding: '4px 12px' }, onClick: runPreflight, disabled: preflightLoading, children: [preflightLoading ? '⏳' : '🔄', " ", t('setup.welcome.recheck')] })] }), preflightLoading && preflightChecks.length === 0 ? (_jsxs("div", { style: { textAlign: 'center', padding: 16, color: '#6b7280', fontSize: 13 }, children: ["\u23F3 ", t('setup.welcome.checking')] })) : (_jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: preflightChecks.map(check => (_jsxs("div", { style: {
704
+ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 12px',
705
+ backgroundColor: check.status === 'ok' ? '#f0fdf4' : check.status === 'warn' ? '#fffbeb' : '#fef2f2',
706
+ border: `1px solid ${check.status === 'ok' ? '#bbf7d0' : check.status === 'warn' ? '#fde68a' : '#fecaca'}`,
707
+ borderRadius: 6, fontSize: 13,
708
+ }, children: [_jsx("span", { style: { fontSize: 16, flexShrink: 0 }, children: check.status === 'ok' ? '✅' : check.status === 'warn' ? '⚠️' : '❌' }), _jsxs("div", { style: { flex: 1 }, children: [_jsx("span", { style: { fontWeight: 600, color: '#111827' }, children: check.label }), _jsx("span", { style: { color: '#6b7280', marginLeft: 8 }, children: check.detail })] })] }, check.key))) }))] }), _jsx("div", { style: S.center, children: _jsxs("button", { style: S.btn('lg'), onClick: goNext, children: [t('setup.welcome.start'), " \u2192"] }) })] })), step === 'modules' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\uD83D\uDCE6" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.modules.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.modules.description') })] })] }), _jsx("div", { style: S.grid2, children: availableModules.map(mod => {
638
709
  const isSelected = selectedModules.includes(mod.key);
639
710
  const isDetected = detectedModules.includes(mod.key);
640
711
  return (_jsxs("div", { style: S.moduleCard(isSelected, !!mod.required), onClick: () => toggleModule(mod.key), children: [_jsxs("div", { style: S.moduleHeader, children: [_jsxs("div", { style: S.moduleLeft, children: [_jsx("span", { style: { fontSize: 20 }, children: mod.icon }), _jsx("span", { style: S.moduleName, children: mod.label })] }), _jsxs("div", { style: S.moduleBadges, children: [mod.discovered && _jsx("span", { style: S.badge('new'), children: "Nouveau" }), isDetected && _jsx("span", { style: S.badge('installed'), children: t('setup.modules.installed') }), mod.required && _jsx("span", { style: S.badge('required'), children: t('setup.modules.required') }), _jsx("input", { type: "checkbox", checked: isSelected, disabled: mod.required, readOnly: true, style: S.checkbox })] })] }), _jsx("div", { style: S.moduleDesc, children: mod.description }), mod.dependsOn?.length ? (_jsxs("div", { style: { fontSize: 11, color: '#9ca3af', marginTop: 4 }, children: ["Depend de : ", mod.dependsOn.join(', ')] })) : null] }, mod.key));
@@ -644,9 +715,11 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
644
715
  color: mod.type === 'business' ? '#1e40af' : '#6b21a8',
645
716
  }, children: mod.type })] }), _jsxs("div", { style: { fontSize: 12, color: '#6b7280', fontFamily: 'monospace' }, children: ["v", mod.version] }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: 4 }, children: [_jsx("span", { style: { fontSize: 12, fontWeight: 600, color: mod.installed ? '#059669' : '#6b7280' }, children: mod.installed ? 'ON' : 'OFF' }), _jsx("button", { style: S.toggleBtn(mod.installed, wireBusy === mod.name), onClick: (e) => { e.stopPropagation(); handleWireToggle(mod); }, disabled: wireBusy !== null, children: wireBusy === mod.name
646
717
  ? (mod.installed ? 'Decablage...' : 'Cablage...')
647
- : (mod.installed ? 'Desinstaller' : 'Installer') })] })] }, mod.name))) }))] }), _jsxs("div", { style: S.navRow, children: [_jsxs("button", { style: S.btn('outline'), onClick: goBack, children: ["\u2190 ", t('setup.back')] }), _jsxs("button", { style: S.btn('primary', !canGoNext()), onClick: goNext, disabled: !canGoNext(), children: [t('setup.next'), " \u2192"] })] })] })), step === 'dialect' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\uD83D\uDCBE" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.dialect.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.dialect.description') })] })] }), _jsx("div", { style: S.grid3, children: DIALECT_INFO.map(d => (_jsxs("div", { style: S.dialectCard(dialect === d.key, !!d.premium), onClick: () => !d.premium && selectDialect(d.key), title: d.premium ? `${d.name} — disponible en version Premium` : d.name, children: [_jsx("div", { style: S.dialectIcon, children: d.icon }), _jsx("div", { style: S.dialectName, children: d.name }), d.premium && (_jsx("span", { style: { ...S.badge('premium'), marginLeft: 0, marginTop: 4 }, children: "Premium" }))] }, d.key))) }), _jsxs("div", { style: S.navRow, children: [_jsxs("button", { style: S.btn('outline'), onClick: goBack, children: ["\u2190 ", t('setup.back')] }), _jsxs("button", { style: S.btn('primary'), onClick: goNext, children: [t('setup.next'), " \u2192"] })] })] })), step === 'database' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\uD83D\uDDC4\uFE0F" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.database.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.database.description') })] })] }), dialect === 'sqlite' ? (_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.name') }), _jsx("input", { style: S.input, value: dbConfig.name, onChange: e => { setDbConfig({ ...dbConfig, name: e.target.value }); setDbTestResult(null); }, placeholder: dbNamePrefix }), _jsxs("p", { style: { fontSize: 11, color: '#9ca3af', marginTop: 4 }, children: [t('setup.database.sqliteInfo'), " ", _jsxs("code", { style: { fontFamily: 'monospace', backgroundColor: '#f3f4f6', padding: '1px 4px', borderRadius: 3 }, children: ["./data/", dbConfig.name, ".db"] })] })] })) : dialect === 'spanner' ? (_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.spannerPath') }), _jsx("input", { style: S.input, value: dbConfig.name, onChange: e => { setDbConfig({ ...dbConfig, name: e.target.value }); setDbTestResult(null); }, placeholder: "my-project/my-instance/mydb" }), _jsx("p", { style: { fontSize: 11, color: '#9ca3af', marginTop: 4 }, children: t('setup.database.spannerInfo') })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.host') }), _jsx("input", { style: S.input, value: dbConfig.host, onChange: e => { setDbConfig({ ...dbConfig, host: e.target.value }); setDbTestResult(null); } })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.port') }), _jsx("input", { style: S.input, type: "number", value: dbConfig.port, onChange: e => { setDbConfig({ ...dbConfig, port: parseInt(e.target.value) || 0 }); setDbTestResult(null); } })] })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.name') }), _jsx("input", { style: S.input, value: dbConfig.name, onChange: e => { setDbConfig({ ...dbConfig, name: e.target.value }); setDbTestResult(null); } })] }), _jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.user') }), _jsx("input", { style: S.input, value: dbConfig.user, onChange: e => { setDbConfig({ ...dbConfig, user: e.target.value }); setDbTestResult(null); }, placeholder: dialect === 'hsqldb' ? 'SA' : '' })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.password') }), _jsx("input", { style: S.input, type: "password", value: dbConfig.password, onChange: e => { setDbConfig({ ...dbConfig, password: e.target.value }); setDbTestResult(null); } })] })] })] })), dialect === 'hsqldb' && (_jsxs("div", { style: { ...S.alert('warning'), marginTop: 12, fontSize: 12 }, children: [_jsx("strong", { children: "Prerequis :" }), " Le serveur HSQLDB doit etre lance avant le bridge.", _jsx("br", {}), _jsxs("code", { style: { fontFamily: 'monospace', backgroundColor: '#fef3c7', padding: '2px 6px', borderRadius: 3, display: 'inline-block', marginTop: 4, fontSize: 11 }, children: ["java -cp hsqldb*.jar org.hsqldb.server.Server --database.0 file:./data/", dbConfig.name, " --dbname.0 ", dbConfig.name] })] })), dialect !== 'mongodb' && dialect !== 'sqlite' && (_jsxs("div", { style: { ...S.alert('warning'), marginTop: 12 }, children: [t('setup.database.driverHint'), ' ', _jsx("code", { style: { fontFamily: 'monospace', backgroundColor: '#fef3c7', padding: '1px 4px', borderRadius: 3 }, children: DRIVER_HINTS[dialect] })] })), JDBC_DIALECTS.includes(dialect) && (_jsx(JarUploadInline, { dialect: dialect, jarEndpoint: ep.uploadJar, dbConfig: dbConfig })), dialect !== 'sqlite' && dialect !== 'spanner' && (_jsxs("div", { style: { ...S.checkRow, marginTop: 12, padding: '10px 14px', backgroundColor: '#fffbeb', border: '1px solid #fde68a', borderRadius: 8 }, children: [_jsx("input", { type: "checkbox", style: S.checkbox, checked: createIfNotExists, onChange: e => setCreateIfNotExists(e.target.checked) }), _jsxs("div", { children: [_jsx("div", { style: { fontSize: 13, fontWeight: 500 }, children: t('setup.database.createIfNotExists') }), _jsx("div", { style: { fontSize: 12, color: '#92400e' }, children: t('setup.database.createIfNotExistsDesc') })] })] })), dialect !== 'sqlite' && dialect !== 'spanner' && (_jsxs("div", { style: { ...S.flex(16), marginTop: 16 }, children: [_jsxs("button", { style: S.btn('outline', dbTesting), onClick: testDb, disabled: dbTesting, children: [dbTesting ? '⏳ ' : '🗄️ ', dbTesting ? t('setup.database.testing') : t('setup.database.test')] }), dbTestResult && (_jsx("span", { style: { fontSize: 13, color: dbTestResult.ok ? '#059669' : '#dc2626' }, children: dbTestResult.ok
648
- ? `✅ ${t('setup.database.success')}${dbTestResult.dbVersion ? ` (v${dbTestResult.dbVersion})` : ''}`
649
- : `❌ ${t('setup.database.error')}: ${dbTestResult.error}` }))] })), _jsxs("div", { style: S.navRow, children: [_jsxs("button", { style: S.btn('outline'), onClick: goBack, children: ["\u2190 ", t('setup.back')] }), _jsxs("button", { style: S.btn('primary', !canGoNext()), onClick: goNext, disabled: !canGoNext(), children: [t('setup.next'), " \u2192"] })] })] })), step === 'admin' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\uD83D\uDC64" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.admin.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.admin.description') })] })] }), _jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.firstName') }), _jsx("input", { style: S.input, value: adminConfig.firstName, onChange: e => setAdminConfig({ ...adminConfig, firstName: e.target.value }) })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.lastName') }), _jsx("input", { style: S.input, value: adminConfig.lastName, onChange: e => setAdminConfig({ ...adminConfig, lastName: e.target.value }) })] })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.email') }), _jsx("input", { style: S.input, type: "email", value: adminConfig.email, onChange: e => setAdminConfig({ ...adminConfig, email: e.target.value }), placeholder: "admin@example.com" })] }), _jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.password') }), _jsx("input", { style: S.input, type: "password", value: adminConfig.password, onChange: e => setAdminConfig({ ...adminConfig, password: e.target.value }) })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.confirmPassword') }), _jsx("input", { style: S.input, type: "password", value: adminConfig.confirmPassword, onChange: e => setAdminConfig({ ...adminConfig, confirmPassword: e.target.value }) })] })] }), adminConfig.password && adminConfig.confirmPassword && adminConfig.password !== adminConfig.confirmPassword && (_jsx("p", { style: { fontSize: 13, color: '#dc2626' }, children: t('setup.admin.passwordMismatch') })), _jsxs("div", { style: S.navRow, children: [_jsxs("button", { style: S.btn('outline'), onClick: goBack, children: ["\u2190 ", t('setup.back')] }), _jsxs("button", { style: S.btn('primary', !canGoNext()), onClick: goNext, disabled: !canGoNext(), children: [t('setup.next'), " \u2192"] })] })] })), step === 'summary' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\u2699\uFE0F" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.summary.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.summary.description') })] })] }), _jsxs("div", { style: S.summaryCard, children: [_jsx("div", { style: S.summaryTitle, children: t('setup.summary.dbConfig') }), _jsxs("div", { style: S.summaryText, children: [_jsx("span", { style: { fontFamily: 'monospace' }, children: dbSummaryLabel() }), dialect !== 'sqlite' && dbConfig.user && _jsxs("span", { style: { display: 'block', marginTop: 4 }, children: ["Utilisateur: ", dbConfig.user] })] })] }), _jsxs("div", { style: S.summaryCard, children: [_jsx("div", { style: S.summaryTitle, children: t('setup.summary.adminConfig') }), _jsxs("div", { style: S.summaryText, children: [_jsxs("div", { children: [adminConfig.firstName, " ", adminConfig.lastName] }), _jsx("div", { children: adminConfig.email })] })] }), _jsxs("div", { style: S.summaryCard, children: [_jsx("div", { style: S.summaryTitle, children: t('setup.modules.title') }), _jsx("div", { style: S.flexWrap, children: selectedModules.map(key => {
718
+ : (mod.installed ? 'Desinstaller' : 'Installer') })] })] }, mod.name))) }))] }), _jsxs("div", { style: S.navRow, children: [_jsxs("button", { style: S.btn('outline'), onClick: goBack, children: ["\u2190 ", t('setup.back')] }), _jsxs("button", { style: S.btn('primary', !canGoNext()), onClick: goNext, disabled: !canGoNext(), children: [t('setup.next'), " \u2192"] })] })] })), step === 'dialect' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\uD83D\uDCBE" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.dialect.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.dialect.description') })] })] }), _jsx("div", { style: S.grid3, children: DIALECT_INFO.map(d => (_jsxs("div", { style: S.dialectCard(dialect === d.key, !!d.premium), onClick: () => !d.premium && selectDialect(d.key), title: d.premium ? `${d.name} — disponible en version Premium` : d.name, children: [_jsx("div", { style: S.dialectIcon, children: d.icon }), _jsx("div", { style: S.dialectName, children: d.name }), d.premium && (_jsx("span", { style: { ...S.badge('premium'), marginLeft: 0, marginTop: 4 }, children: "Premium" }))] }, d.key))) }), _jsxs("div", { style: S.navRow, children: [_jsxs("button", { style: S.btn('outline'), onClick: goBack, children: ["\u2190 ", t('setup.back')] }), _jsxs("button", { style: S.btn('primary'), onClick: goNext, children: [t('setup.next'), " \u2192"] })] })] })), step === 'database' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\uD83D\uDDC4\uFE0F" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.database.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.database.description') })] })] }), dialect === 'sqlite' ? (_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.name') }), _jsx("input", { style: S.input, value: dbConfig.name, onChange: e => { setDbConfig({ ...dbConfig, name: e.target.value }); setDbTestResult(null); }, placeholder: dbNamePrefix }), _jsxs("p", { style: { fontSize: 11, color: '#9ca3af', marginTop: 4 }, children: [t('setup.database.sqliteInfo'), " ", _jsxs("code", { style: { fontFamily: 'monospace', backgroundColor: '#f3f4f6', padding: '1px 4px', borderRadius: 3 }, children: ["./data/", dbConfig.name, ".db"] })] })] })) : dialect === 'spanner' ? (_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.spannerPath') }), _jsx("input", { style: S.input, value: dbConfig.name, onChange: e => { setDbConfig({ ...dbConfig, name: e.target.value }); setDbTestResult(null); }, placeholder: "my-project/my-instance/mydb" }), _jsx("p", { style: { fontSize: 11, color: '#9ca3af', marginTop: 4 }, children: t('setup.database.spannerInfo') })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.host') }), _jsx("input", { style: S.input, value: dbConfig.host, onChange: e => { setDbConfig({ ...dbConfig, host: e.target.value }); setDbTestResult(null); } })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.port') }), _jsx("input", { style: S.input, type: "number", value: dbConfig.port, onChange: e => { setDbConfig({ ...dbConfig, port: parseInt(e.target.value) || 0 }); setDbTestResult(null); } })] })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.name') }), _jsx("input", { style: S.input, value: dbConfig.name, onChange: e => { setDbConfig({ ...dbConfig, name: e.target.value }); setDbTestResult(null); } })] }), _jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.user') }), _jsx("input", { style: S.input, value: dbConfig.user, onChange: e => { setDbConfig({ ...dbConfig, user: e.target.value }); setDbTestResult(null); }, placeholder: dialect === 'hsqldb' ? 'SA' : '' })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.password') }), _jsx("input", { style: S.input, type: "password", value: dbConfig.password, onChange: e => { setDbConfig({ ...dbConfig, password: e.target.value }); setDbTestResult(null); } })] })] })] })), dialect === 'hsqldb' && (_jsxs("div", { style: { ...S.alert('warning'), marginTop: 12, fontSize: 12 }, children: [_jsx("strong", { children: "Prerequis :" }), " Le serveur HSQLDB doit etre lance avant le bridge.", _jsx("br", {}), _jsxs("code", { style: { fontFamily: 'monospace', backgroundColor: '#fef3c7', padding: '2px 6px', borderRadius: 3, display: 'inline-block', marginTop: 4, fontSize: 11 }, children: ["java -cp hsqldb*.jar org.hsqldb.server.Server --database.0 file:./data/", dbConfig.name, " --dbname.0 ", dbConfig.name] })] })), dialect !== 'mongodb' && dialect !== 'sqlite' && (_jsxs("div", { style: { ...S.alert('warning'), marginTop: 12 }, children: [t('setup.database.driverHint'), ' ', _jsx("code", { style: { fontFamily: 'monospace', backgroundColor: '#fef3c7', padding: '1px 4px', borderRadius: 3 }, children: DRIVER_HINTS[dialect] })] })), JDBC_DIALECTS.includes(dialect) && (_jsx(JarUploadInline, { dialect: dialect, jarEndpoint: ep.uploadJar, dbConfig: dbConfig })), dialect !== 'sqlite' && dialect !== 'spanner' && (_jsxs("div", { style: { marginTop: 12, padding: '12px 14px', backgroundColor: '#fffbeb', border: '1px solid #fde68a', borderRadius: 8 }, children: [_jsxs("div", { style: S.checkRow, children: [_jsx("input", { type: "checkbox", style: S.checkbox, checked: createIfNotExists, onChange: e => setCreateIfNotExists(e.target.checked) }), _jsxs("div", { children: [_jsx("div", { style: { fontSize: 13, fontWeight: 500 }, children: t('setup.database.createIfNotExists') }), _jsx("div", { style: { fontSize: 12, color: '#92400e' }, children: t('setup.database.createIfNotExistsDesc') })] })] }), _jsxs("div", { style: { marginTop: 10, display: 'flex', alignItems: 'center', gap: 12 }, children: [_jsxs("button", { style: { ...S.btn('outline', creatingDb), fontSize: 12, padding: '6px 14px', backgroundColor: '#fef3c7' }, onClick: createDatabase, disabled: creatingDb || !dbConfig.name, children: [creatingDb ? '⏳ ' : '🗃️ ', creatingDb ? t('setup.database.creating') : t('setup.database.createDb')] }), createDbResult && (_jsx("span", { style: { fontSize: 12, color: createDbResult.ok ? '#059669' : '#dc2626' }, children: createDbResult.ok
719
+ ? `✅ ${createDbResult.detail || t('setup.database.createDbSuccess')}`
720
+ : `❌ ${createDbResult.error}` }))] })] })), dialect !== 'sqlite' && dialect !== 'spanner' && (_jsxs("div", { style: { ...S.flex(16), marginTop: 16 }, children: [_jsxs("button", { style: S.btn('outline', dbTesting), onClick: testDb, disabled: dbTesting, children: [dbTesting ? '⏳ ' : '🗄️ ', dbTesting ? t('setup.database.testing') : t('setup.database.test')] }), dbTestResult && (_jsxs("div", { style: { fontSize: 13 }, children: [_jsx("span", { style: { color: dbTestResult.ok ? '#059669' : '#dc2626' }, children: dbTestResult.ok
721
+ ? `✅ ${t('setup.database.success')}${dbTestResult.dbVersion ? ` (v${dbTestResult.dbVersion})` : ''}`
722
+ : `❌ ${t('setup.database.error')}: ${dbTestResult.error}` }), dbTestResult.ok && (_jsxs("div", { style: { fontSize: 11, color: '#6b7280', marginTop: 4, fontFamily: 'monospace', backgroundColor: '#f3f4f6', padding: '4px 8px', borderRadius: 4 }, children: [dialect, "://", dbConfig.user ? dbConfig.user + '@' : '', dbConfig.host, ":", dbConfig.port, "/", dbConfig.name] }))] }))] })), _jsxs("div", { style: S.navRow, children: [_jsxs("button", { style: S.btn('outline'), onClick: goBack, children: ["\u2190 ", t('setup.back')] }), _jsxs("button", { style: S.btn('primary', !canGoNext()), onClick: goNext, disabled: !canGoNext(), children: [t('setup.next'), " \u2192"] })] })] })), step === 'admin' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\uD83D\uDC64" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.admin.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.admin.description') })] })] }), _jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.firstName') }), _jsx("input", { style: S.input, value: adminConfig.firstName, onChange: e => setAdminConfig({ ...adminConfig, firstName: e.target.value }) })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.lastName') }), _jsx("input", { style: S.input, value: adminConfig.lastName, onChange: e => setAdminConfig({ ...adminConfig, lastName: e.target.value }) })] })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.email') }), _jsx("input", { style: S.input, type: "email", value: adminConfig.email, onChange: e => setAdminConfig({ ...adminConfig, email: e.target.value }), placeholder: "admin@example.com" })] }), _jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.password') }), _jsx("input", { style: S.input, type: "password", value: adminConfig.password, onChange: e => setAdminConfig({ ...adminConfig, password: e.target.value }) })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.confirmPassword') }), _jsx("input", { style: S.input, type: "password", value: adminConfig.confirmPassword, onChange: e => setAdminConfig({ ...adminConfig, confirmPassword: e.target.value }) })] })] }), adminConfig.password && adminConfig.confirmPassword && adminConfig.password !== adminConfig.confirmPassword && (_jsx("p", { style: { fontSize: 13, color: '#dc2626' }, children: t('setup.admin.passwordMismatch') })), _jsxs("div", { style: S.navRow, children: [_jsxs("button", { style: S.btn('outline'), onClick: goBack, children: ["\u2190 ", t('setup.back')] }), _jsxs("button", { style: S.btn('primary', !canGoNext()), onClick: goNext, disabled: !canGoNext(), children: [t('setup.next'), " \u2192"] })] })] })), step === 'summary' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\u2699\uFE0F" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.summary.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.summary.description') })] })] }), _jsxs("div", { style: S.summaryCard, children: [_jsx("div", { style: S.summaryTitle, children: t('setup.summary.dbConfig') }), _jsxs("div", { style: S.summaryText, children: [_jsx("span", { style: { fontFamily: 'monospace' }, children: dbSummaryLabel() }), dialect !== 'sqlite' && dbConfig.user && _jsxs("span", { style: { display: 'block', marginTop: 4 }, children: ["Utilisateur: ", dbConfig.user] })] })] }), _jsxs("div", { style: S.summaryCard, children: [_jsx("div", { style: S.summaryTitle, children: t('setup.summary.adminConfig') }), _jsxs("div", { style: S.summaryText, children: [_jsxs("div", { children: [adminConfig.firstName, " ", adminConfig.lastName] }), _jsx("div", { children: adminConfig.email })] })] }), _jsxs("div", { style: S.summaryCard, children: [_jsx("div", { style: S.summaryTitle, children: t('setup.modules.title') }), _jsx("div", { style: S.flexWrap, children: selectedModules.map(key => {
650
723
  const mod = availableModules.find(m => m.key === key);
651
724
  return mod ? (_jsxs("span", { style: {
652
725
  display: 'inline-flex', alignItems: 'center', gap: 4,
@@ -1,3 +1,3 @@
1
- import type { DialectType, DialectInfo } from '../types/index';
1
+ import type { DialectType, DialectInfo } from '../types/index.js';
2
2
  export declare const DIALECT_INFO: Record<DialectType, DialectInfo>;
3
3
  export declare const ALL_DIALECTS: DialectType[];
package/dist/index.d.ts CHANGED
@@ -1,22 +1,24 @@
1
- export { needsSetup, runInstall } from './lib/setup';
2
- export { testDbConnection } from './lib/db-test';
3
- export { composeDbUri } from './lib/compose-uri';
4
- export { writeEnvLocal } from './lib/env-writer';
5
- export { DIALECT_INFO, ALL_DIALECTS } from './data/dialects';
6
- export { MODULES, resolveModuleDependencies } from './data/module-definitions';
7
- export { discoverNpmModules } from './lib/discover-modules';
8
- export { loadSetupJson } from './lib/load-setup-json';
9
- export type { SetupJson, SetupJsonRbac, SetupJsonSeed, SetupJsonCategory, SetupJsonPermission, SetupJsonRole } from './lib/load-setup-json';
10
- export { createTestDbHandler } from './api/test-db.route';
11
- export { createInstallHandler } from './api/install.route';
12
- export { createStatusHandler } from './api/status.route';
13
- export { createDetectModulesHandler } from './api/detect-modules.route';
14
- export { createInstallModulesHandler } from './api/install-modules.route';
15
- export { createReconfigHandlers } from './api/reconfig.route';
16
- export { createUploadJarHandlers } from './api/upload-jar.route';
17
- export { createWireModuleHandler } from './api/wire-module.route';
18
- export { createSetupJsonHandler } from './api/upload-setup-json.route';
19
- export { default as ReconfigPanel } from './components/ReconfigPanel';
20
- export { default as SetupWizard } from './components/SetupWizard';
21
- export { setupMenuContribution } from './lib/menu';
22
- export type { DialectType, DialectInfo, DbConfig, InstallConfig, SeedOptions, SeedDefinition, MostaSetupConfig, ModuleDefinition, } from './types/index';
1
+ export { needsSetup, runInstall } from './lib/setup.js';
2
+ export { testDbConnection } from './lib/db-test.js';
3
+ export { composeDbUri } from './lib/compose-uri.js';
4
+ export { writeEnvLocal } from './lib/env-writer.js';
5
+ export { DIALECT_INFO, ALL_DIALECTS } from './data/dialects.js';
6
+ export { MODULES, resolveModuleDependencies } from './data/module-definitions.js';
7
+ export { discoverNpmModules } from './lib/discover-modules.js';
8
+ export { loadSetupJson } from './lib/load-setup-json.js';
9
+ export type { SetupJson, SetupJsonRbac, SetupJsonSeed, SetupJsonCategory, SetupJsonPermission, SetupJsonRole } from './lib/load-setup-json.js';
10
+ export { createTestDbHandler } from './api/test-db.route.js';
11
+ export { createInstallHandler } from './api/install.route.js';
12
+ export { createStatusHandler } from './api/status.route.js';
13
+ export { createDetectModulesHandler } from './api/detect-modules.route.js';
14
+ export { createInstallModulesHandler } from './api/install-modules.route.js';
15
+ export { createReconfigHandlers } from './api/reconfig.route.js';
16
+ export { createUploadJarHandlers } from './api/upload-jar.route.js';
17
+ export { createWireModuleHandler } from './api/wire-module.route.js';
18
+ export { createSetupJsonHandler } from './api/upload-setup-json.route.js';
19
+ export { createPreflightHandler } from './api/preflight.route.js';
20
+ export type { PreflightCheck } from './api/preflight.route.js';
21
+ export { createCreateDbHandler } from './api/create-db.route.js';
22
+ export { default as ReconfigPanel } from './components/ReconfigPanel.js';
23
+ export { default as SetupWizard } from './components/SetupWizard.js';
24
+ export type { DialectType, DialectInfo, DbConfig, InstallConfig, SeedOptions, SeedDefinition, MostaSetupConfig, ModuleDefinition, } from './types/index.js';
package/dist/index.js CHANGED
@@ -1,28 +1,28 @@
1
1
  // @mosta/setup — Barrel exports
2
2
  // Author: Dr Hamid MADANI drmdh@msn.com
3
3
  // Core
4
- export { needsSetup, runInstall } from './lib/setup';
5
- export { testDbConnection } from './lib/db-test';
6
- export { composeDbUri } from './lib/compose-uri';
7
- export { writeEnvLocal } from './lib/env-writer';
4
+ export { needsSetup, runInstall } from './lib/setup.js';
5
+ export { testDbConnection } from './lib/db-test.js';
6
+ export { composeDbUri } from './lib/compose-uri.js';
7
+ export { writeEnvLocal } from './lib/env-writer.js';
8
8
  // Data
9
- export { DIALECT_INFO, ALL_DIALECTS } from './data/dialects';
10
- export { MODULES, resolveModuleDependencies } from './data/module-definitions';
9
+ export { DIALECT_INFO, ALL_DIALECTS } from './data/dialects.js';
10
+ export { MODULES, resolveModuleDependencies } from './data/module-definitions.js';
11
11
  // Lib
12
- export { discoverNpmModules } from './lib/discover-modules';
13
- export { loadSetupJson } from './lib/load-setup-json';
12
+ export { discoverNpmModules } from './lib/discover-modules.js';
13
+ export { loadSetupJson } from './lib/load-setup-json.js';
14
14
  // API route factories
15
- export { createTestDbHandler } from './api/test-db.route';
16
- export { createInstallHandler } from './api/install.route';
17
- export { createStatusHandler } from './api/status.route';
18
- export { createDetectModulesHandler } from './api/detect-modules.route';
19
- export { createInstallModulesHandler } from './api/install-modules.route';
20
- export { createReconfigHandlers } from './api/reconfig.route';
21
- export { createUploadJarHandlers } from './api/upload-jar.route';
22
- export { createWireModuleHandler } from './api/wire-module.route';
23
- export { createSetupJsonHandler } from './api/upload-setup-json.route';
15
+ export { createTestDbHandler } from './api/test-db.route.js';
16
+ export { createInstallHandler } from './api/install.route.js';
17
+ export { createStatusHandler } from './api/status.route.js';
18
+ export { createDetectModulesHandler } from './api/detect-modules.route.js';
19
+ export { createInstallModulesHandler } from './api/install-modules.route.js';
20
+ export { createReconfigHandlers } from './api/reconfig.route.js';
21
+ export { createUploadJarHandlers } from './api/upload-jar.route.js';
22
+ export { createWireModuleHandler } from './api/wire-module.route.js';
23
+ export { createSetupJsonHandler } from './api/upload-setup-json.route.js';
24
+ export { createPreflightHandler } from './api/preflight.route.js';
25
+ export { createCreateDbHandler } from './api/create-db.route.js';
24
26
  // Components
25
- export { default as ReconfigPanel } from './components/ReconfigPanel';
26
- export { default as SetupWizard } from './components/SetupWizard';
27
- // Menu contribution
28
- export { setupMenuContribution } from './lib/menu';
27
+ export { default as ReconfigPanel } from './components/ReconfigPanel.js';
28
+ export { default as SetupWizard } from './components/SetupWizard.js';
@@ -1,4 +1,4 @@
1
- import type { DialectType, DbConfig } from '../types/index';
1
+ import type { DialectType, DbConfig } from '../types/index.js';
2
2
  /**
3
3
  * Compose a database connection URI from individual fields.
4
4
  */
@@ -1,6 +1,7 @@
1
- import type { DialectType, DbConfig } from '../types/index';
1
+ import type { DialectType, DbConfig } from '../types/index.js';
2
2
  /**
3
3
  * Test a database connection without affecting the global dialect singleton.
4
+ * Delegates entirely to @mostajs/orm testConnection().
4
5
  */
5
6
  export declare function testDbConnection(params: {
6
7
  dialect: DialectType;
@@ -1,78 +1,22 @@
1
- import { composeDbUri } from './compose-uri';
1
+ import { composeDbUri } from './compose-uri.js';
2
2
  /**
3
3
  * Test a database connection without affecting the global dialect singleton.
4
+ * Delegates entirely to @mostajs/orm testConnection().
4
5
  */
5
6
  export async function testDbConnection(params) {
6
7
  const { dialect, createIfNotExists, ...dbConfig } = params;
7
8
  try {
8
- switch (dialect) {
9
- case 'mongodb': {
10
- const uri = composeDbUri('mongodb', dbConfig);
11
- const mongoose = await import('mongoose');
12
- const conn = mongoose.default.createConnection(uri, {
13
- serverSelectionTimeoutMS: 5000,
14
- connectTimeoutMS: 5000,
15
- });
16
- try {
17
- await conn.asPromise();
18
- return { ok: conn.readyState === 1 };
19
- }
20
- finally {
21
- await conn.close().catch(() => { });
22
- }
23
- }
24
- case 'sqlite':
25
- return { ok: true };
26
- default: {
27
- const uri = composeDbUri(dialect, dbConfig);
28
- // For JDBC bridge dialects, try testing via the bridge HTTP endpoint directly.
29
- // This avoids module singleton issues in Next.js where BridgeManager instances
30
- // may differ between API routes.
31
- const JDBC_DIALECTS = ['hsqldb', 'oracle', 'db2', 'hana', 'sybase'];
32
- if (JDBC_DIALECTS.includes(dialect)) {
33
- const bridgePort = parseInt(process.env.MOSTA_BRIDGE_PORT_BASE || '8765');
34
- // Scan ports 8765..8774 for an active bridge
35
- for (let port = bridgePort; port < bridgePort + 10; port++) {
36
- try {
37
- const healthRes = await fetch(`http://localhost:${port}/health`, {
38
- signal: AbortSignal.timeout(1000),
39
- });
40
- if (!healthRes.ok)
41
- continue;
42
- // Bridge found — test a query
43
- // Use dialect-appropriate ping query
44
- const pingQuery = dialect === 'hsqldb'
45
- ? 'SELECT 1 FROM INFORMATION_SCHEMA.SYSTEM_USERS'
46
- : dialect === 'oracle'
47
- ? 'SELECT 1 FROM DUAL'
48
- : 'SELECT 1';
49
- const queryRes = await fetch(`http://localhost:${port}/query`, {
50
- method: 'POST',
51
- headers: { 'Content-Type': 'application/json' },
52
- body: JSON.stringify({ sql: pingQuery, params: [] }),
53
- signal: AbortSignal.timeout(5000),
54
- });
55
- if (queryRes.ok) {
56
- return { ok: true };
57
- }
58
- const text = await queryRes.text();
59
- return { ok: false, error: `Bridge query failed: ${text}` };
60
- }
61
- catch {
62
- continue;
63
- }
64
- }
65
- return { ok: false, error: `Aucun bridge JDBC actif. Lancez le bridge d'abord.` };
66
- }
67
- const { testConnection } = await import('@mostajs/orm');
68
- const result = await testConnection({
69
- dialect,
70
- uri,
71
- schemaStrategy: createIfNotExists ? 'update' : 'none',
72
- });
73
- return result;
74
- }
9
+ // SQLite: always OK (file auto-created)
10
+ if (dialect === 'sqlite') {
11
+ return { ok: true };
75
12
  }
13
+ const uri = composeDbUri(dialect, dbConfig);
14
+ const { testConnection } = await import('@mostajs/orm');
15
+ return await testConnection({
16
+ dialect,
17
+ uri,
18
+ schemaStrategy: createIfNotExists ? 'update' : 'none',
19
+ });
76
20
  }
77
21
  catch (err) {
78
22
  const message = err instanceof Error ? err.message : 'Connexion echouee';
@@ -1,4 +1,4 @@
1
- import { type ModuleDefinition } from '../data/module-definitions';
1
+ import { type ModuleDefinition } from '../data/module-definitions.js';
2
2
  /**
3
3
  * Discover @mostajs packages from npm registry and merge with static list.
4
4
  *
@@ -4,7 +4,7 @@ import { exec } from 'child_process';
4
4
  import { promisify } from 'util';
5
5
  import fs from 'fs';
6
6
  import path from 'path';
7
- import { MODULES } from '../data/module-definitions';
7
+ import { MODULES } from '../data/module-definitions.js';
8
8
  const execAsync = promisify(exec);
9
9
  /**
10
10
  * Discover @mostajs packages from npm registry and merge with static list.
@@ -1,4 +1,4 @@
1
- import type { DialectType } from '../types/index';
1
+ import type { DialectType } from '../types/index.js';
2
2
  export interface EnvWriterOptions {
3
3
  dialect: DialectType;
4
4
  uri: string;
@@ -1,4 +1,4 @@
1
- import type { MostaSetupConfig } from '../types/index';
1
+ import type { MostaSetupConfig } from '../types/index.js';
2
2
  export interface SetupJsonCategory {
3
3
  name: string;
4
4
  label: string;
@@ -45,9 +45,19 @@ export interface SetupJson {
45
45
  dbNamePrefix?: string;
46
46
  };
47
47
  env?: Record<string, string>;
48
+ modules?: SetupJsonModule[];
48
49
  rbac?: SetupJsonRbac;
49
50
  seeds?: SetupJsonSeed[];
50
51
  }
52
+ export interface SetupJsonModule {
53
+ key: string;
54
+ packageName: string;
55
+ label?: string;
56
+ description?: string;
57
+ icon?: string;
58
+ required?: boolean;
59
+ dependsOn?: string[];
60
+ }
51
61
  /**
52
62
  * Load a setup.json and return a MostaSetupConfig.
53
63
  *
@@ -58,10 +58,15 @@ function validate(json) {
58
58
  }
59
59
  }
60
60
  function buildConfig(json, repoFactory) {
61
+ // Derive MOSTAJS_MODULES from modules[] section
62
+ const extraEnvVars = { ...(json.env ?? {}) };
63
+ if (json.modules?.length) {
64
+ extraEnvVars.MOSTAJS_MODULES = json.modules.map(m => m.key).join(',');
65
+ }
61
66
  const config = {
62
67
  appName: json.app.name,
63
68
  defaultPort: json.app.port,
64
- extraEnvVars: json.env ? { ...json.env } : undefined,
69
+ extraEnvVars: Object.keys(extraEnvVars).length > 0 ? extraEnvVars : undefined,
65
70
  };
66
71
  // ── seedRBAC ───────────────────────────────────────────
67
72
  if (json.rbac) {
@@ -88,7 +93,7 @@ function buildConfig(json, repoFactory) {
88
93
  if (json.rbac.roles?.length) {
89
94
  const roleRepo = await getRepo('role');
90
95
  const allPermIds = Object.values(permissionMap);
91
- for (const roleDef of json.rbac.roles) {
96
+ for (const roleDef of json.rbac.roles.filter(r => r.name)) {
92
97
  const permissionIds = roleDef.permissions.includes('*')
93
98
  ? allPermIds
94
99
  : roleDef.permissions.map(code => permissionMap[code]).filter(Boolean);
@@ -1,4 +1,4 @@
1
- import type { InstallConfig, MostaSetupConfig } from '../types/index';
1
+ import type { InstallConfig, MostaSetupConfig } from '../types/index.js';
2
2
  /**
3
3
  * Check if the app needs initial setup (0 users in DB).
4
4
  * Provide a countUsers function from your app.
package/dist/lib/setup.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // @mosta/setup — Core setup logic
2
2
  // Author: Dr Hamid MADANI drmdh@msn.com
3
- import { composeDbUri } from './compose-uri';
4
- import { writeEnvLocal } from './env-writer';
3
+ import { composeDbUri } from './compose-uri.js';
4
+ import { writeEnvLocal } from './env-writer.js';
5
5
  /**
6
6
  * Check if the app needs initial setup (0 users in DB).
7
7
  * Provide a countUsers function from your app.
@@ -2,6 +2,7 @@ import type { ModuleRegistration } from '@mostajs/socle';
2
2
  /**
3
3
  * Setup provides the installation wizard and reconfiguration panel.
4
4
  * No schemas of its own — orchestrates other modules' seeds.
5
+ * Menu contributions are declared by the host app, not by this module.
5
6
  */
6
7
  export declare function register(registry: {
7
8
  register(r: ModuleRegistration): void;
package/dist/register.js CHANGED
@@ -1,9 +1,9 @@
1
1
  // @mostajs/setup — Runtime module registration
2
2
  // Author: Dr Hamid MADANI drmdh@msn.com
3
- import { setupMenuContribution } from './lib/menu.js';
4
3
  /**
5
4
  * Setup provides the installation wizard and reconfiguration panel.
6
5
  * No schemas of its own — orchestrates other modules' seeds.
6
+ * Menu contributions are declared by the host app, not by this module.
7
7
  */
8
8
  export function register(registry) {
9
9
  registry.register({
@@ -19,6 +19,5 @@ export function register(registry) {
19
19
  icon: 'Wrench',
20
20
  register: './dist/register.js',
21
21
  },
22
- menu: setupMenuContribution,
23
22
  });
24
23
  }
@@ -28,7 +28,7 @@ export interface InstallConfig {
28
28
  seed?: SeedOptions;
29
29
  modules?: string[];
30
30
  }
31
- export type { ModuleDefinition } from '../data/module-definitions';
31
+ export type { ModuleDefinition } from '../data/module-definitions.js';
32
32
  export interface SeedOptions {
33
33
  [key: string]: boolean;
34
34
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/setup",
3
- "version": "1.5.1",
3
+ "version": "2.0.1",
4
4
  "description": "Reusable setup wizard module — multi-dialect DB configuration, .env.local writer, seed runner",
5
5
  "author": "Dr Hamid MADANI <drmdh@msn.com>",
6
6
  "license": "MIT",
@@ -33,11 +33,6 @@
33
33
  "import": "./dist/types/index.js",
34
34
  "default": "./dist/types/index.js"
35
35
  },
36
- "./lib/menu": {
37
- "types": "./dist/lib/menu.d.ts",
38
- "import": "./dist/lib/menu.js",
39
- "default": "./dist/lib/menu.js"
40
- },
41
36
  "./components/ReconfigPanel": {
42
37
  "types": "./dist/components/ReconfigPanel.d.ts",
43
38
  "import": "./dist/components/ReconfigPanel.js",
@@ -104,7 +99,7 @@
104
99
  "prepublishOnly": "npm run build"
105
100
  },
106
101
  "dependencies": {
107
- "@mostajs/orm": "^1.4.1",
102
+ "@mostajs/orm": "^1.4.15",
108
103
  "bcryptjs": "^2.4.3"
109
104
  },
110
105
  "devDependencies": {
@@ -151,6 +151,45 @@
151
151
  }
152
152
  }
153
153
  },
154
+ "modules": {
155
+ "type": "array",
156
+ "description": "Modules to use in the project. Exported by MostaSetup Studio. Shown in the wizard with install commands.",
157
+ "items": {
158
+ "type": "object",
159
+ "required": ["key", "packageName"],
160
+ "additionalProperties": false,
161
+ "properties": {
162
+ "key": {
163
+ "type": "string",
164
+ "description": "Unique module identifier (e.g. 'orm', 'auth', 'ticketing')"
165
+ },
166
+ "packageName": {
167
+ "type": "string",
168
+ "description": "npm package name (e.g. '@mostajs/orm')"
169
+ },
170
+ "label": {
171
+ "type": "string",
172
+ "description": "Display label"
173
+ },
174
+ "description": {
175
+ "type": "string"
176
+ },
177
+ "icon": {
178
+ "type": "string",
179
+ "description": "Emoji icon"
180
+ },
181
+ "required": {
182
+ "type": "boolean",
183
+ "description": "Whether this module is required (cannot be deselected)"
184
+ },
185
+ "dependsOn": {
186
+ "type": "array",
187
+ "items": { "type": "string" },
188
+ "description": "Module keys this module depends on"
189
+ }
190
+ }
191
+ }
192
+ },
154
193
  "seeds": {
155
194
  "type": "array",
156
195
  "description": "Optional seed datasets shown as checkboxes in the install wizard",
@@ -1,2 +0,0 @@
1
- import type { ModuleMenuContribution } from '@mostajs/menu';
2
- export declare const setupMenuContribution: ModuleMenuContribution;
package/dist/lib/menu.js DELETED
@@ -1,22 +0,0 @@
1
- // @mostajs/setup — Menu contribution
2
- // Author: Dr Hamid MADANI drmdh@msn.com
3
- import { Wrench, Settings2 } from 'lucide-react';
4
- export const setupMenuContribution = {
5
- moduleKey: 'setup',
6
- mergeIntoGroup: 'Administration',
7
- order: 100,
8
- items: [
9
- {
10
- label: 'setup.title',
11
- href: '/setup',
12
- icon: Wrench,
13
- permission: 'admin:access',
14
- },
15
- {
16
- label: 'setup.reconfig.title',
17
- href: '/dashboard/settings/reconfig',
18
- icon: Settings2,
19
- permission: 'admin:settings',
20
- },
21
- ],
22
- };