@mostajs/setup 1.4.3 → 1.4.5

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,79 @@ 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
+ }
15
83
  return Response.json({
16
84
  ok: true,
17
85
  jars: listJarFiles(),
18
86
  dialects: getJdbcDialectStatus(),
87
+ bridges,
19
88
  });
20
89
  }
21
90
  catch (err) {
@@ -81,23 +150,39 @@ export function createUploadJarHandlers() {
81
150
  const { action } = body;
82
151
  if (action === 'stop') {
83
152
  const bridgePort = body.port || 8765;
84
- // Kill bridge on the given port
153
+ const bridgePid = body.pid || 0;
85
154
  try {
155
+ // 1. Try BridgeManager
86
156
  const { BridgeManager } = await import('@mostajs/orm');
87
157
  const manager = BridgeManager.getInstance();
88
158
  const bridges = manager.list();
89
159
  const bridge = bridges.find((b) => b.port === bridgePort);
90
160
  if (bridge) {
91
161
  await manager.stop(bridge.key);
92
- return Response.json({ ok: true, message: `Bridge arrete sur le port ${bridgePort}` });
93
162
  }
94
- // Fallback: kill process on port
163
+ // 2. Kill by PID if provided
164
+ if (bridgePid > 0) {
165
+ try {
166
+ process.kill(bridgePid, 'SIGKILL');
167
+ }
168
+ catch { /* already dead */ }
169
+ }
170
+ // 3. Fallback: kill process on port
95
171
  const { execSync } = await import('child_process');
96
172
  try {
97
173
  execSync(`fuser -k ${bridgePort}/tcp 2>/dev/null`, { stdio: 'ignore' });
98
174
  }
99
175
  catch { /* port may already be free */ }
100
- return Response.json({ ok: true, message: `Port ${bridgePort} libere` });
176
+ // 4. Clean PID file
177
+ try {
178
+ const { existsSync, unlinkSync } = await import('fs');
179
+ const { join } = await import('path');
180
+ const pidFile = join(process.env.MOSTA_JAR_DIR || join(process.cwd(), 'jar_files'), `.bridge-${bridgePort}.pid`);
181
+ if (existsSync(pidFile))
182
+ unlinkSync(pidFile);
183
+ }
184
+ catch { /* non-critical */ }
185
+ return Response.json({ ok: true, message: `Bridge arrete (port ${bridgePort})` });
101
186
  }
102
187
  catch (err) {
103
188
  const msg = err instanceof Error ? err.message : 'Erreur';
@@ -215,24 +215,31 @@ 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
220
  const [bridgeLoading, setBridgeLoading] = useState(false);
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 [killingPort, setKillingPort] = 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 a bridge is active, track its port
235
+ const active = (data.bridges || []).find((b) => b.status === 'active');
236
+ if (active)
237
+ setBridgePort(active.port);
232
238
  }
233
239
  })
234
240
  .catch(() => { });
235
241
  }, [dialect, jarEndpoint]);
242
+ useEffect(() => { loadStatus(); }, [loadStatus]);
236
243
  const handleUpload = async (e) => {
237
244
  const file = e.target.files?.[0];
238
245
  if (!file || !file.name.endsWith('.jar'))
@@ -273,6 +280,7 @@ function JarUploadInline({ dialect, jarEndpoint, dbConfig }) {
273
280
  if (result.ok) {
274
281
  setBridgePort(result.port || 8765);
275
282
  setMessage({ ok: true, text: `Bridge JDBC lance sur le port ${result.port || 8765}` });
283
+ loadStatus();
276
284
  }
277
285
  else {
278
286
  setMessage({ ok: false, text: result.error || 'Echec lancement du bridge' });
@@ -285,19 +293,21 @@ function JarUploadInline({ dialect, jarEndpoint, dbConfig }) {
285
293
  setBridgeLoading(false);
286
294
  }
287
295
  };
288
- const handleStopBridge = async () => {
289
- setBridgeLoading(true);
296
+ const handleStopBridge = async (port, pid) => {
297
+ setKillingPort(port);
290
298
  setMessage(null);
291
299
  try {
292
300
  const res = await fetch(jarEndpoint, {
293
301
  method: 'PATCH',
294
302
  headers: { 'Content-Type': 'application/json' },
295
- body: JSON.stringify({ action: 'stop', port: bridgePort || 8765 }),
303
+ body: JSON.stringify({ action: 'stop', port, pid }),
296
304
  });
297
305
  const result = await res.json();
298
306
  if (result.ok) {
299
- setBridgePort(null);
307
+ if (bridgePort === port)
308
+ setBridgePort(null);
300
309
  setMessage({ ok: true, text: result.message || 'Bridge arrete' });
310
+ loadStatus();
301
311
  }
302
312
  else {
303
313
  setMessage({ ok: false, text: result.error || 'Echec arret du bridge' });
@@ -307,10 +317,13 @@ function JarUploadInline({ dialect, jarEndpoint, dbConfig }) {
307
317
  setMessage({ ok: false, text: 'Erreur reseau' });
308
318
  }
309
319
  finally {
310
- setBridgeLoading(false);
320
+ setKillingPort(null);
311
321
  }
312
322
  };
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 }))] }));
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 }))] }));
314
327
  }
315
328
  // ── Main Component ───────────────────────────────────────────
316
329
  export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNamePrefix = 'mydb', persistState = true, }) {
@@ -25,6 +25,45 @@ export async function testDbConnection(params) {
25
25
  return { ok: true };
26
26
  default: {
27
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
+ }
28
67
  const { testConnection } = await import('@mostajs/orm');
29
68
  const result = await testConnection({
30
69
  dialect,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/setup",
3
- "version": "1.4.3",
3
+ "version": "1.4.5",
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",