@mostajs/setup 1.5.0 → 2.0.0

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.
@@ -1,6 +1,6 @@
1
1
  // @mosta/setup — Detect modules API route factory
2
2
  // Author: Dr Hamid MADANI drmdh@msn.com
3
- import { discoverNpmModules } from '../lib/discover-modules';
3
+ import { discoverNpmModules } from '../lib/discover-modules.js';
4
4
  export function createDetectModulesHandler() {
5
5
  async function GET() {
6
6
  const result = await discoverNpmModules();
@@ -4,7 +4,7 @@ import { exec } from 'child_process';
4
4
  import { promisify } from 'util';
5
5
  import fs from 'fs';
6
6
  import path from 'path';
7
- import { MODULES, resolveModuleDependencies } from '../data/module-definitions';
7
+ import { MODULES, resolveModuleDependencies } from '../data/module-definitions.js';
8
8
  const execAsync = promisify(exec);
9
9
  /**
10
10
  * Factory for POST /api/setup/install-modules
@@ -1,4 +1,4 @@
1
- import type { MostaSetupConfig } from '../types/index';
1
+ import type { MostaSetupConfig } from '../types/index.js';
2
2
  type NeedsSetupFn = () => Promise<boolean>;
3
3
  /**
4
4
  * Creates a POST handler for running the installation.
@@ -2,7 +2,7 @@
2
2
  // Author: Dr Hamid MADANI drmdh@msn.com
3
3
  //
4
4
  // Copy to: src/app/api/setup/install/route.ts
5
- import { runInstall } from '../lib/setup';
5
+ import { runInstall } from '../lib/setup.js';
6
6
  /**
7
7
  * Creates a POST handler for running the installation.
8
8
  */
@@ -1,8 +1,8 @@
1
1
  // @mostajs/setup — API Route factory for reconfiguration
2
2
  // Author: Dr Hamid MADANI drmdh@msn.com
3
- import { testDbConnection } from '../lib/db-test';
4
- import { writeEnvLocal } from '../lib/env-writer';
5
- import { composeDbUri } from '../lib/compose-uri';
3
+ import { testDbConnection } from '../lib/db-test.js';
4
+ import { writeEnvLocal } from '../lib/env-writer.js';
5
+ import { composeDbUri } from '../lib/compose-uri.js';
6
6
  /**
7
7
  * Creates handlers for the reconfiguration API.
8
8
  *
@@ -2,7 +2,7 @@
2
2
  // Author: Dr Hamid MADANI drmdh@msn.com
3
3
  //
4
4
  // Copy to: src/app/api/setup/test-db/route.ts
5
- import { testDbConnection } from '../lib/db-test';
5
+ import { testDbConnection } from '../lib/db-test.js';
6
6
  /**
7
7
  * Creates a POST handler for testing DB connections.
8
8
  */
@@ -376,7 +376,7 @@ export function createUploadJarHandlers() {
376
376
  if (!dialect) {
377
377
  return Response.json({ ok: false, error: 'dialect requis' }, { status: 400 });
378
378
  }
379
- const { composeDbUri } = await import('../lib/compose-uri');
379
+ const { composeDbUri } = await import('../lib/compose-uri.js');
380
380
  const uri = composeDbUri(dialect, {
381
381
  host: host || 'localhost',
382
382
  port: port || 0,
@@ -25,8 +25,10 @@ export function createSetupJsonHandler(needsSetup) {
25
25
  exists: true,
26
26
  config: {
27
27
  appName: json.app?.name,
28
+ dbNamePrefix: json.app?.dbNamePrefix,
28
29
  hasRbac: !!(json.rbac?.roles?.length || json.rbac?.permissions?.length),
29
30
  seedCount: json.seeds?.length ?? 0,
31
+ modules: json.modules ?? [],
30
32
  },
31
33
  });
32
34
  }
package/dist/cli/init.js CHANGED
File without changes
@@ -18,5 +18,25 @@ export interface SetupWizardProps {
18
18
  dbNamePrefix?: string;
19
19
  /** Whether to persist wizard state in sessionStorage (default: true) */
20
20
  persistState?: boolean;
21
+ /**
22
+ * Show the modules selection step even without a detectModules endpoint.
23
+ * When true and no detectModules endpoint, uses the built-in module list.
24
+ * Default: true
25
+ */
26
+ showModules?: boolean;
27
+ /**
28
+ * Full module definitions from setup.json (section "modules").
29
+ * When provided, the wizard shows these modules directly
30
+ * instead of calling the detectModules endpoint.
31
+ */
32
+ declaredModules?: {
33
+ key: string;
34
+ packageName?: string;
35
+ label?: string;
36
+ description?: string;
37
+ icon?: string;
38
+ required?: boolean;
39
+ dependsOn?: string[];
40
+ }[];
21
41
  }
22
- export default function SetupWizard({ t: tProp, onComplete, endpoints, dbNamePrefix, persistState, }: SetupWizardProps): import("react/jsx-runtime").JSX.Element;
42
+ export default function SetupWizard({ t: tProp, onComplete, endpoints, dbNamePrefix, persistState, showModules, declaredModules, }: SetupWizardProps): import("react/jsx-runtime").JSX.Element;
@@ -4,7 +4,7 @@
4
4
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
5
5
  import { useState, useEffect, useCallback } from 'react';
6
6
  // ── Constants ────────────────────────────────────────────────
7
- const STEPS = ['welcome', 'modules', 'dialect', 'database', 'admin', 'summary'];
7
+ const ALL_STEPS = ['welcome', 'modules', 'dialect', 'database', 'admin', 'summary'];
8
8
  const DIALECT_DEFAULTS = {
9
9
  mongodb: { host: 'localhost', port: 27017, name: 'mydb_prod', user: '', password: '' },
10
10
  sqlite: { host: '', port: 0, name: 'mydb', user: '', password: '' },
@@ -344,16 +344,21 @@ function JarUploadInline({ dialect, jarEndpoint, dbConfig }) {
344
344
  return (_jsxs("div", { style: S.jarBox, children: [_jsxs("div", { style: S.flex(8), children: [_jsx("span", { style: S.jarTitle, children: "Driver JDBC" }), jarStatus?.hasJar ? (_jsx("span", { style: { ...S.badge('installed'), marginLeft: 0 }, children: jarStatus.jarFile })) : (_jsx("span", { style: { fontSize: 12, color: '#6b7280' }, children: "Aucun JAR installe" }))] }), _jsxs("div", { style: { marginTop: 8 }, children: [_jsxs("label", { style: { ...S.btn('primary', uploading), cursor: uploading ? 'wait' : 'pointer', fontSize: 12, padding: '6px 12px' }, children: [uploading ? 'Upload...' : 'Uploader un .jar', _jsx("input", { type: "file", accept: ".jar", onChange: handleUpload, disabled: uploading, style: { display: 'none' } })] }), _jsx("span", { style: { fontSize: 11, color: '#9ca3af', marginLeft: 8 }, children: "Ex: hsqldb*.jar, ojdbc*.jar" })] }), isHsqldb && jarStatus?.hasJar && (_jsxs("div", { style: { marginTop: 10, padding: '10px 12px', backgroundColor: '#fef9c3', borderRadius: 6, border: '1px solid #fde68a' }, children: [_jsxs("div", { style: { fontSize: 12, fontWeight: 600, color: '#92400e', marginBottom: 6 }, children: ["Serveur HSQLDB", _jsxs("span", { style: { fontWeight: 400, color: '#6b7280', marginLeft: 4 }, children: ["(port SGBD : ", dbConfig.port || 9001, ")"] }), serverInfo?.running && (_jsxs("span", { style: { fontWeight: 400, color: '#059669', marginLeft: 8 }, children: ["En marche sur port ", serverInfo.port, serverInfo.pid > 0 ? ` — PID ${serverInfo.pid}` : ''] })), !serverInfo?.running && (_jsx("span", { style: { fontWeight: 400, color: '#dc2626', marginLeft: 8 }, children: "Arrete" }))] }), _jsxs("div", { style: { display: 'flex', gap: 8 }, children: [_jsx("button", { style: btnSmall('#059669', loading === 'start-server' || serverInfo?.running), onClick: () => patchAction({ action: 'start-server', dialect, name: dbConfig.name, host: dbConfig.host, port: dbConfig.port || 9001 }, 'start-server'), disabled: loading === 'start-server' || !!serverInfo?.running, children: loading === 'start-server' ? 'Demarrage...' : `Demarrer le serveur (port ${dbConfig.port || 9001})` }), _jsx("button", { style: btnSmall('#dc2626', loading === 'stop-server' || !serverInfo?.running), onClick: () => patchAction({ action: 'stop-server', port: serverInfo?.port || dbConfig.port || 9001 }, 'stop-server'), disabled: loading === 'stop-server' || !serverInfo?.running, children: loading === 'stop-server' ? 'Arret...' : 'Arreter le serveur' })] })] })), jarStatus?.hasJar && (_jsxs("div", { style: { marginTop: 10, padding: '10px 12px', backgroundColor: '#f0fdf4', borderRadius: 6, border: '1px solid #bbf7d0' }, children: [_jsxs("div", { style: { fontSize: 12, fontWeight: 600, color: '#166534', marginBottom: 6 }, children: ["Bridge JDBC", _jsxs("span", { style: { fontWeight: 400, color: '#6b7280', marginLeft: 4 }, children: ["(port bridge : ", bridgePort || 8765, ")"] }), bridgePort && (_jsxs("span", { style: { fontWeight: 400, color: '#059669', marginLeft: 8 }, children: ["Actif sur port ", bridgePort] })), !bridgePort && bridges.length === 0 && (_jsx("span", { style: { fontWeight: 400, color: '#dc2626', marginLeft: 8 }, children: "Inactif" }))] }), _jsx("div", { style: { display: 'flex', gap: 8, marginBottom: bridges.length > 0 ? 8 : 0 }, children: _jsx("button", { style: btnSmall('#059669', loading === 'start-bridge' || bridges.length > 0), onClick: () => patchAction({ action: 'start', dialect, ...dbConfig }, 'start-bridge'), disabled: loading === 'start-bridge' || bridges.length > 0, children: loading === 'start-bridge' ? 'Lancement...' : `Lancer le bridge (SGBD ${dbConfig.host || 'localhost'}:${dbConfig.port || 9001})` }) }), bridges.map(b => (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, padding: '4px 0', fontSize: 12 }, children: [_jsx("span", { style: { width: 8, height: 8, borderRadius: '50%', backgroundColor: b.status === 'active' ? '#22c55e' : '#f59e0b', flexShrink: 0 } }), _jsxs("span", { style: { color: '#374151', fontFamily: 'monospace', fontSize: 11 }, children: ["Bridge port :", b.port, " ", b.pid > 0 ? `(PID ${b.pid})` : '', " ", b.jdbcUrl ? `— ${b.jdbcUrl}` : ''] }), _jsx("button", { style: { ...btnSmall('#dc2626', loading === `kill-${b.port}`), fontSize: 11, padding: '2px 8px', marginLeft: 'auto' }, onClick: () => patchAction({ action: 'stop', port: b.port, pid: b.pid }, `kill-${b.port}`), disabled: loading === `kill-${b.port}`, children: loading === `kill-${b.port}` ? '...' : 'Kill' })] }, b.port)))] })), message && (_jsx("p", { style: { fontSize: 12, color: message.ok ? '#059669' : '#dc2626', marginTop: 8 }, children: message.text }))] }));
345
345
  }
346
346
  // ── Main Component ───────────────────────────────────────────
347
- export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNamePrefix = 'mydb', persistState = true, }) {
347
+ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNamePrefix = 'mydb', persistState = true, showModules = true, declaredModules, }) {
348
348
  const t = tProp || ((k) => k);
349
+ // Modules step is shown unless explicitly disabled
350
+ const hasModulesStep = showModules !== false;
351
+ const STEPS = hasModulesStep
352
+ ? ALL_STEPS
353
+ : ALL_STEPS.filter(s => s !== 'modules');
349
354
  const ep = {
350
- detectModules: endpoints.detectModules || '/api/setup/detect-modules',
355
+ detectModules: endpoints.detectModules || '',
351
356
  testDb: endpoints.testDb || '/api/setup/test-db',
352
- installModules: endpoints.installModules || '/api/setup/install-modules',
357
+ installModules: endpoints.installModules || '',
353
358
  install: endpoints.install || '/api/setup/install',
354
359
  uploadJar: endpoints.uploadJar || '/api/setup/upload-jar',
355
- wireModule: endpoints.wireModule || '/api/setup/wire-module',
356
- seed: endpoints.seed || '/api/setup/seed',
360
+ wireModule: endpoints.wireModule || '',
361
+ seed: endpoints.seed || '',
357
362
  };
358
363
  // --- State ---
359
364
  const [currentStep, setCurrentStep] = useState(0);
@@ -413,6 +418,28 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
413
418
  }, [hydrated, persistState, currentStep, dialect, dbConfig, adminConfig, seedOptions, selectedModules]);
