@mostajs/setup 2.0.0 → 2.0.2

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
+ }
@@ -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
+ }
package/dist/cli/init.js CHANGED
File without changes
@@ -4,7 +4,7 @@
4
4
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
5
5
  import { useState, useEffect, useCallback } from 'react';
6
6
  /** Dialects that require a JDBC bridge JAR */
7
- const JDBC_DIALECTS = ['hsqldb', 'oracle', 'db2', 'sybase', 'hana'];
7
+ const JDBC_DIALECTS = ['hsqldb', 'db2', 'sybase', 'hana'];
8
8
  const DIALECTS = [
9
9
  { key: 'mongodb', name: 'MongoDB', icon: '🍃', defaultPort: 27017, defaultUser: '', defaultHost: 'localhost', requiresAuth: false },
10
10
  { key: 'sqlite', name: 'SQLite', icon: '📄', defaultPort: 0, defaultUser: '', defaultHost: '', requiresAuth: false },
@@ -12,7 +12,7 @@ const DIALECTS = [
12
12
  { key: 'mysql', name: 'MySQL', icon: '🐬', defaultPort: 3306, defaultUser: 'root', defaultHost: 'localhost', requiresAuth: true },
13
13
  { key: 'mariadb', name: 'MariaDB', icon: '🦭', defaultPort: 3306, defaultUser: 'root', defaultHost: 'localhost', requiresAuth: true },
14
14
  { key: 'mssql', name: 'SQL Server', icon: '🟦', defaultPort: 1433, defaultUser: 'sa', defaultHost: 'localhost', requiresAuth: true },
15
- { key: 'oracle', name: 'Oracle', icon: '🔴', defaultPort: 1521, defaultUser: 'system', defaultHost: 'localhost', requiresAuth: true, premium: true, jdbc: true },
15
+ { key: 'oracle', name: 'Oracle', icon: '🔴', defaultPort: 1521, defaultUser: 'system', defaultHost: 'localhost', requiresAuth: true },
16
16
  { key: 'cockroachdb', name: 'CockroachDB', icon: '🪳', defaultPort: 26257, defaultUser: 'root', defaultHost: 'localhost', requiresAuth: true },
17
17
  { key: 'db2', name: 'IBM DB2', icon: '🏢', defaultPort: 50000, defaultUser: 'db2inst1', defaultHost: 'localhost', requiresAuth: true, premium: true, jdbc: true },
18
18
  { key: 'hana', name: 'SAP HANA', icon: '💎', defaultPort: 39013, defaultUser: 'SYSTEM', defaultHost: 'localhost', requiresAuth: true, premium: true, jdbc: true },
@@ -13,6 +13,10 @@ 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;
@@ -11,7 +11,7 @@ const DIALECT_DEFAULTS = {
11
11
  postgres: { host: 'localhost', port: 5432, name: 'mydb', user: 'postgres', password: '' },
12
12
  mysql: { host: 'localhost', port: 3306, name: 'mydb', user: 'root', password: '' },
13
13
  mariadb: { host: 'localhost', port: 3306, name: 'mydb', user: 'root', password: '' },
14
- oracle: { host: 'localhost', port: 1521, name: 'mydb', user: 'system', password: '' },
14
+ oracle: { host: 'localhost', port: 1521, name: 'XEPDB1', user: 'system', password: '' },
15
15
  mssql: { host: 'localhost', port: 1433, name: 'mydb', user: 'sa', password: '' },
16
16
  cockroachdb: { host: 'localhost', port: 26257, name: 'mydb', user: 'root', password: '' },
17
17
  db2: { host: 'localhost', port: 50000, name: 'mydb', user: 'db2inst1', password: '' },
@@ -27,7 +27,7 @@ const DIALECT_INFO = [
27
27
  { key: 'mysql', name: 'MySQL', icon: '🐬' },
28
28
  { key: 'mariadb', name: 'MariaDB', icon: '🦭' },
29
29
  { key: 'mssql', name: 'SQL Server', icon: '🟦' },
30
- { key: 'oracle', name: 'Oracle', icon: '🔴', premium: true, jdbc: true },
30
+ { key: 'oracle', name: 'Oracle', icon: '🔴' },
31
31
  { key: 'cockroachdb', name: 'CockroachDB', icon: '🪳' },
32
32
  { key: 'db2', name: 'IBM DB2', icon: '🏢', premium: true, jdbc: true },
33
33
  { key: 'hana', name: 'SAP HANA', icon: '💎', premium: true, jdbc: true },
@@ -35,7 +35,7 @@ const DIALECT_INFO = [
35
35
  { key: 'spanner', name: 'Cloud Spanner', icon: '☁️', premium: true },
36
36
  { key: 'sybase', name: 'Sybase ASE', icon: '🔷', premium: true, jdbc: true },
37
37
  ];
38
- const JDBC_DIALECTS = ['hsqldb', 'oracle', 'db2', 'hana', 'sybase'];
38
+ const JDBC_DIALECTS = ['hsqldb', 'db2', 'hana', 'sybase'];
39
39
  const DRIVER_HINTS = {
40
40
  mongodb: 'npm install mongoose',
41
41
  sqlite: 'npm install better-sqlite3',
@@ -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(() => {
@@ -549,6 +556,47 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
549
556
  setDbConfig({ ...defaults, name: defaults.name === 'mydb' ? dbNamePrefix : defaults.name === 'mydb_prod' ? `${dbNamePrefix}_prod` : defaults.name.replace('mydb', dbNamePrefix) });
550
557
  setDbTestResult(null);
551
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
+ }
552
600
  // --- Test DB ---
553
601
  async function testDb() {
554
602
  setDbTesting(true);
@@ -652,7 +700,12 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
652
700
  onComplete?.();
653
701
  }
654
702
  // ── Render ─────────────────────────────────────────────────
655
- 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 => {
656
709
  const isSelected = selectedModules.includes(mod.key);
657
710
  const isDetected = detectedModules.includes(mod.key);
658
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));
@@ -662,7 +715,9 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
662
715
  color: mod.type === 'business' ? '#1e40af' : '#6b21a8',
663
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
664
717
  ? (mod.installed ? 'Decablage...' : 'Cablage...')
665
- : (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 && (_jsxs("div", { style: { fontSize: 13 }, children: [_jsx("span", { style: { color: dbTestResult.ok ? '#059669' : '#dc2626' }, children: dbTestResult.ok
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
666
721
  ? `✅ ${t('setup.database.success')}${dbTestResult.dbVersion ? ` (v${dbTestResult.dbVersion})` : ''}`
667
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 => {
668
723
  const mod = availableModules.find(m => m.key === key);
package/dist/index.d.ts CHANGED
@@ -16,7 +16,9 @@ export { createReconfigHandlers } from './api/reconfig.route.js';
16
16
  export { createUploadJarHandlers } from './api/upload-jar.route.js';
17
17
  export { createWireModuleHandler } from './api/wire-module.route.js';
18
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';
19
22
  export { default as ReconfigPanel } from './components/ReconfigPanel.js';
20
23
  export { default as SetupWizard } from './components/SetupWizard.js';
21
- export { setupMenuContribution } from './lib/menu.js';
22
24
  export type { DialectType, DialectInfo, DbConfig, InstallConfig, SeedOptions, SeedDefinition, MostaSetupConfig, ModuleDefinition, } from './types/index.js';
package/dist/index.js CHANGED
@@ -21,8 +21,8 @@ export { createReconfigHandlers } from './api/reconfig.route.js';
21
21
  export { createUploadJarHandlers } from './api/upload-jar.route.js';
22
22
  export { createWireModuleHandler } from './api/wire-module.route.js';
23
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
27
  export { default as ReconfigPanel } from './components/ReconfigPanel.js';
26
28
  export { default as SetupWizard } from './components/SetupWizard.js';
27
- // Menu contribution
28
- export { setupMenuContribution } from './lib/menu.js';
@@ -1,6 +1,7 @@
1
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
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';
package/dist/lib/setup.js CHANGED
@@ -40,7 +40,8 @@ export async function runInstall(installConfig, setupConfig) {
40
40
  port: setupConfig.defaultPort,
41
41
  });
42
42
  // 2. For JDBC dialects, persist bridge port so app always reconnects to the same bridge
43
- const JDBC_DIALECTS = ['hsqldb', 'oracle', 'db2', 'hana', 'sybase'];
43
+ // Note: Oracle uses native oracledb driver, not JDBC bridge
44
+ const JDBC_DIALECTS = ['hsqldb', 'db2', 'hana', 'sybase'];
44
45
  if (JDBC_DIALECTS.includes(installConfig.dialect)) {
45
46
  const bridgePort = process.env.MOSTA_BRIDGE_PORT_BASE || '8765';
46
47
  extraVars['MOSTA_BRIDGE_PORT_BASE'] = bridgePort;
@@ -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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/setup",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
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": {
@@ -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
- };