@mostajs/setup 1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Dr Hamid MADANI <drmdh@msn.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # @mostajs/setup
2
+
3
+ > Reusable setup wizard module — multi-dialect DB configuration, .env.local writer, seed runner.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@mostajs/setup.svg)](https://www.npmjs.com/package/@mostajs/setup)
6
+ [![license](https://img.shields.io/npm/l/@mostajs/setup.svg)](LICENSE)
7
+
8
+ Part of the [@mosta suite](https://mostajs.dev).
9
+
10
+ ---
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install @mostajs/setup @mostajs/orm
16
+ ```
17
+
18
+ ## Quick Start
19
+
20
+ ### 1. Create setup config
21
+
22
+ ```typescript
23
+ import type { MostaSetupConfig } from '@mostajs/setup'
24
+
25
+ export const setupConfig: MostaSetupConfig = {
26
+ appName: 'MyApp',
27
+ defaultPort: 3000,
28
+ seedRBAC: async () => { /* seed roles & permissions */ },
29
+ createAdmin: async ({ email, hashedPassword, firstName, lastName }) => { /* create admin user */ },
30
+ }
31
+ ```
32
+
33
+ ### 2. API routes
34
+
35
+ ```typescript
36
+ // app/api/setup/status/route.ts
37
+ import { createStatusHandler, needsSetup } from '@mostajs/setup'
38
+ export const { GET } = createStatusHandler(() => needsSetup(countUsers))
39
+
40
+ // app/api/setup/test-db/route.ts
41
+ import { createTestDbHandler } from '@mostajs/setup'
42
+ export const { POST } = createTestDbHandler(() => needsSetup(countUsers))
43
+
44
+ // app/api/setup/install/route.ts
45
+ import { createInstallHandler } from '@mostajs/setup'
46
+ export const { POST } = createInstallHandler(() => needsSetup(countUsers), setupConfig)
47
+ ```
48
+
49
+ ## Features
50
+
51
+ - **13 database dialects** — all dialects supported by @mostajs/orm
52
+ - **Connection testing** — test DB connection before saving
53
+ - **URI composition** — auto-build connection URI from form fields
54
+ - **.env.local writer** — persist DB config to disk
55
+ - **Seed runner** — RBAC + admin user creation
56
+ - **Dialect metadata** — names, icons, default ports, categories
57
+
58
+ ## API Reference
59
+
60
+ | Export | Description |
61
+ |--------|-------------|
62
+ | `needsSetup(countFn)` | Check if app needs initial setup |
63
+ | `runInstall(config)` | Full installation flow |
64
+ | `testDbConnection(config)` | Test DB connection |
65
+ | `composeDbUri(config)` | Build connection URI |
66
+ | `writeEnvLocal(vars)` | Write/update .env.local |
67
+ | `DIALECT_INFO` / `ALL_DIALECTS` | Dialect metadata |
68
+ | `createStatusHandler()` | GET /setup/status factory |
69
+ | `createTestDbHandler()` | POST /setup/test-db factory |
70
+ | `createInstallHandler()` | POST /setup/install factory |
71
+
72
+ ## Related Packages
73
+
74
+ - [@mostajs/orm](https://www.npmjs.com/package/@mostajs/orm) — Multi-dialect ORM (required)
75
+ - [@mostajs/auth](https://www.npmjs.com/package/@mostajs/auth) — Authentication (for RBAC seeding)
76
+
77
+ ## License
78
+
79
+ MIT — © 2025 Dr Hamid MADANI <drmdh@msn.com>
@@ -0,0 +1,17 @@
1
+ import { NextResponse } from 'next/server';
2
+ import type { MostaSetupConfig } from '../types';
3
+ type NeedsSetupFn = () => Promise<boolean>;
4
+ /**
5
+ * Creates a POST handler for running the installation.
6
+ */
7
+ export declare function createInstallHandler(needsSetup: NeedsSetupFn, setupConfig: MostaSetupConfig): {
8
+ POST: (req: Request) => Promise<NextResponse<{
9
+ error: string;
10
+ }> | NextResponse<{
11
+ ok: boolean;
12
+ error?: string;
13
+ needsRestart: boolean;
14
+ seeded?: string[];
15
+ }>>;
16
+ };
17
+ export {};
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ // @mosta/setup — API Route template for install
3
+ // Author: Dr Hamid MADANI drmdh@msn.com
4
+ //
5
+ // Copy to: src/app/api/setup/install/route.ts
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.createInstallHandler = createInstallHandler;
8
+ const server_1 = require("next/server");
9
+ const setup_1 = require("../lib/setup");
10
+ /**
11
+ * Creates a POST handler for running the installation.
12
+ */
13
+ function createInstallHandler(needsSetup, setupConfig) {
14
+ async function POST(req) {
15
+ if (!(await needsSetup())) {
16
+ return server_1.NextResponse.json({ error: 'Already installed' }, { status: 400 });
17
+ }
18
+ const body = await req.json();
19
+ const result = await (0, setup_1.runInstall)(body, setupConfig);
20
+ return server_1.NextResponse.json(result);
21
+ }
22
+ return { POST };
23
+ }
@@ -0,0 +1,11 @@
1
+ import { NextResponse } from 'next/server';
2
+ type NeedsSetupFn = () => Promise<boolean>;
3
+ /**
4
+ * Creates a GET handler for checking setup status.
5
+ */
6
+ export declare function createStatusHandler(needsSetup: NeedsSetupFn): {
7
+ GET: () => Promise<NextResponse<{
8
+ needsSetup: boolean;
9
+ }>>;
10
+ };
11
+ export {};
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ // @mosta/setup — API Route template for status check
3
+ // Author: Dr Hamid MADANI drmdh@msn.com
4
+ //
5
+ // Copy to: src/app/api/setup/status/route.ts
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.createStatusHandler = createStatusHandler;
8
+ const server_1 = require("next/server");
9
+ /**
10
+ * Creates a GET handler for checking setup status.
11
+ */
12
+ function createStatusHandler(needsSetup) {
13
+ async function GET() {
14
+ const needed = await needsSetup();
15
+ return server_1.NextResponse.json({ needsSetup: needed });
16
+ }
17
+ return { GET };
18
+ }
@@ -0,0 +1,14 @@
1
+ import { NextResponse } from 'next/server';
2
+ type NeedsSetupFn = () => Promise<boolean>;
3
+ /**
4
+ * Creates a POST handler for testing DB connections.
5
+ */
6
+ export declare function createTestDbHandler(needsSetup: NeedsSetupFn): {
7
+ POST: (req: Request) => Promise<NextResponse<{
8
+ error: string;
9
+ }> | NextResponse<{
10
+ ok: boolean;
11
+ error?: string;
12
+ }>>;
13
+ };
14
+ export {};
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ // @mosta/setup — API Route template for test-db
3
+ // Author: Dr Hamid MADANI drmdh@msn.com
4
+ //
5
+ // Copy to: src/app/api/setup/test-db/route.ts
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.createTestDbHandler = createTestDbHandler;
8
+ const server_1 = require("next/server");
9
+ const db_test_1 = require("../lib/db-test");
10
+ /**
11
+ * Creates a POST handler for testing DB connections.
12
+ */
13
+ function createTestDbHandler(needsSetup) {
14
+ async function POST(req) {
15
+ if (!(await needsSetup())) {
16
+ return server_1.NextResponse.json({ error: 'Already installed' }, { status: 400 });
17
+ }
18
+ const body = await req.json();
19
+ const { dialect, host, port, name, user, password } = body;
20
+ if (!dialect || !name) {
21
+ return server_1.NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
22
+ }
23
+ const result = await (0, db_test_1.testDbConnection)({
24
+ dialect: dialect,
25
+ host: host || 'localhost',
26
+ port: port || 27017,
27
+ name,
28
+ user: user || '',
29
+ password: password || '',
30
+ });
31
+ return server_1.NextResponse.json(result);
32
+ }
33
+ return { POST };
34
+ }
@@ -0,0 +1,3 @@
1
+ import type { DialectType, DialectInfo } from '../types';
2
+ export declare const DIALECT_INFO: Record<DialectType, DialectInfo>;
3
+ export declare const ALL_DIALECTS: DialectType[];
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ALL_DIALECTS = exports.DIALECT_INFO = void 0;
4
+ exports.DIALECT_INFO = {
5
+ mongodb: {
6
+ name: 'MongoDB',
7
+ icon: '🍃',
8
+ defaultPort: 27017,
9
+ defaultUser: '',
10
+ defaultHost: 'localhost',
11
+ driverHint: null,
12
+ requiresAuth: false,
13
+ category: 'document',
14
+ },
15
+ sqlite: {
16
+ name: 'SQLite',
17
+ icon: '📄',
18
+ defaultPort: 0,
19
+ defaultUser: '',
20
+ defaultHost: '',
21
+ driverHint: null,
22
+ requiresAuth: false,
23
+ category: 'file',
24
+ },
25
+ postgres: {
26
+ name: 'PostgreSQL',
27
+ icon: '🐘',
28
+ defaultPort: 5432,
29
+ defaultUser: 'postgres',
30
+ defaultHost: 'localhost',
31
+ driverHint: 'npm install pg',
32
+ requiresAuth: true,
33
+ category: 'sql',
34
+ },
35
+ mysql: {
36
+ name: 'MySQL',
37
+ icon: '🐬',
38
+ defaultPort: 3306,
39
+ defaultUser: 'root',
40
+ defaultHost: 'localhost',
41
+ driverHint: 'npm install mysql2',
42
+ requiresAuth: true,
43
+ category: 'sql',
44
+ },
45
+ mariadb: {
46
+ name: 'MariaDB',
47
+ icon: '🦭',
48
+ defaultPort: 3306,
49
+ defaultUser: 'root',
50
+ defaultHost: 'localhost',
51
+ driverHint: 'npm install mariadb',
52
+ requiresAuth: true,
53
+ category: 'sql',
54
+ },
55
+ oracle: {
56
+ name: 'Oracle',
57
+ icon: '🔴',
58
+ defaultPort: 1521,
59
+ defaultUser: 'system',
60
+ defaultHost: 'localhost',
61
+ driverHint: 'npm install oracledb',
62
+ requiresAuth: true,
63
+ category: 'enterprise',
64
+ },
65
+ mssql: {
66
+ name: 'Microsoft SQL Server',
67
+ icon: '🟦',
68
+ defaultPort: 1433,
69
+ defaultUser: 'sa',
70
+ defaultHost: 'localhost',
71
+ driverHint: 'npm install tedious',
72
+ requiresAuth: true,
73
+ category: 'enterprise',
74
+ },
75
+ cockroachdb: {
76
+ name: 'CockroachDB',
77
+ icon: '🪳',
78
+ defaultPort: 26257,
79
+ defaultUser: 'root',
80
+ defaultHost: 'localhost',
81
+ driverHint: 'npm install pg',
82
+ requiresAuth: true,
83
+ category: 'distributed',
84
+ },
85
+ db2: {
86
+ name: 'IBM DB2',
87
+ icon: '🏢',
88
+ defaultPort: 50000,
89
+ defaultUser: 'db2inst1',
90
+ defaultHost: 'localhost',
91
+ driverHint: 'npm install ibm_db',
92
+ requiresAuth: true,
93
+ category: 'enterprise',
94
+ },
95
+ hana: {
96
+ name: 'SAP HANA',
97
+ icon: '💎',
98
+ defaultPort: 39013,
99
+ defaultUser: 'SYSTEM',
100
+ defaultHost: 'localhost',
101
+ driverHint: 'npm install @sap/hana-client',
102
+ requiresAuth: true,
103
+ category: 'enterprise',
104
+ },
105
+ hsqldb: {
106
+ name: 'HyperSQL',
107
+ icon: '⚡',
108
+ defaultPort: 9001,
109
+ defaultUser: 'SA',
110
+ defaultHost: 'localhost',
111
+ driverHint: null,
112
+ requiresAuth: false,
113
+ category: 'legacy',
114
+ },
115
+ spanner: {
116
+ name: 'Google Cloud Spanner',
117
+ icon: '☁️',
118
+ defaultPort: 0,
119
+ defaultUser: '',
120
+ defaultHost: '',
121
+ driverHint: 'npm install @google-cloud/spanner',
122
+ requiresAuth: false,
123
+ category: 'distributed',
124
+ },
125
+ sybase: {
126
+ name: 'Sybase ASE',
127
+ icon: '🔷',
128
+ defaultPort: 5000,
129
+ defaultUser: 'sa',
130
+ defaultHost: 'localhost',
131
+ driverHint: 'npm install sybase',
132
+ requiresAuth: true,
133
+ category: 'legacy',
134
+ },
135
+ };
136
+ exports.ALL_DIALECTS = Object.keys(exports.DIALECT_INFO);
@@ -0,0 +1,9 @@
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 { createTestDbHandler } from './api/test-db.route';
7
+ export { createInstallHandler } from './api/install.route';
8
+ export { createStatusHandler } from './api/status.route';
9
+ export type { DialectType, DialectInfo, DbConfig, InstallConfig, SeedOptions, SeedDefinition, MostaSetupConfig, } from './types';
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ // @mosta/setup — Barrel exports
3
+ // Author: Dr Hamid MADANI drmdh@msn.com
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.createStatusHandler = exports.createInstallHandler = exports.createTestDbHandler = exports.ALL_DIALECTS = exports.DIALECT_INFO = exports.writeEnvLocal = exports.composeDbUri = exports.testDbConnection = exports.runInstall = exports.needsSetup = void 0;
6
+ // Core
7
+ var setup_1 = require("./lib/setup");
8
+ Object.defineProperty(exports, "needsSetup", { enumerable: true, get: function () { return setup_1.needsSetup; } });
9
+ Object.defineProperty(exports, "runInstall", { enumerable: true, get: function () { return setup_1.runInstall; } });
10
+ var db_test_1 = require("./lib/db-test");
11
+ Object.defineProperty(exports, "testDbConnection", { enumerable: true, get: function () { return db_test_1.testDbConnection; } });
12
+ var compose_uri_1 = require("./lib/compose-uri");
13
+ Object.defineProperty(exports, "composeDbUri", { enumerable: true, get: function () { return compose_uri_1.composeDbUri; } });
14
+ var env_writer_1 = require("./lib/env-writer");
15
+ Object.defineProperty(exports, "writeEnvLocal", { enumerable: true, get: function () { return env_writer_1.writeEnvLocal; } });
16
+ // Data
17
+ var dialects_1 = require("./data/dialects");
18
+ Object.defineProperty(exports, "DIALECT_INFO", { enumerable: true, get: function () { return dialects_1.DIALECT_INFO; } });
19
+ Object.defineProperty(exports, "ALL_DIALECTS", { enumerable: true, get: function () { return dialects_1.ALL_DIALECTS; } });
20
+ // API route factories
21
+ var test_db_route_1 = require("./api/test-db.route");
22
+ Object.defineProperty(exports, "createTestDbHandler", { enumerable: true, get: function () { return test_db_route_1.createTestDbHandler; } });
23
+ var install_route_1 = require("./api/install.route");
24
+ Object.defineProperty(exports, "createInstallHandler", { enumerable: true, get: function () { return install_route_1.createInstallHandler; } });
25
+ var status_route_1 = require("./api/status.route");
26
+ Object.defineProperty(exports, "createStatusHandler", { enumerable: true, get: function () { return status_route_1.createStatusHandler; } });
@@ -0,0 +1,5 @@
1
+ import type { DialectType, DbConfig } from '../types';
2
+ /**
3
+ * Compose a database connection URI from individual fields.
4
+ */
5
+ export declare function composeDbUri(dialect: DialectType, config: DbConfig): string;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.composeDbUri = composeDbUri;
4
+ /**
5
+ * Compose a database connection URI from individual fields.
6
+ */
7
+ function composeDbUri(dialect, config) {
8
+ const { host, port, name, user, password } = config;
9
+ const eu = encodeURIComponent(user);
10
+ const ep = encodeURIComponent(password);
11
+ switch (dialect) {
12
+ case 'mongodb':
13
+ if (user && password)
14
+ return `mongodb://${eu}:${ep}@${host}:${port}/${name}`;
15
+ return `mongodb://${host}:${port}/${name}`;
16
+ case 'sqlite':
17
+ return `./data/${name}.db`;
18
+ case 'postgres':
19
+ case 'cockroachdb':
20
+ return `postgresql://${eu}:${ep}@${host}:${port}/${name}`;
21
+ case 'mysql':
22
+ return `mysql://${eu}:${ep}@${host}:${port}/${name}`;
23
+ case 'mariadb':
24
+ return `mariadb://${eu}:${ep}@${host}:${port}/${name}`;
25
+ case 'oracle':
26
+ return `oracle://${eu}:${ep}@${host}:${port}/${name}`;
27
+ case 'mssql':
28
+ return `mssql://${eu}:${ep}@${host}:${port}/${name}`;
29
+ case 'db2':
30
+ return `db2://${eu}:${ep}@${host}:${port}/${name}`;
31
+ case 'hana':
32
+ return `hana://${eu}:${ep}@${host}:${port}`;
33
+ case 'hsqldb':
34
+ return `hsqldb:hsql://${host}:${port}/${name}`;
35
+ case 'spanner':
36
+ return `spanner://projects/${name}`;
37
+ case 'sybase':
38
+ return `sybase://${eu}:${ep}@${host}:${port}/${name}`;
39
+ default:
40
+ return `mongodb://${host}:${port}/${name}`;
41
+ }
42
+ }
@@ -0,0 +1,10 @@
1
+ import type { DialectType, DbConfig } from '../types';
2
+ /**
3
+ * Test a database connection without affecting the global dialect singleton.
4
+ */
5
+ export declare function testDbConnection(params: {
6
+ dialect: DialectType;
7
+ } & DbConfig): Promise<{
8
+ ok: boolean;
9
+ error?: string;
10
+ }>;
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.testDbConnection = testDbConnection;
37
+ const compose_uri_1 = require("./compose-uri");
38
+ /**
39
+ * Test a database connection without affecting the global dialect singleton.
40
+ */
41
+ async function testDbConnection(params) {
42
+ const { dialect, ...dbConfig } = params;
43
+ try {
44
+ switch (dialect) {
45
+ case 'mongodb': {
46
+ const uri = (0, compose_uri_1.composeDbUri)('mongodb', dbConfig);
47
+ const mongoose = await Promise.resolve().then(() => __importStar(require('mongoose')));
48
+ const conn = mongoose.default.createConnection(uri, {
49
+ serverSelectionTimeoutMS: 5000,
50
+ connectTimeoutMS: 5000,
51
+ });
52
+ try {
53
+ await conn.asPromise();
54
+ return { ok: conn.readyState === 1 };
55
+ }
56
+ finally {
57
+ await conn.close().catch(() => { });
58
+ }
59
+ }
60
+ case 'sqlite':
61
+ return { ok: true };
62
+ default: {
63
+ const uri = (0, compose_uri_1.composeDbUri)(dialect, dbConfig);
64
+ const { testConnection } = await Promise.resolve().then(() => __importStar(require('@mostajs/orm')));
65
+ const ok = await testConnection({ dialect, uri, schemaStrategy: 'none' });
66
+ return { ok };
67
+ }
68
+ }
69
+ }
70
+ catch (err) {
71
+ const message = err instanceof Error ? err.message : 'Connexion echouee';
72
+ return { ok: false, error: message };
73
+ }
74
+ }
@@ -0,0 +1,14 @@
1
+ import type { DialectType } from '../types';
2
+ export interface EnvWriterOptions {
3
+ dialect: DialectType;
4
+ uri: string;
5
+ /** Extra variables to write (e.g. STRIPE_KEY, SMTP_HOST) */
6
+ extraVars?: Record<string, string>;
7
+ /** Default app port (default: 3000) */
8
+ port?: number;
9
+ }
10
+ /**
11
+ * Write or update .env.local with DB_DIALECT + SGBD_URI.
12
+ * Preserves commented lines. Returns true if dialect changed (needs restart).
13
+ */
14
+ export declare function writeEnvLocal(options: EnvWriterOptions): Promise<boolean>;
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.writeEnvLocal = writeEnvLocal;
40
+ // @mosta/setup — Write/update .env.local
41
+ // Author: Dr Hamid MADANI drmdh@msn.com
42
+ const fs_1 = __importDefault(require("fs"));
43
+ const path_1 = __importDefault(require("path"));
44
+ /**
45
+ * Write or update .env.local with DB_DIALECT + SGBD_URI.
46
+ * Preserves commented lines. Returns true if dialect changed (needs restart).
47
+ */
48
+ async function writeEnvLocal(options) {
49
+ const { dialect, uri, extraVars, port = 3000 } = options;
50
+ const envPath = path_1.default.resolve(process.cwd(), '.env.local');
51
+ let content = '';
52
+ let previousDialect = null;
53
+ try {
54
+ content = fs_1.default.readFileSync(envPath, 'utf-8');
55
+ const match = content.match(/^DB_DIALECT=(.+)$/m);
56
+ if (match)
57
+ previousDialect = match[1].trim();
58
+ }
59
+ catch {
60
+ // .env.local doesn't exist yet
61
+ }
62
+ const schemaValue = dialect !== 'mongodb' ? 'update' : undefined;
63
+ if (content) {
64
+ // Replace only active (uncommented) lines
65
+ content = upsertEnvLine(content, 'DB_DIALECT', dialect);
66
+ content = upsertEnvLine(content, 'SGBD_URI', uri);
67
+ if (schemaValue) {
68
+ content = upsertEnvLine(content, 'DB_SCHEMA_STRATEGY', schemaValue);
69
+ }
70
+ else if (/^DB_SCHEMA_STRATEGY=/m.test(content)) {
71
+ content = content.replace(/^DB_SCHEMA_STRATEGY=.*$/m, '#DB_SCHEMA_STRATEGY=update');
72
+ }
73
+ // Write extra vars
74
+ if (extraVars) {
75
+ for (const [key, val] of Object.entries(extraVars)) {
76
+ content = upsertEnvLine(content, key, val);
77
+ }
78
+ }
79
+ }
80
+ else {
81
+ // Fresh .env.local
82
+ const { randomBytes } = await Promise.resolve().then(() => __importStar(require('crypto')));
83
+ const secret = randomBytes(32).toString('base64');
84
+ const lines = [
85
+ `DB_DIALECT=${dialect}`,
86
+ `SGBD_URI=${uri}`,
87
+ ...(schemaValue ? [`DB_SCHEMA_STRATEGY=${schemaValue}`] : []),
88
+ '',
89
+ '# NextAuth Configuration',
90
+ `NEXTAUTH_URL=http://localhost:${port}`,
91
+ `NEXTAUTH_SECRET=${secret}`,
92
+ `AUTH_SECRET=${secret}`,
93
+ `NEXT_PUBLIC_APP_URL=http://localhost:${port}`,
94
+ '',
95
+ '# Environment',
96
+ 'NODE_ENV=development',
97
+ `PORT=${port}`,
98
+ '',
99
+ ...(extraVars
100
+ ? ['# App Configuration', ...Object.entries(extraVars).map(([k, v]) => `${k}=${v}`), '']
101
+ : []),
102
+ ];
103
+ content = lines.join('\n') + '\n';
104
+ }
105
+ fs_1.default.writeFileSync(envPath, content, 'utf-8');
106
+ return previousDialect !== null && previousDialect !== dialect;
107
+ }
108
+ function upsertEnvLine(content, key, value) {
109
+ const regex = new RegExp(`^${key}=.*$`, 'm');
110
+ if (regex.test(content)) {
111
+ return content.replace(regex, `${key}=${value}`);
112
+ }
113
+ return content.trimEnd() + `\n${key}=${value}\n`;
114
+ }
@@ -0,0 +1,22 @@
1
+ import type { InstallConfig, MostaSetupConfig } from '../types';
2
+ /**
3
+ * Check if the app needs initial setup (0 users in DB).
4
+ * Provide a countUsers function from your app.
5
+ */
6
+ export declare function needsSetup(countUsers: () => Promise<number>): Promise<boolean>;
7
+ /**
8
+ * Run the complete installation flow.
9
+ *
10
+ * 1. Compose URI and write .env.local
11
+ * 2. Set process.env in-memory
12
+ * 3. Disconnect existing dialect singleton
13
+ * 4. Seed RBAC (via config.seedRBAC callback)
14
+ * 5. Create first admin user
15
+ * 6. Run optional seeds
16
+ */
17
+ export declare function runInstall(installConfig: InstallConfig, setupConfig: MostaSetupConfig): Promise<{
18
+ ok: boolean;
19
+ error?: string;
20
+ needsRestart: boolean;
21
+ seeded?: string[];
22
+ }>;
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.needsSetup = needsSetup;
37
+ exports.runInstall = runInstall;
38
+ // @mosta/setup — Core setup logic
39
+ // Author: Dr Hamid MADANI drmdh@msn.com
40
+ const compose_uri_1 = require("./compose-uri");
41
+ const env_writer_1 = require("./env-writer");
42
+ /**
43
+ * Check if the app needs initial setup (0 users in DB).
44
+ * Provide a countUsers function from your app.
45
+ */
46
+ async function needsSetup(countUsers) {
47
+ try {
48
+ const count = await countUsers();
49
+ return count === 0;
50
+ }
51
+ catch {
52
+ return true;
53
+ }
54
+ }
55
+ /**
56
+ * Run the complete installation flow.
57
+ *
58
+ * 1. Compose URI and write .env.local
59
+ * 2. Set process.env in-memory
60
+ * 3. Disconnect existing dialect singleton
61
+ * 4. Seed RBAC (via config.seedRBAC callback)
62
+ * 5. Create first admin user
63
+ * 6. Run optional seeds
64
+ */
65
+ async function runInstall(installConfig, setupConfig) {
66
+ try {
67
+ // 1. Compose URI and write .env.local
68
+ const uri = (0, compose_uri_1.composeDbUri)(installConfig.dialect, installConfig.db);
69
+ const needsRestart = await (0, env_writer_1.writeEnvLocal)({
70
+ dialect: installConfig.dialect,
71
+ uri,
72
+ extraVars: setupConfig.extraEnvVars,
73
+ port: setupConfig.defaultPort,
74
+ });
75
+ // 2. Set process.env in-memory
76
+ process.env.DB_DIALECT = installConfig.dialect;
77
+ process.env.SGBD_URI = uri;
78
+ if (installConfig.dialect !== 'mongodb') {
79
+ process.env.DB_SCHEMA_STRATEGY = 'update';
80
+ }
81
+ // 3. Disconnect existing dialect singleton
82
+ const { disconnectDialect } = await Promise.resolve().then(() => __importStar(require('@mostajs/orm')));
83
+ await disconnectDialect();
84
+ // 4. Seed RBAC
85
+ const seeded = [];
86
+ if (setupConfig.seedRBAC) {
87
+ await setupConfig.seedRBAC();
88
+ seeded.push('categories', 'permissions', 'roles');
89
+ }
90
+ // 5. Create admin user
91
+ if (setupConfig.createAdmin) {
92
+ const bcrypt = await Promise.resolve().then(() => __importStar(require('bcryptjs')));
93
+ const hashedPassword = await bcrypt.hash(installConfig.admin.password, 12);
94
+ await setupConfig.createAdmin({
95
+ email: installConfig.admin.email,
96
+ hashedPassword,
97
+ firstName: installConfig.admin.firstName,
98
+ lastName: installConfig.admin.lastName,
99
+ });
100
+ seeded.push('admin');
101
+ }
102
+ // 6. Optional seeds
103
+ if (setupConfig.optionalSeeds && installConfig.seed) {
104
+ for (const seedDef of setupConfig.optionalSeeds) {
105
+ if (installConfig.seed[seedDef.key]) {
106
+ await seedDef.run({});
107
+ seeded.push(seedDef.key);
108
+ }
109
+ }
110
+ }
111
+ return { ok: true, needsRestart, seeded };
112
+ }
113
+ catch (err) {
114
+ const message = err instanceof Error ? err.message : 'Erreur installation';
115
+ return { ok: false, error: message, needsRestart: false };
116
+ }
117
+ }
@@ -0,0 +1,61 @@
1
+ export type DialectType = 'mongodb' | 'sqlite' | 'postgres' | 'mysql' | 'mariadb' | 'oracle' | 'mssql' | 'cockroachdb' | 'db2' | 'hana' | 'hsqldb' | 'spanner' | 'sybase';
2
+ export interface DialectInfo {
3
+ name: string;
4
+ icon: string;
5
+ defaultPort: number;
6
+ defaultUser: string;
7
+ defaultHost: string;
8
+ driverHint: string | null;
9
+ requiresAuth: boolean;
10
+ category: 'document' | 'file' | 'sql' | 'enterprise' | 'distributed' | 'legacy';
11
+ }
12
+ export interface DbConfig {
13
+ host: string;
14
+ port: number;
15
+ name: string;
16
+ user: string;
17
+ password: string;
18
+ }
19
+ export interface InstallConfig {
20
+ dialect: DialectType;
21
+ db: DbConfig;
22
+ admin: {
23
+ email: string;
24
+ password: string;
25
+ firstName: string;
26
+ lastName: string;
27
+ };
28
+ seed?: SeedOptions;
29
+ }
30
+ export interface SeedOptions {
31
+ [key: string]: boolean;
32
+ }
33
+ export interface SeedDefinition {
34
+ key: string;
35
+ label: string;
36
+ description: string;
37
+ icon?: string;
38
+ default?: boolean;
39
+ run: (repos: any) => Promise<void>;
40
+ }
41
+ export interface MostaSetupConfig {
42
+ /** Application name (shown in wizard) */
43
+ appName: string;
44
+ /** Default port (default: 3000) */
45
+ defaultPort?: number;
46
+ /** Enabled dialects (default: all 13) */
47
+ enabledDialects?: DialectType[];
48
+ /** Callback to seed RBAC (permissions, roles, categories) */
49
+ seedRBAC?: () => Promise<void>;
50
+ /** Create first admin user — called with hashed password */
51
+ createAdmin?: (admin: {
52
+ email: string;
53
+ hashedPassword: string;
54
+ firstName: string;
55
+ lastName: string;
56
+ }) => Promise<void>;
57
+ /** Optional seeds shown in the wizard */
58
+ optionalSeeds?: SeedDefinition[];
59
+ /** Extra env vars to write to .env.local */
60
+ extraEnvVars?: Record<string, string>;
61
+ }
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ // @mosta/setup — Types
3
+ // Author: Dr Hamid MADANI drmdh@msn.com
4
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@mostajs/setup",
3
+ "version": "1.0.0",
4
+ "description": "Reusable setup wizard module — multi-dialect DB configuration, .env.local writer, seed runner",
5
+ "author": "Dr Hamid MADANI <drmdh@msn.com>",
6
+ "license": "MIT",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.js",
14
+ "default": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "LICENSE",
20
+ "README.md"
21
+ ],
22
+ "keywords": [
23
+ "setup",
24
+ "wizard",
25
+ "installer",
26
+ "database",
27
+ "multi-dialect",
28
+ "nextjs",
29
+ "mosta"
30
+ ],
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/apolocine/mosta-setup"
34
+ },
35
+ "homepage": "https://mostajs.dev/packages/setup",
36
+ "engines": {
37
+ "node": ">=18.0.0"
38
+ },
39
+ "scripts": {
40
+ "build": "tsc",
41
+ "prepublishOnly": "npm run build"
42
+ },
43
+ "dependencies": {
44
+ "@mostajs/orm": "^1.0.0",
45
+ "bcryptjs": "^2.4.3"
46
+ },
47
+ "peerDependencies": {
48
+ "next": ">=14",
49
+ "react": ">=18"
50
+ },
51
+ "peerDependenciesMeta": {
52
+ "next": {
53
+ "optional": true
54
+ },
55
+ "react": {
56
+ "optional": true
57
+ }
58
+ },
59
+ "optionalDependencies": {
60
+ "mongoose": "^8.0.0"
61
+ },
62
+ "devDependencies": {
63
+ "@types/bcryptjs": "^2.4.0",
64
+ "@types/node": "^25.3.3",
65
+ "@types/react": "^19.0.0",
66
+ "next": "^15.0.0",
67
+ "react": "^19.0.0",
68
+ "typescript": "^5.6.0"
69
+ }
70
+ }