@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.
@@ -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 ALL_STEPS = ['welcome', 'modules', 'dialect', 'database', 'admin', 'summary'];
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))) }))] }), _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 => {
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 === '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 => {
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
+ }
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/setup",
3
- "version": "2.1.5",
3
+ "version": "2.1.7",
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",