@mostajs/setup 2.1.21 → 2.1.24

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.
@@ -124,6 +124,29 @@ export function createSetupRoutes(config) {
124
124
  }
125
125
  },
126
126
  },
127
+ // Create admin user — delegates to @mostajs/rbac via module discovery
128
+ 'create-admin': {
129
+ POST: async (req) => {
130
+ const body = await req.json();
131
+ if (!body?.email || !body?.password || !body?.firstName) {
132
+ return Response.json({ ok: false, error: 'email, password, firstName requis' }, { status: 400 });
133
+ }
134
+ try {
135
+ // Try rbac module createAdmin (works in both ORM and NET mode)
136
+ const { createAdmin } = await import('@mostajs/rbac/lib/create-admin');
137
+ const result = await createAdmin({
138
+ email: body.email,
139
+ password: body.password,
140
+ firstName: body.firstName,
141
+ lastName: body.lastName || '',
142
+ });
143
+ return Response.json(result);
144
+ }
145
+ catch (e) {
146
+ return Response.json({ ok: false, error: e instanceof Error ? e.message : 'Erreur creation admin' });
147
+ }
148
+ },
149
+ },
127
150
  };
128
151
  return table;
129
152
  }
@@ -369,6 +369,8 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
369
369
  const [netTesting, setNetTesting] = useState(false);
370
370
  const [schemaUploadStatus, setSchemaUploadStatus] = useState(null);
371
371
  const [schemasReady, setSchemasReady] = useState(false);
372
+ const [adminSaving, setAdminSaving] = useState(false);
373
+ const [adminSaveResult, setAdminSaveResult] = useState(null);
372
374
  const [currentStep, setCurrentStep] = useState(0);
373
375
  const [dialect, setDialect] = useState('mongodb');
374
376
  const [dbConfig, setDbConfig] = useState({ ...DIALECT_DEFAULTS.mongodb, name: `${dbNamePrefix}_prod` });
@@ -965,7 +967,41 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
965
967
  }, 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
968
  padding: 12, borderRadius: 8, marginBottom: 16,
