@mostajs/setup 2.1.27 → 2.1.29
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/README.md +45 -1142
- package/dist/api/routes.js +25 -1
- package/dist/components/SetupWizard.js +22 -88
- package/dist/lib/load-setup-json.js +1 -1
- package/dist/lib/setup.js +25 -6
- package/package.json +1 -1
package/dist/api/routes.js
CHANGED
|
@@ -85,7 +85,31 @@ export function createSetupRoutes(config) {
|
|
|
85
85
|
const { NetClient } = await import('../lib/net-client.js');
|
|
86
86
|
const client = new NetClient({ url: body.url });
|
|
87
87
|
const health = await client.health();
|
|
88
|
-
|
|
88
|
+
// Auto-save MOSTA_DATA, MOSTA_NET_URL, MOSTA_NET_TRANSPORT in .env.local
|
|
89
|
+
try {
|
|
90
|
+
const { readFileSync, writeFileSync, existsSync } = await import('fs');
|
|
91
|
+
const { resolve } = await import('path');
|
|
92
|
+
const envPath = resolve(process.cwd(), '.env.local');
|
|
93
|
+
let content = existsSync(envPath) ? readFileSync(envPath, 'utf-8') : '';
|
|
94
|
+
const updates = {
|
|
95
|
+
'MOSTA_DATA': 'net',
|
|
96
|
+
'MOSTA_NET_URL': body.url,
|
|
97
|
+
'MOSTA_NET_TRANSPORT': body.transport || 'rest',
|
|
98
|
+
};
|
|
99
|
+
for (const [key, val] of Object.entries(updates)) {
|
|
100
|
+
const regex = new RegExp(`^${key}=.*$`, 'm');
|
|
101
|
+
if (regex.test(content)) {
|
|
102
|
+
content = content.replace(regex, `${key}=${val}`);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
content += `\n${key}=${val}`;
|
|
106
|
+
}
|
|
107
|
+
process.env[key] = val;
|
|
108
|
+
}
|
|
109
|
+
writeFileSync(envPath, content);
|
|
110
|
+
}
|
|
111
|
+
catch { }
|
|
112
|
+
return Response.json({ ok: true, ...health, saved: true });
|
|
89
113
|
}
|
|
90
114
|
catch (e) {
|
|
91
115
|
return Response.json({ ok: false, error: e instanceof Error ? e.message : 'Connexion echouee' });
|
|
@@ -836,7 +836,7 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
|
|
|
836
836
|
const res = await fetch(ep.setupJson.replace('setup-json', 'net-test'), {
|
|
837
837
|
method: 'POST',
|
|
838
838
|
headers: { 'Content-Type': 'application/json' },
|
|
839
|
-
body: JSON.stringify({ url: netUrl }),
|
|
839
|
+
body: JSON.stringify({ url: netUrl, transport: netTransport }),
|
|
840
840
|
});
|
|
841
841
|
const data = await res.json();
|
|
842
842
|
setNetTestResult(data);
|
|
@@ -849,49 +849,24 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
|
|
|
849
849
|
padding: 12, borderRadius: 8, marginBottom: 16,
|
|
850
850
|
backgroundColor: netTestResult.ok ? '#f0fdf4' : '#fef2f2',
|
|
851
851
|
border: `1px solid ${netTestResult.ok ? '#bbf7d0' : '#fecaca'}`,
|
|
852
|
-
}, 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 &&
|
|
853
|
-
padding: 16, borderRadius: 8, marginBottom: 16,
|
|
854
|
-
backgroundColor: '#fffbeb', border: '1px solid #fde68a',
|
|
855
|
-
}, 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) => {
|
|
852
|
+
}, 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 && (_jsxs("div", { style: { padding: 16, borderRadius: 8, marginBottom: 16, backgroundColor: '#f8fafc', border: '1px solid #e2e8f0' }, children: [_jsxs("div", { style: { display: 'flex', gap: 8, alignItems: 'center', marginBottom: 12, flexWrap: 'wrap' }, children: [_jsx("span", { style: { fontSize: 13, fontWeight: 600, color: '#64748b', minWidth: 65 }, children: "Etape 1:" }), _jsx("button", { style: { ...S.btn('primary'), fontSize: 13 }, onClick: () => document.getElementById('schemaFileInput')?.click(), children: "Uploader schemas.json" }), _jsx("input", { id: "schemaFileInput", type: "file", accept: ".json", style: { display: 'none' }, onChange: async (e) => {
|
|
856
853
|
const file = e.target.files?.[0];
|
|
857
854
|
if (!file)
|
|
858
855
|
return;
|
|
859
|
-
setSchemaUploadStatus({ phase: '
|
|
856
|
+
setSchemaUploadStatus({ phase: 'Envoi...', color: '#2563eb' });
|
|
860
857
|
try {
|
|
861
858
|
const text = await file.text();
|
|
862
859
|
const schemas = JSON.parse(text);
|
|
863
|
-
const res = await fetch(netUrl + '/api/upload-schemas
|
|
864
|
-
method: 'POST',
|
|
865
|
-
|
|
866
|
-
body: JSON.stringify({ schemas }),
|
|
860
|
+
const res = await fetch(netUrl + '/api/upload-schemas', {
|
|
861
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
862
|
+
body: JSON.stringify({ schemas: Array.isArray(schemas) ? schemas : [schemas] }),
|
|
867
863
|
});
|
|
868
864
|
const data = await res.json();
|
|
869
865
|
if (data.ok) {
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
await new Promise(r => setTimeout(r, 1500));
|
|
875
|
-
setSchemaUploadStatus({ phase: `⏳ En attente du serveur... (${i + 1}/30)`, color: '#d97706' });
|
|
876
|
-
try {
|
|
877
|
-
const h = await fetch(netUrl + '/health');
|
|
878
|
-
if (h.ok) {
|
|
879
|
-
const hd = await h.json();
|
|
880
|
-
if (hd.entities?.length > 0) {
|
|
881
|
-
setSchemaUploadStatus({ phase: `✅ Serveur prêt — ${hd.entities.length} entités`, color: '#16a34a' });
|
|
882
|
-
setSchemasReady(true);
|
|
883
|
-
setNetTestResult({ ...netTestResult, entities: hd.entities });
|
|
884
|
-
break;
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
catch { }
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
else {
|
|
892
|
-
setSchemaUploadStatus({ phase: `✅ ${data.count} schemas chargés`, color: '#16a34a' });
|
|
893
|
-
setSchemasReady(true);
|
|
894
|
-
}
|
|
866
|
+
setSchemaUploadStatus({ phase: `✅ ${data.count} schemas uploades`, color: '#16a34a' });
|
|
867
|
+
const h = await fetch(netUrl + '/health').then(r => r.json());
|
|
868
|
+
if (h.entities)
|
|
869
|
+
setNetTestResult({ ...netTestResult, entities: h.entities });
|
|
895
870
|
}
|
|
896
871
|
else {
|
|
897
872
|
setSchemaUploadStatus({ phase: `❌ ${data.error}`, color: '#dc2626' });
|
|
@@ -901,74 +876,33 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
|
|
|
901
876
|
setSchemaUploadStatus({ phase: `❌ ${err.message}`, color: '#dc2626' });
|
|
902
877
|
}
|
|
903
878
|
e.target.value = '';
|
|
904
|
-
} }), _jsx("
|
|
905
|
-
|
|
906
|
-
if (!file)
|
|
907
|
-
return;
|
|
908
|
-
setSchemaUploadStatus({ phase: '📤 Envoi du ZIP...', color: '#2563eb' });
|
|
879
|
+
} }), _jsx("span", { style: { fontSize: 12, color: schemaUploadStatus?.color || '#94a3b8' }, children: schemaUploadStatus?.phase || ((netTestResult.entities?.length ?? 0) > 0 ? `✅ ${netTestResult.entities.length} schemas` : 'Aucun schema') })] }), _jsxs("div", { style: { display: 'flex', gap: 8, alignItems: 'center', marginBottom: 12, flexWrap: 'wrap' }, children: [_jsx("span", { style: { fontSize: 13, fontWeight: 600, color: '#64748b', minWidth: 65 }, children: "Etape 2:" }), _jsx("button", { style: { ...S.btn('primary'), fontSize: 13, backgroundColor: '#f59e0b', color: '#000' }, disabled: (netTestResult.entities?.length ?? 0) === 0, onClick: async () => {
|
|
880
|
+
setSchemaUploadStatus({ phase: 'Application du schema...', color: '#2563eb' });
|
|
909
881
|
try {
|
|
910
|
-
const
|
|
911
|
-
formData.append('file', file);
|
|
912
|
-
const res = await fetch(netUrl + '/api/upload-schemas', {
|
|
913
|
-
method: 'POST',
|
|
914
|
-
body: formData,
|
|
915
|
-
});
|
|
882
|
+
const res = await fetch(netUrl + '/api/apply-schema', { method: 'POST' });
|
|
916
883
|
const data = await res.json();
|
|
917
|
-
|
|
918
|
-
setSchemaUploadStatus({ phase: `✅ ${data.count} schemas importés depuis ZIP`, color: '#16a34a' });
|
|
919
|
-
setSchemasReady(true);
|
|
920
|
-
// Refresh test result
|
|
921
|
-
const h = await fetch(netUrl + '/health').then(r => r.json());
|
|
922
|
-
if (h.entities)
|
|
923
|
-
setNetTestResult({ ...netTestResult, entities: h.entities });
|
|
924
|
-
}
|
|
925
|
-
else {
|
|
926
|
-
setSchemaUploadStatus({ phase: `❌ ${data.error}`, color: '#dc2626' });
|
|
927
|
-
}
|
|
884
|
+
setSchemaUploadStatus({ phase: data.ok ? `✅ ${data.message || 'Schema applique'}` : `❌ ${data.error || data.message}`, color: data.ok ? '#16a34a' : '#dc2626' });
|
|
928
885
|
}
|
|
929
886
|
catch (err) {
|
|
930
887
|
setSchemaUploadStatus({ phase: `❌ ${err.message}`, color: '#dc2626' });
|
|
931
888
|
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
const schemasPath = prompt('Chemin vers le répertoire des schemas (*.schema.ts) :', './src/dal/schemas');
|
|
935
|
-
if (!schemasPath)
|
|
936
|
-
return;
|
|
937
|
-
setSchemaUploadStatus({ phase: '🔍 Scan en cours...', color: '#2563eb' });
|
|
889
|
+
}, children: "Appliquer le schema" })] }), _jsxs("div", { style: { display: 'flex', gap: 8, alignItems: 'center', marginBottom: 8, flexWrap: 'wrap' }, children: [_jsx("span", { style: { fontSize: 13, fontWeight: 600, color: '#64748b', minWidth: 65 }, children: "Etape 3:" }), _jsx("button", { style: { ...S.btn('primary'), fontSize: 13, backgroundColor: '#22c55e' }, disabled: (netTestResult.entities?.length ?? 0) === 0, onClick: async () => {
|
|
890
|
+
setSchemaUploadStatus({ phase: 'Enregistrement...', color: '#2563eb' });
|
|
938
891
|
try {
|
|
939
|
-
const res = await fetch(netUrl + '/api/
|
|
940
|
-
method: 'POST',
|
|
941
|
-
headers: { 'Content-Type': 'application/json' },
|
|
942
|
-
body: JSON.stringify({ path: schemasPath }),
|
|
943
|
-
});
|
|
892
|
+
const res = await fetch(netUrl + '/api/save-config', { method: 'POST' });
|
|
944
893
|
const data = await res.json();
|
|
945
|
-
if (data.ok
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
method: 'POST',
|
|
949
|
-
headers: { 'Content-Type': 'application/json' },
|
|
950
|
-
body: JSON.stringify({ path: schemasPath }),
|
|
951
|
-
});
|
|
952
|
-
const genData = await genRes.json();
|
|
953
|
-
if (genData.ok) {
|
|
954
|
-
setSchemaUploadStatus({ phase: `✅ ${genData.count} schemas scannés et générés`, color: '#16a34a' });
|
|
955
|
-
setSchemasReady(true);
|
|
956
|
-
const h = await fetch(netUrl + '/health').then(r => r.json());
|
|
957
|
-
if (h.entities)
|
|
958
|
-
setNetTestResult({ ...netTestResult, entities: h.entities });
|
|
959
|
-
}
|
|
894
|
+
if (data.ok) {
|
|
895
|
+
setSchemaUploadStatus({ phase: '✅ Config enregistree', color: '#16a34a' });
|
|
896
|
+
setSchemasReady(true);
|
|
960
897
|
}
|
|
961
898
|
else {
|
|
962
|
-
setSchemaUploadStatus({ phase: `❌
|
|
899
|
+
setSchemaUploadStatus({ phase: `❌ ${data.error || data.message}`, color: '#dc2626' });
|
|
963
900
|
}
|
|
964
901
|
}
|
|
965
902
|
catch (err) {
|
|
966
903
|
setSchemaUploadStatus({ phase: `❌ ${err.message}`, color: '#dc2626' });
|
|
967
904
|
}
|
|
968
|
-
}, children: "
|
|
969
|
-
padding: 12, borderRadius: 8, marginBottom: 16,
|
|
970
|
-
backgroundColor: '#f0fdf4', border: '1px solid #bbf7d0',
|
|
971
|
-
}, 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') })), setupMode === 'net' && (_jsxs("div", { style: { marginTop: 16, marginBottom: 16 }, children: [_jsx("button", { style: { ...S.btn('primary'), backgroundColor: '#16a34a' }, disabled: adminSaving || !adminConfig.email || !adminConfig.password || !adminConfig.firstName || adminConfig.password !== adminConfig.confirmPassword, onClick: async () => {
|
|
905
|
+
}, children: "Enregistrer la config" })] }), schemaUploadStatus && (_jsx("div", { style: { fontSize: 13, fontWeight: 500, color: schemaUploadStatus.color, marginTop: 4 }, children: schemaUploadStatus.phase }))] })), _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') })), setupMode === 'net' && (_jsxs("div", { style: { marginTop: 16, marginBottom: 16 }, children: [_jsx("button", { style: { ...S.btn('primary'), backgroundColor: '#16a34a' }, disabled: adminSaving || !adminConfig.email || !adminConfig.password || !adminConfig.firstName || adminConfig.password !== adminConfig.confirmPassword, onClick: async () => {
|
|
972
906
|
setAdminSaving(true);
|
|
973
907
|
setAdminSaveResult(null);
|
|
974
908
|
try {
|
|
@@ -93,7 +93,7 @@ function buildConfig(json, repoFactory) {
|
|
|
93
93
|
if (json.rbac.roles?.length) {
|
|
94
94
|
const roleRepo = await getRepo('role');
|
|
95
95
|
const allPermIds = Object.values(permissionMap);
|
|
96
|
-
for (const roleDef of json.rbac.roles.filter(r => r.name)) {
|
|
96
|
+
for (const roleDef of json.rbac.roles.filter(r => r.name && r.name.trim() !== '')) {
|
|
97
97
|
const permissionIds = roleDef.permissions.includes('*')
|
|
98
98
|
? allPermIds
|
|
99
99
|
: roleDef.permissions.map(code => permissionMap[code]).filter(Boolean);
|
package/dist/lib/setup.js
CHANGED
|
@@ -179,16 +179,18 @@ async function runNetInstall(installConfig, setupConfig) {
|
|
|
179
179
|
extraVars['MOSTAJS_MODULES'] = installConfig.modules.join(',');
|
|
180
180
|
}
|
|
181
181
|
const seeded = [];
|
|
182
|
-
// 2. Verify NET server is reachable (retry
|
|
182
|
+
// 2. Verify NET server is reachable (retry with backoff, max 20s)
|
|
183
183
|
let health = null;
|
|
184
|
-
|
|
184
|
+
let retryDelay = 500;
|
|
185
|
+
for (let i = 0; i < 15; i++) {
|
|
185
186
|
try {
|
|
186
187
|
health = await net.health();
|
|
187
188
|
if (health.entities?.length > 0)
|
|
188
189
|
break;
|
|
189
190
|
}
|
|
190
191
|
catch { }
|
|
191
|
-
await new Promise(r => setTimeout(r,
|
|
192
|
+
await new Promise(r => setTimeout(r, retryDelay));
|
|
193
|
+
retryDelay = Math.min(retryDelay * 1.5, 3000);
|
|
192
194
|
}
|
|
193
195
|
if (!health) {
|
|
194
196
|
return { ok: false, error: 'Serveur NET non joignable', needsRestart: false };
|
|
@@ -237,8 +239,24 @@ async function runNetInstall(installConfig, setupConfig) {
|
|
|
237
239
|
// Si le serveur redémarre, attendre qu'il soit de retour
|
|
238
240
|
if (result.needsRestart) {
|
|
239
241
|
console.log('[Setup] Serveur NET redémarre pour charger les schemas...');
|
|
240
|
-
|
|
241
|
-
|
|
242
|
+
// Phase 1 : attendre que le serveur soit DOWN (max 10s)
|
|
243
|
+
let serverDown = false;
|
|
244
|
+
for (let i = 0; i < 20; i++) {
|
|
245
|
+
try {
|
|
246
|
+
await net.health();
|
|
247
|
+
// Encore up — attendre
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
serverDown = true;
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
await new Promise(r => setTimeout(r, 500));
|
|
254
|
+
}
|
|
255
|
+
if (!serverDown) {
|
|
256
|
+
console.log('[Setup] Serveur n\'a pas redémarré — continue quand même');
|
|
257
|
+
}
|
|
258
|
+
// Phase 2 : attendre que le serveur soit UP avec schemas (max 60s, backoff)
|
|
259
|
+
let backoff = 500;
|
|
242
260
|
for (let i = 0; i < 30; i++) {
|
|
243
261
|
try {
|
|
244
262
|
const h = await net.health();
|
|
@@ -248,7 +266,8 @@ async function runNetInstall(installConfig, setupConfig) {
|
|
|
248
266
|
}
|
|
249
267
|
}
|
|
250
268
|
catch { }
|
|
251
|
-
await new Promise(r => setTimeout(r,
|
|
269
|
+
await new Promise(r => setTimeout(r, backoff));
|
|
270
|
+
backoff = Math.min(backoff * 1.5, 3000); // backoff: 500→750→1125→...→3000ms max
|
|
252
271
|
}
|
|
253
272
|
// Recharger la collection map
|
|
254
273
|
await net.loadCollectionMap();
|
package/package.json
CHANGED