@mostajs/setup 1.0.0 → 1.1.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 +895 -37
- package/dist/api/detect-modules.route.d.ts +3 -0
- package/dist/api/detect-modules.route.js +10 -0
- package/dist/api/install-modules.route.d.ts +7 -0
- package/dist/api/install-modules.route.js +112 -0
- package/dist/api/install.route.d.ts +2 -10
- package/dist/api/install.route.js +5 -9
- package/dist/api/status.route.d.ts +1 -4
- package/dist/api/status.route.js +2 -6
- package/dist/api/test-db.route.d.ts +1 -7
- package/dist/api/test-db.route.js +6 -10
- package/dist/data/dialects.d.ts +1 -1
- package/dist/data/dialects.js +2 -5
- package/dist/data/module-definitions.d.ts +18 -0
- package/dist/data/module-definitions.js +105 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +13 -21
- package/dist/lib/compose-uri.d.ts +1 -1
- package/dist/lib/compose-uri.js +1 -4
- package/dist/lib/db-test.d.ts +1 -1
- package/dist/lib/db-test.js +6 -42
- package/dist/lib/discover-modules.d.ts +13 -0
- package/dist/lib/discover-modules.js +69 -0
- package/dist/lib/env-writer.d.ts +1 -1
- package/dist/lib/env-writer.js +7 -46
- package/dist/lib/setup.d.ts +1 -1
- package/dist/lib/setup.js +13 -46
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js +1 -2
- package/package.json +22 -20
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// @mosta/setup — Dynamic npm module discovery
|
|
2
|
+
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
3
|
+
import { exec } from 'child_process';
|
|
4
|
+
import { promisify } from 'util';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { MODULES } from '../data/module-definitions';
|
|
8
|
+
const execAsync = promisify(exec);
|
|
9
|
+
/**
|
|
10
|
+
* Discover @mostajs packages from npm registry and merge with static list.
|
|
11
|
+
*
|
|
12
|
+
* - Known modules keep their rich metadata (required, dependsOn, icon)
|
|
13
|
+
* - New packages found on npm are added with `discovered: true`
|
|
14
|
+
* - Detects which packages are already installed in node_modules/
|
|
15
|
+
* - Falls back to static list if npm search fails (offline, timeout)
|
|
16
|
+
*/
|
|
17
|
+
export async function discoverNpmModules() {
|
|
18
|
+
const staticModules = [...MODULES];
|
|
19
|
+
const modulesByPackage = new Map(staticModules.map((m) => [m.packageName, m]));
|
|
20
|
+
// Try npm search with 10s timeout
|
|
21
|
+
try {
|
|
22
|
+
const { stdout } = await execAsync('npm search @mostajs --json', {
|
|
23
|
+
timeout: 10_000,
|
|
24
|
+
});
|
|
25
|
+
const npmResults = JSON.parse(stdout);
|
|
26
|
+
for (const pkg of npmResults) {
|
|
27
|
+
// Skip if not @mostajs scoped
|
|
28
|
+
if (!pkg.name.startsWith('@mostajs/'))
|
|
29
|
+
continue;
|
|
30
|
+
// Skip if already in static list
|
|
31
|
+
if (modulesByPackage.has(pkg.name))
|
|
32
|
+
continue;
|
|
33
|
+
// Derive key from package name: @mostajs/foo-bar -> foo-bar
|
|
34
|
+
const key = pkg.name.replace('@mostajs/', '');
|
|
35
|
+
staticModules.push({
|
|
36
|
+
key,
|
|
37
|
+
packageName: pkg.name,
|
|
38
|
+
label: pkg.name,
|
|
39
|
+
description: pkg.description || '',
|
|
40
|
+
icon: '📦',
|
|
41
|
+
default: false,
|
|
42
|
+
discovered: true,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// npm search failed (offline, timeout) — use static list only
|
|
48
|
+
}
|
|
49
|
+
// Detect installed packages: check node_modules/ and local packages/
|
|
50
|
+
const installed = [];
|
|
51
|
+
const nodeModulesBase = path.resolve(process.cwd(), 'node_modules', '@mostajs');
|
|
52
|
+
const packagesDir = path.resolve(process.cwd(), 'packages');
|
|
53
|
+
for (const mod of staticModules) {
|
|
54
|
+
// Check node_modules/@mostajs/<name>
|
|
55
|
+
const npmDir = mod.packageName.replace('@mostajs/', '');
|
|
56
|
+
const nmPath = path.join(nodeModulesBase, npmDir);
|
|
57
|
+
// Check local packages/<localDir>
|
|
58
|
+
const localPath = mod.localDir ? path.join(packagesDir, mod.localDir) : null;
|
|
59
|
+
try {
|
|
60
|
+
if (fs.existsSync(nmPath) || (localPath && fs.existsSync(localPath))) {
|
|
61
|
+
installed.push(mod.key);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// ignore
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return { modules: staticModules, installed };
|
|
69
|
+
}
|
package/dist/lib/env-writer.d.ts
CHANGED
package/dist/lib/env-writer.js
CHANGED
|
@@ -1,57 +1,18 @@
|
|
|
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
1
|
// @mosta/setup — Write/update .env.local
|
|
41
2
|
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
42
|
-
|
|
43
|
-
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
44
5
|
/**
|
|
45
6
|
* Write or update .env.local with DB_DIALECT + SGBD_URI.
|
|
46
7
|
* Preserves commented lines. Returns true if dialect changed (needs restart).
|
|
47
8
|
*/
|
|
48
|
-
async function writeEnvLocal(options) {
|
|
9
|
+
export async function writeEnvLocal(options) {
|
|
49
10
|
const { dialect, uri, extraVars, port = 3000 } = options;
|
|
50
|
-
const envPath =
|
|
11
|
+
const envPath = path.resolve(process.cwd(), '.env.local');
|
|
51
12
|
let content = '';
|
|
52
13
|
let previousDialect = null;
|
|
53
14
|
try {
|
|
54
|
-
content =
|
|
15
|
+
content = fs.readFileSync(envPath, 'utf-8');
|
|
55
16
|
const match = content.match(/^DB_DIALECT=(.+)$/m);
|
|
56
17
|
if (match)
|
|
57
18
|
previousDialect = match[1].trim();
|
|
@@ -79,7 +40,7 @@ async function writeEnvLocal(options) {
|
|
|
79
40
|
}
|
|
80
41
|
else {
|
|
81
42
|
// Fresh .env.local
|
|
82
|
-
const { randomBytes } = await
|
|
43
|
+
const { randomBytes } = await import('crypto');
|
|
83
44
|
const secret = randomBytes(32).toString('base64');
|
|
84
45
|
const lines = [
|
|
85
46
|
`DB_DIALECT=${dialect}`,
|
|
@@ -102,7 +63,7 @@ async function writeEnvLocal(options) {
|
|
|
102
63
|
];
|
|
103
64
|
content = lines.join('\n') + '\n';
|
|
104
65
|
}
|
|
105
|
-
|
|
66
|
+
fs.writeFileSync(envPath, content, 'utf-8');
|
|
106
67
|
return previousDialect !== null && previousDialect !== dialect;
|
|
107
68
|
}
|
|
108
69
|
function upsertEnvLine(content, key, value) {
|
package/dist/lib/setup.d.ts
CHANGED
package/dist/lib/setup.js
CHANGED
|
@@ -1,49 +1,12 @@
|
|
|
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
1
|
// @mosta/setup — Core setup logic
|
|
39
2
|
// Author: Dr Hamid MADANI drmdh@msn.com
|
|
40
|
-
|
|
41
|
-
|
|
3
|
+
import { composeDbUri } from './compose-uri';
|
|
4
|
+
import { writeEnvLocal } from './env-writer';
|
|
42
5
|
/**
|
|
43
6
|
* Check if the app needs initial setup (0 users in DB).
|
|
44
7
|
* Provide a countUsers function from your app.
|
|
45
8
|
*/
|
|
46
|
-
async function needsSetup(countUsers) {
|
|
9
|
+
export async function needsSetup(countUsers) {
|
|
47
10
|
try {
|
|
48
11
|
const count = await countUsers();
|
|
49
12
|
return count === 0;
|
|
@@ -62,14 +25,18 @@ async function needsSetup(countUsers) {
|
|
|
62
25
|
* 5. Create first admin user
|
|
63
26
|
* 6. Run optional seeds
|
|
64
27
|
*/
|
|
65
|
-
async function runInstall(installConfig, setupConfig) {
|
|
28
|
+
export async function runInstall(installConfig, setupConfig) {
|
|
66
29
|
try {
|
|
67
30
|
// 1. Compose URI and write .env.local
|
|
68
|
-
const uri =
|
|
69
|
-
const
|
|
31
|
+
const uri = composeDbUri(installConfig.dialect, installConfig.db);
|
|
32
|
+
const extraVars = { ...setupConfig.extraEnvVars };
|
|
33
|
+
if (installConfig.modules?.length) {
|
|
34
|
+
extraVars['MOSTAJS_MODULES'] = installConfig.modules.join(',');
|
|
35
|
+
}
|
|
36
|
+
const needsRestart = await writeEnvLocal({
|
|
70
37
|
dialect: installConfig.dialect,
|
|
71
38
|
uri,
|
|
72
|
-
extraVars
|
|
39
|
+
extraVars,
|
|
73
40
|
port: setupConfig.defaultPort,
|
|
74
41
|
});
|
|
75
42
|
// 2. Set process.env in-memory
|
|
@@ -79,7 +46,7 @@ async function runInstall(installConfig, setupConfig) {
|
|
|
79
46
|
process.env.DB_SCHEMA_STRATEGY = 'update';
|
|
80
47
|
}
|
|
81
48
|
// 3. Disconnect existing dialect singleton
|
|
82
|
-
const { disconnectDialect } = await
|
|
49
|
+
const { disconnectDialect } = await import('@mostajs/orm');
|
|
83
50
|
await disconnectDialect();
|
|
84
51
|
// 4. Seed RBAC
|
|
85
52
|
const seeded = [];
|
|
@@ -89,7 +56,7 @@ async function runInstall(installConfig, setupConfig) {
|
|
|
89
56
|
}
|
|
90
57
|
// 5. Create admin user
|
|
91
58
|
if (setupConfig.createAdmin) {
|
|
92
|
-
const bcrypt = await
|
|
59
|
+
const bcrypt = await import('bcryptjs');
|
|
93
60
|
const hashedPassword = await bcrypt.hash(installConfig.admin.password, 12);
|
|
94
61
|
await setupConfig.createAdmin({
|
|
95
62
|
email: installConfig.admin.email,
|
package/dist/types/index.d.ts
CHANGED
|
@@ -26,7 +26,9 @@ export interface InstallConfig {
|
|
|
26
26
|
lastName: string;
|
|
27
27
|
};
|
|
28
28
|
seed?: SeedOptions;
|
|
29
|
+
modules?: string[];
|
|
29
30
|
}
|
|
31
|
+
export type { ModuleDefinition } from '../data/module-definitions';
|
|
30
32
|
export interface SeedOptions {
|
|
31
33
|
[key: string]: boolean;
|
|
32
34
|
}
|
|
@@ -43,8 +45,6 @@ export interface MostaSetupConfig {
|
|
|
43
45
|
appName: string;
|
|
44
46
|
/** Default port (default: 3000) */
|
|
45
47
|
defaultPort?: number;
|
|
46
|
-
/** Enabled dialects (default: all 13) */
|
|
47
|
-
enabledDialects?: DialectType[];
|
|
48
48
|
/** Callback to seed RBAC (permissions, roles, categories) */
|
|
49
49
|
seedRBAC?: () => Promise<void>;
|
|
50
50
|
/** Create first admin user — called with hashed password */
|
package/dist/types/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,17 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mostajs/setup",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.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",
|
|
7
|
+
"type": "module",
|
|
7
8
|
"main": "dist/index.js",
|
|
8
9
|
"types": "dist/index.d.ts",
|
|
9
10
|
"exports": {
|
|
10
11
|
".": {
|
|
11
12
|
"types": "./dist/index.d.ts",
|
|
12
13
|
"import": "./dist/index.js",
|
|
13
|
-
"require": "./dist/index.js",
|
|
14
14
|
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./lib/*": {
|
|
17
|
+
"types": "./dist/lib/*.d.ts",
|
|
18
|
+
"import": "./dist/lib/*.js",
|
|
19
|
+
"default": "./dist/lib/*.js"
|
|
20
|
+
},
|
|
21
|
+
"./data/*": {
|
|
22
|
+
"types": "./dist/data/*.d.ts",
|
|
23
|
+
"import": "./dist/data/*.js",
|
|
24
|
+
"default": "./dist/data/*.js"
|
|
25
|
+
},
|
|
26
|
+
"./api/*": {
|
|
27
|
+
"types": "./dist/api/*.d.ts",
|
|
28
|
+
"import": "./dist/api/*.js",
|
|
29
|
+
"default": "./dist/api/*.js"
|
|
30
|
+
},
|
|
31
|
+
"./types": {
|
|
32
|
+
"types": "./dist/types/index.d.ts",
|
|
33
|
+
"import": "./dist/types/index.js",
|
|
34
|
+
"default": "./dist/types/index.js"
|
|
15
35
|
}
|
|
16
36
|
},
|
|
17
37
|
"files": [
|
|
@@ -44,27 +64,9 @@
|
|
|
44
64
|
"@mostajs/orm": "^1.0.0",
|
|
45
65
|
"bcryptjs": "^2.4.3"
|
|
46
66
|
},
|
|
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
67
|
"devDependencies": {
|
|
63
68
|
"@types/bcryptjs": "^2.4.0",
|
|
64
69
|
"@types/node": "^25.3.3",
|
|
65
|
-
"@types/react": "^19.0.0",
|
|
66
|
-
"next": "^15.0.0",
|
|
67
|
-
"react": "^19.0.0",
|
|
68
70
|
"typescript": "^5.6.0"
|
|
69
71
|
}
|
|
70
72
|
}
|