@mostajs/setup 1.4.4 → 1.4.6

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.
@@ -12,10 +12,129 @@ export function createUploadJarHandlers() {
12
12
  async function GET() {
13
13
  try {
14
14
  const { listJarFiles, getJdbcDialectStatus } = await import('@mostajs/orm');
15
+ // Scan active bridges: BridgeManager bridges + PID files + port probing
16
+ const bridges = [];
17
+ // 1. Check BridgeManager known bridges
18
+ try {
19
+ const { BridgeManager } = await import('@mostajs/orm');
20
+ const manager = BridgeManager.getInstance();
21
+ for (const b of manager.list()) {
22
+ bridges.push({ port: b.port, pid: b.pid, status: 'active', jdbcUrl: b.jdbcUrl });
23
+ }
24
+ }
25
+ catch { /* ignore */ }
26
+ // 2. Scan PID files for orphan bridges
27
+ try {
28
+ const { existsSync, readdirSync, readFileSync } = await import('fs');
29
+ const { join } = await import('path');
30
+ const jarDir = process.env.MOSTA_JAR_DIR || join(process.cwd(), 'jar_files');
31
+ if (existsSync(jarDir)) {
32
+ const pidFiles = readdirSync(jarDir).filter((f) => f.startsWith('.bridge-') && f.endsWith('.pid'));
33
+ for (const file of pidFiles) {
34
+ const portMatch = file.match(/\.bridge-(\d+)\.pid/);
35
+ if (!portMatch)
36
+ continue;
37
+ const port = parseInt(portMatch[1]);
38
+ // Skip if already known from BridgeManager
39
+ if (bridges.some(b => b.port === port))
40
+ continue;
41
+ const pidStr = readFileSync(join(jarDir, file), 'utf-8').trim();
42
+ const pid = parseInt(pidStr);
43
+ if (isNaN(pid))
44
+ continue;
45
+ // Check if process is alive
46
+ let alive = false;
47
+ try {
48
+ process.kill(pid, 0);
49
+ alive = true;
50
+ }
51
+ catch { /* dead */ }
52
+ if (alive) {
53
+ // Probe health
54
+ let jdbcUrl;
55
+ try {
56
+ const res = await fetch(`http://localhost:${port}/health`, { signal: AbortSignal.timeout(1000) });
57
+ if (res.ok) {
58
+ const h = await res.json();
59
+ jdbcUrl = h.jdbcUrl;
60
+ }
61
+ }
62
+ catch { /* not responding */ }
63
+ bridges.push({ port, pid, status: jdbcUrl ? 'active' : 'orphan', jdbcUrl });
64
+ }
65
+ }
66
+ }
67
+ }
68
+ catch { /* ignore */ }
69
+ // 3. Probe common bridge ports for unknown bridges (no PID file)
70
+ const basePort = parseInt(process.env.MOSTA_BRIDGE_PORT_BASE || '8765');
71
+ for (let port = basePort; port < basePort + 5; port++) {
72
+ if (bridges.some(b => b.port === port))
73
+ continue;
74
+ try {
75
+ const res = await fetch(`http://localhost:${port}/health`, { signal: AbortSignal.timeout(500) });
76
+ if (res.ok) {
77
+ const h = await res.json();
78
+ bridges.push({ port, pid: 0, status: 'active', jdbcUrl: h.jdbcUrl });
79
+ }
80
+ }
81
+ catch { /* not a bridge */ }
82
+ }
83
+ // Check HSQLDB server status
84
+ let hsqldbServer = null;
85
+ try {
86
+ const { existsSync, readdirSync, readFileSync } = await import('fs');
87
+ const { join } = await import('path');
88
+ const jarDir = process.env.MOSTA_JAR_DIR || join(process.cwd(), 'jar_files');
89
+ if (existsSync(jarDir)) {
90
+ const serverPidFiles = readdirSync(jarDir).filter((f) => f.startsWith('.hsqldb-server-') && f.endsWith('.pid'));
91
+ for (const file of serverPidFiles) {
92
+ const portMatch = file.match(/\.hsqldb-server-(\d+)\.pid/);
93
+ if (!portMatch)
94
+ continue;
95
+ const port = parseInt(portMatch[1]);
96
+ const pidStr = readFileSync(join(jarDir, file), 'utf-8').trim();
97
+ const pid = parseInt(pidStr);
98
+ let alive = false;
99
+ if (pid > 0) {
100
+ try {
101
+ process.kill(pid, 0);
102
+ alive = true;
103
+ }
104
+ catch { /* dead */ }
105
+ }
106
+ if (alive) {
107
+ hsqldbServer = { running: true, port, pid };
108
+ }
109
+ else {
110
+ // Clean stale PID file
111
+ try {
112
+ (await import('fs')).unlinkSync(join(jarDir, file));
113
+ }
114
+ catch { /* ignore */ }
115
+ }
116
+ }
117
+ }
118
+ // Also check port 9001 if no PID file found
119
+ if (!hsqldbServer) {
120
+ const { execSync } = await import('child_process');
121
+ try {
122
+ const out = execSync('fuser 9001/tcp 2>/dev/null', { encoding: 'utf-8' }).trim();
123
+ if (out) {
124
+ const pid = parseInt(out);
125
+ hsqldbServer = { running: true, port: 9001, pid: isNaN(pid) ? 0 : pid };
126
+ }
127
+ }
128
+ catch { /* not running */ }
129
+ }
130
+ }
131
+ catch { /* ignore */ }
15
132
  return Response.json({
16
133
  ok: true,
17
134
  jars: listJarFiles(),
18
135
  dialects: getJdbcDialectStatus(),
136
+ bridges,
137
+ hsqldbServer,
19
138
  });
20
139
  }
21
140
  catch (err) {
@@ -70,34 +189,163 @@ export function createUploadJarHandlers() {
70
189
  }
71
190
  }
72
191
  /**
73
- * PATCH — Start or stop the JDBC bridge.
74
- * Start: { action: 'start', dialect: string, host: string, port: number, name: string, user?: string, password?: string }
75
- * Stop: { action: 'stop', port: number }
76
- * Returns: { ok: true, port: number } or { ok: false, error: string }
192
+ * PATCH — Manage JDBC bridge and HSQLDB server.
193
+ * start-server: { action: 'start-server', dialect, name, host?, port? }
194
+ * stop-server: { action: 'stop-server', port? }
195
+ * start: { action: 'start', dialect, host, port, name, user?, password? }
196
+ * stop: { action: 'stop', port, pid? }
77
197
  */
78
198
  async function PATCH(req) {
79
199
  try {
80
200
  const body = await req.json();
81
201
  const { action } = body;
202
+ // ── Start HSQLDB server ──
203
+ if (action === 'start-server') {
204
+ const { dialect, name, host, port: sgbdPort } = body;
205
+ if (dialect !== 'hsqldb') {
206
+ return Response.json({ ok: false, error: 'start-server supporte uniquement hsqldb' }, { status: 400 });
207
+ }
208
+ const actualPort = sgbdPort || 9001;
209
+ // Check if already running
210
+ try {
211
+ const check = await fetch(`http://${host || 'localhost'}:${actualPort}`, { signal: AbortSignal.timeout(500) });
212
+ void check;
213
+ return Response.json({ ok: true, message: `Serveur HSQLDB deja en marche sur le port ${actualPort}`, port: actualPort, alreadyRunning: true });
214
+ }
215
+ catch { /* not running — proceed */ }
216
+ // Find JAR
217
+ const { JdbcNormalizer } = await import('@mostajs/orm');
218
+ const jarPath = JdbcNormalizer.findJar('hsqldb');
219
+ if (!jarPath) {
220
+ return Response.json({ ok: false, error: 'JAR HSQLDB non trouve. Uploadez hsqldb*.jar d\'abord.' });
221
+ }
222
+ // Launch server
223
+ const { spawn: spawnChild } = await import('child_process');
224
+ const { join } = await import('path');
225
+ const { writeFileSync, existsSync, mkdirSync } = await import('fs');
226
+ const dataDir = join(process.cwd(), 'data');
227
+ if (!existsSync(dataDir))
228
+ mkdirSync(dataDir, { recursive: true });
229
+ const dbAlias = name || 'mydb';
230
+ const serverProc = spawnChild('java', [
231
+ '-cp', jarPath,
232
+ 'org.hsqldb.server.Server',
233
+ '--database.0', `file:${join(dataDir, dbAlias)}`,
234
+ '--dbname.0', dbAlias,
235
+ '--port', String(actualPort),
236
+ ], {
237
+ stdio: ['ignore', 'pipe', 'pipe'],
238
+ detached: true,
239
+ });
240
+ serverProc.unref();
241
+ const serverPid = serverProc.pid || 0;
242
+ // Save PID file for cleanup
243
+ const jarDir = process.env.MOSTA_JAR_DIR || join(process.cwd(), 'jar_files');
244
+ if (existsSync(jarDir)) {
245
+ writeFileSync(join(jarDir, `.hsqldb-server-${actualPort}.pid`), String(serverPid));
246
+ }
247
+ // Log stderr
248
+ serverProc.stderr?.on('data', (data) => {
249
+ const msg = data.toString().trim();
250
+ if (msg)
251
+ console.error(`[HSQLDB:server] ${msg}`);
252
+ });
253
+ // Wait for server to be ready
254
+ const startTime = Date.now();
255
+ let serverReady = false;
256
+ while (Date.now() - startTime < 8000) {
257
+ try {
258
+ await new Promise(r => setTimeout(r, 500));
259
+ const sock = await fetch(`http://localhost:${actualPort}`, { signal: AbortSignal.timeout(500) });
260
+ void sock;
261
+ serverReady = true;
262
+ break;
263
+ }
264
+ catch {
265
+ // HSQLDB server doesn't speak HTTP, but we can check if the port is listening
266
+ // Try a TCP connection test via the bridge health check pattern
267
+ }
268
+ }
269
+ // Alternative: check if process is still alive and port is open
270
+ if (!serverReady) {
271
+ try {
272
+ process.kill(serverPid, 0); // check alive
273
+ // Port might be open but not HTTP — that's fine for HSQLDB
274
+ serverReady = true;
275
+ }
276
+ catch { /* process died */ }
277
+ }
278
+ if (!serverReady) {
279
+ return Response.json({ ok: false, error: 'Le serveur HSQLDB n\'a pas demarre dans le delai imparti' });
280
+ }
281
+ return Response.json({ ok: true, port: actualPort, pid: serverPid, message: `Serveur HSQLDB lance (port ${actualPort}, PID ${serverPid}, alias: ${dbAlias})` });
282
+ }
283
+ // ── Stop HSQLDB server ──
284
+ if (action === 'stop-server') {
285
+ const sgbdPort = body.port || 9001;
286
+ try {
287
+ const { execSync } = await import('child_process');
288
+ const { existsSync, unlinkSync, readFileSync } = await import('fs');
289
+ const { join } = await import('path');
290
+ const jarDir = process.env.MOSTA_JAR_DIR || join(process.cwd(), 'jar_files');
291
+ // Kill by PID file
292
+ const pidFile = join(jarDir, `.hsqldb-server-${sgbdPort}.pid`);
293
+ if (existsSync(pidFile)) {
294
+ const pid = parseInt(readFileSync(pidFile, 'utf-8').trim());
295
+ if (pid > 0) {
296
+ try {
297
+ process.kill(pid, 'SIGKILL');
298
+ }
299
+ catch { /* dead */ }
300
+ }
301
+ unlinkSync(pidFile);
302
+ }
303
+ // Also kill anything on the port
304
+ try {
305
+ execSync(`fuser -k ${sgbdPort}/tcp 2>/dev/null`, { stdio: 'ignore' });
306
+ }
307
+ catch { /* already free */ }
308
+ return Response.json({ ok: true, message: `Serveur HSQLDB arrete (port ${sgbdPort})` });
309
+ }
310
+ catch (err) {
311
+ return Response.json({ ok: false, error: err instanceof Error ? err.message : 'Erreur' });
312
+ }
313
+ }
82
314
  if (action === 'stop') {
83
315
  const bridgePort = body.port || 8765;
84
- // Kill bridge on the given port
316
+ const bridgePid = body.pid || 0;
85
317
  try {
318
+ // 1. Try BridgeManager
86
319
  const { BridgeManager } = await import('@mostajs/orm');
87
320
  const manager = BridgeManager.getInstance();
88
321
  const bridges = manager.list();
89
322
  const bridge = bridges.find((b) => b.port === bridgePort);
90
323
  if (bridge) {
91
324
  await manager.stop(bridge.key);
92
- return Response.json({ ok: true, message: `Bridge arrete sur le port ${bridgePort}` });
93
325
  }
94
- // Fallback: kill process on port
326
+ // 2. Kill by PID if provided
327
+ if (bridgePid > 0) {
328
+ try {
329
+ process.kill(bridgePid, 'SIGKILL');
330
+ }
331
+ catch { /* already dead */ }
332
+ }
333
+ // 3. Fallback: kill process on port
95
334
  const { execSync } = await import('child_process');
96
335
  try {
97
336
  execSync(`fuser -k ${bridgePort}/tcp 2>/dev/null`, { stdio: 'ignore' });
98
337
  }
99
338
  catch { /* port may already be free */ }
100
- return Response.json({ ok: true, message: `Port ${bridgePort} libere` });
339
+ // 4. Clean PID file
340
+ try {
341
+ const { existsSync, unlinkSync } = await import('fs');
342
+ const { join } = await import('path');
343
+ const pidFile = join(process.env.MOSTA_JAR_DIR || join(process.cwd(), 'jar_files'), `.bridge-${bridgePort}.pid`);
344
+ if (existsSync(pidFile))
345
+ unlinkSync(pidFile);
346
+ }
347
+ catch { /* non-critical */ }
348
+ return Response.json({ ok: true, message: `Bridge arrete (port ${bridgePort})` });
101
349
  }
102
350
  catch (err) {
103
351
  const msg = err instanceof Error ? err.message : 'Erreur';
@@ -215,24 +215,36 @@ async function fetchRetry(url, init, retries = 3, delay = 2000) {
215
215
  }
216
216
  return fetch(url, init);
217
217
  }
218
- // ── JAR Upload Sub-component ─────────────────────────────────
219
218
  function JarUploadInline({ dialect, jarEndpoint, dbConfig }) {
220
219
  const [uploading, setUploading] = useState(false);
221
- const [bridgeLoading, setBridgeLoading] = useState(false);
220
+ const [loading, setLoading] = useState(null); // tracks which action is loading
222
221
  const [bridgePort, setBridgePort] = useState(null);
223
222
  const [message, setMessage] = useState(null);
224
223
  const [jarStatus, setJarStatus] = useState(null);
225
- useEffect(() => {
224
+ const [bridges, setBridges] = useState([]);
225
+ const [serverInfo, setServerInfo] = useState(null);
226
+ const loadStatus = useCallback(() => {
226
227
  fetch(jarEndpoint)
227
228
  .then(r => r.json())
228
229
  .then(data => {
229
230
  if (data.ok) {
230
231
  const s = data.dialects?.find((d) => d.dialect === dialect);
231
232
  setJarStatus(s || { hasJar: false, jarFile: null });
233
+ setBridges(data.bridges || []);
234
+ if (data.hsqldbServer)
235
+ setServerInfo(data.hsqldbServer);
236
+ else
237
+ setServerInfo(null);
238
+ const active = (data.bridges || []).find((b) => b.status === 'active');
239
+ if (active)
240
+ setBridgePort(active.port);
241
+ else
242
+ setBridgePort(null);
232
243
  }
233
244
  })
234
245
  .catch(() => { });
235
246
  }, [dialect, jarEndpoint]);
247
+ useEffect(() => { loadStatus(); }, [loadStatus]);
236
248
  const handleUpload = async (e) => {
237
249
  const file = e.target.files?.[0];
238
250
  if (!file || !file.name.endsWith('.jar'))
@@ -260,57 +272,42 @@ function JarUploadInline({ dialect, jarEndpoint, dbConfig }) {
260
272
  e.target.value = '';
261
273
  }
262
274
  };
263
- const handleStartBridge = async () => {
264
- setBridgeLoading(true);
265
- setMessage(null);
266
- try {
267
- const res = await fetch(jarEndpoint, {
268
- method: 'PATCH',
269
- headers: { 'Content-Type': 'application/json' },
270
- body: JSON.stringify({ action: 'start', dialect, ...dbConfig }),
271
- });
272
- const result = await res.json();
273
- if (result.ok) {
274
- setBridgePort(result.port || 8765);
275
- setMessage({ ok: true, text: `Bridge JDBC lance sur le port ${result.port || 8765}` });
276
- }
277
- else {
278
- setMessage({ ok: false, text: result.error || 'Echec lancement du bridge' });
279
- }
280
- }
281
- catch {
282
- setMessage({ ok: false, text: 'Erreur reseau' });
283
- }
284
- finally {
285
- setBridgeLoading(false);
286
- }
287
- };
288
- const handleStopBridge = async () => {
289
- setBridgeLoading(true);
275
+ const patchAction = async (payload, actionLabel) => {
276
+ setLoading(actionLabel);
290
277
  setMessage(null);
291
278
  try {
292
279
  const res = await fetch(jarEndpoint, {
293
280
  method: 'PATCH',
294
281
  headers: { 'Content-Type': 'application/json' },
295
- body: JSON.stringify({ action: 'stop', port: bridgePort || 8765 }),
282
+ body: JSON.stringify(payload),
296
283
  });
297
284
  const result = await res.json();
298
285
  if (result.ok) {
299
- setBridgePort(null);
300
- setMessage({ ok: true, text: result.message || 'Bridge arrete' });
286
+ setMessage({ ok: true, text: result.message || actionLabel + ' OK' });
287
+ if (result.port && payload.action === 'start')
288
+ setBridgePort(result.port);
289
+ if (payload.action === 'stop')
290
+ setBridgePort(null);
301
291
  }
302
292
  else {
303
- setMessage({ ok: false, text: result.error || 'Echec arret du bridge' });
293
+ setMessage({ ok: false, text: result.error || 'Echec ' + actionLabel });
304
294
  }
295
+ loadStatus();
305
296
  }
306
297
  catch {
307
298
  setMessage({ ok: false, text: 'Erreur reseau' });
308
299
  }
309
300
  finally {
310
- setBridgeLoading(false);
301
+ setLoading(null);
311
302
  }
312
303
  };
313
- 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" })), bridgePort && (_jsxs("span", { style: { fontSize: 11, color: '#059669', fontWeight: 600 }, children: ["Bridge actif port ", bridgePort] }))] }), _jsxs("div", { style: { ...S.flex(8), marginTop: 8, flexWrap: 'wrap' }, 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' } })] }), jarStatus?.hasJar && !bridgePort && (_jsx("button", { style: { ...S.btn('primary', bridgeLoading), fontSize: 12, padding: '6px 12px', backgroundColor: bridgeLoading ? '#6b7280' : '#059669' }, onClick: handleStartBridge, disabled: bridgeLoading, children: bridgeLoading ? 'Lancement...' : 'Lancer le bridge' })), bridgePort && (_jsx("button", { style: { ...S.btn('primary', bridgeLoading), fontSize: 12, padding: '6px 12px', backgroundColor: bridgeLoading ? '#6b7280' : '#dc2626' }, onClick: handleStopBridge, disabled: bridgeLoading, children: bridgeLoading ? 'Arret...' : 'Arreter le bridge' })), _jsx("span", { style: { fontSize: 11, color: '#9ca3af' }, children: "Ex: hsqldb*.jar, ojdbc*.jar, db2jcc*.jar" })] }), message && (_jsx("p", { style: { fontSize: 12, color: message.ok ? '#059669' : '#dc2626', marginTop: 8 }, children: message.text }))] }));
304
+ const isHsqldb = dialect === 'hsqldb';
305
+ const btnSmall = (color, disabled) => ({
306
+ ...S.btn('primary', disabled), fontSize: 12, padding: '6px 14px',
307
+ backgroundColor: disabled ? '#9ca3af' : color, cursor: disabled ? 'not-allowed' : 'pointer',
308
+ borderRadius: 6, border: 'none', color: '#fff', fontWeight: 600,
309
+ });
310
+ 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", serverInfo?.running && (_jsxs("span", { style: { fontWeight: 400, color: '#059669', marginLeft: 8 }, children: ["En marche (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 }, 'start-server'), disabled: loading === 'start-server' || !!serverInfo?.running, children: loading === 'start-server' ? 'Demarrage...' : 'Demarrer le serveur' }), _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", bridgePort && _jsxs("span", { style: { fontWeight: 400, marginLeft: 8 }, children: ["Actif port ", bridgePort] }), !bridgePort && bridges.length === 0 && _jsx("span", { style: { fontWeight: 400, color: '#6b7280', 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' }) }), 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: [":", 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 }))] }));
314
311
  }
315
312
  // ── Main Component ───────────────────────────────────────────
316
313
  export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNamePrefix = 'mydb', persistState = true, }) {
@@ -537,7 +534,7 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
537
534
  const isSelected = selectedModules.includes(mod.key);
538
535
  const isDetected = detectedModules.includes(mod.key);
539
536
  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));
540
- }) }), _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); } })] }), dialect !== 'hsqldb' && (_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); } })] }), _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 !== '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
537
+ }) }), _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
541
538
  ? `✅ ${t('setup.database.success')}${dbTestResult.dbVersion ? ` (v${dbTestResult.dbVersion})` : ''}`
542
539
  : `❌ ${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 => {
543
540
  const mod = availableModules.find(m => m.key === key);
@@ -28,6 +28,8 @@ export function composeDbUri(dialect, config) {
28
28
  case 'hana':
29
29
  return `hana://${eu}:${ep}@${host}:${port}`;
30
30
  case 'hsqldb':
31
+ if (user)
32
+ return `hsqldb:hsql://${eu}:${ep}@${host}:${port}/${name}`;
31
33
  return `hsqldb:hsql://${host}:${port}/${name}`;
32
34
  case 'spanner':
33
35
  return `spanner://projects/${name}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/setup",
3
- "version": "1.4.4",
3
+ "version": "1.4.6",
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",