@mostajs/setup 2.1.20 → 2.1.22

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.
@@ -367,6 +367,8 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
367
367
  const [netApiKey, setNetApiKey] = useState('');
368
368
  const [netTestResult, setNetTestResult] = useState(null);
369
369
  const [netTesting, setNetTesting] = useState(false);
370
+ const [schemaUploadStatus, setSchemaUploadStatus] = useState(null);
371
+ const [schemasReady, setSchemasReady] = useState(false);
370
372
  const [currentStep, setCurrentStep] = useState(0);
371
373
  const [dialect, setDialect] = useState('mongodb');
372
374
  const [dbConfig, setDbConfig] = useState({ ...DIALECT_DEFAULTS.mongodb, name: `${dbNamePrefix}_prod` });
@@ -771,7 +773,8 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
771
773
  return dbConfig.name.trim() !== '';
772
774
  return dbTestResult?.ok === true;
773
775
  case 'net-config':
774
- return netTestResult?.ok === true;
776
+ // OK si serveur connecté ET (schemas déjà chargés OU uploadés)
777
+ return netTestResult?.ok === true && ((netTestResult.entities?.length ?? 0) > 0 || schemasReady);
775
778
  case 'admin':
776
779
  return adminConfig.firstName.trim() !== '' && adminConfig.lastName.trim() !== '' &&
777
780
  adminConfig.email.trim() !== '' && adminConfig.password.length >= 6 &&
@@ -843,7 +846,126 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
843
846
  padding: 12, borderRadius: 8, marginBottom: 16,
844
847
  backgroundColor: netTestResult.ok ? '#f0fdf4' : '#fef2f2',
845
848
  border: `1px solid ${netTestResult.ok ? '#bbf7d0' : '#fecaca'}`,
