@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.
@@ -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
- return Response.json({ ok: true, ...health });
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
- if (setupConfig.seedRBAC) {
79
- await setupConfig.seedRBAC();
80
- seeded.push('categories', 'permissions', 'roles');
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
- // 5. Create admin user
83
- if (setupConfig.createAdmin) {
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 (runtime registry or legacy)
96
- if (setupConfig.runModuleSeeds) {
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
- await seedDef.run({});
104
- seeded.push(seedDef.key);
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 up to 10s if just restarted)
182
+ // 2. Verify NET server is reachable (retry with backoff, max 20s)
146
183
  let health = null;
147
- for (let i = 0; i < 10; i++) {
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, 1000));
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
- await new Promise(r => setTimeout(r, 4000)); // Laisser le temps au process.exit + restart
204
- // Poll health jusqu'à ce que le serveur soit de retour (max 30s)
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, 1000));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/setup",
3
- "version": "2.1.25",
3
+ "version": "2.1.28",
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",