@mostajs/setup 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Creates handlers for JAR file management API.
3
+ *
4
+ * GET — list uploaded JARs and JDBC dialect status
5
+ * POST — upload a new JAR file (multipart/form-data)
6
+ * DELETE — remove a JAR file
7
+ */
8
+ export declare function createUploadJarHandlers(): {
9
+ GET: () => Promise<Response>;
10
+ POST: (req: Request) => Promise<Response>;
11
+ DELETE: (req: Request) => Promise<Response>;
12
+ };
@@ -0,0 +1,73 @@
1
+ // @mostajs/setup — API Route factory for JAR file upload/list/delete
2
+ // Delegates to @mostajs/orm jar-upload module
3
+ // Author: Dr Hamid MADANI drmdh@msn.com
4
+ /**
5
+ * Creates handlers for JAR file management API.
6
+ *
7
+ * GET — list uploaded JARs and JDBC dialect status
8
+ * POST — upload a new JAR file (multipart/form-data)
9
+ * DELETE — remove a JAR file
10
+ */
11
+ export function createUploadJarHandlers() {
12
+ async function GET() {
13
+ try {
14
+ const { listJarFiles, getJdbcDialectStatus } = await import('@mostajs/orm');
15
+ return Response.json({
16
+ ok: true,
17
+ jars: listJarFiles(),
18
+ dialects: getJdbcDialectStatus(),
19
+ });
20
+ }
21
+ catch (err) {
22
+ const message = err instanceof Error ? err.message : 'Erreur serveur';
23
+ return Response.json({ ok: false, error: message }, { status: 500 });
24
+ }
25
+ }
26
+ async function POST(req) {
27
+ try {
28
+ const { saveJarFile } = await import('@mostajs/orm');
29
+ const formData = await req.formData();
30
+ const file = formData.get('jar');
31
+ if (!file) {
32
+ return Response.json({ ok: false, error: 'Aucun fichier JAR fourni. Utilisez le champ "jar".' }, { status: 400 });
33
+ }
34
+ if (!file.name.endsWith('.jar')) {
35
+ return Response.json({ ok: false, error: 'Le fichier doit etre un .jar' }, { status: 400 });
36
+ }
37
+ // Max 50MB
38
+ if (file.size > 50 * 1024 * 1024) {
39
+ return Response.json({ ok: false, error: 'Le fichier JAR depasse la limite de 50 MB' }, { status: 400 });
40
+ }
41
+ const arrayBuffer = await file.arrayBuffer();
42
+ const buffer = Buffer.from(arrayBuffer);
43
+ const result = saveJarFile(file.name, buffer);
44
+ if (!result.ok) {
45
+ return Response.json(result, { status: 400 });
46
+ }
47
+ return Response.json(result);
48
+ }
49
+ catch (err) {
50
+ const message = err instanceof Error ? err.message : 'Erreur serveur';
51
+ return Response.json({ ok: false, error: message }, { status: 500 });
52
+ }
53
+ }
54
+ async function DELETE(req) {
55
+ try {
56
+ const { deleteJarFile } = await import('@mostajs/orm');
57
+ const { fileName } = await req.json();
58
+ if (!fileName) {
59
+ return Response.json({ ok: false, error: 'fileName requis' }, { status: 400 });
60
+ }
61
+ const result = deleteJarFile(fileName);
62
+ if (!result.ok) {
63
+ return Response.json(result, { status: 404 });
64
+ }
65
+ return Response.json(result);
66
+ }
67
+ catch (err) {
68
+ const message = err instanceof Error ? err.message : 'Erreur serveur';
69
+ return Response.json({ ok: false, error: message }, { status: 500 });
70
+ }
71
+ }
72
+ return { GET, POST, DELETE };
73
+ }
@@ -3,6 +3,8 @@ export interface ReconfigPanelProps {
3
3
  apiEndpoint?: string;
4
4
  /** API endpoint for module detection (default: '/api/setup/detect-modules') */
5
5
  detectEndpoint?: string;
6
+ /** API endpoint for JAR upload (default: '/api/setup/upload-jar') */
7
+ jarEndpoint?: string;
6
8
  /** Translate function */
7
9
  t?: (key: string) => string;
8
10
  /** Called after successful DB change */
@@ -14,4 +16,4 @@ export interface ReconfigPanelProps {
14
16
  /** Callback to run seed after DB change */
15
17
  onSeedRequested?: () => Promise<void>;
16
18
  }
17
- export default function ReconfigPanel({ apiEndpoint, detectEndpoint, t, onDbChanged, onModulesChanged, showSeedOption, onSeedRequested, }: ReconfigPanelProps): import("react/jsx-runtime").JSX.Element;
19
+ export default function ReconfigPanel({ apiEndpoint, detectEndpoint, jarEndpoint, t, onDbChanged, onModulesChanged, showSeedOption, onSeedRequested, }: ReconfigPanelProps): import("react/jsx-runtime").JSX.Element;
@@ -3,6 +3,8 @@
3
3
  'use client';
4
4
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
5
5
  import { useState, useEffect, useCallback } from 'react';
6
+ /** Dialects that require a JDBC bridge JAR */
7
+ const JDBC_DIALECTS = ['hsqldb', 'oracle', 'db2', 'sybase', 'hana'];
6
8
  const DIALECTS = [
7
9
  { key: 'mongodb', name: 'MongoDB', icon: '🍃', defaultPort: 27017, defaultUser: '', defaultHost: 'localhost', requiresAuth: false },
8
10
  { key: 'sqlite', name: 'SQLite', icon: '📄', defaultPort: 0, defaultUser: '', defaultHost: '', requiresAuth: false },
@@ -10,13 +12,13 @@ const DIALECTS = [
10
12
  { key: 'mysql', name: 'MySQL', icon: '🐬', defaultPort: 3306, defaultUser: 'root', defaultHost: 'localhost', requiresAuth: true },
11
13
  { key: 'mariadb', name: 'MariaDB', icon: '🦭', defaultPort: 3306, defaultUser: 'root', defaultHost: 'localhost', requiresAuth: true },
12
14
  { key: 'mssql', name: 'SQL Server', icon: '🟦', defaultPort: 1433, defaultUser: 'sa', defaultHost: 'localhost', requiresAuth: true },
13
- { key: 'oracle', name: 'Oracle', icon: '🔴', defaultPort: 1521, defaultUser: 'system', defaultHost: 'localhost', requiresAuth: true, premium: true },
15
+ { key: 'oracle', name: 'Oracle', icon: '🔴', defaultPort: 1521, defaultUser: 'system', defaultHost: 'localhost', requiresAuth: true, premium: true, jdbc: true },
14
16
  { key: 'cockroachdb', name: 'CockroachDB', icon: '🪳', defaultPort: 26257, defaultUser: 'root', defaultHost: 'localhost', requiresAuth: true },
15
- { key: 'db2', name: 'IBM DB2', icon: '🏢', defaultPort: 50000, defaultUser: 'db2inst1', defaultHost: 'localhost', requiresAuth: true, premium: true },
16
- { key: 'hana', name: 'SAP HANA', icon: '💎', defaultPort: 39013, defaultUser: 'SYSTEM', defaultHost: 'localhost', requiresAuth: true, premium: true },
17
- { key: 'hsqldb', name: 'HyperSQL', icon: '⚡', defaultPort: 9001, defaultUser: 'SA', defaultHost: 'localhost', requiresAuth: false },
17
+ { key: 'db2', name: 'IBM DB2', icon: '🏢', defaultPort: 50000, defaultUser: 'db2inst1', defaultHost: 'localhost', requiresAuth: true, premium: true, jdbc: true },
18
+ { key: 'hana', name: 'SAP HANA', icon: '💎', defaultPort: 39013, defaultUser: 'SYSTEM', defaultHost: 'localhost', requiresAuth: true, premium: true, jdbc: true },
19
+ { key: 'hsqldb', name: 'HyperSQL', icon: '⚡', defaultPort: 9001, defaultUser: 'SA', defaultHost: 'localhost', requiresAuth: false, jdbc: true },
18
20
  { key: 'spanner', name: 'Cloud Spanner', icon: '☁️', defaultPort: 0, defaultUser: '', defaultHost: '', requiresAuth: false, premium: true },
19
- { key: 'sybase', name: 'Sybase ASE', icon: '🔷', defaultPort: 5000, defaultUser: 'sa', defaultHost: 'localhost', requiresAuth: true, premium: true },
21
+ { key: 'sybase', name: 'Sybase ASE', icon: '🔷', defaultPort: 5000, defaultUser: 'sa', defaultHost: 'localhost', requiresAuth: true, premium: true, jdbc: true },
20
22
  ];
21
23
  // ── Styles ────────────────────────────────────────────────────
22
24
  const S = {
@@ -108,7 +110,7 @@ const S = {
108
110
  },
109
111
  };
110
112
  // ── Component ────────────────────────────────────────────────
111
- export default function ReconfigPanel({ apiEndpoint = '/api/setup/reconfig', detectEndpoint = '/api/setup/detect-modules', t = (k) => k, onDbChanged, onModulesChanged, showSeedOption = true, onSeedRequested, }) {
113
+ export default function ReconfigPanel({ apiEndpoint = '/api/setup/reconfig', detectEndpoint = '/api/setup/detect-modules', jarEndpoint = '/api/setup/upload-jar', t = (k) => k, onDbChanged, onModulesChanged, showSeedOption = true, onSeedRequested, }) {
112
114
  // --- State ---
113
115
  const [loading, setLoading] = useState(true);
114
116
  const [currentDialect, setCurrentDialect] = useState('');
@@ -125,6 +127,11 @@ export default function ReconfigPanel({ apiEndpoint = '/api/setup/reconfig', det
125
127
  const [dbMessage, setDbMessage] = useState(null);
126
128
  const [wantSeed, setWantSeed] = useState(true);
127
129
  const [seeding, setSeeding] = useState(false);
130
+ // JAR upload
131
+ const [jarUploading, setJarUploading] = useState(false);
132
+ const [jarMessage, setJarMessage] = useState(null);
133
+ const [jarFiles, setJarFiles] = useState([]);
134
+ const [jdbcStatus, setJdbcStatus] = useState([]);
128
135
  // Module saving
129
136
  const [moduleSaving, setModuleSaving] = useState(false);
130
137
  const [moduleMessage, setModuleMessage] = useState(null);
@@ -159,6 +166,18 @@ export default function ReconfigPanel({ apiEndpoint = '/api/setup/reconfig', det
159
166
  password: '',
160
167
  });
161
168
  }
169
+ // Load JAR status
170
+ try {
171
+ const jarRes = await fetch(jarEndpoint);
172
+ const jarData = await jarRes.json();
173
+ if (jarData.ok) {
174
+ setJarFiles(jarData.jars || []);
175
+ setJdbcStatus(jarData.dialects || []);
176
+ }
177
+ }
178
+ catch {
179
+ // JAR endpoint may not exist — ignore
180
+ }
162
181
  }
163
182
  catch {
164
183
  // ignore
@@ -166,8 +185,72 @@ export default function ReconfigPanel({ apiEndpoint = '/api/setup/reconfig', det
166
185
  finally {
167
186
  setLoading(false);
168
187
  }
169
- }, [apiEndpoint, detectEndpoint]);
188
+ }, [apiEndpoint, detectEndpoint, jarEndpoint]);
170
189
  useEffect(() => { loadConfig(); }, [loadConfig]);
190
+ // --- JAR upload ---
191
+ const handleJarUpload = async (e) => {
192
+ const file = e.target.files?.[0];
193
+ if (!file)
194
+ return;
195
+ if (!file.name.endsWith('.jar')) {
196
+ setJarMessage({ type: 'error', text: 'Le fichier doit etre un .jar' });
197
+ return;
198
+ }
199
+ setJarUploading(true);
200
+ setJarMessage(null);
201
+ try {
202
+ const formData = new FormData();
203
+ formData.append('jar', file);
204
+ const res = await fetch(jarEndpoint, { method: 'POST', body: formData });
205
+ const result = await res.json();
206
+ if (result.ok) {
207
+ const msg = result.replaced
208
+ ? `${result.fileName} uploade (remplace ${result.replaced})`
209
+ : `${result.fileName} uploade`;
210
+ setJarMessage({ type: 'success', text: result.dialect ? `${msg} — dialect: ${result.dialect}` : msg });
211
+ // Refresh JAR list
212
+ const jarRes = await fetch(jarEndpoint);
213
+ const jarData = await jarRes.json();
214
+ if (jarData.ok) {
215
+ setJarFiles(jarData.jars || []);
216
+ setJdbcStatus(jarData.dialects || []);
217
+ }
218
+ }
219
+ else {
220
+ setJarMessage({ type: 'error', text: result.error || 'Erreur upload' });
221
+ }
222
+ }
223
+ catch {
224
+ setJarMessage({ type: 'error', text: 'Erreur reseau' });
225
+ }
226
+ finally {
227
+ setJarUploading(false);
228
+ // Reset input
229
+ e.target.value = '';
230
+ }
231
+ };
232
+ const handleJarDelete = async (fileName) => {
233
+ try {
234
+ const res = await fetch(jarEndpoint, {
235
+ method: 'DELETE',
236
+ headers: { 'Content-Type': 'application/json' },
237
+ body: JSON.stringify({ fileName }),
238
+ });
239
+ const result = await res.json();
240
+ if (result.ok) {
241
+ setJarMessage({ type: 'success', text: `${fileName} supprime` });
242
+ const jarRes = await fetch(jarEndpoint);
243
+ const jarData = await jarRes.json();
244
+ if (jarData.ok) {
245
+ setJarFiles(jarData.jars || []);
246
+ setJdbcStatus(jarData.dialects || []);
247
+ }
248
+ }
249
+ }
250
+ catch {
251
+ // ignore
252
+ }
253
+ };
171
254
  // --- Dialect change ---
172
255
  const handleDialectChange = (dialect) => {
173
256
  setSelectedDialect(dialect);
@@ -328,7 +411,11 @@ export default function ReconfigPanel({ apiEndpoint = '/api/setup/reconfig', det
328
411
  ...S.moduleCard(isActive, isRequired || !isInstalled),
329
412
  opacity: isInstalled ? 1 : 0.5,
330
413
  }, onClick: () => isInstalled && toggleModule(mod.key), children: [_jsxs("div", { style: S.moduleHeader, children: [_jsx("span", { style: S.moduleIcon, children: mod.icon }), _jsx("span", { style: S.moduleName, children: mod.label }), isRequired && _jsx("span", { style: S.moduleBadge('required'), children: "requis" }), isActive && !isRequired && _jsx("span", { style: S.moduleBadge('active'), children: "actif" }), !isInstalled && _jsx("span", { style: S.moduleBadge('dep'), children: "non installe" })] }), _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));
331
- }) }), _jsx("div", { style: { marginTop: 16, display: 'flex', gap: 8 }, children: _jsx("button", { style: S.btn('primary'), onClick: handleSaveModules, disabled: moduleSaving, children: moduleSaving ? 'Enregistrement...' : 'Enregistrer les modules' }) })] }), _jsxs("div", { style: S.section, children: [_jsxs("div", { style: S.sectionTitle, children: [_jsx("span", { children: "\uD83D\uDDC4\uFE0F" }), " Base de donnees", currentDialect && (_jsxs("span", { style: S.currentBadge, children: ["Actuelle : ", DIALECTS.find((d) => d.key === currentDialect)?.name || currentDialect] }))] }), _jsx("div", { style: S.sectionDesc, children: "Changez le dialecte ou les parametres de connexion. La connexion sera testee avant application." }), dbMessage && (_jsx("div", { style: S.alert(dbMessage.type), children: dbMessage.text })), _jsxs("div", { style: { marginBottom: 20 }, children: [_jsx("div", { style: { ...S.label, marginBottom: 8, fontSize: 13 }, children: "Dialecte" }), _jsx("div", { style: S.dialectGrid, children: DIALECTS.map((d) => (_jsxs("div", { style: {
414
+ }) }), _jsx("div", { style: { marginTop: 16, display: 'flex', gap: 8 }, children: _jsx("button", { style: S.btn('primary'), onClick: handleSaveModules, disabled: moduleSaving, children: moduleSaving ? 'Enregistrement...' : 'Enregistrer les modules' }) })] }), _jsxs("div", { style: S.section, children: [_jsxs("div", { style: S.sectionTitle, children: [_jsx("span", { children: "\u2615" }), " Drivers JDBC"] }), _jsx("div", { style: S.sectionDesc, children: "Uploadez les fichiers JAR des drivers JDBC pour les bases de donnees enterprise. Les dialects JDBC (HyperSQL, Oracle, DB2, SAP HANA, Sybase) utilisent un bridge Java pour se connecter." }), jarMessage && (_jsx("div", { style: S.alert(jarMessage.type), children: jarMessage.text })), jdbcStatus.length > 0 && (_jsx("div", { style: { marginBottom: 16 }, children: _jsxs("table", { style: { width: '100%', borderCollapse: 'collapse', fontSize: 13 }, children: [_jsx("thead", { children: _jsxs("tr", { style: { borderBottom: '2px solid #e5e7eb', textAlign: 'left' }, children: [_jsx("th", { style: { padding: '8px 12px', fontWeight: 600 }, children: "Dialect" }), _jsx("th", { style: { padding: '8px 12px', fontWeight: 600 }, children: "Statut" }), _jsx("th", { style: { padding: '8px 12px', fontWeight: 600 }, children: "Fichier JAR" }), _jsx("th", { style: { padding: '8px 12px', fontWeight: 600 } })] }) }), _jsx("tbody", { children: jdbcStatus.map((s) => (_jsxs("tr", { style: { borderBottom: '1px solid #f3f4f6' }, children: [_jsx("td", { style: { padding: '8px 12px', fontWeight: 500 }, children: s.label }), _jsx("td", { style: { padding: '8px 12px' }, children: s.hasJar ? (_jsx("span", { style: { color: '#059669', fontWeight: 600, fontSize: 12 }, children: "Pret" })) : (_jsx("span", { style: { color: '#9ca3af', fontSize: 12 }, children: "Non installe" })) }), _jsx("td", { style: { padding: '8px 12px', fontSize: 12, color: '#6b7280', fontFamily: 'monospace' }, children: s.jarFile || '—' }), _jsx("td", { style: { padding: '8px 12px' }, children: s.hasJar && s.jarFile && (_jsx("button", { style: { ...S.btn('danger'), padding: '3px 8px', fontSize: 11 }, onClick: () => handleJarDelete(s.jarFile), children: "Supprimer" })) })] }, s.dialect))) })] }) })), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 12 }, children: [_jsxs("label", { style: {
415
+ ...S.btn('primary'),
416
+ cursor: jarUploading ? 'wait' : 'pointer',
417
+ opacity: jarUploading ? 0.6 : 1,
418
+ }, children: [jarUploading ? 'Upload en cours...' : 'Uploader un fichier .jar', _jsx("input", { type: "file", accept: ".jar", onChange: handleJarUpload, disabled: jarUploading, style: { display: 'none' } })] }), _jsx("span", { style: { fontSize: 12, color: '#9ca3af' }, children: "Formats acceptes : hsqldb*.jar, ojdbc*.jar, db2jcc*.jar, ngdbc*.jar, jconn*.jar" })] })] }), _jsxs("div", { style: S.section, children: [_jsxs("div", { style: S.sectionTitle, children: [_jsx("span", { children: "\uD83D\uDDC4\uFE0F" }), " Base de donnees", currentDialect && (_jsxs("span", { style: S.currentBadge, children: ["Actuelle : ", DIALECTS.find((d) => d.key === currentDialect)?.name || currentDialect] }))] }), _jsx("div", { style: S.sectionDesc, children: "Changez le dialecte ou les parametres de connexion. La connexion sera testee avant application." }), dbMessage && (_jsx("div", { style: S.alert(dbMessage.type), children: dbMessage.text })), _jsxs("div", { style: { marginBottom: 20 }, children: [_jsx("div", { style: { ...S.label, marginBottom: 8, fontSize: 13 }, children: "Dialecte" }), _jsx("div", { style: S.dialectGrid, children: DIALECTS.map((d) => (_jsxs("div", { style: {
332
419
  ...S.dialectCard(selectedDialect === d.key && !d.premium),
333
420
  ...(d.premium ? { opacity: 0.45, cursor: 'not-allowed', filter: 'grayscale(0.5)' } : {}),
334
421
  }, onClick: () => !d.premium && handleDialectChange(d.key), title: d.premium ? `${d.name} — disponible en version Premium` : d.name, children: [_jsx("div", { style: S.dialectIcon, children: d.icon }), _jsxs("div", { style: S.dialectName, children: [d.name, d.key === currentDialect && _jsx("span", { style: { fontSize: 9, color: '#059669' }, children: " (actuel)" }), d.premium && (_jsx("div", { style: {
package/dist/index.d.ts CHANGED
@@ -11,6 +11,7 @@ export { createStatusHandler } from './api/status.route';
11
11
  export { createDetectModulesHandler } from './api/detect-modules.route';
12
12
  export { createInstallModulesHandler } from './api/install-modules.route';
13
13
  export { createReconfigHandlers } from './api/reconfig.route';
14
+ export { createUploadJarHandlers } from './api/upload-jar.route';
14
15
  export { default as ReconfigPanel } from './components/ReconfigPanel';
15
16
  export { setupMenuContribution } from './lib/menu';
16
17
  export type { DialectType, DialectInfo, DbConfig, InstallConfig, SeedOptions, SeedDefinition, MostaSetupConfig, ModuleDefinition, } from './types/index';
package/dist/index.js CHANGED
@@ -17,6 +17,7 @@ export { createStatusHandler } from './api/status.route';
17
17
  export { createDetectModulesHandler } from './api/detect-modules.route';
18
18
  export { createInstallModulesHandler } from './api/install-modules.route';
19
19
  export { createReconfigHandlers } from './api/reconfig.route';
20
+ export { createUploadJarHandlers } from './api/upload-jar.route';
20
21
  // Components
21
22
  export { default as ReconfigPanel } from './components/ReconfigPanel';
22
23
  // Menu contribution
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/setup",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
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",
@@ -47,6 +47,11 @@
47
47
  "types": "./dist/api/reconfig.route.d.ts",
48
48
  "import": "./dist/api/reconfig.route.js",
49
49
  "default": "./dist/api/reconfig.route.js"
50
+ },
51
+ "./api/upload-jar": {
52
+ "types": "./dist/api/upload-jar.route.d.ts",
53
+ "import": "./dist/api/upload-jar.route.js",
54
+ "default": "./dist/api/upload-jar.route.js"
50
55
  }
51
56
  },
52
57
  "files": [
@@ -76,7 +81,7 @@
76
81
  "prepublishOnly": "npm run build"
77
82
  },
78
83
  "dependencies": {
79
- "@mostajs/orm": "^1.2.0",
84
+ "@mostajs/orm": "^1.3.0",
80
85
  "bcryptjs": "^2.4.3"
81
86
  },
82
87
  "devDependencies": {