414
419
  // --- Detect modules ---
415
420
  useEffect(() => {
421
+ // From setup.json (passed as prop)
422
+ if (declaredModules && declaredModules.length > 0) {
423
+ const mods = declaredModules.map(m => ({
424
+ key: m.key,
425
+ label: m.label ?? m.key,
426
+ description: m.description ?? '',
427
+ icon: m.icon ?? '📦',
428
+ required: m.required,
429
+ default: true,
430
+ dependsOn: m.dependsOn,
431
+ }));
432
+ setAvailableModules(mods);
433
+ setSelectedModules(declaredModules.map(m => m.key));
434
+ setModulesDetected(true);
435
+ return;
436
+ }
437
+ // No modules and no endpoint → skip
438
+ if (!ep.detectModules) {
439
+ setModulesDetected(true);
440
+ return;
441
+ }
442
+ // From API endpoint
416
443
  fetch(ep.detectModules)
417
444
  .then(r => r.json())
418
445
  .then((data) => {
@@ -460,8 +487,10 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
460
487
  }
461
488
  });
462
489
  }, [availableModules]);
463
- // --- Wire modules (load after installation success) ---
490
+ // --- Wire modules (load after installation success, only if endpoint provided) ---
464
491
  const loadWireModules = useCallback(async () => {
492
+ if (!ep.wireModule)
493
+ return;
465
494
  setWireLoading(true);
466
495
  try {
467
496
  const res = await fetch(ep.wireModule);
@@ -633,9 +662,9 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
633
662
  color: mod.type === 'business' ? '#1e40af' : '#6b21a8',
634
663
  }, children: mod.type })] }), _jsxs("div", { style: { fontSize: 12, color: '#6b7280', fontFamily: 'monospace' }, children: ["v", mod.version] }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: 4 }, children: [_jsx("span", { style: { fontSize: 12, fontWeight: 600, color: mod.installed ? '#059669' : '#6b7280' }, children: mod.installed ? 'ON' : 'OFF' }), _jsx("button", { style: S.toggleBtn(mod.installed, wireBusy === mod.name), onClick: (e) => { e.stopPropagation(); handleWireToggle(mod); }, disabled: wireBusy !== null, children: wireBusy === mod.name
635
664
  ? (mod.installed ? 'Decablage...' : 'Cablage...')
636
- : (mod.installed ? 'Desinstaller' : 'Installer') })] })] }, mod.name))) }))] }), _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 === 'dialect' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\uD83D\uDCBE" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.dialect.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.dialect.description') })] })] }), _jsx("div", { style: S.grid3, children: DIALECT_INFO.map(d => (_jsxs("div", { style: S.dialectCard(dialect === d.key, !!d.premium), onClick: () => !d.premium && selectDialect(d.key), title: d.premium ? `${d.name} — disponible en version Premium` : d.name, children: [_jsx("div", { style: S.dialectIcon, children: d.icon }), _jsx("div", { style: S.dialectName, children: d.name }), d.premium && (_jsx("span", { style: { ...S.badge('premium'), marginLeft: 0, marginTop: 4 }, children: "Premium" }))] }, d.key))) }), _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'), onClick: goNext, children: [t('setup.next'), " \u2192"] })] })] })), step === 'database' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\uD83D\uDDC4\uFE0F" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.database.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.database.description') })] })] }), dialect === 'sqlite' ? (_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.name') }), _jsx("input", { style: S.input, value: dbConfig.name, onChange: e => { setDbConfig({ ...dbConfig, name: e.target.value }); setDbTestResult(null); }, placeholder: dbNamePrefix }), _jsxs("p", { style: { fontSize: 11, color: '#9ca3af', marginTop: 4 }, children: [t('setup.database.sqliteInfo'), " ", _jsxs("code", { style: { fontFamily: 'monospace', backgroundColor: '#f3f4f6', padding: '1px 4px', borderRadius: 3 }, children: ["./data/", dbConfig.name, ".db"] })] })] })) : dialect === 'spanner' ? (_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.spannerPath') }), _jsx("input", { style: S.input, value: dbConfig.name, onChange: e => { setDbConfig({ ...dbConfig, name: e.target.value }); setDbTestResult(null); }, placeholder: "my-project/my-instance/mydb" }), _jsx("p", { style: { fontSize: 11, color: '#9ca3af', marginTop: 4 }, children: t('setup.database.spannerInfo') })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.host') }), _jsx("input", { style: S.input, value: dbConfig.host, onChange: e => { setDbConfig({ ...dbConfig, host: e.target.value }); setDbTestResult(null); } })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.port') }), _jsx("input", { style: S.input, type: "number", value: dbConfig.port, onChange: e => { setDbConfig({ ...dbConfig, port: parseInt(e.target.value) || 0 }); setDbTestResult(null); } })] })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.name') }), _jsx("input", { style: S.input, value: dbConfig.name, onChange: e => { setDbConfig({ ...dbConfig, name: e.target.value }); setDbTestResult(null); } })] }), _jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.user') }), _jsx("input", { style: S.input, value: dbConfig.user, onChange: e => { setDbConfig({ ...dbConfig, user: e.target.value }); setDbTestResult(null); }, placeholder: dialect === 'hsqldb' ? 'SA' : '' })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.password') }), _jsx("input", { style: S.input, type: "password", value: dbConfig.password, onChange: e => { setDbConfig({ ...dbConfig, password: e.target.value }); setDbTestResult(null); } })] })] })] })), dialect === 'hsqldb' && (_jsxs("div", { style: { ...S.alert('warning'), marginTop: 12, fontSize: 12 }, children: [_jsx("strong", { children: "Prerequis :" }), " Le serveur HSQLDB doit etre lance avant le bridge.", _jsx("br", {}), _jsxs("code", { style: { fontFamily: 'monospace', backgroundColor: '#fef3c7', padding: '2px 6px', borderRadius: 3, display: 'inline-block', marginTop: 4, fontSize: 11 }, children: ["java -cp hsqldb*.jar org.hsqldb.server.Server --database.0 file:./data/", dbConfig.name, " --dbname.0 ", dbConfig.name] })] })), dialect !== 'mongodb' && dialect !== 'sqlite' && (_jsxs("div", { style: { ...S.alert('warning'), marginTop: 12 }, children: [t('setup.database.driverHint'), ' ', _jsx("code", { style: { fontFamily: 'monospace', backgroundColor: '#fef3c7', padding: '1px 4px', borderRadius: 3 }, children: DRIVER_HINTS[dialect] })] })), JDBC_DIALECTS.includes(dialect) && (_jsx(JarUploadInline, { dialect: dialect, jarEndpoint: ep.uploadJar, dbConfig: dbConfig })), dialect !== 'sqlite' && dialect !== 'spanner' && (_jsxs("div", { style: { ...S.checkRow, marginTop: 12, padding: '10px 14px', backgroundColor: '#fffbeb', border: '1px solid #fde68a', borderRadius: 8 }, children: [_jsx("input", { type: "checkbox", style: S.checkbox, checked: createIfNotExists, onChange: e => setCreateIfNotExists(e.target.checked) }), _jsxs("div", { children: [_jsx("div", { style: { fontSize: 13, fontWeight: 500 }, children: t('setup.database.createIfNotExists') }), _jsx("div", { style: { fontSize: 12, color: '#92400e' }, children: t('setup.database.createIfNotExistsDesc') })] })] })), dialect !== 'sqlite' && dialect !== 'spanner' && (_jsxs("div", { style: { ...S.flex(16), marginTop: 16 }, children: [_jsxs("button", { style: S.btn('outline', dbTesting), onClick: testDb, disabled: dbTesting, children: [dbTesting ? '⏳ ' : '🗄️ ', dbTesting ? t('setup.database.testing') : t('setup.database.test')] }), dbTestResult && (_jsx("span", { style: { fontSize: 13, color: dbTestResult.ok ? '#059669' : '#dc2626' }, children: dbTestResult.ok
637
- ? `✅ ${t('setup.database.success')}${dbTestResult.dbVersion ? ` (v${dbTestResult.dbVersion})` : ''}`
638
- : `❌ ${t('setup.database.error')}: ${dbTestResult.error}` }))] })), _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 => {
665
+ : (mod.installed ? 'Desinstaller' : 'Installer') })] })] }, mod.name))) }))] }), _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 === 'dialect' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\uD83D\uDCBE" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.dialect.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.dialect.description') })] })] }), _jsx("div", { style: S.grid3, children: DIALECT_INFO.map(d => (_jsxs("div", { style: S.dialectCard(dialect === d.key, !!d.premium), onClick: () => !d.premium && selectDialect(d.key), title: d.premium ? `${d.name} — disponible en version Premium` : d.name, children: [_jsx("div", { style: S.dialectIcon, children: d.icon }), _jsx("div", { style: S.dialectName, children: d.name }), d.premium && (_jsx("span", { style: { ...S.badge('premium'), marginLeft: 0, marginTop: 4 }, children: "Premium" }))] }, d.key))) }), _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'), onClick: goNext, children: [t('setup.next'), " \u2192"] })] })] })), step === 'database' && (_jsxs("div", { children: [_jsxs("div", { style: S.sectionHeader, children: [_jsx("span", { style: S.sectionIcon, children: "\uD83D\uDDC4\uFE0F" }), _jsxs("div", { children: [_jsx("div", { style: S.sectionTitle, children: t('setup.database.title') }), _jsx("div", { style: S.sectionDesc, children: t('setup.database.description') })] })] }), dialect === 'sqlite' ? (_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.name') }), _jsx("input", { style: S.input, value: dbConfig.name, onChange: e => { setDbConfig({ ...dbConfig, name: e.target.value }); setDbTestResult(null); }, placeholder: dbNamePrefix }), _jsxs("p", { style: { fontSize: 11, color: '#9ca3af', marginTop: 4 }, children: [t('setup.database.sqliteInfo'), " ", _jsxs("code", { style: { fontFamily: 'monospace', backgroundColor: '#f3f4f6', padding: '1px 4px', borderRadius: 3 }, children: ["./data/", dbConfig.name, ".db"] })] })] })) : dialect === 'spanner' ? (_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.spannerPath') }), _jsx("input", { style: S.input, value: dbConfig.name, onChange: e => { setDbConfig({ ...dbConfig, name: e.target.value }); setDbTestResult(null); }, placeholder: "my-project/my-instance/mydb" }), _jsx("p", { style: { fontSize: 11, color: '#9ca3af', marginTop: 4 }, children: t('setup.database.spannerInfo') })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.host') }), _jsx("input", { style: S.input, value: dbConfig.host, onChange: e => { setDbConfig({ ...dbConfig, host: e.target.value }); setDbTestResult(null); } })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.port') }), _jsx("input", { style: S.input, type: "number", value: dbConfig.port, onChange: e => { setDbConfig({ ...dbConfig, port: parseInt(e.target.value) || 0 }); setDbTestResult(null); } })] })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.name') }), _jsx("input", { style: S.input, value: dbConfig.name, onChange: e => { setDbConfig({ ...dbConfig, name: e.target.value }); setDbTestResult(null); } })] }), _jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.user') }), _jsx("input", { style: S.input, value: dbConfig.user, onChange: e => { setDbConfig({ ...dbConfig, user: e.target.value }); setDbTestResult(null); }, placeholder: dialect === 'hsqldb' ? 'SA' : '' })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: t('setup.database.password') }), _jsx("input", { style: S.input, type: "password", value: dbConfig.password, onChange: e => { setDbConfig({ ...dbConfig, password: e.target.value }); setDbTestResult(null); } })] })] })] })), dialect === 'hsqldb' && (_jsxs("div", { style: { ...S.alert('warning'), marginTop: 12, fontSize: 12 }, children: [_jsx("strong", { children: "Prerequis :" }), " Le serveur HSQLDB doit etre lance avant le bridge.", _jsx("br", {}), _jsxs("code", { style: { fontFamily: 'monospace', backgroundColor: '#fef3c7', padding: '2px 6px', borderRadius: 3, display: 'inline-block', marginTop: 4, fontSize: 11 }, children: ["java -cp hsqldb*.jar org.hsqldb.server.Server --database.0 file:./data/", dbConfig.name, " --dbname.0 ", dbConfig.name] })] })), dialect !== 'mongodb' && dialect !== 'sqlite' && (_jsxs("div", { style: { ...S.alert('warning'), marginTop: 12 }, children: [t('setup.database.driverHint'), ' ', _jsx("code", { style: { fontFamily: 'monospace', backgroundColor: '#fef3c7', padding: '1px 4px', borderRadius: 3 }, children: DRIVER_HINTS[dialect] })] })), JDBC_DIALECTS.includes(dialect) && (_jsx(JarUploadInline, { dialect: dialect, jarEndpoint: ep.uploadJar, dbConfig: dbConfig })), dialect !== 'sqlite' && dialect !== 'spanner' && (_jsxs("div", { style: { ...S.checkRow, marginTop: 12, padding: '10px 14px', backgroundColor: '#fffbeb', border: '1px solid #fde68a', borderRadius: 8 }, children: [_jsx("input", { type: "checkbox", style: S.checkbox, checked: createIfNotExists, onChange: e => setCreateIfNotExists(e.target.checked) }), _jsxs("div", { children: [_jsx("div", { style: { fontSize: 13, fontWeight: 500 }, children: t('setup.database.createIfNotExists') }), _jsx("div", { style: { fontSize: 12, color: '#92400e' }, children: t('setup.database.createIfNotExistsDesc') })] })] })), dialect !== 'sqlite' && dialect !== 'spanner' && (_jsxs("div", { style: { ...S.flex(16), marginTop: 16 }, children: [_jsxs("button", { style: S.btn('outline', dbTesting), onClick: testDb, disabled: dbTesting, children: [dbTesting ? '⏳ ' : '🗄️ ', dbTesting ? t('setup.database.testing') : t('setup.database.test')] }), dbTestResult && (_jsxs("div", { style: { fontSize: 13 }, children: [_jsx("span", { style: { color: dbTestResult.ok ? '#059669' : '#dc2626' }, children: dbTestResult.ok
666
+ ? `✅ ${t('setup.database.success')}${dbTestResult.dbVersion ? ` (v${dbTestResult.dbVersion})` : ''}`
667
+ : `❌ ${t('setup.database.error')}: ${dbTestResult.error}` }), dbTestResult.ok && (_jsxs("div", { style: { fontSize: 11, color: '#6b7280', marginTop: 4, fontFamily: 'monospace', backgroundColor: '#f3f4f6', padding: '4px 8px', borderRadius: 4 }, children: [dialect, "://", dbConfig.user ? dbConfig.user + '@' : '', dbConfig.host, ":", dbConfig.port, "/", dbConfig.name] }))] }))] })), _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 => {
639
668
  const mod = availableModules.find(m => m.key === key);
640
669
  return mod ? (_jsxs("span", { style: {
641
670
  display: 'inline-flex', alignItems: 'center', gap: 4,
@@ -1,3 +1,3 @@
1
- import type { DialectType, DialectInfo } from '../types/index';
1
+ import type { DialectType, DialectInfo } from '../types/index.js';
2
2
  export declare const DIALECT_INFO: Record<DialectType, DialectInfo>;
3
3
  export declare const ALL_DIALECTS: DialectType[];
package/dist/index.d.ts CHANGED
@@ -1,22 +1,22 @@
1
- export { needsSetup, runInstall } from './lib/setup';
2
- export { testDbConnection } from './lib/db-test';
3
- export { composeDbUri } from './lib/compose-uri';
4
- export { writeEnvLocal } from './lib/env-writer';
5
- export { DIALECT_INFO, ALL_DIALECTS } from './data/dialects';
6
- export { MODULES, resolveModuleDependencies } from './data/module-definitions';
7
- export { discoverNpmModules } from './lib/discover-modules';
8
- export { loadSetupJson } from './lib/load-setup-json';
9
- export type { SetupJson, SetupJsonRbac, SetupJsonSeed, SetupJsonCategory, SetupJsonPermission, SetupJsonRole } from './lib/load-setup-json';
10
- export { createTestDbHandler } from './api/test-db.route';
11
- export { createInstallHandler } from './api/install.route';
12
- export { createStatusHandler } from './api/status.route';
13
- export { createDetectModulesHandler } from './api/detect-modules.route';
14
- export { createInstallModulesHandler } from './api/install-modules.route';
15
- export { createReconfigHandlers } from './api/reconfig.route';
16
- export { createUploadJarHandlers } from './api/upload-jar.route';
17
- export { createWireModuleHandler } from './api/wire-module.route';
18
- export { createSetupJsonHandler } from './api/upload-setup-json.route';
19
- export { default as ReconfigPanel } from './components/ReconfigPanel';
20
- export { default as SetupWizard } from './components/SetupWizard';
21
- export { setupMenuContribution } from './lib/menu';
22
- export type { DialectType, DialectInfo, DbConfig, InstallConfig, SeedOptions, SeedDefinition, MostaSetupConfig, ModuleDefinition, } from './types/index';
1
+ export { needsSetup, runInstall } from './lib/setup.js';
2
+ export { testDbConnection } from './lib/db-test.js';
3
+ export { composeDbUri } from './lib/compose-uri.js';
4
+ export { writeEnvLocal } from './lib/env-writer.js';
5
+ export { DIALECT_INFO, ALL_DIALECTS } from './data/dialects.js';
6
+ export { MODULES, resolveModuleDependencies } from './data/module-definitions.js';
7
+ export { discoverNpmModules } from './lib/discover-modules.js';
8
+ export { loadSetupJson } from './lib/load-setup-json.js';
9
+ export type { SetupJson, SetupJsonRbac, SetupJsonSeed, SetupJsonCategory, SetupJsonPermission, SetupJsonRole } from './lib/load-setup-json.js';
10
+ export { createTestDbHandler } from './api/test-db.route.js';
11
+ export { createInstallHandler } from './api/install.route.js';
12
+ export { createStatusHandler } from './api/status.route.js';
13
+ export { createDetectModulesHandler } from './api/detect-modules.route.js';
14
+ export { createInstallModulesHandler } from './api/install-modules.route.js';
15
+ export { createReconfigHandlers } from './api/reconfig.route.js';
16
+ export { createUploadJarHandlers } from './api/upload-jar.route.js';
17
+ export { createWireModuleHandler } from './api/wire-module.route.js';
18
+ export { createSetupJsonHandler } from './api/upload-setup-json.route.js';
19
+ export { default as ReconfigPanel } from './components/ReconfigPanel.js';
20
+ export { default as SetupWizard } from './components/SetupWizard.js';
21
+ export { setupMenuContribution } from './lib/menu.js';
22
+ export type { DialectType, DialectInfo, DbConfig, InstallConfig, SeedOptions, SeedDefinition, MostaSetupConfig, ModuleDefinition, } from './types/index.js';
package/dist/index.js CHANGED
@@ -1,28 +1,28 @@
1
1
  // @mosta/setup — Barrel exports
2
2
  // Author: Dr Hamid MADANI drmdh@msn.com
3
3
  // Core
4
- export { needsSetup, runInstall } from './lib/setup';
5
- export { testDbConnection } from './lib/db-test';
6
- export { composeDbUri } from './lib/compose-uri';
7
- export { writeEnvLocal } from './lib/env-writer';
4
+ export { needsSetup, runInstall } from './lib/setup.js';
5
+ export { testDbConnection } from './lib/db-test.js';
6
+ export { composeDbUri } from './lib/compose-uri.js';
7
+ export { writeEnvLocal } from './lib/env-writer.js';
8
8
  // Data
9
- export { DIALECT_INFO, ALL_DIALECTS } from './data/dialects';
10
- export { MODULES, resolveModuleDependencies } from './data/module-definitions';
9
+ export { DIALECT_INFO, ALL_DIALECTS } from './data/dialects.js';
10
+ export { MODULES, resolveModuleDependencies } from './data/module-definitions.js';
11
11
  // Lib
12
- export { discoverNpmModules } from './lib/discover-modules';
13
- export { loadSetupJson } from './lib/load-setup-json';
12
+ export { discoverNpmModules } from './lib/discover-modules.js';
13
+ export { loadSetupJson } from './lib/load-setup-json.js';
14
14
  // API route factories
15
- export { createTestDbHandler } from './api/test-db.route';
16
- export { createInstallHandler } from './api/install.route';
17
- export { createStatusHandler } from './api/status.route';
18
- export { createDetectModulesHandler } from './api/detect-modules.route';
19
- export { createInstallModulesHandler } from './api/install-modules.route';
20
- export { createReconfigHandlers } from './api/reconfig.route';
21
- export { createUploadJarHandlers } from './api/upload-jar.route';
22
- export { createWireModuleHandler } from './api/wire-module.route';
23
- export { createSetupJsonHandler } from './api/upload-setup-json.route';
15
+ export { createTestDbHandler } from './api/test-db.route.js';
16
+ export { createInstallHandler } from './api/install.route.js';
17
+ export { createStatusHandler } from './api/status.route.js';
18
+ export { createDetectModulesHandler } from './api/detect-modules.route.js';
19
+ export { createInstallModulesHandler } from './api/install-modules.route.js';
20
+ export { createReconfigHandlers } from './api/reconfig.route.js';
21
+ export { createUploadJarHandlers } from './api/upload-jar.route.js';
22
+ export { createWireModuleHandler } from './api/wire-module.route.js';
23
+ export { createSetupJsonHandler } from './api/upload-setup-json.route.js';
24
24
  // Components
25
- export { default as ReconfigPanel } from './components/ReconfigPanel';
26
- export { default as SetupWizard } from './components/SetupWizard';
25
+ export { default as ReconfigPanel } from './components/ReconfigPanel.js';
26
+ export { default as SetupWizard } from './components/SetupWizard.js';
27
27
  // Menu contribution
28
- export { setupMenuContribution } from './lib/menu';
28
+ export { setupMenuContribution } from './lib/menu.js';
@@ -1,4 +1,4 @@
1
- import type { DialectType, DbConfig } from '../types/index';
1
+ import type { DialectType, DbConfig } from '../types/index.js';
2
2
  /**
3
3
  * Compose a database connection URI from individual fields.
4
4
  */
@@ -1,4 +1,4 @@
1
- import type { DialectType, DbConfig } from '../types/index';
1
+ import type { DialectType, DbConfig } from '../types/index.js';
2
2
  /**
3
3
  * Test a database connection without affecting the global dialect singleton.
4
4
  */
@@ -1,4 +1,4 @@
1
- import { composeDbUri } from './compose-uri';
1
+ import { composeDbUri } from './compose-uri.js';
2
2
  /**
3
3
  * Test a database connection without affecting the global dialect singleton.
4
4
  */
@@ -1,4 +1,4 @@
1
- import { type ModuleDefinition } from '../data/module-definitions';
1
+ import { type ModuleDefinition } from '../data/module-definitions.js';
2
2
  /**
3
3
  * Discover @mostajs packages from npm registry and merge with static list.
4
4
  *
@@ -4,7 +4,7 @@ import { exec } from 'child_process';
4
4
  import { promisify } from 'util';
5
5
  import fs from 'fs';
6
6
  import path from 'path';
7
- import { MODULES } from '../data/module-definitions';
7
+ import { MODULES } from '../data/module-definitions.js';
8
8
  const execAsync = promisify(exec);
9
9
  /**
10
10
  * Discover @mostajs packages from npm registry and merge with static list.
@@ -1,4 +1,4 @@
1
- import type { DialectType } from '../types/index';
1
+ import type { DialectType } from '../types/index.js';
2
2
  export interface EnvWriterOptions {
3
3
  dialect: DialectType;
4
4
  uri: string;
@@ -1,4 +1,4 @@
1
- import type { MostaSetupConfig } from '../types/index';
1
+ import type { MostaSetupConfig } from '../types/index.js';
2
2
  export interface SetupJsonCategory {
3
3
  name: string;
4
4
  label: string;
@@ -45,9 +45,19 @@ export interface SetupJson {
45
45
  dbNamePrefix?: string;
46
46
  };
47
47
  env?: Record<string, string>;
48
+ modules?: SetupJsonModule[];
48
49
  rbac?: SetupJsonRbac;
49
50
  seeds?: SetupJsonSeed[];
50
51
  }
52
+ export interface SetupJsonModule {
53
+ key: string;
54
+ packageName: string;
55
+ label?: string;
56
+ description?: string;
57
+ icon?: string;
58
+ required?: boolean;
59
+ dependsOn?: string[];
60
+ }
51
61
  /**
52
62
  * Load a setup.json and return a MostaSetupConfig.
53
63
  *
@@ -58,10 +58,15 @@ function validate(json) {
58
58
  }
59
59
  }
60
60
  function buildConfig(json, repoFactory) {
61
+ // Derive MOSTAJS_MODULES from modules[] section
62
+ const extraEnvVars = { ...(json.env ?? {}) };
63
+ if (json.modules?.length) {
64
+ extraEnvVars.MOSTAJS_MODULES = json.modules.map(m => m.key).join(',');
65
+ }
61
66
  const config = {
62
67
  appName: json.app.name,
63
68
  defaultPort: json.app.port,
64
- extraEnvVars: json.env ? { ...json.env } : undefined,
69
+ extraEnvVars: Object.keys(extraEnvVars).length > 0 ? extraEnvVars : undefined,
65
70
  };
66
71
  // ── seedRBAC ───────────────────────────────────────────
67
72
  if (json.rbac) {
@@ -88,7 +93,7 @@ function buildConfig(json, repoFactory) {
88
93
  if (json.rbac.roles?.length) {
89
94
  const roleRepo = await getRepo('role');
90
95
  const allPermIds = Object.values(permissionMap);
91
- for (const roleDef of json.rbac.roles) {
96
+ for (const roleDef of json.rbac.roles.filter(r => r.name)) {
92
97
  const permissionIds = roleDef.permissions.includes('*')
93
98
  ? allPermIds
94
99
  : roleDef.permissions.map(code => permissionMap[code]).filter(Boolean);
@@ -1,4 +1,4 @@
1
- import type { InstallConfig, MostaSetupConfig } from '../types/index';
1
+ import type { InstallConfig, MostaSetupConfig } from '../types/index.js';
2
2
  /**
3
3
  * Check if the app needs initial setup (0 users in DB).
4
4
  * Provide a countUsers function from your app.
package/dist/lib/setup.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // @mosta/setup — Core setup logic
2
2
  // Author: Dr Hamid MADANI drmdh@msn.com
3
- import { composeDbUri } from './compose-uri';
4
- import { writeEnvLocal } from './env-writer';
3
+ import { composeDbUri } from './compose-uri.js';
4
+ import { writeEnvLocal } from './env-writer.js';
5
5
  /**
6
6
  * Check if the app needs initial setup (0 users in DB).
7
7
  * Provide a countUsers function from your app.
@@ -28,7 +28,7 @@ export interface InstallConfig {
28
28
  seed?: SeedOptions;
29
29
  modules?: string[];
30
30
  }
31
- export type { ModuleDefinition } from '../data/module-definitions';
31
+ export type { ModuleDefinition } from '../data/module-definitions.js';
32
32
  export interface SeedOptions {
33
33
  [key: string]: boolean;
34
34
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/setup",
3
- "version": "1.5.0",
3
+ "version": "2.0.0",
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",
@@ -151,6 +151,45 @@
151
151
  }
152
152
  }
153
153
  },
154
+ "modules": {
155
+ "type": "array",
156
+ "description": "Modules to use in the project. Exported by MostaSetup Studio. Shown in the wizard with install commands.",
157
+ "items": {
158
+ "type": "object",
159
+ "required": ["key", "packageName"],
160
+ "additionalProperties": false,
161
+ "properties": {
162
+ "key": {
163
+ "type": "string",
164
+ "description": "Unique module identifier (e.g. 'orm', 'auth', 'ticketing')"
165
+ },
166
+ "packageName": {
167
+ "type": "string",
168
+ "description": "npm package name (e.g. '@mostajs/orm')"
169
+ },
170
+ "label": {
171
+ "type": "string",
172
+ "description": "Display label"
173
+ },
174
+ "description": {
175
+ "type": "string"
176
+ },
177
+ "icon": {
178
+ "type": "string",
179
+ "description": "Emoji icon"
180
+ },
181
+ "required": {
182
+ "type": "boolean",
183
+ "description": "Whether this module is required (cannot be deselected)"
184
+ },
185
+ "dependsOn": {
186
+ "type": "array",
187
+ "items": { "type": "string" },
188
+ "description": "Module keys this module depends on"
189
+ }
190
+ }
191
+ }
192
+ },
154
193
  "seeds": {
155
194
  "type": "array",
156
195
  "description": "Optional seed datasets shown as checkboxes in the install wizard",