@mostajs/setup 1.4.5 → 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.
@@ -80,11 +80,61 @@ export function createUploadJarHandlers() {
80
80
  }
81
81
  catch { /* not a bridge */ }
82
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 */ }
83
132
  return Response.json({
84
133
  ok: true,
85
134
  jars: listJarFiles(),
86
135
  dialects: getJdbcDialectStatus(),
87
136
  bridges,
137
+ hsqldbServer,
88
138
  });
89
139
  }
90
140
  catch (err) {
@@ -139,15 +189,128 @@ export function createUploadJarHandlers() {
139
189
  }
140
190
  }
141
191
  /**
142
- * PATCH — Start or stop the JDBC bridge.
143
- * Start: { action: 'start', dialect: string, host: string, port: number, name: string, user?: string, password?: string }
144
- * Stop: { action: 'stop', port: number }
145
- * 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? }
146
197
  */
147
198
  async function PATCH(req) {
148
199
  try {
149
200
  const body = await req.json();
150
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
+ }
151
314
  if (action === 'stop') {
152
315
  const bridgePort = body.port || 8765;
153
316
  const bridgePid = body.pid || 0;
@@ -217,12 +217,12 @@ async function fetchRetry(url, init, retries = 3, delay = 2000) {
217
217
  }
218
218
  function JarUploadInline({ dialect, jarEndpoint, dbConfig }) {
219
219
  const [uploading, setUploading] = useState(false);
220
- const [bridgeLoading, setBridgeLoading] = useState(false);
220
+ const [loading, setLoading] = useState(null); // tracks which action is loading
221
221
  const [bridgePort, setBridgePort] = useState(null);
222
222
  const [message, setMessage] = useState(null);
223
223
  const [jarStatus, setJarStatus] = useState(null);
224
224
  const [bridges, setBridges] = useState([]);
225
- const [killingPort, setKillingPort] = useState(null);
225
+ const [serverInfo, setServerInfo] = useState(null);
226
226
  const loadStatus = useCallback(() => {
227
227
  fetch(jarEndpoint)
228
228
  .then(r => r.json())
@@ -231,10 +231,15 @@ function JarUploadInline({ dialect, jarEndpoint, dbConfig }) {
231
231
  const s = data.dialects?.find((d) => d.dialect === dialect);
232
232
  setJarStatus(s || { hasJar: false, jarFile: null });
233
233
  setBridges(data.bridges || []);
234
- // If a bridge is active, track its port
234
+ if (data.hsqldbServer)
235
+ setServerInfo(data.hsqldbServer);
236
+ else
237
+ setServerInfo(null);
235
238
  const active = (data.bridges || []).find((b) => b.status === 'active');
236
239
  if (active)
237
240
  setBridgePort(active.port);
241
+ else
242
+ setBridgePort(null);
238
243
  }
239
244
  })
240
245
  .catch(() => { });
@@ -267,63 +272,42 @@ function JarUploadInline({ dialect, jarEndpoint, dbConfig }) {
267
272
  e.target.value = '';
268
273
  }
269
274
  };
270
- const handleStartBridge = async () => {
271
- setBridgeLoading(true);
272
- setMessage(null);
273
- try {
274
- const res = await fetch(jarEndpoint, {
275
- method: 'PATCH',
276
- headers: { 'Content-Type': 'application/json' },
277
- body: JSON.stringify({ action: 'start', dialect, ...dbConfig }),
278
- });
279
- const result = await res.json();
280
- if (result.ok) {
281
- setBridgePort(result.port || 8765);
282
- setMessage({ ok: true, text: `Bridge JDBC lance sur le port ${result.port || 8765}` });
283
- loadStatus();
284
- }
285
- else {
286
- setMessage({ ok: false, text: result.error || 'Echec lancement du bridge' });
287
- }
288
- }
289
- catch {
290
- setMessage({ ok: false, text: 'Erreur reseau' });
291
- }
292
- finally {
293
- setBridgeLoading(false);
294
- }
295
- };
296
- const handleStopBridge = async (port, pid) => {
297
- setKillingPort(port);
275
+ const patchAction = async (payload, actionLabel) => {
276
+ setLoading(actionLabel);
298
277
  setMessage(null);
299
278
  try {
300
279
  const res = await fetch(jarEndpoint, {
301
280
  method: 'PATCH',
302
281
  headers: { 'Content-Type': 'application/json' },
303
- body: JSON.stringify({ action: 'stop', port, pid }),
282
+ body: JSON.stringify(payload),
304
283
  });
305
284
  const result = await res.json();
306
285
  if (result.ok) {
307
- if (bridgePort === port)
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')
308
290
  setBridgePort(null);
309
- setMessage({ ok: true, text: result.message || 'Bridge arrete' });
310
- loadStatus();
311
291
  }
312
292
  else {
313
- setMessage({ ok: false, text: result.error || 'Echec arret du bridge' });
293
+ setMessage({ ok: false, text: result.error || 'Echec ' + actionLabel });
314
294
  }
295
+ loadStatus();
315
296
  }
316
297
  catch {
317
298
  setMessage({ ok: false, text: 'Erreur reseau' });
318
299
  }
319
300
  finally {
320
- setKillingPort(null);
301
+ setLoading(null);
321
302
  }
322
303
  };
323
- 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 && bridges.length === 0 && (_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' })), _jsx("span", { style: { fontSize: 11, color: '#9ca3af' }, children: "Ex: hsqldb*.jar, ojdbc*.jar, db2jcc*.jar" })] }), bridges.length > 0 && (_jsxs("div", { style: { marginTop: 8, padding: '8px 10px', backgroundColor: '#f0fdf4', borderRadius: 6, border: '1px solid #bbf7d0' }, children: [_jsx("div", { style: { fontSize: 11, fontWeight: 600, color: '#166534', marginBottom: 4 }, children: "Bridges actifs" }), 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: {
324
- ...S.btn('primary', killingPort === b.port), fontSize: 11, padding: '2px 8px',
325
- backgroundColor: killingPort === b.port ? '#6b7280' : '#dc2626', marginLeft: 'auto',
326
- }, onClick: () => handleStopBridge(b.port, b.pid), disabled: killingPort === b.port, children: killingPort === b.port ? '...' : 'Kill' })] }, b.port))), bridges.length === 0 || bridges.some(b => b.status === 'active') ? null : (_jsx("button", { style: { ...S.btn('primary', bridgeLoading), fontSize: 12, padding: '6px 12px', backgroundColor: '#059669', marginTop: 6 }, onClick: handleStartBridge, disabled: bridgeLoading, children: bridgeLoading ? 'Lancement...' : 'Lancer le bridge' }))] })), 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 }))] }));
327
311
  }
328
312
  // ── Main Component ───────────────────────────────────────────
329
313
  export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNamePrefix = 'mydb', persistState = true, }) {
@@ -550,7 +534,7 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
550
534
  const isSelected = selectedModules.includes(mod.key);
551
535
  const isDetected = detectedModules.includes(mod.key);
552
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));
553
- }) }), _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
554
538
  ? `✅ ${t('setup.database.success')}${dbTestResult.dbVersion ? ` (v${dbTestResult.dbVersion})` : ''}`
555
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 => {
556
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.5",
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",