@mostajs/setup 1.1.3 → 1.2.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.
- package/README.md +87 -3
- package/dist/components/ReconfigPanel.js +19 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -24,7 +24,8 @@ Part of the [@mosta suite](https://mostajs.dev).
|
|
|
24
24
|
5. [Les 13 dialectes supportes](#5-les-13-dialectes-supportes)
|
|
25
25
|
6. [Systeme de modules](#6-systeme-de-modules)
|
|
26
26
|
7. [Exemples avances](#7-exemples-avances)
|
|
27
|
-
8. [
|
|
27
|
+
8. [Reconfiguration (post-installation)](#8-reconfiguration-post-installation)
|
|
28
|
+
9. [FAQ / Troubleshooting](#9-faq--troubleshooting)
|
|
28
29
|
|
|
29
30
|
---
|
|
30
31
|
|
|
@@ -56,7 +57,10 @@ npm install @mostajs/setup @mostajs/orm
|
|
|
56
57
|
│ ├── test-db.route.ts # Factory POST /api/setup/test-db
|
|
57
58
|
│ ├── install.route.ts # Factory POST /api/setup/install
|
|
58
59
|
│ ├── detect-modules.route.ts # Factory GET /api/setup/detect-modules
|
|
59
|
-
│
|
|
60
|
+
│ ├── install-modules.route.ts # Factory POST /api/setup/install-modules
|
|
61
|
+
│ └── reconfig.route.ts # Factory GET+POST /api/setup/reconfig
|
|
62
|
+
├── components/
|
|
63
|
+
│ └── ReconfigPanel.tsx # UI reconfiguration (modules + DB)
|
|
60
64
|
├── types/
|
|
61
65
|
│ └── index.ts # Tous les types TypeScript
|
|
62
66
|
└── index.ts # Barrel exports
|
|
@@ -922,7 +926,87 @@ const dialectChanged = await writeEnvLocal({
|
|
|
922
926
|
|
|
923
927
|
---
|
|
924
928
|
|
|
925
|
-
## 8.
|
|
929
|
+
## 8. Reconfiguration (post-installation)
|
|
930
|
+
|
|
931
|
+
Apres l'installation initiale, le module fournit un **panneau de reconfiguration** permettant de :
|
|
932
|
+
- Changer de base de donnees (dialecte, connexion, test)
|
|
933
|
+
- Activer / desactiver des modules @mostajs
|
|
934
|
+
- Optionnellement re-seeder la nouvelle base
|
|
935
|
+
|
|
936
|
+
### Integration dans le projet hote
|
|
937
|
+
|
|
938
|
+
Ce module exporte un **composant React** (`ReconfigPanel`) et une **factory API** (`createReconfigHandlers`),
|
|
939
|
+
mais ne cree pas de pages Next.js. Le projet hote doit creer la route API et la page.
|
|
940
|
+
|
|
941
|
+
#### 1. Route API
|
|
942
|
+
|
|
943
|
+
**`src/app/api/setup/reconfig/route.ts`**
|
|
944
|
+
```typescript
|
|
945
|
+
import { createReconfigHandlers } from '@mostajs/setup/api/reconfig'
|
|
946
|
+
|
|
947
|
+
const { GET, POST } = createReconfigHandlers()
|
|
948
|
+
export { GET, POST }
|
|
949
|
+
```
|
|
950
|
+
|
|
951
|
+
#### 2. Page de reconfiguration
|
|
952
|
+
|
|
953
|
+
**`src/app/dashboard/settings/reconfig/page.tsx`**
|
|
954
|
+
```tsx
|
|
955
|
+
'use client'
|
|
956
|
+
import ReconfigPanel from '@mostajs/setup/components/ReconfigPanel'
|
|
957
|
+
|
|
958
|
+
export default function ReconfigPage() {
|
|
959
|
+
return (
|
|
960
|
+
<div className="space-y-6">
|
|
961
|
+
<h1 className="text-2xl font-bold">Reconfiguration</h1>
|
|
962
|
+
<ReconfigPanel
|
|
963
|
+
apiEndpoint="/api/setup/reconfig"
|
|
964
|
+
detectEndpoint="/api/setup/detect-modules"
|
|
965
|
+
showSeedOption
|
|
966
|
+
onDbChanged={() => window.location.reload()}
|
|
967
|
+
onSeedRequested={async () => {
|
|
968
|
+
await fetch('/api/setup/install', {
|
|
969
|
+
method: 'POST',
|
|
970
|
+
headers: { 'Content-Type': 'application/json' },
|
|
971
|
+
body: JSON.stringify({ action: 'seed-only' }),
|
|
972
|
+
})
|
|
973
|
+
}}
|
|
974
|
+
/>
|
|
975
|
+
</div>
|
|
976
|
+
)
|
|
977
|
+
}
|
|
978
|
+
```
|
|
979
|
+
|
|
980
|
+
#### 3. Menu dynamique
|
|
981
|
+
|
|
982
|
+
Le module exporte `setupMenuContribution` qui declare la route `/dashboard/settings/reconfig`
|
|
983
|
+
dans le groupe "Administration". Importez-le via le deep import :
|
|
984
|
+
|
|
985
|
+
```tsx
|
|
986
|
+
import { setupMenuContribution } from '@mostajs/setup/lib/menu'
|
|
987
|
+
```
|
|
988
|
+
|
|
989
|
+
#### 4. Props de ReconfigPanel
|
|
990
|
+
|
|
991
|
+
| Prop | Type | Description |
|
|
992
|
+
|------|------|-------------|
|
|
993
|
+
| `apiEndpoint` | `string` | URL de l'API reconfig (ex: `/api/setup/reconfig`) |
|
|
994
|
+
| `detectEndpoint` | `string` | URL de l'API detect-modules (ex: `/api/setup/detect-modules`) |
|
|
995
|
+
| `t` | `(key: string) => string` | Fonction de traduction (optionnel) |
|
|
996
|
+
| `showSeedOption` | `boolean` | Afficher la checkbox "Re-seeder" lors d'un changement de DB |
|
|
997
|
+
| `onDbChanged` | `() => void` | Callback apres changement de DB reussi |
|
|
998
|
+
| `onModulesChanged` | `(modules: string[]) => void` | Callback apres maj des modules |
|
|
999
|
+
| `onSeedRequested` | `() => Promise<void>` | Callback pour executer le seed |
|
|
1000
|
+
|
|
1001
|
+
#### 5. Pourquoi le projet hote doit creer les pages ?
|
|
1002
|
+
|
|
1003
|
+
Les modules `@mostajs/*` sont des **bibliotheques npm** (composants, hooks, utilitaires), pas des applications.
|
|
1004
|
+
Next.js App Router exige que les fichiers `page.tsx` soient dans le dossier `src/app/` du projet.
|
|
1005
|
+
Un package npm ne peut pas injecter de pages dans le routeur — c'est donc au projet hote de creer ces fichiers wrapper, meme s'ils ne font qu'importer et afficher un composant du module.
|
|
1006
|
+
|
|
1007
|
+
---
|
|
1008
|
+
|
|
1009
|
+
## 9. FAQ / Troubleshooting
|
|
926
1010
|
|
|
927
1011
|
### L'installation tourne en boucle (GET /setup se repete)
|
|
928
1012
|
|
|
@@ -10,8 +10,13 @@ const DIALECTS = [
|
|
|
10
10
|
{ key: 'mysql', name: 'MySQL', icon: '🐬', defaultPort: 3306, defaultUser: 'root', defaultHost: 'localhost', requiresAuth: true },
|
|
11
11
|
{ key: 'mariadb', name: 'MariaDB', icon: '🦭', defaultPort: 3306, defaultUser: 'root', defaultHost: 'localhost', requiresAuth: true },
|
|
12
12
|
{ key: 'mssql', name: 'SQL Server', icon: '🟦', defaultPort: 1433, defaultUser: 'sa', defaultHost: 'localhost', requiresAuth: true },
|
|
13
|
-
{ key: 'oracle', name: 'Oracle', icon: '🔴', defaultPort: 1521, defaultUser: 'system', defaultHost: 'localhost', requiresAuth: true },
|
|
13
|
+
{ key: 'oracle', name: 'Oracle', icon: '🔴', defaultPort: 1521, defaultUser: 'system', defaultHost: 'localhost', requiresAuth: true, premium: true },
|
|
14
14
|
{ key: 'cockroachdb', name: 'CockroachDB', icon: '🪳', defaultPort: 26257, defaultUser: 'root', defaultHost: 'localhost', requiresAuth: true },
|
|
15
|
+
{ key: 'db2', name: 'IBM DB2', icon: '🏢', defaultPort: 50000, defaultUser: 'db2inst1', defaultHost: 'localhost', requiresAuth: true, premium: true },
|
|
16
|
+
{ key: 'hana', name: 'SAP HANA', icon: '💎', defaultPort: 39013, defaultUser: 'SYSTEM', defaultHost: 'localhost', requiresAuth: true, premium: true },
|
|
17
|
+
{ key: 'hsqldb', name: 'HyperSQL', icon: '⚡', defaultPort: 9001, defaultUser: 'SA', defaultHost: 'localhost', requiresAuth: false },
|
|
18
|
+
{ key: 'spanner', name: 'Cloud Spanner', icon: '☁️', defaultPort: 0, defaultUser: '', defaultHost: '', requiresAuth: false, premium: true },
|
|
19
|
+
{ key: 'sybase', name: 'Sybase ASE', icon: '🔷', defaultPort: 5000, defaultUser: 'sa', defaultHost: 'localhost', requiresAuth: true, premium: true },
|
|
15
20
|
];
|
|
16
21
|
// ── Styles ────────────────────────────────────────────────────
|
|
17
22
|
const S = {
|
|
@@ -323,5 +328,17 @@ export default function ReconfigPanel({ apiEndpoint = '/api/setup/reconfig', det
|
|
|
323
328
|
...S.moduleCard(isActive, isRequired || !isInstalled),
|
|
324
329
|
opacity: isInstalled ? 1 : 0.5,
|
|
325
330
|
}, onClick: () => isInstalled && toggleModule(mod.key), children: [_jsxs("div", { style: S.moduleHeader, children: [_jsx("span", { style: S.moduleIcon, children: mod.icon }), _jsx("span", { style: S.moduleName, children: mod.label }), isRequired && _jsx("span", { style: S.moduleBadge('required'), children: "requis" }), isActive && !isRequired && _jsx("span", { style: S.moduleBadge('active'), children: "actif" }), !isInstalled && _jsx("span", { style: S.moduleBadge('dep'), children: "non installe" })] }), _jsx("div", { style: S.moduleDesc, children: mod.description }), mod.dependsOn?.length ? (_jsxs("div", { style: { fontSize: 11, color: '#9ca3af', marginTop: 4 }, children: ["Depend de : ", mod.dependsOn.join(', ')] })) : null] }, mod.key));
|
|
326
|
-
}) }), _jsx("div", { style: { marginTop: 16, display: 'flex', gap: 8 }, children: _jsx("button", { style: S.btn('primary'), onClick: handleSaveModules, disabled: moduleSaving, children: moduleSaving ? 'Enregistrement...' : 'Enregistrer les modules' }) })] }), _jsxs("div", { style: S.section, children: [_jsxs("div", { style: S.sectionTitle, children: [_jsx("span", { children: "\uD83D\uDDC4\uFE0F" }), " Base de donnees", currentDialect && (_jsxs("span", { style: S.currentBadge, children: ["Actuelle : ", DIALECTS.find((d) => d.key === currentDialect)?.name || currentDialect] }))] }), _jsx("div", { style: S.sectionDesc, children: "Changez le dialecte ou les parametres de connexion. La connexion sera testee avant application." }), dbMessage && (_jsx("div", { style: S.alert(dbMessage.type), children: dbMessage.text })), _jsxs("div", { style: { marginBottom: 20 }, children: [_jsx("div", { style: { ...S.label, marginBottom: 8, fontSize: 13 }, children: "Dialecte" }), _jsx("div", { style: S.dialectGrid, children: DIALECTS.map((d) => (_jsxs("div", { style:
|
|
331
|
+
}) }), _jsx("div", { style: { marginTop: 16, display: 'flex', gap: 8 }, children: _jsx("button", { style: S.btn('primary'), onClick: handleSaveModules, disabled: moduleSaving, children: moduleSaving ? 'Enregistrement...' : 'Enregistrer les modules' }) })] }), _jsxs("div", { style: S.section, children: [_jsxs("div", { style: S.sectionTitle, children: [_jsx("span", { children: "\uD83D\uDDC4\uFE0F" }), " Base de donnees", currentDialect && (_jsxs("span", { style: S.currentBadge, children: ["Actuelle : ", DIALECTS.find((d) => d.key === currentDialect)?.name || currentDialect] }))] }), _jsx("div", { style: S.sectionDesc, children: "Changez le dialecte ou les parametres de connexion. La connexion sera testee avant application." }), dbMessage && (_jsx("div", { style: S.alert(dbMessage.type), children: dbMessage.text })), _jsxs("div", { style: { marginBottom: 20 }, children: [_jsx("div", { style: { ...S.label, marginBottom: 8, fontSize: 13 }, children: "Dialecte" }), _jsx("div", { style: S.dialectGrid, children: DIALECTS.map((d) => (_jsxs("div", { style: {
|
|
332
|
+
...S.dialectCard(selectedDialect === d.key && !d.premium),
|
|
333
|
+
...(d.premium ? { opacity: 0.45, cursor: 'not-allowed', filter: 'grayscale(0.5)' } : {}),
|
|
334
|
+
}, onClick: () => !d.premium && handleDialectChange(d.key), title: d.premium ? `${d.name} — disponible en version Premium` : d.name, children: [_jsx("div", { style: S.dialectIcon, children: d.icon }), _jsxs("div", { style: S.dialectName, children: [d.name, d.key === currentDialect && _jsx("span", { style: { fontSize: 9, color: '#059669' }, children: " (actuel)" }), d.premium && (_jsx("div", { style: {
|
|
335
|
+
fontSize: 9,
|
|
336
|
+
fontWeight: 700,
|
|
337
|
+
color: '#b45309',
|
|
338
|
+
backgroundColor: '#fef3c7',
|
|
339
|
+
padding: '1px 5px',
|
|
340
|
+
borderRadius: 4,
|
|
341
|
+
marginTop: 3,
|
|
342
|
+
display: 'inline-block',
|
|
343
|
+
}, children: "Premium" }))] })] }, d.key))) })] }), !isSqlite && (_jsxs("div", { style: { maxWidth: 500 }, children: [_jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: "Hote" }), _jsx("input", { style: S.input, value: dbForm.host, onChange: (e) => setDbForm({ ...dbForm, host: e.target.value }), placeholder: "localhost" })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: "Port" }), _jsx("input", { style: S.input, type: "number", value: dbForm.port, onChange: (e) => setDbForm({ ...dbForm, port: Number(e.target.value) }) })] })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: "Nom de la base" }), _jsx("input", { style: S.input, value: dbForm.name, onChange: (e) => setDbForm({ ...dbForm, name: e.target.value }), placeholder: "ma_base_de_donnees" })] }), dialectInfo?.requiresAuth && (_jsxs("div", { style: S.formRow, children: [_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: "Utilisateur" }), _jsx("input", { style: S.input, value: dbForm.user, onChange: (e) => setDbForm({ ...dbForm, user: e.target.value }) })] }), _jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: "Mot de passe" }), _jsx("input", { style: S.input, type: "password", value: dbForm.password, onChange: (e) => setDbForm({ ...dbForm, password: e.target.value }) })] })] }))] })), isSqlite && (_jsxs("div", { style: S.formGroup, children: [_jsx("label", { style: S.label, children: "Nom du fichier (sans extension)" }), _jsx("input", { style: { ...S.input, maxWidth: 300 }, value: dbForm.name, onChange: (e) => setDbForm({ ...dbForm, name: e.target.value }), placeholder: "ma_base" }), _jsxs("div", { style: { fontSize: 11, color: '#9ca3af', marginTop: 4 }, children: ["Le fichier sera cree dans ./data/", dbForm.name || 'ma_base', ".db"] })] })), dbTestResult && (_jsx("div", { style: S.alert(dbTestResult.ok ? 'success' : 'error'), children: dbTestResult.ok ? 'Connexion reussie !' : `Echec : ${dbTestResult.error}` })), showSeedOption && onSeedRequested && (selectedDialect !== currentDialect || dbForm.name) && (_jsx("div", { style: { marginBottom: 12, padding: 12, backgroundColor: '#fffbeb', border: '1px solid #fde68a', borderRadius: 8 }, children: _jsxs("label", { style: S.checkboxLabel, children: [_jsx("input", { type: "checkbox", checked: wantSeed, onChange: (e) => setWantSeed(e.target.checked), style: S.checkbox }), _jsxs("div", { children: [_jsx("div", { style: { fontWeight: 600, fontSize: 13 }, children: "Initialiser la nouvelle base (seed)" }), _jsx("div", { style: { fontSize: 12, color: '#92400e' }, children: "Cree les tables/collections, permissions, roles et categories dans la nouvelle base. Recommande si vous changez vers une base vide." })] })] }) })), _jsxs("div", { style: { display: 'flex', gap: 8, marginTop: 16 }, children: [_jsx("button", { style: S.btn('secondary'), onClick: handleTestDb, disabled: dbTesting || (!isSqlite && !dbForm.name), children: dbTesting ? 'Test en cours...' : 'Tester la connexion' }), _jsx("button", { style: S.btn('primary'), onClick: handleApplyDb, disabled: dbSaving || seeding || (!isSqlite && !dbForm.name) || (dbTestResult !== null && !dbTestResult.ok), children: seeding ? 'Seed en cours...' : dbSaving ? 'Application...' : 'Appliquer' })] })] })] }));
|
|
327
344
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mostajs/setup",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.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",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"prepublishOnly": "npm run build"
|
|
77
77
|
},
|
|
78
78
|
"dependencies": {
|
|
79
|
-
"@mostajs/orm": "^1.
|
|
79
|
+
"@mostajs/orm": "^1.2.0",
|
|
80
80
|
"bcryptjs": "^2.4.3"
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|