@mostajs/setup 2.1.5 → 2.1.7
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/routes.js +49 -0
- package/dist/components/SetupWizard.js +43 -9
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/lib/net-client.d.ts +48 -0
- package/dist/lib/net-client.js +93 -0
- package/dist/lib/setup.js +78 -0
- package/dist/types/index.d.ts +8 -0
- package/package.json +1 -1
package/dist/api/routes.js
CHANGED
|
@@ -75,6 +75,55 @@ export function createSetupRoutes(config) {
|
|
|
75
75
|
'wire-module': { GET: wireModule.GET, POST: wireModule.POST },
|
|
76
76
|
'reconfig': { GET: reconfig.GET, POST: reconfig.POST },
|
|
77
77
|
'install': installHandlers,
|
|
78
|
+
// NET mode endpoints
|
|
79
|
+
'net-test': {
|
|
80
|
+
POST: async (req) => {
|
|
81
|
+
const body = await req.json();
|
|
82
|
+
if (!body?.url)
|
|
83
|
+
return Response.json({ ok: false, error: 'URL requise' }, { status: 400 });
|
|
84
|
+
try {
|
|
85
|
+
const { NetClient } = await import('../lib/net-client.js');
|
|
86
|
+
const client = new NetClient({ url: body.url });
|
|
87
|
+
const health = await client.health();
|
|
88
|
+
return Response.json({ ok: true, ...health });
|
|
89
|
+
}
|
|
90
|
+
catch (e) {
|
|
91
|
+
return Response.json({ ok: false, error: e instanceof Error ? e.message : 'Connexion echouee' });
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
'net-schemas': {
|
|
96
|
+
POST: async (req) => {
|
|
97
|
+
const body = await req.json();
|
|
98
|
+
if (!body?.url)
|
|
99
|
+
return Response.json({ ok: false, error: 'URL requise' }, { status: 400 });
|
|
100
|
+
try {
|
|
101
|
+
const { NetClient } = await import('../lib/net-client.js');
|
|
102
|
+
const client = new NetClient({ url: body.url });
|
|
103
|
+
const config = await client.getSchemasConfig();
|
|
104
|
+
return Response.json({ ok: true, ...config });
|
|
105
|
+
}
|
|
106
|
+
catch (e) {
|
|
107
|
+
return Response.json({ ok: false, error: e instanceof Error ? e.message : 'Erreur' });
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
'net-db-test': {
|
|
112
|
+
POST: async (req) => {
|
|
113
|
+
const body = await req.json();
|
|
114
|
+
if (!body?.url)
|
|
115
|
+
return Response.json({ ok: false, error: 'URL requise' }, { status: 400 });
|
|
116
|
+
try {
|
|
117
|
+
const { NetClient } = await import('../lib/net-client.js');
|
|
118
|
+
const client = new NetClient({ url: body.url });
|
|
119
|
+
const result = await client.testDbConnection();
|
|
120
|
+
return Response.json(result);
|
|
121
|
+
}
|
|
122
|
+
catch (e) {
|
|
123
|
+
return Response.json({ ok: false, error: e instanceof Error ? e.message : 'Erreur' });
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
},
|
|
78
127
|
};
|
|
79
128
|
return table;
|
|
80
129
|
}
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
5
5
|
import { useState, useEffect, useCallback } from 'react';
|
|
6
6
|
// ── Constants ────────────────────────────────────────────────
|
|
7
|
-
const
|
|
7
|
+
const ORM_STEPS = ['welcome', 'modules', 'dialect', 'database', 'admin', 'summary'];
|
|
8
|
+
const NET_STEPS = ['welcome', 'modules', 'net-config', 'admin', 'summary'];
|
|
9
|
+
const ALL_STEPS = ['welcome', 'modules', 'dialect', 'database', 'net-config', 'admin', 'summary'];
|
|
8
10
|
const DIALECT_DEFAULTS = {
|
|
9
11
|
mongodb: { host: 'localhost', port: 27017, name: 'mydb_prod', user: '', password: '' },
|
|
10
12
|
sqlite: { host: '', port: 0, name: 'mydb', user: '', password: '' },
|
|
@@ -346,11 +348,6 @@ function JarUploadInline({ dialect, jarEndpoint, dbConfig }) {
|
|
|
346
348
|
// ── Main Component ───────────────────────────────────────────
|
|
347
349
|
export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNamePrefix = 'mydb', persistState = true, showModules = true, declaredModules, }) {
|
|
348
350
|
const t = tProp || ((k) => k);
|
|
349
|
-
// Modules step is shown unless explicitly disabled
|
|
350
|
-
const hasModulesStep = showModules !== false;
|
|
351
|
-
const STEPS = hasModulesStep
|
|
352
|
-
? ALL_STEPS
|
|
353
|
-
: ALL_STEPS.filter(s => s !== 'modules');
|
|
354
351
|
const ep = {
|
|
355
352
|
detectModules: endpoints.detectModules || '',
|
|
356
353
|
testDb: endpoints.testDb || '/api/setup/test-db',
|
|
@@ -364,6 +361,12 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
|
|
|
364
361
|
setupJson: endpoints.setupJson || '/api/setup/setup-json',
|
|
365
362
|
};
|
|
366
363
|
// --- State ---
|
|
364
|
+
const [setupMode, setSetupMode] = useState('orm');
|
|
365
|
+
const [netUrl, setNetUrl] = useState('http://localhost:4488');
|
|
366
|
+
const [netTransport, setNetTransport] = useState('rest');
|
|
367
|
+
const [netApiKey, setNetApiKey] = useState('');
|
|
368
|
+
const [netTestResult, setNetTestResult] = useState(null);
|
|
369
|
+
const [netTesting, setNetTesting] = useState(false);
|
|
367
370
|
const [currentStep, setCurrentStep] = useState(0);
|
|
368
371
|
const [dialect, setDialect] = useState('mongodb');
|
|
369
372
|
const [dbConfig, setDbConfig] = useState({ ...DIALECT_DEFAULTS.mongodb, name: `${dbNamePrefix}_prod` });
|
|
@@ -389,6 +392,12 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
|
|
|
389
392
|
// Create DB
|
|
390
393
|
const [creatingDb, setCreatingDb] = useState(false);
|
|
391
394
|
const [createDbResult, setCreateDbResult] = useState(null);
|
|
395
|
+
// Steps depend on setup mode (ORM direct vs NET remote)
|
|
396
|
+
const hasModulesStep = showModules !== false;
|
|
397
|
+
const baseSteps = setupMode === 'net' ? NET_STEPS : ORM_STEPS;
|
|
398
|
+
const STEPS = hasModulesStep
|
|
399
|
+
? baseSteps
|
|
400
|
+
: baseSteps.filter(s => s !== 'modules');
|
|
392
401
|
const step = STEPS[currentStep];
|
|
393
402
|
// --- Persist / Restore ---
|
|
394
403
|
useEffect(() => {
|
|
@@ -715,8 +724,10 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
|
|
|
715
724
|
method: 'POST',
|
|
716
725
|
headers: { 'Content-Type': 'application/json' },
|
|
717
726
|
body: JSON.stringify({
|
|
727
|
+
mode: setupMode,
|
|
718
728
|
dialect,
|
|
719
729
|
db: dbConfig,
|
|
730
|
+
...(setupMode === 'net' ? { net: { url: netUrl, transport: netTransport, apiKey: netApiKey || undefined } } : {}),
|
|
720
731
|
admin: { email: adminConfig.email, password: adminConfig.password, firstName: adminConfig.firstName, lastName: adminConfig.lastName },
|
|
721
732
|
seed: seedOptions,
|
|
722
733
|
modules: selectedModules,
|
|
@@ -745,11 +756,14 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
|
|
|
745
756
|
if (dialect === 'sqlite' || dialect === 'spanner')
|
|
746
757
|
return dbConfig.name.trim() !== '';
|
|
747
758
|
return dbTestResult?.ok === true;
|
|
759
|
+
case 'net-config':
|
|
760
|
+
return netTestResult?.ok === true;
|
|
748
761
|
case 'admin':
|
|
749
762
|
return adminConfig.firstName.trim() !== '' && adminConfig.lastName.trim() !== '' &&
|
|
750
763
|
adminConfig.email.trim() !== '' && adminConfig.password.length >= 6 &&
|
|
751
764
|
adminConfig.password === adminConfig.confirmPassword;
|
|
752
765
|
case 'summary': return false;
|
|
766
|
+
default: return false;
|
|
753
767
|
}
|
|
754
768
|
}
|
|
755
769
|
function goNext() { if (currentStep < STEPS.length - 1)
|
|
@@ -774,12 +788,12 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
|
|
|
774
788
|
onComplete?.();
|
|
775
789
|
}
|
|
776
790
|
// ── Render ─────────────────────────────────────────────────
|
|
777
|
-
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: {
|
|
791
|
+
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', padding: 16, backgroundColor: '#f0f9ff', border: '1px solid #bae6fd', borderRadius: 10 }, children: [_jsx("div", { style: { fontSize: 14, fontWeight: 600, color: '#374151', marginBottom: 12 }, children: "Comment acceder aux donnees ?" }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsxs("label", { style: { display: 'flex', alignItems: 'flex-start', gap: 10, padding: 12, backgroundColor: setupMode === 'orm' ? '#dbeafe' : '#fff', border: '1px solid ' + (setupMode === 'orm' ? '#3b82f6' : '#e5e7eb'), borderRadius: 8, cursor: 'pointer' }, children: [_jsx("input", { type: "radio", name: "setupMode", checked: setupMode === 'orm', onChange: () => setSetupMode('orm'), style: { marginTop: 3 } }), _jsxs("div", { children: [_jsx("div", { style: { fontWeight: 600, fontSize: 14 }, children: "Acces direct (ORM)" }), _jsx("div", { style: { fontSize: 12, color: '#6b7280' }, children: "L'application se connecte directement a la base de donnees (13 SGBD supportes)" })] })] }), _jsxs("label", { style: { display: 'flex', alignItems: 'flex-start', gap: 10, padding: 12, backgroundColor: setupMode === 'net' ? '#dbeafe' : '#fff', border: '1px solid ' + (setupMode === 'net' ? '#3b82f6' : '#e5e7eb'), borderRadius: 8, cursor: 'pointer' }, children: [_jsx("input", { type: "radio", name: "setupMode", checked: setupMode === 'net', onChange: () => setSetupMode('net'), style: { marginTop: 3 } }), _jsxs("div", { children: [_jsx("div", { style: { fontWeight: 600, fontSize: 14 }, children: "Via @mostajs/net (reseau)" }), _jsx("div", { style: { fontSize: 12, color: '#6b7280' }, children: "L'application communique avec un serveur @mostajs/net distant via REST, GraphQL, JSON-RPC ou WebSocket" })] })] })] })] }), _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: {
|
|
778
792
|
display: 'flex', alignItems: 'center', gap: 10, padding: '8px 12px',
|
|
779
793
|
backgroundColor: check.status === 'ok' ? '#f0fdf4' : check.status === 'warn' ? '#fffbeb' : '#fef2f2',
|
|
780
794
|
border: `1px solid ${check.status === 'ok' ? '#bbf7d0' : check.status === 'warn' ? '#fde68a' : '#fecaca'}`,
|
|
781
795
|
borderRadius: 6, fontSize: 13,
|
|
782
|
-
}, 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))) }))] }),
|
|
796
|
+
}, 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))) }))] }), _jsxs("div", { style: S.center, children: [_jsx("button", { style: S.btn('lg'), onClick: goNext, children: setupMode === 'net' ? '🌐 Configurer via NET →' : '🗄️ Configurer la base de donnees →' }), _jsxs("p", { style: { fontSize: 12, color: '#9ca3af', marginTop: 8 }, children: ["Mode : ", _jsx("b", { children: setupMode === 'net' ? 'Serveur @mostajs/net' : 'Acces direct ORM' }), " \u2014 ", STEPS.length, " etapes"] })] })] })), 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 => {
|
|
783
797
|
const isSelected = selectedModules.includes(mod.key);
|
|
784
798
|
const isDetected = detectedModules.includes(mod.key);
|
|
785
799
|
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));
|
|
@@ -793,7 +807,27 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
|
|
|
793
807
|
? `✅ ${createDbResult.detail || t('setup.database.createDbSuccess')}`
|
|
794
808
|
: `❌ ${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
|
|
795
809
|
? `✅ ${t('setup.database.success')}${dbTestResult.dbVersion ? ` (v${dbTestResult.dbVersion})` : ''}`
|
|
796
|
-
: `❌ ${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 === '
|
|
810
|
+
: `❌ ${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 === 'net-config' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("h2", { style: S.sectionTitle, children: "Serveur @mostajs/net" }), _jsx("p", { style: S.sectionDesc, children: "Configurez la connexion au serveur @mostajs/net distant" })] }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 12, marginBottom: 16 }, children: [_jsxs("div", { children: [_jsx("label", { style: { fontSize: 13, fontWeight: 500, color: '#374151', marginBottom: 4, display: 'block' }, children: "URL du serveur" }), _jsx("input", { style: S.input, value: netUrl, onChange: e => { setNetUrl(e.target.value); setNetTestResult(null); }, placeholder: "http://localhost:4488" })] }), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }, children: [_jsxs("div", { children: [_jsx("label", { style: { fontSize: 13, fontWeight: 500, color: '#374151', marginBottom: 4, display: 'block' }, children: "Transport" }), _jsxs("select", { style: S.input, value: netTransport, onChange: e => setNetTransport(e.target.value), children: [_jsx("option", { value: "rest", children: "REST" }), _jsx("option", { value: "graphql", children: "GraphQL" }), _jsx("option", { value: "jsonrpc", children: "JSON-RPC" }), _jsx("option", { value: "ws", children: "WebSocket" })] })] }), _jsxs("div", { children: [_jsx("label", { style: { fontSize: 13, fontWeight: 500, color: '#374151', marginBottom: 4, display: 'block' }, children: "API Key (optionnel)" }), _jsx("input", { style: S.input, value: netApiKey, onChange: e => setNetApiKey(e.target.value), placeholder: "msk_live_...", type: "password" })] })] })] }), _jsx("button", { style: { ...S.btn('primary'), marginBottom: 16 }, disabled: netTesting || !netUrl, onClick: async () => {
|
|
811
|
+
setNetTesting(true);
|
|
812
|
+
setNetTestResult(null);
|
|
813
|
+
try {
|
|
814
|
+
const res = await fetch(ep.setupJson.replace('setup-json', 'net-test'), {
|
|
815
|
+
method: 'POST',
|
|
816
|
+
headers: { 'Content-Type': 'application/json' },
|
|
817
|
+
body: JSON.stringify({ url: netUrl }),
|
|
818
|
+
});
|
|
819
|
+
const data = await res.json();
|
|
820
|
+
setNetTestResult(data);
|
|
821
|
+
}
|
|
822
|
+
catch (e) {
|
|
823
|
+
setNetTestResult({ ok: false, error: e.message });
|
|
824
|
+
}
|
|
825
|
+
setNetTesting(false);
|
|
826
|
+
}, children: netTesting ? '⏳ Test en cours...' : '🔌 Tester la connexion' }), netTestResult && (_jsx("div", { style: {
|
|
827
|
+
padding: 12, borderRadius: 8, marginBottom: 16,
|
|
828
|
+
backgroundColor: netTestResult.ok ? '#f0fdf4' : '#fef2f2',
|
|
829
|
+
border: `1px solid ${netTestResult.ok ? '#bbf7d0' : '#fecaca'}`,
|
|
830
|
+
}, children: netTestResult.ok ? (_jsxs("div", { children: [_jsx("div", { style: { fontWeight: 600, color: '#166534', marginBottom: 4 }, children: "\u2705 Serveur connecte" }), netTestResult.entities && (_jsxs("div", { style: { fontSize: 13, color: '#374151' }, children: [_jsx("strong", { children: netTestResult.entities.length }), " entites : ", netTestResult.entities.join(', ')] })), netTestResult.transports && (_jsxs("div", { style: { fontSize: 13, color: '#6b7280', marginTop: 4 }, children: ["Transports : ", netTestResult.transports.join(', ')] }))] })) : (_jsxs("div", { style: { color: '#991b1b' }, children: ["\u274C ", netTestResult.error || 'Connexion echouee'] })) }))] })), 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 => {
|
|
797
831
|
const mod = availableModules.find(m => m.key === key);
|
|
798
832
|
return mod ? (_jsxs("span", { style: {
|
|
799
833
|
display: 'inline-flex', alignItems: 'center', gap: 4,
|
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export { loadSetupJson } from './lib/load-setup-json.js';
|
|
|
9
9
|
export type { SetupJson, SetupJsonRbac, SetupJsonSeed, SetupJsonCategory, SetupJsonPermission, SetupJsonRole } from './lib/load-setup-json.js';
|
|
10
10
|
export { createSetupRoutes } from './api/routes.js';
|
|
11
11
|
export type { SetupRoutesConfig } from './api/routes.js';
|
|
12
|
+
export { NetClient } from './lib/net-client.js';
|
|
13
|
+
export type { NetClientConfig, NetHealthResponse } from './lib/net-client.js';
|
|
12
14
|
export { createTestDbHandler } from './api/test-db.route.js';
|
|
13
15
|
export { createInstallHandler } from './api/install.route.js';
|
|
14
16
|
export { createStatusHandler } from './api/status.route.js';
|
|
@@ -23,4 +25,4 @@ export type { PreflightCheck } from './api/preflight.route.js';
|
|
|
23
25
|
export { createCreateDbHandler } from './api/create-db.route.js';
|
|
24
26
|
export { default as ReconfigPanel } from './components/ReconfigPanel.js';
|
|
25
27
|
export { default as SetupWizard } from './components/SetupWizard.js';
|
|
26
|
-
export type { DialectType, DialectInfo, DbConfig, InstallConfig, SeedOptions, SeedDefinition, MostaSetupConfig, ModuleDefinition, } from './types/index.js';
|
|
28
|
+
export type { DialectType, DialectInfo, DbConfig, InstallConfig, SeedOptions, SeedDefinition, MostaSetupConfig, ModuleDefinition, SetupMode, NetConfig, } from './types/index.js';
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,8 @@ export { discoverNpmModules } from './lib/discover-modules.js';
|
|
|
13
13
|
export { loadSetupJson } from './lib/load-setup-json.js';
|
|
14
14
|
// Catch-all route factory (replaces individual route files in host app)
|
|
15
15
|
export { createSetupRoutes } from './api/routes.js';
|
|
16
|
+
// NET client (for setup via @mostajs/net)
|
|
17
|
+
export { NetClient } from './lib/net-client.js';
|
|
16
18
|
// API route factories (individual — still available for granular use)
|
|
17
19
|
export { createTestDbHandler } from './api/test-db.route.js';
|
|
18
20
|
export { createInstallHandler } from './api/install.route.js';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export interface NetClientConfig {
|
|
2
|
+
url: string;
|
|
3
|
+
apiKey?: string;
|
|
4
|
+
}
|
|
5
|
+
export interface NetHealthResponse {
|
|
6
|
+
status: string;
|
|
7
|
+
transports: string[];
|
|
8
|
+
entities: string[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Client for communicating with @mostajs/net server during setup.
|
|
12
|
+
* Used by runNetInstall to seed RBAC, create admin, run seeds via REST.
|
|
13
|
+
*/
|
|
14
|
+
export declare class NetClient {
|
|
15
|
+
private baseUrl;
|
|
16
|
+
private headers;
|
|
17
|
+
constructor(config: NetClientConfig);
|
|
18
|
+
/** Test connectivity to the NET server */
|
|
19
|
+
health(): Promise<NetHealthResponse>;
|
|
20
|
+
/** Test database connection via NET */
|
|
21
|
+
testDbConnection(): Promise<{
|
|
22
|
+
ok: boolean;
|
|
23
|
+
message: string;
|
|
24
|
+
}>;
|
|
25
|
+
/** Get schemas config from NET */
|
|
26
|
+
getSchemasConfig(): Promise<{
|
|
27
|
+
schemasJsonExists: boolean;
|
|
28
|
+
schemaCount: number;
|
|
29
|
+
schemas: {
|
|
30
|
+
name: string;
|
|
31
|
+
collection: string;
|
|
32
|
+
}[];
|
|
33
|
+
}>;
|
|
34
|
+
/** Apply schema (create tables) */
|
|
35
|
+
applySchema(): Promise<{
|
|
36
|
+
ok: boolean;
|
|
37
|
+
message: string;
|
|
38
|
+
tables?: string[];
|
|
39
|
+
}>;
|
|
40
|
+
/** Create an entity via REST */
|
|
41
|
+
create(collection: string, data: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
42
|
+
/** Find one entity by filter */
|
|
43
|
+
findOne(collection: string, filter: Record<string, unknown>): Promise<Record<string, unknown> | null>;
|
|
44
|
+
/** Upsert: find by filter, update if exists, create if not */
|
|
45
|
+
upsert(collection: string, filter: Record<string, unknown>, data: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
46
|
+
/** Count entities in a collection */
|
|
47
|
+
count(collection: string, filter?: Record<string, unknown>): Promise<number>;
|
|
48
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// @mostajs/setup — NET client
|
|
2
|
+
// Communicates with a @mostajs/net server via REST for setup operations
|
|
3
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
4
|
+
/**
|
|
5
|
+
* Client for communicating with @mostajs/net server during setup.
|
|
6
|
+
* Used by runNetInstall to seed RBAC, create admin, run seeds via REST.
|
|
7
|
+
*/
|
|
8
|
+
export class NetClient {
|
|
9
|
+
baseUrl;
|
|
10
|
+
headers;
|
|
11
|
+
constructor(config) {
|
|
12
|
+
this.baseUrl = config.url.replace(/\/$/, '');
|
|
13
|
+
this.headers = { 'Content-Type': 'application/json' };
|
|
14
|
+
if (config.apiKey) {
|
|
15
|
+
this.headers['Authorization'] = `Bearer ${config.apiKey}`;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
// ── Health & Status ──────────────────────────────
|
|
19
|
+
/** Test connectivity to the NET server */
|
|
20
|
+
async health() {
|
|
21
|
+
const res = await fetch(`${this.baseUrl}/health`);
|
|
22
|
+
if (!res.ok)
|
|
23
|
+
throw new Error(`NET server returned ${res.status}`);
|
|
24
|
+
return res.json();
|
|
25
|
+
}
|
|
26
|
+
/** Test database connection via NET */
|
|
27
|
+
async testDbConnection() {
|
|
28
|
+
const res = await fetch(`${this.baseUrl}/api/test-connection`, {
|
|
29
|
+
method: 'POST',
|
|
30
|
+
headers: this.headers,
|
|
31
|
+
});
|
|
32
|
+
return res.json();
|
|
33
|
+
}
|
|
34
|
+
/** Get schemas config from NET */
|
|
35
|
+
async getSchemasConfig() {
|
|
36
|
+
const res = await fetch(`${this.baseUrl}/api/schemas-config`, { headers: this.headers });
|
|
37
|
+
return res.json();
|
|
38
|
+
}
|
|
39
|
+
/** Apply schema (create tables) */
|
|
40
|
+
async applySchema() {
|
|
41
|
+
const res = await fetch(`${this.baseUrl}/api/apply-schema`, {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
headers: this.headers,
|
|
44
|
+
});
|
|
45
|
+
return res.json();
|
|
46
|
+
}
|
|
47
|
+
// ── CRUD ─────────────────────────────────────────
|
|
48
|
+
/** Create an entity via REST */
|
|
49
|
+
async create(collection, data) {
|
|
50
|
+
const res = await fetch(`${this.baseUrl}/api/v1/${collection}`, {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
headers: this.headers,
|
|
53
|
+
body: JSON.stringify(data),
|
|
54
|
+
});
|
|
55
|
+
const json = await res.json();
|
|
56
|
+
if (json.status === 'error')
|
|
57
|
+
throw new Error(json.error?.message || 'Create failed');
|
|
58
|
+
return json.data;
|
|
59
|
+
}
|
|
60
|
+
/** Find one entity by filter */
|
|
61
|
+
async findOne(collection, filter) {
|
|
62
|
+
const params = new URLSearchParams({ filter: JSON.stringify(filter), limit: '1' });
|
|
63
|
+
const res = await fetch(`${this.baseUrl}/api/v1/${collection}?${params}`, { headers: this.headers });
|
|
64
|
+
const json = await res.json();
|
|
65
|
+
if (json.status === 'error')
|
|
66
|
+
return null;
|
|
67
|
+
const data = json.data;
|
|
68
|
+
return Array.isArray(data) && data.length > 0 ? data[0] : null;
|
|
69
|
+
}
|
|
70
|
+
/** Upsert: find by filter, update if exists, create if not */
|
|
71
|
+
async upsert(collection, filter, data) {
|
|
72
|
+
const existing = await this.findOne(collection, filter);
|
|
73
|
+
if (existing) {
|
|
74
|
+
const res = await fetch(`${this.baseUrl}/api/v1/${collection}/${existing.id}`, {
|
|
75
|
+
method: 'PUT',
|
|
76
|
+
headers: this.headers,
|
|
77
|
+
body: JSON.stringify(data),
|
|
78
|
+
});
|
|
79
|
+
const json = await res.json();
|
|
80
|
+
if (json.status === 'error')
|
|
81
|
+
throw new Error(json.error?.message || 'Update failed');
|
|
82
|
+
return json.data;
|
|
83
|
+
}
|
|
84
|
+
return this.create(collection, data);
|
|
85
|
+
}
|
|
86
|
+
/** Count entities in a collection */
|
|
87
|
+
async count(collection, filter) {
|
|
88
|
+
const params = filter ? new URLSearchParams({ filter: JSON.stringify(filter) }) : '';
|
|
89
|
+
const res = await fetch(`${this.baseUrl}/api/v1/${collection}/count${params ? '?' + params : ''}`, { headers: this.headers });
|
|
90
|
+
const json = await res.json();
|
|
91
|
+
return json.data ?? 0;
|
|
92
|
+
}
|
|
93
|
+
}
|
package/dist/lib/setup.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
3
|
import { composeDbUri } from './compose-uri.js';
|
|
4
4
|
import { writeEnvLocal } from './env-writer.js';
|
|
5
|
+
import { NetClient } from './net-client.js';
|
|
5
6
|
/**
|
|
6
7
|
* Check if the app needs initial setup (0 users in DB).
|
|
7
8
|
* Provide a countUsers function from your app.
|
|
@@ -26,6 +27,10 @@ export async function needsSetup(countUsers) {
|
|
|
26
27
|
* 6. Run optional seeds
|
|
27
28
|
*/
|
|
28
29
|
export async function runInstall(installConfig, setupConfig) {
|
|
30
|
+
// Route to NET install if mode is 'net'
|
|
31
|
+
if (installConfig.mode === 'net' && installConfig.net) {
|
|
32
|
+
return runNetInstall(installConfig, setupConfig);
|
|
33
|
+
}
|
|
29
34
|
try {
|
|
30
35
|
// 1. Compose URI and write .env.local
|
|
31
36
|
const uri = composeDbUri(installConfig.dialect, installConfig.db);
|
|
@@ -105,3 +110,76 @@ export async function runInstall(installConfig, setupConfig) {
|
|
|
105
110
|
return { ok: false, error: message, needsRestart: false };
|
|
106
111
|
}
|
|
107
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Run installation via @mostajs/net (remote server).
|
|
115
|
+
* Seeds RBAC, creates admin, runs optional seeds — all via REST.
|
|
116
|
+
*/
|
|
117
|
+
async function runNetInstall(installConfig, setupConfig) {
|
|
118
|
+
try {
|
|
119
|
+
const net = new NetClient({
|
|
120
|
+
url: installConfig.net.url,
|
|
121
|
+
apiKey: installConfig.net.apiKey,
|
|
122
|
+
});
|
|
123
|
+
// 1. Write .env.local with NET config
|
|
124
|
+
const extraVars = {
|
|
125
|
+
MOSTA_NET_URL: installConfig.net.url,
|
|
126
|
+
MOSTA_NET_TRANSPORT: installConfig.net.transport,
|
|
127
|
+
...(installConfig.net.apiKey ? { MOSTA_NET_API_KEY: installConfig.net.apiKey } : {}),
|
|
128
|
+
...setupConfig.extraEnvVars,
|
|
129
|
+
};
|
|
130
|
+
if (installConfig.modules?.length) {
|
|
131
|
+
extraVars['MOSTAJS_MODULES'] = installConfig.modules.join(',');
|
|
132
|
+
}
|
|
133
|
+
await writeEnvLocal({
|
|
134
|
+
dialect: 'net',
|
|
135
|
+
uri: installConfig.net.url,
|
|
136
|
+
extraVars,
|
|
137
|
+
port: setupConfig.defaultPort,
|
|
138
|
+
});
|
|
139
|
+
const seeded = [];
|
|
140
|
+
// 2. Verify NET server is reachable
|
|
141
|
+
const health = await net.health();
|
|
142
|
+
if (!health.entities?.length) {
|
|
143
|
+
return { ok: false, error: 'Serveur NET accessible mais aucun schema chargé', needsRestart: false };
|
|
144
|
+
}
|
|
145
|
+
// 3. Seed RBAC via REST (if setup.json has rbac)
|
|
146
|
+
if (setupConfig.seedRBAC) {
|
|
147
|
+
// seedRBAC uses the repoFactory which works with direct ORM.
|
|
148
|
+
// For NET mode, we need to use the NET client instead.
|
|
149
|
+
// The loadSetupJson with a netRepoFactory will handle this.
|
|
150
|
+
await setupConfig.seedRBAC();
|
|
151
|
+
seeded.push('categories', 'permissions', 'roles');
|
|
152
|
+
}
|
|
153
|
+
// 4. Create admin user via REST
|
|
154
|
+
if (installConfig.admin.email) {
|
|
155
|
+
const bcryptModule = await import('bcryptjs');
|
|
156
|
+
const bcrypt = bcryptModule.default || bcryptModule;
|
|
157
|
+
const hashedPassword = await bcrypt.hash(installConfig.admin.password, 12);
|
|
158
|
+
// Find admin role
|
|
159
|
+
const adminRole = await net.findOne('roles', { name: 'admin' });
|
|
160
|
+
await net.create('users', {
|
|
161
|
+
email: installConfig.admin.email,
|
|
162
|
+
password: hashedPassword,
|
|
163
|
+
firstName: installConfig.admin.firstName,
|
|
164
|
+
lastName: installConfig.admin.lastName,
|
|
165
|
+
status: 'active',
|
|
166
|
+
roles: adminRole ? [adminRole.id] : [],
|
|
167
|
+
});
|
|
168
|
+
seeded.push('admin');
|
|
169
|
+
}
|
|
170
|
+
// 5. Optional seeds via REST
|
|
171
|
+
if (setupConfig.optionalSeeds && installConfig.seed) {
|
|
172
|
+
for (const seedDef of setupConfig.optionalSeeds) {
|
|
173
|
+
if (installConfig.seed[seedDef.key]) {
|
|
174
|
+
await seedDef.run({});
|
|
175
|
+
seeded.push(seedDef.key);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return { ok: true, needsRestart: false, seeded };
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
const message = err instanceof Error ? err.message : 'Erreur installation NET';
|
|
183
|
+
return { ok: false, error: message, needsRestart: false };
|
|
184
|
+
}
|
|
185
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -16,9 +16,17 @@ export interface DbConfig {
|
|
|
16
16
|
user: string;
|
|
17
17
|
password: string;
|
|
18
18
|
}
|
|
19
|
+
export type SetupMode = 'orm' | 'net';
|
|
20
|
+
export interface NetConfig {
|
|
21
|
+
url: string;
|
|
22
|
+
transport: 'rest' | 'graphql' | 'jsonrpc' | 'ws';
|
|
23
|
+
apiKey?: string;
|
|
24
|
+
}
|
|
19
25
|
export interface InstallConfig {
|
|
26
|
+
mode?: SetupMode;
|
|
20
27
|
dialect: DialectType;
|
|
21
28
|
db: DbConfig;
|
|
29
|
+
net?: NetConfig;
|
|
22
30
|
admin: {
|
|
23
31
|
email: string;
|
|
24
32
|
password: string;
|
package/package.json
CHANGED