967
969
  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 => {
970
+ }, 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 () => {
971
+ setAdminSaving(true);
972
+ setAdminSaveResult(null);
973
+ try {
974
+ // 1. Hash password côté serveur via un endpoint dédié, ou côté client
975
+ // On envoie le mot de passe en clair — le serveur Next.js le hashera
976
+ const res = await fetch(ep.setupJson.replace('setup-json', 'create-admin'), {
977
+ method: 'POST',
978
+ headers: { 'Content-Type': 'application/json' },
979
+ body: JSON.stringify({
980
+ url: netUrl,
981
+ email: adminConfig.email,
982
+ password: adminConfig.password,
983
+ firstName: adminConfig.firstName,
984
+ lastName: adminConfig.lastName,
985
+ }),
986
+ });
987
+ const data = await res.json();
988
+ if (data.ok) {
989
+ setAdminSaveResult({ ok: true, message: `✅ Admin ${adminConfig.email} créé sur le serveur NET` });
990
+ }
991
+ else {
992
+ setAdminSaveResult({ ok: false, message: `❌ ${data.error || 'Erreur'}` });
993
+ }
994
+ }
995
+ catch (err) {
996
+ setAdminSaveResult({ ok: false, message: `❌ ${err.message}` });
997
+ }
998
+ setAdminSaving(false);
999
+ }, children: adminSaving ? '⏳ Enregistrement...' : '💾 Enregistrer l\'admin sur le serveur' }), adminSaveResult && (_jsx("div", { style: {
1000
+ marginTop: 8, padding: 8, borderRadius: 6, fontSize: 13, fontWeight: 500,
1001
+ backgroundColor: adminSaveResult.ok ? '#f0fdf4' : '#fef2f2',
1002
+ color: adminSaveResult.ok ? '#166534' : '#991b1b',
1003
+ border: `1px solid ${adminSaveResult.ok ? '#bbf7d0' : '#fecaca'}`,
1004
+ }, children: adminSaveResult.message }))] })), _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 => {
969
1005
  const mod = availableModules.find(m => m.key === key);
970
1006
  return mod ? (_jsxs("span", { style: {
971
1007
  display: 'inline-flex', alignItems: 'center', gap: 4,
@@ -0,0 +1,21 @@
1
+ export interface ModuleDescriptor {
2
+ name: string;
3
+ label: string;
4
+ getSchemas: () => any[];
5
+ seed: (options?: any) => Promise<any>;
6
+ createAdmin?: (options: {
7
+ email: string;
8
+ password: string;
9
+ firstName: string;
10
+ lastName: string;
11
+ }) => Promise<any>;
12
+ }
13
+ /**
14
+ * Discover installed @mostajs modules and return their descriptors.
15
+ * Tries dynamic import of each module's module-info.
16
+ */
17
+ export declare function discoverModules(moduleNames?: string[]): Promise<ModuleDescriptor[]>;
18
+ /**
19
+ * Collect all schemas from discovered modules.
20
+ */
21
+ export declare function collectSchemas(modules: ModuleDescriptor[]): any[];
@@ -0,0 +1,58 @@
1
+ // @mostajs/setup — Module registry
2
+ // Discovers installed @mostajs modules and their schemas/seeds
3
+ // Author: Dr Hamid MADANI drmdh@msn.com
4
+ /**
5
+ * Discover installed @mostajs modules and return their descriptors.
6
+ * Tries dynamic import of each module's module-info.
7
+ */
8
+ export async function discoverModules(moduleNames) {
9
+ const modules = [];
10
+ const candidates = moduleNames || ['rbac', 'audit', 'settings'];
11
+ for (const name of candidates) {
12
+ try {
13
+ // Try importing module-info from each @mostajs module
14
+ let info = null;
15
+ switch (name) {
16
+ case 'rbac':
17
+ case 'auth': // auth delegates to rbac
18
+ info = await import('@mostajs/rbac/lib/module-info').catch(() => null);
19
+ break;
20
+ case 'audit':
21
+ info = await import('@mostajs/audit/lib/module-info').catch(() => null);
22
+ break;
23
+ case 'settings':
24
+ info = await import('@mostajs/settings/lib/module-info').catch(() => null);
25
+ break;
26
+ }
27
+ if (info?.moduleInfo) {
28
+ modules.push({
29
+ name: info.moduleInfo.name,
30
+ label: info.moduleInfo.label,
31
+ getSchemas: info.moduleInfo.schemas || info.getSchemas,
32
+ seed: info.moduleInfo.seed,
33
+ createAdmin: info.moduleInfo.createAdmin,
34
+ });
35
+ }
36
+ }
37
+ catch {
38
+ // Module not installed — skip
39
+ }
40
+ }
41
+ return modules;
42
+ }
43
+ /**
44
+ * Collect all schemas from discovered modules.
45
+ */
46
+ export function collectSchemas(modules) {
47
+ const schemas = [];
48
+ const seen = new Set();
49
+ for (const mod of modules) {
50
+ for (const schema of mod.getSchemas()) {
51
+ if (!seen.has(schema.name)) {
52
+ seen.add(schema.name);
53
+ schemas.push(schema);
54
+ }
55
+ }
56
+ }
57
+ return schemas;
58
+ }
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.21",
3
+ "version": "2.1.24",
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",
@@ -104,8 +104,11 @@
104
104
  "prepublishOnly": "npm run build"
105
105
  },
106
106
  "dependencies": {
107
+ "@mostajs/audit": "^2.0.2",
107
108
  "@mostajs/net": "^2.0.0",
108
109
  "@mostajs/orm": "^1.7.0",
110
+ "@mostajs/rbac": "^2.0.3",
111
+ "@mostajs/settings": "^2.0.3",
109
112
  "bcryptjs": "^2.4.3"
110
113
  },
111
114
  "devDependencies": {