@mostajs/setup 2.1.25 → 2.1.28
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 +1 -1
- package/dist/lib/load-setup-json.js +1 -1
- package/dist/lib/setup.js +76 -20
- 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);
|
|
@@ -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
|
@@ -73,14 +73,50 @@ export async function runInstall(installConfig, setupConfig) {
|
|
|
73
73
|
// 4. Disconnect existing dialect singleton
|
|
74
74
|
const { disconnectDialect } = await import('@mostajs/orm');
|
|
75
75
|
await disconnectDialect();
|
|
76
|
-
// 4. Seed RBAC
|
|
77
76
|
const seeded = [];
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
// 4. Discover modules and seed RBAC
|
|
78
|
+
const { discoverModules } = await import('./module-registry.js');
|
|
79
|
+
const modules = await discoverModules(installConfig.modules);
|
|
80
|
+
// 4a. Read setup.json for RBAC + seed data
|
|
81
|
+
const fs = await import('fs');
|
|
82
|
+
const path = await import('path');
|
|
83
|
+
let setupJson = null;
|
|
84
|
+
const setupJsonPath = path.resolve(process.cwd(), 'setup.json');
|
|
85
|
+
if (fs.existsSync(setupJsonPath)) {
|
|
86
|
+
try {
|
|
87
|
+
setupJson = JSON.parse(fs.readFileSync(setupJsonPath, 'utf-8'));
|
|
88
|
+
}
|
|
89
|
+
catch { }
|
|
81
90
|
}
|
|
82
|
-
//
|
|
83
|
-
|
|
91
|
+
// 4b. Seed each module (RBAC gets setup.json.rbac data from Studio)
|
|
92
|
+
for (const mod of modules) {
|
|
93
|
+
try {
|
|
94
|
+
const seedData = setupJson?.[mod.name] || {};
|
|
95
|
+
await mod.seed(seedData);
|
|
96
|
+
seeded.push(mod.name);
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
console.error(`[Setup] Module "${mod.name}" seed failed:`, err);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// 5. Create admin via rbac module (not setup — rbac handles hash + role)
|
|
103
|
+
const rbacModule = modules.find(m => m.createAdmin);
|
|
104
|
+
if (rbacModule && installConfig.admin?.email) {
|
|
105
|
+
try {
|
|
106
|
+
await rbacModule.createAdmin({
|
|
107
|
+
email: installConfig.admin.email,
|
|
108
|
+
password: installConfig.admin.password,
|
|
109
|
+
firstName: installConfig.admin.firstName,
|
|
110
|
+
lastName: installConfig.admin.lastName,
|
|
111
|
+
});
|
|
112
|
+
seeded.push('admin');
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
console.error('[Setup] createAdmin failed:', err);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else if (setupConfig.createAdmin) {
|
|
119
|
+
// Legacy fallback — app provides its own createAdmin
|
|
84
120
|
const bcryptModule = await import('bcryptjs');
|
|
85
121
|
const bcrypt = bcryptModule.default || bcryptModule;
|
|
86
122
|
const hashedPassword = await bcrypt.hash(installConfig.admin.password, 12);
|
|
@@ -92,16 +128,17 @@ export async function runInstall(installConfig, setupConfig) {
|
|
|
92
128
|
});
|
|
93
129
|
seeded.push('admin');
|
|
94
130
|
}
|
|
95
|
-
// 6. Optional seeds (
|
|
96
|
-
if (setupConfig.
|
|
97
|
-
await setupConfig.runModuleSeeds(installConfig.modules);
|
|
98
|
-
seeded.push('module-seeds');
|
|
99
|
-
}
|
|
100
|
-
else if (setupConfig.optionalSeeds && installConfig.seed) {
|
|
131
|
+
// 6. Optional seeds from setup.json (app-specific: activities, clients, plans)
|
|
132
|
+
if (setupConfig.optionalSeeds && installConfig.seed) {
|
|
101
133
|
for (const seedDef of setupConfig.optionalSeeds) {
|
|
102
134
|
if (installConfig.seed[seedDef.key]) {
|
|
103
|
-
|
|
104
|
-
|
|
135
|
+
try {
|
|
136
|
+
await seedDef.run({});
|
|
137
|
+
seeded.push(seedDef.key);
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
console.error(`[Setup] Seed "${seedDef.key}" failed:`, err);
|
|
141
|
+
}
|
|
105
142
|
}
|
|
106
143
|
}
|
|
107
144
|
}
|
|
@@ -142,16 +179,18 @@ async function runNetInstall(installConfig, setupConfig) {
|
|
|
142
179
|
extraVars['MOSTAJS_MODULES'] = installConfig.modules.join(',');
|
|
143
180
|
}
|
|
144
181
|
const seeded = [];
|
|
145
|
-
// 2. Verify NET server is reachable (retry
|
|
182
|
+
// 2. Verify NET server is reachable (retry with backoff, max 20s)
|
|
146
183
|
let health = null;
|
|
147
|
-
|
|
184
|
+
let retryDelay = 500;
|
|
185
|
+
for (let i = 0; i < 15; i++) {
|
|
148
186
|
try {
|
|
149
187
|
health = await net.health();
|
|
150
188
|
if (health.entities?.length > 0)
|
|
151
189
|
break;
|
|
152
190
|
}
|
|
153
191
|
catch { }
|
|
154
|
-
await new Promise(r => setTimeout(r,
|
|
192
|
+
await new Promise(r => setTimeout(r, retryDelay));
|
|
193
|
+
retryDelay = Math.min(retryDelay * 1.5, 3000);
|
|
155
194
|
}
|
|
156
195
|
if (!health) {
|
|
157
196
|
return { ok: false, error: 'Serveur NET non joignable', needsRestart: false };
|
|
@@ -200,8 +239,24 @@ async function runNetInstall(installConfig, setupConfig) {
|
|
|
200
239
|
// Si le serveur redémarre, attendre qu'il soit de retour
|
|
201
240
|
if (result.needsRestart) {
|
|
202
241
|
console.log('[Setup] Serveur NET redémarre pour charger les schemas...');
|
|
203
|
-
|
|
204
|
-
|
|
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;
|
|
205
260
|
for (let i = 0; i < 30; i++) {
|
|
206
261
|
try {
|
|
207
262
|
const h = await net.health();
|
|
@@ -211,7 +266,8 @@ async function runNetInstall(installConfig, setupConfig) {
|
|
|
211
266
|
}
|
|
212
267
|
}
|
|
213
268
|
catch { }
|
|
214
|
-
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
|
|
215
271
|
}
|
|
216
272
|
// Recharger la collection map
|
|
217
273
|
await net.loadCollectionMap();
|
package/package.json
CHANGED