846
- }, 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'] })) })), _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: setupMode === 'net' ? 'Serveur @mostajs/net' : t('setup.summary.dbConfig') }), _jsxs("div", { style: S.summaryText, children: [_jsx("span", { style: { fontFamily: 'monospace' }, children: dbSummaryLabel() }), setupMode === 'net' && netTransport && _jsxs("span", { style: { display: 'block', marginTop: 4 }, children: ["Transport: ", netTransport] }), setupMode !== 'net' && 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 => {
849
+ }, 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'] })) })), netTestResult?.ok && netTestResult.entities?.length === 0 && !schemasReady && (_jsxs("div", { style: {
850
+ padding: 16, borderRadius: 8, marginBottom: 16,
851
+ backgroundColor: '#fffbeb', border: '1px solid #fde68a',
852
+ }, children: [_jsx("div", { style: { fontWeight: 600, color: '#92400e', marginBottom: 8 }, children: "\u26A0\uFE0F Le serveur n'a aucun schema \u2014 envoyez les schemas pour continuer" }), _jsxs("div", { style: { display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: 8 }, children: [_jsx("button", { style: { ...S.btn('primary'), fontSize: 13 }, onClick: () => document.getElementById('schemaFileInput')?.click(), children: "\uD83D\uDCC4 Envoyer schemas.json" }), _jsx("input", { id: "schemaFileInput", type: "file", accept: ".json", style: { display: 'none' }, onChange: async (e) => {
853
+ const file = e.target.files?.[0];
854
+ if (!file)
855
+ return;
856
+ setSchemaUploadStatus({ phase: '📤 Envoi du fichier...', color: '#2563eb' });
857
+ try {
858
+ const text = await file.text();
859
+ const schemas = JSON.parse(text);
860
+ const res = await fetch(netUrl + '/api/upload-schemas-json', {
861
+ method: 'POST',
862
+ headers: { 'Content-Type': 'application/json' },
863
+ body: JSON.stringify({ schemas }),
864
+ });
865
+ const data = await res.json();
866
+ if (data.ok) {
867
+ if (data.needsRestart) {
868
+ setSchemaUploadStatus({ phase: '⏳ Serveur redémarre...', color: '#d97706' });
869
+ // Poll health
870
+ for (let i = 0; i < 30; i++) {
871
+ await new Promise(r => setTimeout(r, 1500));
872
+ setSchemaUploadStatus({ phase: `⏳ En attente du serveur... (${i + 1}/30)`, color: '#d97706' });
873
+ try {
874
+ const h = await fetch(netUrl + '/health');
875
+ if (h.ok) {
876
+ const hd = await h.json();
877
+ if (hd.entities?.length > 0) {
878
+ setSchemaUploadStatus({ phase: `✅ Serveur prêt — ${hd.entities.length} entités`, color: '#16a34a' });
879
+ setSchemasReady(true);
880
+ setNetTestResult({ ...netTestResult, entities: hd.entities });
881
+ break;
882
+ }
883
+ }
884
+ }
885
+ catch { }
886
+ }
887
+ }
888
+ else {
889
+ setSchemaUploadStatus({ phase: `✅ ${data.count} schemas chargés`, color: '#16a34a' });
890
+ setSchemasReady(true);
891
+ }
892
+ }
893
+ else {
894
+ setSchemaUploadStatus({ phase: `❌ ${data.error}`, color: '#dc2626' });
895
+ }
896
+ }
897
+ catch (err) {
898
+ setSchemaUploadStatus({ phase: `❌ ${err.message}`, color: '#dc2626' });
899
+ }
900
+ e.target.value = '';
901
+ } }), _jsx("button", { style: { ...S.btn('outline'), fontSize: 13 }, onClick: () => document.getElementById('schemaZipInput')?.click(), children: "\uD83D\uDCE6 Envoyer ZIP de schemas" }), _jsx("input", { id: "schemaZipInput", type: "file", accept: ".zip", style: { display: 'none' }, onChange: async (e) => {
902
+ const file = e.target.files?.[0];
903
+ if (!file)
904
+ return;
905
+ setSchemaUploadStatus({ phase: '📤 Envoi du ZIP...', color: '#2563eb' });
906
+ try {
907
+ const formData = new FormData();
908
+ formData.append('file', file);
909
+ const res = await fetch(netUrl + '/api/upload-schemas', {
910
+ method: 'POST',
911
+ body: formData,
912
+ });
913
+ const data = await res.json();
914
+ if (data.ok) {
915
+ setSchemaUploadStatus({ phase: `✅ ${data.count} schemas importés depuis ZIP`, color: '#16a34a' });
916
+ setSchemasReady(true);
917
+ // Refresh test result
918
+ const h = await fetch(netUrl + '/health').then(r => r.json());
919
+ if (h.entities)
920
+ setNetTestResult({ ...netTestResult, entities: h.entities });
921
+ }
922
+ else {
923
+ setSchemaUploadStatus({ phase: `❌ ${data.error}`, color: '#dc2626' });
924
+ }
925
+ }
926
+ catch (err) {
927
+ setSchemaUploadStatus({ phase: `❌ ${err.message}`, color: '#dc2626' });
928
+ }
929
+ e.target.value = '';
930
+ } }), _jsx("button", { style: { ...S.btn('outline'), fontSize: 13 }, onClick: async () => {
931
+ const schemasPath = prompt('Chemin vers le répertoire des schemas (*.schema.ts) :', './src/dal/schemas');
932
+ if (!schemasPath)
933
+ return;
934
+ setSchemaUploadStatus({ phase: '🔍 Scan en cours...', color: '#2563eb' });
935
+ try {
936
+ const res = await fetch(netUrl + '/api/scan-schemas', {
937
+ method: 'POST',
938
+ headers: { 'Content-Type': 'application/json' },
939
+ body: JSON.stringify({ path: schemasPath }),
940
+ });
941
+ const data = await res.json();
942
+ if (data.ok && data.count > 0) {
943
+ // Générer et appliquer
944
+ const genRes = await fetch(netUrl + '/api/generate-schemas', {
945
+ method: 'POST',
946
+ headers: { 'Content-Type': 'application/json' },
947
+ body: JSON.stringify({ path: schemasPath }),
948
+ });
949
+ const genData = await genRes.json();
950
+ if (genData.ok) {
951
+ setSchemaUploadStatus({ phase: `✅ ${genData.count} schemas scannés et générés`, color: '#16a34a' });
952
+ setSchemasReady(true);
953
+ const h = await fetch(netUrl + '/health').then(r => r.json());
954
+ if (h.entities)
955
+ setNetTestResult({ ...netTestResult, entities: h.entities });
956
+ }
957
+ }
958
+ else {
959
+ setSchemaUploadStatus({ phase: `❌ Aucun schema trouvé dans ${schemasPath}`, color: '#dc2626' });
960
+ }
961
+ }
962
+ catch (err) {
963
+ setSchemaUploadStatus({ phase: `❌ ${err.message}`, color: '#dc2626' });
964
+ }
965
+ }, children: "\uD83D\uDCC1 Scanner un r\u00E9pertoire" })] }), schemaUploadStatus && (_jsx("div", { style: { fontSize: 13, fontWeight: 500, color: schemaUploadStatus.color }, children: schemaUploadStatus.phase }))] })), netTestResult?.ok && (netTestResult.entities?.length ?? 0) > 0 && (_jsx("div", { style: {
966
+ padding: 12, borderRadius: 8, marginBottom: 16,
967
+ backgroundColor: '#f0fdf4', border: '1px solid #bbf7d0',
968
+ }, children: _jsxs("div", { style: { fontWeight: 600, color: '#166534' }, children: ["\u2705 Serveur pr\u00EAt \u2014 ", netTestResult.entities?.length, " entit\u00E9s charg\u00E9es"] }) })), _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: setupMode === 'net' ? 'Serveur @mostajs/net' : t('setup.summary.dbConfig') }), _jsxs("div", { style: S.summaryText, children: [_jsx("span", { style: { fontFamily: 'monospace' }, children: dbSummaryLabel() }), setupMode === 'net' && netTransport && _jsxs("span", { style: { display: 'block', marginTop: 4 }, children: ["Transport: ", netTransport] }), setupMode !== 'net' && 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 => {
847
969
  const mod = availableModules.find(m => m.key === key);
848
970
  return mod ? (_jsxs("span", { style: {
849
971
  display: 'inline-flex', alignItems: 'center', gap: 4,
package/dist/lib/setup.js CHANGED
@@ -142,8 +142,20 @@ async function runNetInstall(installConfig, setupConfig) {
142
142
  extraVars['MOSTAJS_MODULES'] = installConfig.modules.join(',');
143
143
  }
144
144
  const seeded = [];
145
- // 2. Verify NET server is reachable
146
- const health = await net.health();
145
+ // 2. Verify NET server is reachable (retry up to 10s if just restarted)
146
+ let health = null;
147
+ for (let i = 0; i < 10; i++) {
148
+ try {
149
+ health = await net.health();
150
+ if (health.entities?.length > 0)
151
+ break;
152
+ }
153
+ catch { }
154
+ await new Promise(r => setTimeout(r, 1000));
155
+ }
156
+ if (!health) {
157
+ return { ok: false, error: 'Serveur NET non joignable', needsRestart: false };
158
+ }
147
159
  // 3. Read setup.json for RBAC definitions
148
160
  const fs = await import('fs');
149
161
  const path = await import('path');
@@ -155,8 +167,8 @@ async function runNetInstall(installConfig, setupConfig) {
155
167
  }
156
168
  catch { }
157
169
  }
158
- // 4. If server has no entities, try sending schemas via POST /api/upload-schemas-json
159
- if (!health.entities?.length) {
170
+ // 4. If server has no entities (schemas not yet uploaded), try sending them
171
+ if (!health?.entities?.length) {
160
172
  let schemasToSend = [];
161
173
  // Try schemas.json local
162
174
  const schemasJsonPath = path.resolve(process.cwd(), 'schemas.json');
@@ -217,6 +229,7 @@ async function runNetInstall(installConfig, setupConfig) {
217
229
  }
218
230
  }
219
231
  await net.loadCollectionMap();
232
+ console.log(`[Setup NET] Serveur prêt — ${health?.entities?.length ?? 0} entités, collectionMap chargée`);
220
233
  if (setupJson?.rbac) {
221
234
  const rbac = setupJson.rbac;
222
235
  // 3a. Upsert categories
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/setup",
3
- "version": "2.1.20",
3
+ "version": "2.1.22",
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",