@mostajs/setup 1.5.1 → 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;
@@ -344,10 +344,10 @@ 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 only shown if detectModules endpoint is explicitly provided
350
- const hasModulesStep = !!endpoints.detectModules;
349
+ // Modules step is shown unless explicitly disabled
350
+ const hasModulesStep = showModules !== false;
351
351
  const STEPS = hasModulesStep
352
352
  ? ALL_STEPS
353
353
  : ALL_STEPS.filter(s => s !== 'modules');
@@ -416,12 +416,30 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
416
416
  }
417
417
  catch { }
418
418
  }, [hydrated, persistState, currentStep, dialect, dbConfig, adminConfig, seedOptions, selectedModules]);
419
- // --- Detect modules (only if endpoint is provided) ---
419
+ // --- Detect modules ---
420
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
421
438
  if (!ep.detectModules) {
422
439
  setModulesDetected(true);
423
440
  return;
424
441
  }
442
+ // From API endpoint
425
443
  fetch(ep.detectModules)
426
444
  .then(r => r.json())
427
445
  .then((data) => {
@@ -644,9 +662,9 @@ export default function SetupWizard({ t: tProp, onComplete, endpoints = {}, dbNa
644
662
  color: mod.type === 'business' ? '#1e40af' : '#6b21a8',
645
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
646
664
  ? (mod.installed ? 'Decablage...' : 'Cablage...')
647
- : (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
648
- ? `✅ ${t('setup.database.success')}${dbTestResult.dbVersion ? ` (v${dbTestResult.dbVersion})` : ''}`
649
- : `❌ ${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 => {
650
668
  const mod = availableModules.find(m => m.key === key);
651
669
  return mod ? (_jsxs("span", { style: {
652
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.1",
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",