@mostajs/setup 2.1.8 → 2.1.10

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.
@@ -841,7 +841,7 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
841
841
  padding: 12, borderRadius: 8, marginBottom: 16,
842
842
  backgroundColor: netTestResult.ok ? '#f0fdf4' : '#fef2f2',
843
843
  border: `1px solid ${netTestResult.ok ? '#bbf7d0' : '#fecaca'}`,
844
- }, 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 => {
844
+ }, 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: 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 => {
845
845
  const mod = availableModules.find(m => m.key === key);
846
846
  return mod ? (_jsxs("span", { style: {
847
847
  display: 'inline-flex', alignItems: 'center', gap: 4,
package/dist/lib/setup.js CHANGED
@@ -142,20 +142,61 @@ async function runNetInstall(installConfig, setupConfig) {
142
142
  if (!health.entities?.length) {
143
143
  return { ok: false, error: 'Serveur NET accessible mais aucun schema chargé', needsRestart: false };
144
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');
145
+ // 3. Seed RBAC via NET REST
146
+ // Read setup.json directly to get RBAC definitions
147
+ const fs = await import('fs');
148
+ const path = await import('path');
149
+ let setupJson = null;
150
+ const setupJsonPath = path.resolve(process.cwd(), 'setup.json');
151
+ if (fs.existsSync(setupJsonPath)) {
152
+ try {
153
+ setupJson = JSON.parse(fs.readFileSync(setupJsonPath, 'utf-8'));
154
+ }
155
+ catch { }
156
+ }
157
+ if (setupJson?.rbac) {
158
+ const rbac = setupJson.rbac;
159
+ // 3a. Upsert categories
160
+ if (rbac.categories?.length) {
161
+ for (const cat of rbac.categories) {
162
+ await net.upsert('permission_categories', { name: cat.name }, {
163
+ name: cat.name, label: cat.label, description: cat.description ?? '',
164
+ icon: cat.icon ?? '', order: cat.order ?? 0, system: cat.system ?? true,
165
+ });
166
+ }
167
+ seeded.push('categories');
168
+ }
169
+ // 3b. Upsert permissions — build code→ID map
170
+ const permissionMap = {};
171
+ if (rbac.permissions?.length) {
172
+ for (const pDef of rbac.permissions) {
173
+ const displayName = pDef.name ?? pDef.code;
174
+ const perm = await net.upsert('permissions', { name: displayName }, {
175
+ name: displayName, description: pDef.description, category: pDef.category,
176
+ });
177
+ permissionMap[pDef.code] = perm.id;
178
+ }
179
+ seeded.push('permissions');
180
+ }
181
+ // 3c. Upsert roles with permission IDs
182
+ if (rbac.roles?.length) {
183
+ const allPermIds = Object.values(permissionMap);
184
+ for (const roleDef of rbac.roles) {
185
+ const permissionIds = roleDef.permissions.includes('*')
186
+ ? allPermIds
187
+ : roleDef.permissions.map((code) => permissionMap[code]).filter(Boolean);
188
+ await net.upsert('roles', { name: roleDef.name }, {
189
+ name: roleDef.name, description: roleDef.description ?? '', permissions: permissionIds,
190
+ });
191
+ }
192
+ seeded.push('roles');
193
+ }
152
194
  }
153
- // 4. Create admin user via REST
195
+ // 4. Create admin user via NET REST
154
196
  if (installConfig.admin.email) {
155
197
  const bcryptModule = await import('bcryptjs');
156
198
  const bcrypt = bcryptModule.default || bcryptModule;
157
199
  const hashedPassword = await bcrypt.hash(installConfig.admin.password, 12);
158
- // Find admin role
159
200
  const adminRole = await net.findOne('roles', { name: 'admin' });
160
201
  await net.create('users', {
161
202
  email: installConfig.admin.email,
@@ -167,13 +208,44 @@ async function runNetInstall(installConfig, setupConfig) {
167
208
  });
168
209
  seeded.push('admin');
169
210
  }
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);
211
+ // 5. Optional seeds via NET REST
212
+ if (setupJson?.seeds && installConfig.seed) {
213
+ for (const seedDef of setupJson.seeds) {
214
+ if (!installConfig.seed[seedDef.key])
215
+ continue;
216
+ // Resolve lookupFields
217
+ const resolved = {};
218
+ if (seedDef.lookupFields) {
219
+ for (const [field, lookup] of Object.entries(seedDef.lookupFields)) {
220
+ const found = await net.findOne(lookup.collection, { [lookup.match]: lookup.value });
221
+ if (found)
222
+ resolved[field] = found.id;
223
+ }
224
+ }
225
+ for (const rawItem of seedDef.data) {
226
+ const item = { ...(seedDef.defaults ?? {}), ...resolved, ...rawItem };
227
+ // Hash field if configured
228
+ if (seedDef.hashField && item[seedDef.hashField]) {
229
+ const bcryptModule = await import('bcryptjs');
230
+ const bcrypt = bcryptModule.default || bcryptModule;
231
+ item[seedDef.hashField] = await bcrypt.hash(String(item[seedDef.hashField]), 12);
232
+ }
233
+ // Resolve role name → role ID
234
+ if (seedDef.roleField && item[seedDef.roleField]) {
235
+ const role = await net.findOne('roles', { name: String(item[seedDef.roleField]) });
236
+ if (role)
237
+ item.roles = [role.id];
238
+ delete item[seedDef.roleField];
239
+ }
240
+ // Upsert or create
241
+ if (seedDef.match) {
242
+ await net.upsert(seedDef.collection, { [seedDef.match]: item[seedDef.match] }, item);
243
+ }
244
+ else {
245
+ await net.create(seedDef.collection, item);
246
+ }
176
247
  }
248
+ seeded.push(seedDef.key);
177
249
  }
178
250
  }
179
251
  return { ok: true, needsRestart: false, seeded };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/setup",
3
- "version": "2.1.8",
3
+ "version": "2.1.10",
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",