@mostajs/setup 1.5.1 → 2.0.1
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.
- package/dist/api/create-db.route.d.ts +7 -0
- package/dist/api/create-db.route.js +29 -0
- package/dist/api/detect-modules.route.js +1 -1
- package/dist/api/install-modules.route.js +1 -1
- package/dist/api/install.route.d.ts +1 -1
- package/dist/api/install.route.js +1 -1
- package/dist/api/preflight.route.d.ts +13 -0
- package/dist/api/preflight.route.js +136 -0
- package/dist/api/reconfig.route.js +3 -3
- package/dist/api/test-db.route.js +1 -1
- package/dist/api/upload-jar.route.js +1 -1
- package/dist/api/upload-setup-json.route.js +2 -0
- package/dist/components/SetupWizard.d.ts +25 -1
- package/dist/components/SetupWizard.js +81 -8
- package/dist/data/dialects.d.ts +1 -1
- package/dist/index.d.ts +24 -22
- package/dist/index.js +21 -21
- package/dist/lib/compose-uri.d.ts +1 -1
- package/dist/lib/db-test.d.ts +2 -1
- package/dist/lib/db-test.js +12 -68
- package/dist/lib/discover-modules.d.ts +1 -1
- package/dist/lib/discover-modules.js +1 -1
- package/dist/lib/env-writer.d.ts +1 -1
- package/dist/lib/load-setup-json.d.ts +11 -1
- package/dist/lib/load-setup-json.js +7 -2
- package/dist/lib/setup.d.ts +1 -1
- package/dist/lib/setup.js +2 -2
- package/dist/register.d.ts +1 -0
- package/dist/register.js +1 -2
- package/dist/types/index.d.ts +1 -1
- package/package.json +2 -7
- package/schemas/setup.schema.json +39 -0
- package/dist/lib/menu.d.ts +0 -2
- package/dist/lib/menu.js +0 -22
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// @mosta/setup — Create Database route (delegates to @mostajs/orm)
|
|
2
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
+
//
|
|
4
|
+
// Copy to: src/app/api/setup/create-db/route.ts
|
|
5
|
+
import { composeDbUri } from '../lib/compose-uri.js';
|
|
6
|
+
/**
|
|
7
|
+
* Creates a POST handler that creates a database.
|
|
8
|
+
* Delegates entirely to @mostajs/orm createDatabase().
|
|
9
|
+
*/
|
|
10
|
+
export function createCreateDbHandler() {
|
|
11
|
+
async function POST(req) {
|
|
12
|
+
try {
|
|
13
|
+
const body = await req.json();
|
|
14
|
+
const { dialect, host, port, name, user, password } = body;
|
|
15
|
+
if (!dialect || !name) {
|
|
16
|
+
return Response.json({ ok: false, error: 'dialect et name requis' }, { status: 400 });
|
|
17
|
+
}
|
|
18
|
+
const uri = composeDbUri(dialect, { host, port, name, user, password });
|
|
19
|
+
const { createDatabase } = await import('@mostajs/orm');
|
|
20
|
+
const result = await createDatabase(dialect, uri, name);
|
|
21
|
+
return Response.json(result);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
const message = err instanceof Error ? err.message : 'Erreur création base';
|
|
25
|
+
return Response.json({ ok: false, error: message });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return { POST };
|
|
29
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @mosta/setup — Detect modules API route factory
|
|
2
2
|
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
-
import { discoverNpmModules } from '../lib/discover-modules';
|
|
3
|
+
import { discoverNpmModules } from '../lib/discover-modules.js';
|
|
4
4
|
export function createDetectModulesHandler() {
|
|
5
5
|
async function GET() {
|
|
6
6
|
const result = await discoverNpmModules();
|
|
@@ -4,7 +4,7 @@ import { exec } from 'child_process';
|
|
|
4
4
|
import { promisify } from 'util';
|
|
5
5
|
import fs from 'fs';
|
|
6
6
|
import path from 'path';
|
|
7
|
-
import { MODULES, resolveModuleDependencies } from '../data/module-definitions';
|
|
7
|
+
import { MODULES, resolveModuleDependencies } from '../data/module-definitions.js';
|
|
8
8
|
const execAsync = promisify(exec);
|
|
9
9
|
/**
|
|
10
10
|
* Factory for POST /api/setup/install-modules
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface PreflightCheck {
|
|
2
|
+
key: string;
|
|
3
|
+
label: string;
|
|
4
|
+
status: 'ok' | 'warn' | 'fail';
|
|
5
|
+
detail: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Creates a GET handler for preflight environment checks.
|
|
9
|
+
* Returns an array of checks: env file, dialect, URI, setup.json, DB connection, users count, Node.js version.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createPreflightHandler(): {
|
|
12
|
+
GET: () => Promise<Response>;
|
|
13
|
+
};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// @mosta/setup — Preflight checks: env, DB connection, DB exists, users
|
|
2
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
+
//
|
|
4
|
+
// Copy to: src/app/api/setup/preflight/route.ts
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
/**
|
|
8
|
+
* Creates a GET handler for preflight environment checks.
|
|
9
|
+
* Returns an array of checks: env file, dialect, URI, setup.json, DB connection, users count, Node.js version.
|
|
10
|
+
*/
|
|
11
|
+
export function createPreflightHandler() {
|
|
12
|
+
async function GET() {
|
|
13
|
+
const checks = [];
|
|
14
|
+
const cwd = process.cwd();
|
|
15
|
+
// 1. .env.local
|
|
16
|
+
const envExists = existsSync(join(cwd, '.env.local'));
|
|
17
|
+
checks.push({
|
|
18
|
+
key: 'envFile',
|
|
19
|
+
label: '.env.local',
|
|
20
|
+
status: envExists ? 'ok' : 'warn',
|
|
21
|
+
detail: envExists ? 'Fichier trouvé' : 'Fichier absent — sera créé lors de l\'installation',
|
|
22
|
+
});
|
|
23
|
+
// 2. DB_DIALECT
|
|
24
|
+
const dialect = process.env.DB_DIALECT;
|
|
25
|
+
checks.push({
|
|
26
|
+
key: 'dialect',
|
|
27
|
+
label: 'DB_DIALECT',
|
|
28
|
+
status: dialect ? 'ok' : 'warn',
|
|
29
|
+
detail: dialect ? `Dialect configuré : ${dialect}` : 'Non défini — à configurer',
|
|
30
|
+
});
|
|
31
|
+
// 3. SGBD_URI (masquer le mot de passe)
|
|
32
|
+
const uri = process.env.SGBD_URI;
|
|
33
|
+
checks.push({
|
|
34
|
+
key: 'uri',
|
|
35
|
+
label: 'SGBD_URI',
|
|
36
|
+
status: uri ? 'ok' : 'warn',
|
|
37
|
+
detail: uri
|
|
38
|
+
? `URI : ${uri.replace(/\/\/([^:]+):([^@]+)@/, '//$1:***@')}`
|
|
39
|
+
: 'Non définie — à configurer',
|
|
40
|
+
});
|
|
41
|
+
// 4. setup.json
|
|
42
|
+
const setupJsonExists = existsSync(join(cwd, 'setup.json'));
|
|
43
|
+
checks.push({
|
|
44
|
+
key: 'setupJson',
|
|
45
|
+
label: 'setup.json',
|
|
46
|
+
status: setupJsonExists ? 'ok' : 'warn',
|
|
47
|
+
detail: setupJsonExists ? 'Fichier de configuration trouvé' : 'Fichier absent',
|
|
48
|
+
});
|
|
49
|
+
// 5. DB connection + users
|
|
50
|
+
if (dialect && uri) {
|
|
51
|
+
try {
|
|
52
|
+
const { getDialect, BaseRepository, registerSchemas } = await import('@mostajs/orm');
|
|
53
|
+
const d = await getDialect();
|
|
54
|
+
if (d) {
|
|
55
|
+
checks.push({
|
|
56
|
+
key: 'dbConnection',
|
|
57
|
+
label: 'Connexion DB',
|
|
58
|
+
status: 'ok',
|
|
59
|
+
detail: `Connecté (${d.constructor.name})`,
|
|
60
|
+
});
|
|
61
|
+
// Count users
|
|
62
|
+
try {
|
|
63
|
+
const UserSchema = {
|
|
64
|
+
name: 'User', collection: 'users', timestamps: true,
|
|
65
|
+
fields: { email: { type: 'string' } }, relations: {}, indexes: [],
|
|
66
|
+
};
|
|
67
|
+
registerSchemas([UserSchema]);
|
|
68
|
+
const repo = new BaseRepository(UserSchema, d);
|
|
69
|
+
const count = await repo.count();
|
|
70
|
+
checks.push({
|
|
71
|
+
key: 'users',
|
|
72
|
+
label: 'Utilisateurs',
|
|
73
|
+
status: count > 0 ? 'ok' : 'warn',
|
|
74
|
+
detail: count > 0
|
|
75
|
+
? `${count} utilisateur(s) trouvé(s)`
|
|
76
|
+
: 'Aucun utilisateur — installation requise',
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
checks.push({
|
|
81
|
+
key: 'users',
|
|
82
|
+
label: 'Utilisateurs',
|
|
83
|
+
status: 'warn',
|
|
84
|
+
detail: 'Table users inexistante — installation requise',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
checks.push({
|
|
90
|
+
key: 'dbConnection',
|
|
91
|
+
label: 'Connexion DB',
|
|
92
|
+
status: 'fail',
|
|
93
|
+
detail: 'Connexion échouée (dialect null)',
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
const msg = err instanceof Error ? err.message : 'Erreur inconnue';
|
|
99
|
+
const dbNotExist = msg.includes('does not exist') || msg.includes('Unknown database');
|
|
100
|
+
checks.push({
|
|
101
|
+
key: 'dbConnection',
|
|
102
|
+
label: 'Connexion DB',
|
|
103
|
+
status: 'fail',
|
|
104
|
+
detail: dbNotExist
|
|
105
|
+
? 'Base de données introuvable — à créer'
|
|
106
|
+
: `Erreur : ${msg}`,
|
|
107
|
+
});
|
|
108
|
+
if (dbNotExist) {
|
|
109
|
+
checks.push({
|
|
110
|
+
key: 'dbExists',
|
|
111
|
+
label: 'Base de données',
|
|
112
|
+
status: 'fail',
|
|
113
|
+
detail: 'La base n\'existe pas — utilisez l\'étape Connexion pour la créer',
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
checks.push({
|
|
120
|
+
key: 'dbConnection',
|
|
121
|
+
label: 'Connexion DB',
|
|
122
|
+
status: 'warn',
|
|
123
|
+
detail: 'Pas de configuration DB — à configurer',
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
// 6. Node.js
|
|
127
|
+
checks.push({
|
|
128
|
+
key: 'node',
|
|
129
|
+
label: 'Node.js',
|
|
130
|
+
status: 'ok',
|
|
131
|
+
detail: `Version ${process.version}`,
|
|
132
|
+
});
|
|
133
|
+
return Response.json({ checks });
|
|
134
|
+
}
|
|
135
|
+
return { GET };
|
|
136
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// @mostajs/setup — API Route factory for reconfiguration
|
|
2
2
|
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
-
import { testDbConnection } from '../lib/db-test';
|
|
4
|
-
import { writeEnvLocal } from '../lib/env-writer';
|
|
5
|
-
import { composeDbUri } from '../lib/compose-uri';
|
|
3
|
+
import { testDbConnection } from '../lib/db-test.js';
|
|
4
|
+
import { writeEnvLocal } from '../lib/env-writer.js';
|
|
5
|
+
import { composeDbUri } from '../lib/compose-uri.js';
|
|
6
6
|
/**
|
|
7
7
|
* Creates handlers for the reconfiguration API.
|
|
8
8
|
*
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
3
|
//
|
|
4
4
|
// Copy to: src/app/api/setup/test-db/route.ts
|
|
5
|
-
import { testDbConnection } from '../lib/db-test';
|
|
5
|
+
import { testDbConnection } from '../lib/db-test.js';
|
|
6
6
|
/**
|
|
7
7
|
* Creates a POST handler for testing DB connections.
|
|
8
8
|
*/
|
|
@@ -376,7 +376,7 @@ export function createUploadJarHandlers() {
|
|
|
376
376
|
if (!dialect) {
|
|
377
377
|
return Response.json({ ok: false, error: 'dialect requis' }, { status: 400 });
|
|
378
378
|
}
|
|
379
|
-
const { composeDbUri } = await import('../lib/compose-uri');
|
|
379
|
+
const { composeDbUri } = await import('../lib/compose-uri.js');
|
|
380
380
|
const uri = composeDbUri(dialect, {
|
|
381
381
|
host: host || 'localhost',
|
|
382
382
|
port: port || 0,
|
|
@@ -25,8 +25,10 @@ export function createSetupJsonHandler(needsSetup) {
|
|
|
25
25
|
exists: true,
|
|
26
26
|
config: {
|
|
27
27
|
appName: json.app?.name,
|
|
28
|
+
dbNamePrefix: json.app?.dbNamePrefix,
|
|
28
29
|
hasRbac: !!(json.rbac?.roles?.length || json.rbac?.permissions?.length),
|
|
29
30
|
seedCount: json.seeds?.length ?? 0,
|
|
31
|
+
modules: json.modules ?? [],
|
|
30
32
|
},
|
|
31
33
|
});
|
|
32
34
|
}
|
|
@@ -13,10 +13,34 @@ export interface SetupWizardProps {
|
|
|
13
13
|
wireModule?: string;
|
|
14
14
|
/** Seed endpoint — runs module seeds from the runtime registry */
|
|
15
15
|
seed?: string;
|
|
16
|
+
/** Preflight checks endpoint */
|
|
17
|
+
preflight?: string;
|
|
18
|
+
/** Create database endpoint */
|
|
19
|
+
createDb?: string;
|
|
16
20
|
};
|
|
17
21
|
/** Default database name prefix (e.g. 'secuaccessdb') */
|
|
18
22
|
dbNamePrefix?: string;
|
|
19
23
|
/** Whether to persist wizard state in sessionStorage (default: true) */
|
|
20
24
|
persistState?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Show the modules selection step even without a detectModules endpoint.
|
|
27
|
+
* When true and no detectModules endpoint, uses the built-in module list.
|
|
28
|
+
* Default: true
|
|
29
|
+
*/
|
|
30
|
+
showModules?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Full module definitions from setup.json (section "modules").
|
|
33
|
+
* When provided, the wizard shows these modules directly
|
|
34
|
+
* instead of calling the detectModules endpoint.
|
|
35
|
+
*/
|
|
36
|
+
declaredModules?: {
|
|
37
|
+
key: string;
|
|
38
|
+
packageName?: string;
|
|
39
|
+
label?: string;
|
|
40
|
+
description?: string;
|
|
41
|
+
icon?: string;
|
|
42
|
+
required?: boolean;
|
|
43
|
+
dependsOn?: string[];
|
|
44
|
+
}[];
|
|
21
45
|
}
|
|
22
|
-
export default function SetupWizard({ t: tProp, onComplete, endpoints, dbNamePrefix, persistState, }: SetupWizardProps): import("react/jsx-runtime").JSX.Element;
|
|
46
|
+
export default function SetupWizard({ t: tProp, onComplete, endpoints, dbNamePrefix, persistState, showModules, declaredModules, }: SetupWizardProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -344,10 +344,10 @@ function JarUploadInline({ dialect, jarEndpoint, dbConfig }) {
|
|
|
344
344
|
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", _jsxs("span", { style: { fontWeight: 400, color: '#6b7280', marginLeft: 4 }, children: ["(port SGBD : ", dbConfig.port || 9001, ")"] }), serverInfo?.running && (_jsxs("span", { style: { fontWeight: 400, color: '#059669', marginLeft: 8 }, children: ["En marche sur 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 || 9001 }, 'start-server'), disabled: loading === 'start-server' || !!serverInfo?.running, children: loading === 'start-server' ? 'Demarrage...' : `Demarrer le serveur (port ${dbConfig.port || 9001})` }), _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", _jsxs("span", { style: { fontWeight: 400, color: '#6b7280', marginLeft: 4 }, children: ["(port bridge : ", bridgePort || 8765, ")"] }), bridgePort && (_jsxs("span", { style: { fontWeight: 400, color: '#059669', marginLeft: 8 }, children: ["Actif sur port ", bridgePort] })), !bridgePort && bridges.length === 0 && (_jsx("span", { style: { fontWeight: 400, color: '#dc2626', 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 (SGBD ${dbConfig.host || 'localhost'}:${dbConfig.port || 9001})` }) }), 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: ["Bridge port :", 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 }))] }));
|
|
345
345
|
}
|
|
346
346
|
// ── Main Component ───────────────────────────────────────────
|
|
347
|
-
export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNamePrefix = 'mydb', persistState = true, }) {
|
|
347
|
+
export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNamePrefix = 'mydb', persistState = true, showModules = true, declaredModules, }) {
|
|
348
348
|
const t = tProp || ((k) => k);
|
|
349
|
-
// Modules step is
|
|
350
|
-
const hasModulesStep =
|
|
349
|
+
// Modules step is shown unless explicitly disabled
|
|
350
|
+
const hasModulesStep = showModules !== false;
|
|
351
351
|
const STEPS = hasModulesStep
|
|
352
352
|
? ALL_STEPS
|
|
353
353
|
: ALL_STEPS.filter(s => s !== 'modules');
|
|
@@ -359,6 +359,8 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
|
|
|
359
359
|
uploadJar: endpoints.uploadJar || '/api/setup/upload-jar',
|
|
360
360
|
wireModule: endpoints.wireModule || '',
|
|
361
361
|
seed: endpoints.seed || '',
|
|
362
|
+
preflight: endpoints.preflight || '/api/setup/preflight',
|
|
363
|
+
createDb: endpoints.createDb || '/api/setup/create-db',
|
|
362
364
|
};
|
|
363
365
|
// --- State ---
|
|
364
366
|
const [currentStep, setCurrentStep] = useState(0);
|
|
@@ -380,6 +382,11 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
|
|
|
380
382
|
const [wireLoading, setWireLoading] = useState(false);
|
|
381
383
|
const [wireBusy, setWireBusy] = useState(null);
|
|
382
384
|
const [wireMessage, setWireMessage] = useState(null);
|
|
385
|
+
const [preflightChecks, setPreflightChecks] = useState([]);
|
|
386
|
+
const [preflightLoading, setPreflightLoading] = useState(false);
|
|
387
|
+
// Create DB
|
|
388
|
+
const [creatingDb, setCreatingDb] = useState(false);
|
|
389
|
+
const [createDbResult, setCreateDbResult] = useState(null);
|
|
383
390
|
const step = STEPS[currentStep];
|
|
384
391
|
// --- Persist / Restore ---
|
|
385
392
|
useEffect(() => {
|
|
@@ -416,12 +423,30 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
|
|
|
416
423
|
}
|
|
417
424
|
catch { }
|
|
418
425
|
}, [hydrated, persistState, currentStep, dialect, dbConfig, adminConfig, seedOptions, selectedModules]);
|
|
419
|
-
// --- Detect modules
|
|
426
|
+
// --- Detect modules ---
|
|
420
427
|
useEffect(() => {
|
|
428
|
+
// From setup.json (passed as prop)
|
|
429
|
+
if (declaredModules && declaredModules.length > 0) {
|
|
430
|
+
const mods = declaredModules.map(m => ({
|
|
431
|
+
key: m.key,
|
|
432
|
+
label: m.label ?? m.key,
|
|
433
|
+
description: m.description ?? '',
|
|
434
|
+
icon: m.icon ?? '📦',
|
|
435
|
+
required: m.required,
|
|
436
|
+
default: true,
|
|
437
|
+
dependsOn: m.dependsOn,
|
|
438
|
+
}));
|
|
439
|
+
setAvailableModules(mods);
|
|
440
|
+
setSelectedModules(declaredModules.map(m => m.key));
|
|
441
|
+
setModulesDetected(true);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
// No modules and no endpoint → skip
|
|
421
445
|
if (!ep.detectModules) {
|
|
422
446
|
setModulesDetected(true);
|
|
423
447
|
return;
|
|
424
448
|
}
|
|
449
|
+
// From API endpoint
|
|
425
450
|
fetch(ep.detectModules)
|
|
426
451
|
.then(r => r.json())
|
|
427
452
|
.then((data) => {
|
|
@@ -531,6 +556,47 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
|
|
|
531
556
|
setDbConfig({ ...defaults, name: defaults.name === 'mydb' ? dbNamePrefix : defaults.name === 'mydb_prod' ? `${dbNamePrefix}_prod` : defaults.name.replace('mydb', dbNamePrefix) });
|
|
532
557
|
setDbTestResult(null);
|
|
533
558
|
}
|
|
559
|
+
// --- Preflight checks ---
|
|
560
|
+
async function runPreflight() {
|
|
561
|
+
setPreflightLoading(true);
|
|
562
|
+
try {
|
|
563
|
+
const res = await fetch(ep.preflight);
|
|
564
|
+
const data = await res.json();
|
|
565
|
+
setPreflightChecks(data.checks || []);
|
|
566
|
+
}
|
|
567
|
+
catch {
|
|
568
|
+
setPreflightChecks([{ key: 'error', label: 'Preflight', status: 'fail', detail: 'Erreur réseau' }]);
|
|
569
|
+
}
|
|
570
|
+
setPreflightLoading(false);
|
|
571
|
+
}
|
|
572
|
+
// Auto-run preflight on welcome step
|
|
573
|
+
useEffect(() => {
|
|
574
|
+
if (step === 'welcome' && preflightChecks.length === 0) {
|
|
575
|
+
runPreflight();
|
|
576
|
+
}
|
|
577
|
+
}, [step]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
578
|
+
// --- Create Database ---
|
|
579
|
+
async function createDatabase() {
|
|
580
|
+
setCreatingDb(true);
|
|
581
|
+
setCreateDbResult(null);
|
|
582
|
+
try {
|
|
583
|
+
const res = await fetch(ep.createDb, {
|
|
584
|
+
method: 'POST',
|
|
585
|
+
headers: { 'Content-Type': 'application/json' },
|
|
586
|
+
body: JSON.stringify({ dialect, ...dbConfig }),
|
|
587
|
+
});
|
|
588
|
+
const data = await res.json();
|
|
589
|
+
setCreateDbResult(data);
|
|
590
|
+
if (data.ok) {
|
|
591
|
+
// Auto-run test after creation
|
|
592
|
+
setDbTestResult(null);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
catch (err) {
|
|
596
|
+
setCreateDbResult({ ok: false, error: err instanceof Error ? err.message : 'Erreur réseau' });
|
|
597
|
+
}
|
|
598
|
+
setCreatingDb(false);
|
|
599
|
+
}
|
|
534
600
|
// --- Test DB ---
|
|
535
601
|
async function testDb() {
|
|
536
602
|
setDbTesting(true);
|
|
@@ -634,7 +700,12 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
|
|
|
634
700
|
onComplete?.();
|
|
635
701
|
}
|
|
636
702
|
// ── Render ─────────────────────────────────────────────────
|
|
637
|
-
return (_jsx("div", { style: S.wrapper, children: _jsxs("div", { style: S.container, children: [_jsx("div", { style: S.stepperRow, children: STEPS.map((s, i) => (_jsxs("div", { style: S.flex(8), children: [_jsx("div", { style: S.stepCircle(i < currentStep ? 'done' : i === currentStep ? 'current' : 'future'), children: i < currentStep ? '✓' : i + 1 }), _jsx("span", { style: S.stepLabel(i === currentStep), children: t(`setup.steps.${s}`) }), i < STEPS.length - 1 && _jsx("div", { style: S.stepLine(i < currentStep) })] }, s))) }), _jsxs("div", { style: S.card, children: [step === 'welcome' && (_jsxs("div", { style: S.center, children: [_jsx("div", { style: { width: 80, height: 80, borderRadius: '50%', backgroundColor: '#e0f2fe', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 24px', fontSize: 40 }, children: "\uD83D\uDEE1\uFE0F" }), _jsx("h2", { style: { fontSize: 24, fontWeight: 700, color: '#111827', marginBottom: 8 }, children: t('setup.welcome.title') }), _jsx("p", { style: { color: '#6b7280', marginBottom:
|
|
703
|
+
return (_jsx("div", { style: S.wrapper, children: _jsxs("div", { style: S.container, children: [_jsx("div", { style: S.stepperRow, children: STEPS.map((s, i) => (_jsxs("div", { style: S.flex(8), children: [_jsx("div", { style: S.stepCircle(i < currentStep ? 'done' : i === currentStep ? 'current' : 'future'), children: i < currentStep ? '✓' : i + 1 }), _jsx("span", { style: S.stepLabel(i === currentStep), children: t(`setup.steps.${s}`) }), i < STEPS.length - 1 && _jsx("div", { style: S.stepLine(i < currentStep) })] }, s))) }), _jsxs("div", { style: S.card, children: [step === 'welcome' && (_jsxs("div", { children: [_jsxs("div", { style: S.center, children: [_jsx("div", { style: { width: 80, height: 80, borderRadius: '50%', backgroundColor: '#e0f2fe', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 24px', fontSize: 40 }, children: "\uD83D\uDEE1\uFE0F" }), _jsx("h2", { style: { fontSize: 24, fontWeight: 700, color: '#111827', marginBottom: 8 }, children: t('setup.welcome.title') }), _jsx("p", { style: { color: '#6b7280', marginBottom: 16 }, children: t('setup.welcome.description') })] }), _jsxs("div", { style: { margin: '16px 0 24px', padding: 16, backgroundColor: '#f9fafb', border: '1px solid #e5e7eb', borderRadius: 10 }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }, children: [_jsxs("div", { style: { fontSize: 14, fontWeight: 600, color: '#374151' }, children: ["\uD83D\uDD0D ", t('setup.welcome.checks')] }), _jsxs("button", { style: { ...S.btn('outline'), fontSize: 12, padding: '4px 12px' }, onClick: runPreflight, disabled: preflightLoading, children: [preflightLoading ? '⏳' : '🔄', " ", t('setup.welcome.recheck')] })] }), preflightLoading && preflightChecks.length === 0 ? (_jsxs("div", { style: { textAlign: 'center', padding: 16, color: '#6b7280', fontSize: 13 }, children: ["\u23F3 ", t('setup.welcome.checking')] })) : (_jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: preflightChecks.map(check => (_jsxs("div", { style: {
|
|
704
|
+
display: 'flex', alignItems: 'center', gap: 10, padding: '8px 12px',
|
|
705
|
+
backgroundColor: check.status === 'ok' ? '#f0fdf4' : check.status === 'warn' ? '#fffbeb' : '#fef2f2',
|
|
706
|
+
border: `1px solid ${check.status === 'ok' ? '#bbf7d0' : check.status === 'warn' ? '#fde68a' : '#fecaca'}`,
|
|
707
|
+
borderRadius: 6, fontSize: 13,
|
|
708
|
+
}, children: [_jsx("span", { style: { fontSize: 16, flexShrink: 0 }, children: check.status === 'ok' ? '✅' : check.status === 'warn' ? '⚠️' : '❌' }), _jsxs("div", { style: { flex: 1 }, children: [_jsx("span", { style: { fontWeight: 600, color: '#111827' }, children: check.label }), _jsx("span", { style: { color: '#6b7280', marginLeft: 8 }, children: check.detail })] })] }, check.key))) }))] }), _jsx("div", { style: S.center, children: _jsxs("button", { style: S.btn('lg'), onClick: goNext, children: [t('setup.welcome.start'), " \u2192"] }) })] })), step === 'modules' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\uD83D\uDCE6" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.modules.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.modules.description') })] })] }), _jsx("div", { style: S.grid2, children: availableModules.map(mod => {
|
|
638
709
|
const isSelected = selectedModules.includes(mod.key);
|
|
639
710
|
const isDetected = detectedModules.includes(mod.key);
|
|
640
711
|
return (_jsxs("div", { style: S.moduleCard(isSelected, !!mod.required), onClick: () => toggleModule(mod.key), children: [_jsxs("div", { style: S.moduleHeader, children: [_jsxs("div", { style: S.moduleLeft, children: [_jsx("span", { style: { fontSize: 20 }, children: mod.icon }), _jsx("span", { style: S.moduleName, children: mod.label })] }), _jsxs("div", { style: S.moduleBadges, children: [mod.discovered && _jsx("span", { style: S.badge('new'), children: "Nouveau" }), isDetected && _jsx("span", { style: S.badge('installed'), children: t('setup.modules.installed') }), mod.required && _jsx("span", { style: S.badge('required'), children: t('setup.modules.required') }), _jsx("input", { type: "checkbox", checked: isSelected, disabled: mod.required, readOnly: true, style: S.checkbox })] })] }), _jsx("div", { style: S.moduleDesc, children: mod.description }), mod.dependsOn?.length ? (_jsxs("div", { style: { fontSize: 11, color: '#9ca3af', marginTop: 4 }, children: ["Depend de : ", mod.dependsOn.join(', ')] })) : null] }, mod.key));
|
|
@@ -644,9 +715,11 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
|
|
|
644
715
|
color: mod.type === 'business' ? '#1e40af' : '#6b21a8',
|
|
645
716
|
}, children: mod.type })] }), _jsxs("div", { style: { fontSize: 12, color: '#6b7280', fontFamily: 'monospace' }, children: ["v", mod.version] }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: 4 }, children: [_jsx("span", { style: { fontSize: 12, fontWeight: 600, color: mod.installed ? '#059669' : '#6b7280' }, children: mod.installed ? 'ON' : 'OFF' }), _jsx("button", { style: S.toggleBtn(mod.installed, wireBusy === mod.name), onClick: (e) => { e.stopPropagation(); handleWireToggle(mod); }, disabled: wireBusy !== null, children: wireBusy === mod.name
|
|
646
717
|
? (mod.installed ? 'Decablage...' : 'Cablage...')
|
|
647
|
-
: (mod.installed ? 'Desinstaller' : 'Installer') })] })] }, mod.name))) }))] }), _jsxs("div", { style: S.navRow, children: [_jsxs("button", { style: S.btn('outline'), onClick: goBack, children: ["\u2190 ", t('setup.back')] }), _jsxs("button", { style: S.btn('primary', !canGoNext()), onClick: goNext, disabled: !canGoNext(), children: [t('setup.next'), " \u2192"] })] })] })), step === 'dialect' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\uD83D\uDCBE" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.dialect.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.dialect.description') })] })] }), _jsx("div", { style: S.grid3, children: DIALECT_INFO.map(d => (_jsxs("div", { style: S.dialectCard(dialect === d.key, !!d.premium), onClick: () => !d.premium && selectDialect(d.key), title: d.premium ? `${d.name} — disponible en version Premium` : d.name, children: [_jsx("div", { style: S.dialectIcon, children: d.icon }), _jsx("div", { style: S.dialectName, children: d.name }), d.premium && (_jsx("span", { style: { ...S.badge('premium'), marginLeft: 0, marginTop: 4 }, children: "Premium" }))] }, d.key))) }), _jsxs("div", { style: S.navRow, children: [_jsxs("button", { style: S.btn('outline'), onClick: goBack, children: ["\u2190 ", t('setup.back')] }), _jsxs("button", { style: S.btn('primary'), onClick: goNext, children: [t('setup.next'), " \u2192"] })] })] })), step === 'database' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\uD83D\uDDC4\uFE0F" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.database.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.database.description') })] })] }), dialect === 'sqlite' ? (_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.name') }), _jsx("input", { style: S.input, value: dbConfig.name, onChange: e => { setDbConfig({ ...dbConfig, name: e.target.value }); setDbTestResult(null); }, placeholder: dbNamePrefix }), _jsxs("p", { style: { fontSize: 11, color: '#9ca3af', marginTop: 4 }, children: [t('setup.database.sqliteInfo'), " ", _jsxs("code", { style: { fontFamily: 'monospace', backgroundColor: '#f3f4f6', padding: '1px 4px', borderRadius: 3 }, children: ["./data/", dbConfig.name, ".db"] })] })] })) : dialect === 'spanner' ? (_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.spannerPath') }), _jsx("input", { style: S.input, value: dbConfig.name, onChange: e => { setDbConfig({ ...dbConfig, name: e.target.value }); setDbTestResult(null); }, placeholder: "my-project/my-instance/mydb" }), _jsx("p", { style: { fontSize: 11, color: '#9ca3af', marginTop: 4 }, children: t('setup.database.spannerInfo') })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.host') }), _jsx("input", { style: S.input, value: dbConfig.host, onChange: e => { setDbConfig({ ...dbConfig, host: e.target.value }); setDbTestResult(null); } })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.port') }), _jsx("input", { style: S.input, type: "number", value: dbConfig.port, onChange: e => { setDbConfig({ ...dbConfig, port: parseInt(e.target.value) || 0 }); setDbTestResult(null); } })] })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.name') }), _jsx("input", { style: S.input, value: dbConfig.name, onChange: e => { setDbConfig({ ...dbConfig, name: e.target.value }); setDbTestResult(null); } })] }), _jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.user') }), _jsx("input", { style: S.input, value: dbConfig.user, onChange: e => { setDbConfig({ ...dbConfig, user: e.target.value }); setDbTestResult(null); }, placeholder: dialect === 'hsqldb' ? 'SA' : '' })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.password') }), _jsx("input", { style: S.input, type: "password", value: dbConfig.password, onChange: e => { setDbConfig({ ...dbConfig, password: e.target.value }); setDbTestResult(null); } })] })] })] })), dialect === 'hsqldb' && (_jsxs("div", { style: { ...S.alert('warning'), marginTop: 12, fontSize: 12 }, children: [_jsx("strong", { children: "Prerequis :" }), " Le serveur HSQLDB doit etre lance avant le bridge.", _jsx("br", {}), _jsxs("code", { style: { fontFamily: 'monospace', backgroundColor: '#fef3c7', padding: '2px 6px', borderRadius: 3, display: 'inline-block', marginTop: 4, fontSize: 11 }, children: ["java -cp hsqldb*.jar org.hsqldb.server.Server --database.0 file:./data/", dbConfig.name, " --dbname.0 ", dbConfig.name] })] })), dialect !== 'mongodb' && dialect !== 'sqlite' && (_jsxs("div", { style: { ...S.alert('warning'), marginTop: 12 }, children: [t('setup.database.driverHint'), ' ', _jsx("code", { style: { fontFamily: 'monospace', backgroundColor: '#fef3c7', padding: '1px 4px', borderRadius: 3 }, children: DRIVER_HINTS[dialect] })] })), JDBC_DIALECTS.includes(dialect) && (_jsx(JarUploadInline, { dialect: dialect, jarEndpoint: ep.uploadJar, dbConfig: dbConfig })), dialect !== 'sqlite' && dialect !== 'spanner' && (_jsxs("div", { style: {
|
|
648
|
-
|
|
649
|
-
|
|
718
|
+
: (mod.installed ? 'Desinstaller' : 'Installer') })] })] }, mod.name))) }))] }), _jsxs("div", { style: S.navRow, children: [_jsxs("button", { style: S.btn('outline'), onClick: goBack, children: ["\u2190 ", t('setup.back')] }), _jsxs("button", { style: S.btn('primary', !canGoNext()), onClick: goNext, disabled: !canGoNext(), children: [t('setup.next'), " \u2192"] })] })] })), step === 'dialect' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\uD83D\uDCBE" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.dialect.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.dialect.description') })] })] }), _jsx("div", { style: S.grid3, children: DIALECT_INFO.map(d => (_jsxs("div", { style: S.dialectCard(dialect === d.key, !!d.premium), onClick: () => !d.premium && selectDialect(d.key), title: d.premium ? `${d.name} — disponible en version Premium` : d.name, children: [_jsx("div", { style: S.dialectIcon, children: d.icon }), _jsx("div", { style: S.dialectName, children: d.name }), d.premium && (_jsx("span", { style: { ...S.badge('premium'), marginLeft: 0, marginTop: 4 }, children: "Premium" }))] }, d.key))) }), _jsxs("div", { style: S.navRow, children: [_jsxs("button", { style: S.btn('outline'), onClick: goBack, children: ["\u2190 ", t('setup.back')] }), _jsxs("button", { style: S.btn('primary'), onClick: goNext, children: [t('setup.next'), " \u2192"] })] })] })), step === 'database' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\uD83D\uDDC4\uFE0F" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.database.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.database.description') })] })] }), dialect === 'sqlite' ? (_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.name') }), _jsx("input", { style: S.input, value: dbConfig.name, onChange: e => { setDbConfig({ ...dbConfig, name: e.target.value }); setDbTestResult(null); }, placeholder: dbNamePrefix }), _jsxs("p", { style: { fontSize: 11, color: '#9ca3af', marginTop: 4 }, children: [t('setup.database.sqliteInfo'), " ", _jsxs("code", { style: { fontFamily: 'monospace', backgroundColor: '#f3f4f6', padding: '1px 4px', borderRadius: 3 }, children: ["./data/", dbConfig.name, ".db"] })] })] })) : dialect === 'spanner' ? (_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.spannerPath') }), _jsx("input", { style: S.input, value: dbConfig.name, onChange: e => { setDbConfig({ ...dbConfig, name: e.target.value }); setDbTestResult(null); }, placeholder: "my-project/my-instance/mydb" }), _jsx("p", { style: { fontSize: 11, color: '#9ca3af', marginTop: 4 }, children: t('setup.database.spannerInfo') })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.host') }), _jsx("input", { style: S.input, value: dbConfig.host, onChange: e => { setDbConfig({ ...dbConfig, host: e.target.value }); setDbTestResult(null); } })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.port') }), _jsx("input", { style: S.input, type: "number", value: dbConfig.port, onChange: e => { setDbConfig({ ...dbConfig, port: parseInt(e.target.value) || 0 }); setDbTestResult(null); } })] })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.name') }), _jsx("input", { style: S.input, value: dbConfig.name, onChange: e => { setDbConfig({ ...dbConfig, name: e.target.value }); setDbTestResult(null); } })] }), _jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.user') }), _jsx("input", { style: S.input, value: dbConfig.user, onChange: e => { setDbConfig({ ...dbConfig, user: e.target.value }); setDbTestResult(null); }, placeholder: dialect === 'hsqldb' ? 'SA' : '' })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.password') }), _jsx("input", { style: S.input, type: "password", value: dbConfig.password, onChange: e => { setDbConfig({ ...dbConfig, password: e.target.value }); setDbTestResult(null); } })] })] })] })), dialect === 'hsqldb' && (_jsxs("div", { style: { ...S.alert('warning'), marginTop: 12, fontSize: 12 }, children: [_jsx("strong", { children: "Prerequis :" }), " Le serveur HSQLDB doit etre lance avant le bridge.", _jsx("br", {}), _jsxs("code", { style: { fontFamily: 'monospace', backgroundColor: '#fef3c7', padding: '2px 6px', borderRadius: 3, display: 'inline-block', marginTop: 4, fontSize: 11 }, children: ["java -cp hsqldb*.jar org.hsqldb.server.Server --database.0 file:./data/", dbConfig.name, " --dbname.0 ", dbConfig.name] })] })), dialect !== 'mongodb' && dialect !== 'sqlite' && (_jsxs("div", { style: { ...S.alert('warning'), marginTop: 12 }, children: [t('setup.database.driverHint'), ' ', _jsx("code", { style: { fontFamily: 'monospace', backgroundColor: '#fef3c7', padding: '1px 4px', borderRadius: 3 }, children: DRIVER_HINTS[dialect] })] })), JDBC_DIALECTS.includes(dialect) && (_jsx(JarUploadInline, { dialect: dialect, jarEndpoint: ep.uploadJar, dbConfig: dbConfig })), dialect !== 'sqlite' && dialect !== 'spanner' && (_jsxs("div", { style: { marginTop: 12, padding: '12px 14px', backgroundColor: '#fffbeb', border: '1px solid #fde68a', borderRadius: 8 }, children: [_jsxs("div", { style: S.checkRow, children: [_jsx("input", { type: "checkbox", style: S.checkbox, checked: createIfNotExists, onChange: e => setCreateIfNotExists(e.target.checked) }), _jsxs("div", { children: [_jsx("div", { style: { fontSize: 13, fontWeight: 500 }, children: t('setup.database.createIfNotExists') }), _jsx("div", { style: { fontSize: 12, color: '#92400e' }, children: t('setup.database.createIfNotExistsDesc') })] })] }), _jsxs("div", { style: { marginTop: 10, display: 'flex', alignItems: 'center', gap: 12 }, children: [_jsxs("button", { style: { ...S.btn('outline', creatingDb), fontSize: 12, padding: '6px 14px', backgroundColor: '#fef3c7' }, onClick: createDatabase, disabled: creatingDb || !dbConfig.name, children: [creatingDb ? '⏳ ' : '🗃️ ', creatingDb ? t('setup.database.creating') : t('setup.database.createDb')] }), createDbResult && (_jsx("span", { style: { fontSize: 12, color: createDbResult.ok ? '#059669' : '#dc2626' }, children: createDbResult.ok
|
|
719
|
+
? `✅ ${createDbResult.detail || t('setup.database.createDbSuccess')}`
|
|
720
|
+
: `❌ ${createDbResult.error}` }))] })] })), dialect !== 'sqlite' && dialect !== 'spanner' && (_jsxs("div", { style: { ...S.flex(16), marginTop: 16 }, children: [_jsxs("button", { style: S.btn('outline', dbTesting), onClick: testDb, disabled: dbTesting, children: [dbTesting ? '⏳ ' : '🗄️ ', dbTesting ? t('setup.database.testing') : t('setup.database.test')] }), dbTestResult && (_jsxs("div", { style: { fontSize: 13 }, children: [_jsx("span", { style: { color: dbTestResult.ok ? '#059669' : '#dc2626' }, children: dbTestResult.ok
|
|
721
|
+
? `✅ ${t('setup.database.success')}${dbTestResult.dbVersion ? ` (v${dbTestResult.dbVersion})` : ''}`
|
|
722
|
+
: `❌ ${t('setup.database.error')}: ${dbTestResult.error}` }), dbTestResult.ok && (_jsxs("div", { style: { fontSize: 11, color: '#6b7280', marginTop: 4, fontFamily: 'monospace', backgroundColor: '#f3f4f6', padding: '4px 8px', borderRadius: 4 }, children: [dialect, "://", dbConfig.user ? dbConfig.user + '@' : '', dbConfig.host, ":", dbConfig.port, "/", dbConfig.name] }))] }))] })), _jsxs("div", { style: S.navRow, children: [_jsxs("button", { style: S.btn('outline'), onClick: goBack, children: ["\u2190 ", t('setup.back')] }), _jsxs("button", { style: S.btn('primary', !canGoNext()), onClick: goNext, disabled: !canGoNext(), children: [t('setup.next'), " \u2192"] })] })] })), step === 'admin' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\uD83D\uDC64" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.admin.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.admin.description') })] })] }), _jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.firstName') }), _jsx("input", { style: S.input, value: adminConfig.firstName, onChange: e => setAdminConfig({ ...adminConfig, firstName: e.target.value }) })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.lastName') }), _jsx("input", { style: S.input, value: adminConfig.lastName, onChange: e => setAdminConfig({ ...adminConfig, lastName: e.target.value }) })] })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.email') }), _jsx("input", { style: S.input, type: "email", value: adminConfig.email, onChange: e => setAdminConfig({ ...adminConfig, email: e.target.value }), placeholder: "admin@example.com" })] }), _jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.password') }), _jsx("input", { style: S.input, type: "password", value: adminConfig.password, onChange: e => setAdminConfig({ ...adminConfig, password: e.target.value }) })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.admin.confirmPassword') }), _jsx("input", { style: S.input, type: "password", value: adminConfig.confirmPassword, onChange: e => setAdminConfig({ ...adminConfig, confirmPassword: e.target.value }) })] })] }), adminConfig.password && adminConfig.confirmPassword && adminConfig.password !== adminConfig.confirmPassword && (_jsx("p", { style: { fontSize: 13, color: '#dc2626' }, children: t('setup.admin.passwordMismatch') })), _jsxs("div", { style: S.navRow, children: [_jsxs("button", { style: S.btn('outline'), onClick: goBack, children: ["\u2190 ", t('setup.back')] }), _jsxs("button", { style: S.btn('primary', !canGoNext()), onClick: goNext, disabled: !canGoNext(), children: [t('setup.next'), " \u2192"] })] })] })), step === 'summary' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\u2699\uFE0F" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.summary.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.summary.description') })] })] }), _jsxs("div", { style: S.summaryCard, children: [_jsx("div", { style: S.summaryTitle, children: t('setup.summary.dbConfig') }), _jsxs("div", { style: S.summaryText, children: [_jsx("span", { style: { fontFamily: 'monospace' }, children: dbSummaryLabel() }), dialect !== 'sqlite' && dbConfig.user && _jsxs("span", { style: { display: 'block', marginTop: 4 }, children: ["Utilisateur: ", dbConfig.user] })] })] }), _jsxs("div", { style: S.summaryCard, children: [_jsx("div", { style: S.summaryTitle, children: t('setup.summary.adminConfig') }), _jsxs("div", { style: S.summaryText, children: [_jsxs("div", { children: [adminConfig.firstName, " ", adminConfig.lastName] }), _jsx("div", { children: adminConfig.email })] })] }), _jsxs("div", { style: S.summaryCard, children: [_jsx("div", { style: S.summaryTitle, children: t('setup.modules.title') }), _jsx("div", { style: S.flexWrap, children: selectedModules.map(key => {
|
|
650
723
|
const mod = availableModules.find(m => m.key === key);
|
|
651
724
|
return mod ? (_jsxs("span", { style: {
|
|
652
725
|
display: 'inline-flex', alignItems: 'center', gap: 4,
|
package/dist/data/dialects.d.ts
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
export { needsSetup, runInstall } from './lib/setup';
|
|
2
|
-
export { testDbConnection } from './lib/db-test';
|
|
3
|
-
export { composeDbUri } from './lib/compose-uri';
|
|
4
|
-
export { writeEnvLocal } from './lib/env-writer';
|
|
5
|
-
export { DIALECT_INFO, ALL_DIALECTS } from './data/dialects';
|
|
6
|
-
export { MODULES, resolveModuleDependencies } from './data/module-definitions';
|
|
7
|
-
export { discoverNpmModules } from './lib/discover-modules';
|
|
8
|
-
export { loadSetupJson } from './lib/load-setup-json';
|
|
9
|
-
export type { SetupJson, SetupJsonRbac, SetupJsonSeed, SetupJsonCategory, SetupJsonPermission, SetupJsonRole } from './lib/load-setup-json';
|
|
10
|
-
export { createTestDbHandler } from './api/test-db.route';
|
|
11
|
-
export { createInstallHandler } from './api/install.route';
|
|
12
|
-
export { createStatusHandler } from './api/status.route';
|
|
13
|
-
export { createDetectModulesHandler } from './api/detect-modules.route';
|
|
14
|
-
export { createInstallModulesHandler } from './api/install-modules.route';
|
|
15
|
-
export { createReconfigHandlers } from './api/reconfig.route';
|
|
16
|
-
export { createUploadJarHandlers } from './api/upload-jar.route';
|
|
17
|
-
export { createWireModuleHandler } from './api/wire-module.route';
|
|
18
|
-
export { createSetupJsonHandler } from './api/upload-setup-json.route';
|
|
19
|
-
export {
|
|
20
|
-
export {
|
|
21
|
-
export {
|
|
22
|
-
export
|
|
1
|
+
export { needsSetup, runInstall } from './lib/setup.js';
|
|
2
|
+
export { testDbConnection } from './lib/db-test.js';
|
|
3
|
+
export { composeDbUri } from './lib/compose-uri.js';
|
|
4
|
+
export { writeEnvLocal } from './lib/env-writer.js';
|
|
5
|
+
export { DIALECT_INFO, ALL_DIALECTS } from './data/dialects.js';
|
|
6
|
+
export { MODULES, resolveModuleDependencies } from './data/module-definitions.js';
|
|
7
|
+
export { discoverNpmModules } from './lib/discover-modules.js';
|
|
8
|
+
export { loadSetupJson } from './lib/load-setup-json.js';
|
|
9
|
+
export type { SetupJson, SetupJsonRbac, SetupJsonSeed, SetupJsonCategory, SetupJsonPermission, SetupJsonRole } from './lib/load-setup-json.js';
|
|
10
|
+
export { createTestDbHandler } from './api/test-db.route.js';
|
|
11
|
+
export { createInstallHandler } from './api/install.route.js';
|
|
12
|
+
export { createStatusHandler } from './api/status.route.js';
|
|
13
|
+
export { createDetectModulesHandler } from './api/detect-modules.route.js';
|
|
14
|
+
export { createInstallModulesHandler } from './api/install-modules.route.js';
|
|
15
|
+
export { createReconfigHandlers } from './api/reconfig.route.js';
|
|
16
|
+
export { createUploadJarHandlers } from './api/upload-jar.route.js';
|
|
17
|
+
export { createWireModuleHandler } from './api/wire-module.route.js';
|
|
18
|
+
export { createSetupJsonHandler } from './api/upload-setup-json.route.js';
|
|
19
|
+
export { createPreflightHandler } from './api/preflight.route.js';
|
|
20
|
+
export type { PreflightCheck } from './api/preflight.route.js';
|
|
21
|
+
export { createCreateDbHandler } from './api/create-db.route.js';
|
|
22
|
+
export { default as ReconfigPanel } from './components/ReconfigPanel.js';
|
|
23
|
+
export { default as SetupWizard } from './components/SetupWizard.js';
|
|
24
|
+
export type { DialectType, DialectInfo, DbConfig, InstallConfig, SeedOptions, SeedDefinition, MostaSetupConfig, ModuleDefinition, } from './types/index.js';
|
package/dist/index.js
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
// @mosta/setup — Barrel exports
|
|
2
2
|
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
3
|
// Core
|
|
4
|
-
export { needsSetup, runInstall } from './lib/setup';
|
|
5
|
-
export { testDbConnection } from './lib/db-test';
|
|
6
|
-
export { composeDbUri } from './lib/compose-uri';
|
|
7
|
-
export { writeEnvLocal } from './lib/env-writer';
|
|
4
|
+
export { needsSetup, runInstall } from './lib/setup.js';
|
|
5
|
+
export { testDbConnection } from './lib/db-test.js';
|
|
6
|
+
export { composeDbUri } from './lib/compose-uri.js';
|
|
7
|
+
export { writeEnvLocal } from './lib/env-writer.js';
|
|
8
8
|
// Data
|
|
9
|
-
export { DIALECT_INFO, ALL_DIALECTS } from './data/dialects';
|
|
10
|
-
export { MODULES, resolveModuleDependencies } from './data/module-definitions';
|
|
9
|
+
export { DIALECT_INFO, ALL_DIALECTS } from './data/dialects.js';
|
|
10
|
+
export { MODULES, resolveModuleDependencies } from './data/module-definitions.js';
|
|
11
11
|
// Lib
|
|
12
|
-
export { discoverNpmModules } from './lib/discover-modules';
|
|
13
|
-
export { loadSetupJson } from './lib/load-setup-json';
|
|
12
|
+
export { discoverNpmModules } from './lib/discover-modules.js';
|
|
13
|
+
export { loadSetupJson } from './lib/load-setup-json.js';
|
|
14
14
|
// API route factories
|
|
15
|
-
export { createTestDbHandler } from './api/test-db.route';
|
|
16
|
-
export { createInstallHandler } from './api/install.route';
|
|
17
|
-
export { createStatusHandler } from './api/status.route';
|
|
18
|
-
export { createDetectModulesHandler } from './api/detect-modules.route';
|
|
19
|
-
export { createInstallModulesHandler } from './api/install-modules.route';
|
|
20
|
-
export { createReconfigHandlers } from './api/reconfig.route';
|
|
21
|
-
export { createUploadJarHandlers } from './api/upload-jar.route';
|
|
22
|
-
export { createWireModuleHandler } from './api/wire-module.route';
|
|
23
|
-
export { createSetupJsonHandler } from './api/upload-setup-json.route';
|
|
15
|
+
export { createTestDbHandler } from './api/test-db.route.js';
|
|
16
|
+
export { createInstallHandler } from './api/install.route.js';
|
|
17
|
+
export { createStatusHandler } from './api/status.route.js';
|
|
18
|
+
export { createDetectModulesHandler } from './api/detect-modules.route.js';
|
|
19
|
+
export { createInstallModulesHandler } from './api/install-modules.route.js';
|
|
20
|
+
export { createReconfigHandlers } from './api/reconfig.route.js';
|
|
21
|
+
export { createUploadJarHandlers } from './api/upload-jar.route.js';
|
|
22
|
+
export { createWireModuleHandler } from './api/wire-module.route.js';
|
|
23
|
+
export { createSetupJsonHandler } from './api/upload-setup-json.route.js';
|
|
24
|
+
export { createPreflightHandler } from './api/preflight.route.js';
|
|
25
|
+
export { createCreateDbHandler } from './api/create-db.route.js';
|
|
24
26
|
// Components
|
|
25
|
-
export { default as ReconfigPanel } from './components/ReconfigPanel';
|
|
26
|
-
export { default as SetupWizard } from './components/SetupWizard';
|
|
27
|
-
// Menu contribution
|
|
28
|
-
export { setupMenuContribution } from './lib/menu';
|
|
27
|
+
export { default as ReconfigPanel } from './components/ReconfigPanel.js';
|
|
28
|
+
export { default as SetupWizard } from './components/SetupWizard.js';
|
package/dist/lib/db-test.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { DialectType, DbConfig } from '../types/index';
|
|
1
|
+
import type { DialectType, DbConfig } from '../types/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* Test a database connection without affecting the global dialect singleton.
|
|
4
|
+
* Delegates entirely to @mostajs/orm testConnection().
|
|
4
5
|
*/
|
|
5
6
|
export declare function testDbConnection(params: {
|
|
6
7
|
dialect: DialectType;
|
package/dist/lib/db-test.js
CHANGED
|
@@ -1,78 +1,22 @@
|
|
|
1
|
-
import { composeDbUri } from './compose-uri';
|
|
1
|
+
import { composeDbUri } from './compose-uri.js';
|
|
2
2
|
/**
|
|
3
3
|
* Test a database connection without affecting the global dialect singleton.
|
|
4
|
+
* Delegates entirely to @mostajs/orm testConnection().
|
|
4
5
|
*/
|
|
5
6
|
export async function testDbConnection(params) {
|
|
6
7
|
const { dialect, createIfNotExists, ...dbConfig } = params;
|
|
7
8
|
try {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const mongoose = await import('mongoose');
|
|
12
|
-
const conn = mongoose.default.createConnection(uri, {
|
|
13
|
-
serverSelectionTimeoutMS: 5000,
|
|
14
|
-
connectTimeoutMS: 5000,
|
|
15
|
-
});
|
|
16
|
-
try {
|
|
17
|
-
await conn.asPromise();
|
|
18
|
-
return { ok: conn.readyState === 1 };
|
|
19
|
-
}
|
|
20
|
-
finally {
|
|
21
|
-
await conn.close().catch(() => { });
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
case 'sqlite':
|
|
25
|
-
return { ok: true };
|
|
26
|
-
default: {
|
|
27
|
-
const uri = composeDbUri(dialect, dbConfig);
|
|
28
|
-
// For JDBC bridge dialects, try testing via the bridge HTTP endpoint directly.
|
|
29
|
-
// This avoids module singleton issues in Next.js where BridgeManager instances
|
|
30
|
-
// may differ between API routes.
|
|
31
|
-
const JDBC_DIALECTS = ['hsqldb', 'oracle', 'db2', 'hana', 'sybase'];
|
|
32
|
-
if (JDBC_DIALECTS.includes(dialect)) {
|
|
33
|
-
const bridgePort = parseInt(process.env.MOSTA_BRIDGE_PORT_BASE || '8765');
|
|
34
|
-
// Scan ports 8765..8774 for an active bridge
|
|
35
|
-
for (let port = bridgePort; port < bridgePort + 10; port++) {
|
|
36
|
-
try {
|
|
37
|
-
const healthRes = await fetch(`http://localhost:${port}/health`, {
|
|
38
|
-
signal: AbortSignal.timeout(1000),
|
|
39
|
-
});
|
|
40
|
-
if (!healthRes.ok)
|
|
41
|
-
continue;
|
|
42
|
-
// Bridge found — test a query
|
|
43
|
-
// Use dialect-appropriate ping query
|
|
44
|
-
const pingQuery = dialect === 'hsqldb'
|
|
45
|
-
? 'SELECT 1 FROM INFORMATION_SCHEMA.SYSTEM_USERS'
|
|
46
|
-
: dialect === 'oracle'
|
|
47
|
-
? 'SELECT 1 FROM DUAL'
|
|
48
|
-
: 'SELECT 1';
|
|
49
|
-
const queryRes = await fetch(`http://localhost:${port}/query`, {
|
|
50
|
-
method: 'POST',
|
|
51
|
-
headers: { 'Content-Type': 'application/json' },
|
|
52
|
-
body: JSON.stringify({ sql: pingQuery, params: [] }),
|
|
53
|
-
signal: AbortSignal.timeout(5000),
|
|
54
|
-
});
|
|
55
|
-
if (queryRes.ok) {
|
|
56
|
-
return { ok: true };
|
|
57
|
-
}
|
|
58
|
-
const text = await queryRes.text();
|
|
59
|
-
return { ok: false, error: `Bridge query failed: ${text}` };
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return { ok: false, error: `Aucun bridge JDBC actif. Lancez le bridge d'abord.` };
|
|
66
|
-
}
|
|
67
|
-
const { testConnection } = await import('@mostajs/orm');
|
|
68
|
-
const result = await testConnection({
|
|
69
|
-
dialect,
|
|
70
|
-
uri,
|
|
71
|
-
schemaStrategy: createIfNotExists ? 'update' : 'none',
|
|
72
|
-
});
|
|
73
|
-
return result;
|
|
74
|
-
}
|
|
9
|
+
// SQLite: always OK (file auto-created)
|
|
10
|
+
if (dialect === 'sqlite') {
|
|
11
|
+
return { ok: true };
|
|
75
12
|
}
|
|
13
|
+
const uri = composeDbUri(dialect, dbConfig);
|
|
14
|
+
const { testConnection } = await import('@mostajs/orm');
|
|
15
|
+
return await testConnection({
|
|
16
|
+
dialect,
|
|
17
|
+
uri,
|
|
18
|
+
schemaStrategy: createIfNotExists ? 'update' : 'none',
|
|
19
|
+
});
|
|
76
20
|
}
|
|
77
21
|
catch (err) {
|
|
78
22
|
const message = err instanceof Error ? err.message : 'Connexion echouee';
|
|
@@ -4,7 +4,7 @@ import { exec } from 'child_process';
|
|
|
4
4
|
import { promisify } from 'util';
|
|
5
5
|
import fs from 'fs';
|
|
6
6
|
import path from 'path';
|
|
7
|
-
import { MODULES } from '../data/module-definitions';
|
|
7
|
+
import { MODULES } from '../data/module-definitions.js';
|
|
8
8
|
const execAsync = promisify(exec);
|
|
9
9
|
/**
|
|
10
10
|
* Discover @mostajs packages from npm registry and merge with static list.
|
package/dist/lib/env-writer.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MostaSetupConfig } from '../types/index';
|
|
1
|
+
import type { MostaSetupConfig } from '../types/index.js';
|
|
2
2
|
export interface SetupJsonCategory {
|
|
3
3
|
name: string;
|
|
4
4
|
label: string;
|
|
@@ -45,9 +45,19 @@ export interface SetupJson {
|
|
|
45
45
|
dbNamePrefix?: string;
|
|
46
46
|
};
|
|
47
47
|
env?: Record<string, string>;
|
|
48
|
+
modules?: SetupJsonModule[];
|
|
48
49
|
rbac?: SetupJsonRbac;
|
|
49
50
|
seeds?: SetupJsonSeed[];
|
|
50
51
|
}
|
|
52
|
+
export interface SetupJsonModule {
|
|
53
|
+
key: string;
|
|
54
|
+
packageName: string;
|
|
55
|
+
label?: string;
|
|
56
|
+
description?: string;
|
|
57
|
+
icon?: string;
|
|
58
|
+
required?: boolean;
|
|
59
|
+
dependsOn?: string[];
|
|
60
|
+
}
|
|
51
61
|
/**
|
|
52
62
|
* Load a setup.json and return a MostaSetupConfig.
|
|
53
63
|
*
|
|
@@ -58,10 +58,15 @@ function validate(json) {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
function buildConfig(json, repoFactory) {
|
|
61
|
+
// Derive MOSTAJS_MODULES from modules[] section
|
|
62
|
+
const extraEnvVars = { ...(json.env ?? {}) };
|
|
63
|
+
if (json.modules?.length) {
|
|
64
|
+
extraEnvVars.MOSTAJS_MODULES = json.modules.map(m => m.key).join(',');
|
|
65
|
+
}
|
|
61
66
|
const config = {
|
|
62
67
|
appName: json.app.name,
|
|
63
68
|
defaultPort: json.app.port,
|
|
64
|
-
extraEnvVars:
|
|
69
|
+
extraEnvVars: Object.keys(extraEnvVars).length > 0 ? extraEnvVars : undefined,
|
|
65
70
|
};
|
|
66
71
|
// ── seedRBAC ───────────────────────────────────────────
|
|
67
72
|
if (json.rbac) {
|
|
@@ -88,7 +93,7 @@ function buildConfig(json, repoFactory) {
|
|
|
88
93
|
if (json.rbac.roles?.length) {
|
|
89
94
|
const roleRepo = await getRepo('role');
|
|
90
95
|
const allPermIds = Object.values(permissionMap);
|
|
91
|
-
for (const roleDef of json.rbac.roles) {
|
|
96
|
+
for (const roleDef of json.rbac.roles.filter(r => r.name)) {
|
|
92
97
|
const permissionIds = roleDef.permissions.includes('*')
|
|
93
98
|
? allPermIds
|
|
94
99
|
: roleDef.permissions.map(code => permissionMap[code]).filter(Boolean);
|
package/dist/lib/setup.d.ts
CHANGED
package/dist/lib/setup.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @mosta/setup — Core setup logic
|
|
2
2
|
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
-
import { composeDbUri } from './compose-uri';
|
|
4
|
-
import { writeEnvLocal } from './env-writer';
|
|
3
|
+
import { composeDbUri } from './compose-uri.js';
|
|
4
|
+
import { writeEnvLocal } from './env-writer.js';
|
|
5
5
|
/**
|
|
6
6
|
* Check if the app needs initial setup (0 users in DB).
|
|
7
7
|
* Provide a countUsers function from your app.
|
package/dist/register.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { ModuleRegistration } from '@mostajs/socle';
|
|
|
2
2
|
/**
|
|
3
3
|
* Setup provides the installation wizard and reconfiguration panel.
|
|
4
4
|
* No schemas of its own — orchestrates other modules' seeds.
|
|
5
|
+
* Menu contributions are declared by the host app, not by this module.
|
|
5
6
|
*/
|
|
6
7
|
export declare function register(registry: {
|
|
7
8
|
register(r: ModuleRegistration): void;
|
package/dist/register.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// @mostajs/setup — Runtime module registration
|
|
2
2
|
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
-
import { setupMenuContribution } from './lib/menu.js';
|
|
4
3
|
/**
|
|
5
4
|
* Setup provides the installation wizard and reconfiguration panel.
|
|
6
5
|
* No schemas of its own — orchestrates other modules' seeds.
|
|
6
|
+
* Menu contributions are declared by the host app, not by this module.
|
|
7
7
|
*/
|
|
8
8
|
export function register(registry) {
|
|
9
9
|
registry.register({
|
|
@@ -19,6 +19,5 @@ export function register(registry) {
|
|
|
19
19
|
icon: 'Wrench',
|
|
20
20
|
register: './dist/register.js',
|
|
21
21
|
},
|
|
22
|
-
menu: setupMenuContribution,
|
|
23
22
|
});
|
|
24
23
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ export interface InstallConfig {
|
|
|
28
28
|
seed?: SeedOptions;
|
|
29
29
|
modules?: string[];
|
|
30
30
|
}
|
|
31
|
-
export type { ModuleDefinition } from '../data/module-definitions';
|
|
31
|
+
export type { ModuleDefinition } from '../data/module-definitions.js';
|
|
32
32
|
export interface SeedOptions {
|
|
33
33
|
[key: string]: boolean;
|
|
34
34
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mostajs/setup",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Reusable setup wizard module — multi-dialect DB configuration, .env.local writer, seed runner",
|
|
5
5
|
"author": "Dr Hamid MADANI <drmdh@msn.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,11 +33,6 @@
|
|
|
33
33
|
"import": "./dist/types/index.js",
|
|
34
34
|
"default": "./dist/types/index.js"
|
|
35
35
|
},
|
|
36
|
-
"./lib/menu": {
|
|
37
|
-
"types": "./dist/lib/menu.d.ts",
|
|
38
|
-
"import": "./dist/lib/menu.js",
|
|
39
|
-
"default": "./dist/lib/menu.js"
|
|
40
|
-
},
|
|
41
36
|
"./components/ReconfigPanel": {
|
|
42
37
|
"types": "./dist/components/ReconfigPanel.d.ts",
|
|
43
38
|
"import": "./dist/components/ReconfigPanel.js",
|
|
@@ -104,7 +99,7 @@
|
|
|
104
99
|
"prepublishOnly": "npm run build"
|
|
105
100
|
},
|
|
106
101
|
"dependencies": {
|
|
107
|
-
"@mostajs/orm": "^1.4.
|
|
102
|
+
"@mostajs/orm": "^1.4.15",
|
|
108
103
|
"bcryptjs": "^2.4.3"
|
|
109
104
|
},
|
|
110
105
|
"devDependencies": {
|
|
@@ -151,6 +151,45 @@
|
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
},
|
|
154
|
+
"modules": {
|
|
155
|
+
"type": "array",
|
|
156
|
+
"description": "Modules to use in the project. Exported by MostaSetup Studio. Shown in the wizard with install commands.",
|
|
157
|
+
"items": {
|
|
158
|
+
"type": "object",
|
|
159
|
+
"required": ["key", "packageName"],
|
|
160
|
+
"additionalProperties": false,
|
|
161
|
+
"properties": {
|
|
162
|
+
"key": {
|
|
163
|
+
"type": "string",
|
|
164
|
+
"description": "Unique module identifier (e.g. 'orm', 'auth', 'ticketing')"
|
|
165
|
+
},
|
|
166
|
+
"packageName": {
|
|
167
|
+
"type": "string",
|
|
168
|
+
"description": "npm package name (e.g. '@mostajs/orm')"
|
|
169
|
+
},
|
|
170
|
+
"label": {
|
|
171
|
+
"type": "string",
|
|
172
|
+
"description": "Display label"
|
|
173
|
+
},
|
|
174
|
+
"description": {
|
|
175
|
+
"type": "string"
|
|
176
|
+
},
|
|
177
|
+
"icon": {
|
|
178
|
+
"type": "string",
|
|
179
|
+
"description": "Emoji icon"
|
|
180
|
+
},
|
|
181
|
+
"required": {
|
|
182
|
+
"type": "boolean",
|
|
183
|
+
"description": "Whether this module is required (cannot be deselected)"
|
|
184
|
+
},
|
|
185
|
+
"dependsOn": {
|
|
186
|
+
"type": "array",
|
|
187
|
+
"items": { "type": "string" },
|
|
188
|
+
"description": "Module keys this module depends on"
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
},
|
|
154
193
|
"seeds": {
|
|
155
194
|
"type": "array",
|
|
156
195
|
"description": "Optional seed datasets shown as checkboxes in the install wizard",
|
package/dist/lib/menu.d.ts
DELETED
package/dist/lib/menu.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
// @mostajs/setup — Menu contribution
|
|
2
|
-
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
-
import { Wrench, Settings2 } from 'lucide-react';
|
|
4
|
-
export const setupMenuContribution = {
|
|
5
|
-
moduleKey: 'setup',
|
|
6
|
-
mergeIntoGroup: 'Administration',
|
|
7
|
-
order: 100,
|
|
8
|
-
items: [
|
|
9
|
-
{
|
|
10
|
-
label: 'setup.title',
|
|
11
|
-
href: '/setup',
|
|
12
|
-
icon: Wrench,
|
|
13
|
-
permission: 'admin:access',
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
label: 'setup.reconfig.title',
|
|
17
|
-
href: '/dashboard/settings/reconfig',
|
|
18
|
-
icon: Settings2,
|
|
19
|
-
permission: 'admin:settings',
|
|
20
|
-
},
|
|
21
|
-
],
|
|
22
|
-
};
|