@monodog/backend 1.2.2 → 1.2.4

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/dist/cli.js CHANGED
@@ -45,12 +45,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
45
45
  const fs = __importStar(require("fs"));
46
46
  const path = __importStar(require("path"));
47
47
  const index_1 = require("./index"); // Assume index.ts exports this function
48
+ const config_loader_1 = require("./config-loader");
49
+ const appConfig = (0, config_loader_1.loadConfig)();
48
50
  // --- Argument Parsing ---
49
51
  // 1. Get arguments excluding the node executable and script name
50
52
  const args = process.argv.slice(2);
51
53
  // Default settings
54
+ const DEFAULT_PORT = 4000;
52
55
  let serve = false;
53
- let rootPath = process.cwd(); // Default to the current working directory
56
+ let rootPath = path.resolve(appConfig.workspace.root_dir ?? process.cwd()); // Default to the current working directory
57
+ let port = appConfig.server.port ?? DEFAULT_PORT; //Default port
58
+ let host = appConfig.server.host ?? 'localhost'; //Default port
54
59
  // Simple argument parsing loop
55
60
  for (let i = 0; i < args.length; i++) {
56
61
  const arg = args[i];
@@ -68,6 +73,22 @@ for (let i = 0; i < args.length; i++) {
68
73
  process.exit(1);
69
74
  }
70
75
  }
76
+ else if (arg === '--port') {
77
+ // Look at the next argument for the port number
78
+ if (i + 1 < args.length) {
79
+ const portValue = parseInt(args[i + 1], 10);
80
+ if (isNaN(portValue) || portValue <= 0 || portValue > 65535) {
81
+ console.error('Error: --port requires a valid port number (1-65535).');
82
+ process.exit(1);
83
+ }
84
+ port = portValue;
85
+ i++; // Skip the next argument
86
+ }
87
+ else {
88
+ console.error('Error: --port requires a number argument.');
89
+ process.exit(1);
90
+ }
91
+ }
71
92
  else if (arg === '-h' || arg === '--help') {
72
93
  console.log(`
73
94
  Monodog CLI - Monorepo Analysis Engine
@@ -88,10 +109,12 @@ Example:
88
109
  }
89
110
  // --- Execution Logic ---
90
111
  if (serve) {
112
+ console.log(`\nInitializing Configurations...`);
113
+ createConfigFileIfMissing(rootPath ?? process.cwd());
91
114
  console.log(`Starting Monodog API server...`);
92
115
  console.log(`Analyzing monorepo at root: ${rootPath}`);
93
116
  // Start the Express server and begin analysis
94
- (0, index_1.startServer)(rootPath);
117
+ (0, index_1.startServer)(rootPath, port, host);
95
118
  copyPackageToWorkspace(rootPath);
96
119
  }
97
120
  else {
@@ -167,3 +190,40 @@ function copyPackageToWorkspace(rootDir) {
167
190
  process.exit(1);
168
191
  }
169
192
  }
193
+ function createConfigFileIfMissing(rootPath) {
194
+ // --- CONFIGURATION ---
195
+ const configFileName = 'monodog-conf.json';
196
+ const configFilePath = path.resolve(rootPath, configFileName);
197
+ // The default content for the configuration file
198
+ const defaultContent = {
199
+ "workspace": {
200
+ "root_dir": "./packages/backend"
201
+ },
202
+ "database": {
203
+ "path": "./monodog.db"
204
+ },
205
+ "server": {
206
+ "host": "0.0.0.0",
207
+ "port": 4000
208
+ }
209
+ };
210
+ const contentString = JSON.stringify(defaultContent, null, 2);
211
+ // ---------------------
212
+ console.log(`\n[monodog] Checking for ${configFileName}...`);
213
+ if (fs.existsSync(configFilePath)) {
214
+ console.log(`[monodog] ${configFileName} already exists. Skipping creation.`);
215
+ }
216
+ else {
217
+ try {
218
+ // Write the default content to the file
219
+ fs.writeFileSync(configFilePath, contentString, 'utf-8');
220
+ console.log(`[monodog] Successfully generated default ${configFileName} in the workspace root.`);
221
+ console.log('[monodog] Please review and update settings like "host" and "port".');
222
+ }
223
+ catch (err) {
224
+ const message = err instanceof Error ? err.message : String(err);
225
+ console.error(`[monodog Error] Failed to generate ${configFileName}:`, message);
226
+ process.exit(1);
227
+ }
228
+ }
229
+ }
@@ -0,0 +1,97 @@
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.loadConfig = loadConfig;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ // Global variable to hold the loaded config
40
+ let config = null;
41
+ /**
42
+ * Loads the monodog-conf.json file from the monorepo root.
43
+ * This should be called only once during application startup.
44
+ * @returns The application configuration object.
45
+ */
46
+ function loadConfig() {
47
+ if (config) {
48
+ return config; // Return cached config if already loaded
49
+ }
50
+ // 1. Determine the path to the config file
51
+ // We assume the backend package is running from the monorepo root (cwd is root)
52
+ // or that we can navigate up to the root from the current file's location.
53
+ const rootPath = path.resolve(__dirname, '../../../'); // Adjust based on your folder depth if needed
54
+ const configPath = path.resolve(rootPath, 'monodog-conf.json');
55
+ if (!fs.existsSync(configPath)) {
56
+ console.error(`ERROR: Configuration file not found at ${configPath}`);
57
+ // A missing config is a critical failure, so we exit
58
+ process.exit(1);
59
+ }
60
+ try {
61
+ // 2. Read and parse the JSON file
62
+ const fileContent = fs.readFileSync(configPath, 'utf-8');
63
+ const parsedConfig = JSON.parse(fileContent);
64
+ // 3. Optional: Add validation logic here (e.g., check if ports are numbers)
65
+ // Cache and return
66
+ config = parsedConfig;
67
+ process.stderr.write(`[Config] Loaded configuration from: ${configPath}`);
68
+ return config;
69
+ }
70
+ catch (error) {
71
+ console.error("ERROR: Failed to read or parse monodog-conf.json.");
72
+ console.error(error);
73
+ process.exit(1);
74
+ }
75
+ }
76
+ // --- Example Usage ---
77
+ // In your main application file (e.g., packages/backend/src/index.ts):
78
+ /*
79
+
80
+ import { loadConfig } from './config-loader';
81
+
82
+ // Load configuration on startup
83
+ const appConfig = loadConfig();
84
+
85
+ // Access the variables easily
86
+ const dbHost = appConfig.database.host;
87
+ const serverPort = appConfig.server.port;
88
+ const workspaceRoot = appConfig.workspace.root_dir;
89
+
90
+ console.log(`\nStarting server on port: ${serverPort}`);
91
+ console.log(`Database connecting to host: ${dbHost}`);
92
+
93
+ // Example server start logic
94
+ // app.listen(serverPort, appConfig.server.host, () => {
95
+ // console.log(`Server running at http://${appConfig.server.host}:${serverPort}`);
96
+ // });
97
+ */
package/dist/index.js CHANGED
@@ -8,7 +8,6 @@ const express_1 = __importDefault(require("express"));
8
8
  const cors_1 = __importDefault(require("cors"));
9
9
  const path_1 = __importDefault(require("path"));
10
10
  const fs_1 = __importDefault(require("fs"));
11
- // import { glob } from 'glob';
12
11
  const body_parser_1 = require("body-parser");
13
12
  const monorepo_scanner_1 = require("@monodog/monorepo-scanner");
14
13
  const ci_status_1 = require("@monodog/ci-status");
@@ -19,11 +18,9 @@ const client_1 = require("@prisma/client");
19
18
  // import { validateConfig } from '../../apps/dashboard/src/components/modules/config-inspector/utils/config.utils';
20
19
  const gitService_1 = require("./gitService");
21
20
  const prisma = new client_1.PrismaClient();
22
- const DEFAULT_PORT = 4000;
23
21
  // The main function exported and called by the CLI
24
- function startServer(rootPath) {
22
+ function startServer(rootPath, port, host) {
25
23
  const app = (0, express_1.default)();
26
- const port = process.env.PORT ? parseInt(process.env.PORT) : DEFAULT_PORT;
27
24
  // --- Middleware ---
28
25
  // 1. Logging Middleware
29
26
  app.use((_req, _res, next) => {
@@ -32,17 +29,6 @@ function startServer(rootPath) {
32
29
  });
33
30
  app.use((0, cors_1.default)());
34
31
  app.use((0, body_parser_1.json)());
35
- // // 2. CORS (Critical for the frontend app to talk to this local server)
36
- // // In a production setup, this would be highly restricted, but for local monorepo tools,
37
- // // we often allow all origins or restrict to a known local hostname/port.
38
- // app.use((_req: Request, res: Response, next: NextFunction) => {
39
- // res.setHeader('Access-Control-Allow-Origin', '*'); // Adjust this in production
40
- // res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
41
- // res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
42
- // next();
43
- // });
44
- // // 3. JSON body parser
45
- // app.use(express.json());
46
32
  // Health check
47
33
  app.get('/api/health', (_, res) => {
48
34
  res.json({
@@ -1080,6 +1066,144 @@ function startServer(rootPath) {
1080
1066
  });
1081
1067
  }
1082
1068
  });
1069
+ // Update package configuration with path from frontend - preserving all fields, no backups
1070
+ app.put('/api/packages/update', async (req, res) => {
1071
+ try {
1072
+ const { packageName, config, packagePath } = req.body;
1073
+ if (!packageName || !config || !packagePath) {
1074
+ return res.status(400).json({
1075
+ success: false,
1076
+ error: 'Package name, configuration, and package path are required',
1077
+ });
1078
+ }
1079
+ console.log('💾 Updating package configuration for:', packageName);
1080
+ console.log('📁 Package path:', packagePath);
1081
+ // Validate JSON syntax with better error handling
1082
+ let newConfig;
1083
+ try {
1084
+ newConfig = JSON.parse(config);
1085
+ }
1086
+ catch (error) {
1087
+ console.error('JSON parsing error:', error);
1088
+ return res.status(400).json({
1089
+ success: false,
1090
+ error: 'Invalid JSON configuration',
1091
+ message: `JSON parsing error: ${error instanceof Error ? error.message : 'Invalid format'}`,
1092
+ });
1093
+ }
1094
+ const packageJsonPath = path_1.default.join(packagePath, 'package.json');
1095
+ // Security check: ensure the path is valid
1096
+ if (!fs_1.default.existsSync(packagePath)) {
1097
+ return res.status(404).json({
1098
+ success: false,
1099
+ error: 'Package directory not found',
1100
+ });
1101
+ }
1102
+ // Check if package.json exists
1103
+ if (!fs_1.default.existsSync(packageJsonPath)) {
1104
+ return res.status(404).json({
1105
+ success: false,
1106
+ error: 'package.json not found in the specified directory',
1107
+ });
1108
+ }
1109
+ // Read the existing package.json to preserve all fields
1110
+ const existingContent = await fs_1.default.promises.readFile(packageJsonPath, 'utf8');
1111
+ let existingConfig;
1112
+ try {
1113
+ existingConfig = JSON.parse(existingContent);
1114
+ }
1115
+ catch (error) {
1116
+ return res.status(500).json({
1117
+ success: false,
1118
+ error: 'Existing package.json contains invalid JSON',
1119
+ message: `Error parsing existing package.json: ${error instanceof Error ? error.message : 'Invalid JSON'}`,
1120
+ });
1121
+ }
1122
+ // Merge the new configuration with existing configuration
1123
+ const mergedConfig = {
1124
+ ...existingConfig,
1125
+ name: newConfig.name || existingConfig.name,
1126
+ version: newConfig.version || existingConfig.version,
1127
+ description: newConfig.description !== undefined
1128
+ ? newConfig.description
1129
+ : existingConfig.description,
1130
+ license: newConfig.license !== undefined
1131
+ ? newConfig.license
1132
+ : existingConfig.license,
1133
+ repository: newConfig.repository || existingConfig.repository,
1134
+ scripts: newConfig.scripts || existingConfig.scripts,
1135
+ dependencies: newConfig.dependencies || existingConfig.dependencies,
1136
+ devDependencies: newConfig.devDependencies || existingConfig.devDependencies,
1137
+ peerDependencies: newConfig.peerDependencies || existingConfig.peerDependencies,
1138
+ };
1139
+ // Write the merged configuration back
1140
+ const formattedConfig = JSON.stringify(mergedConfig, null, 2);
1141
+ await fs_1.default.promises.writeFile(packageJsonPath, formattedConfig, 'utf8');
1142
+ // Update the package in the database - use correct field names based on your Prisma schema
1143
+ const updateData = {
1144
+ // Use 'lastUpdated' instead of 'updatedAt' based on the error message
1145
+ lastUpdated: new Date(),
1146
+ };
1147
+ // Only update fields that exist in your Prisma schema
1148
+ if (newConfig.version)
1149
+ updateData.version = newConfig.version;
1150
+ if (newConfig.description !== undefined)
1151
+ updateData.description = newConfig.description || '';
1152
+ if (newConfig.license !== undefined)
1153
+ updateData.license = newConfig.license || '';
1154
+ if (newConfig.scripts)
1155
+ updateData.scripts = JSON.stringify(newConfig.scripts);
1156
+ if (newConfig.repository)
1157
+ updateData.repository = JSON.stringify(newConfig.repository);
1158
+ if (newConfig.dependencies)
1159
+ updateData.dependencies = JSON.stringify(newConfig.dependencies);
1160
+ if (newConfig.devDependencies)
1161
+ updateData.devDependencies = JSON.stringify(newConfig.devDependencies);
1162
+ if (newConfig.peerDependencies)
1163
+ updateData.peerDependencies = JSON.stringify(newConfig.peerDependencies);
1164
+ console.log('📝 Updating database with:', updateData);
1165
+ const updatedPackage = await prisma.package.update({
1166
+ where: { name: packageName },
1167
+ data: updateData,
1168
+ });
1169
+ // Transform the response
1170
+ const transformedPackage = {
1171
+ ...updatedPackage,
1172
+ maintainers: updatedPackage.maintainers
1173
+ ? JSON.parse(updatedPackage.maintainers)
1174
+ : [],
1175
+ scripts: updatedPackage.scripts ? JSON.parse(updatedPackage.scripts) : {},
1176
+ repository: updatedPackage.repository
1177
+ ? JSON.parse(updatedPackage.repository)
1178
+ : {},
1179
+ dependencies: updatedPackage.dependencies
1180
+ ? JSON.parse(updatedPackage.dependencies)
1181
+ : {},
1182
+ devDependencies: updatedPackage.devDependencies
1183
+ ? JSON.parse(updatedPackage.devDependencies)
1184
+ : {},
1185
+ peerDependencies: updatedPackage.peerDependencies
1186
+ ? JSON.parse(updatedPackage.peerDependencies)
1187
+ : {},
1188
+ };
1189
+ // Return success response
1190
+ return res.json({
1191
+ success: true,
1192
+ message: 'Package configuration updated successfully',
1193
+ package: transformedPackage,
1194
+ preservedFields: true,
1195
+ });
1196
+ }
1197
+ catch (error) {
1198
+ console.error('Error updating package configuration:', error);
1199
+ // Ensure we always return JSON, even for errors
1200
+ return res.status(500).json({
1201
+ success: false,
1202
+ error: 'Failed to update package configuration',
1203
+ message: error instanceof Error ? error.message : 'Unknown error occurred',
1204
+ });
1205
+ }
1206
+ });
1083
1207
  // Error handling middleware
1084
1208
  app.use((error, _req, res, _next) => {
1085
1209
  console.error('Error:', error);
@@ -1096,9 +1220,11 @@ function startServer(rootPath) {
1096
1220
  timestamp: Date.now(),
1097
1221
  });
1098
1222
  });
1099
- const PORT = process.env.PORT || 4000;
1100
- app.listen(PORT, () => {
1101
- console.log(`🚀 Backend server running on http://localhost:${PORT}`);
1223
+ const PORT = parseInt(port ? port.toString() : '4000');
1224
+ app.listen(PORT, host, async () => {
1225
+ const pcount = await prisma.package.count();
1226
+ console.log(`[Database] Total packages found: ${pcount}`);
1227
+ console.log(`🚀 Backend server running on http://${host}:${PORT}`);
1102
1228
  console.log(`📊 API endpoints available:`);
1103
1229
  console.log(` - GET /api/health`);
1104
1230
  console.log(` - GET /api/packages/refresh`);
@@ -1117,7 +1243,7 @@ function startServer(rootPath) {
1117
1243
  }).on('error', (err) => {
1118
1244
  // Handle common errors like EADDRINUSE (port already in use)
1119
1245
  if (err.message.includes('EADDRINUSE')) {
1120
- console.error(`Error: Port ${port} is already in use. Please specify a different port via the PORT environment variable.`);
1246
+ console.error(`Error: Port ${port} is already in use. Please specify a different port via configuration file.`);
1121
1247
  process.exit(1);
1122
1248
  }
1123
1249
  else {
package/get-db-url.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { loadConfig } from './src/config-loader';
2
+
3
+ function generateUrl() {
4
+ const appConfig = loadConfig();
5
+
6
+ const DATABASE_URL = `${appConfig.database.path}`;
7
+ process.env.DATABASE_URL = DATABASE_URL;
8
+ process.env.DATABASE_PORT = `${appConfig.database.port}`;
9
+
10
+ process.stdout.write(DATABASE_URL);
11
+
12
+ }
13
+
14
+ generateUrl();
package/package.json CHANGED
@@ -1,12 +1,28 @@
1
1
  {
2
2
  "name": "@monodog/backend",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "description": "Backend API server for monodog monorepo dashboard",
5
5
  "main": "dist/index.js",
6
6
  "license": "MIT",
7
7
  "bin": {
8
8
  "monodog-cli": "src/cli.ts"
9
9
  },
10
+ "scripts": {
11
+ "dev": "tsx watch src/index.ts",
12
+ "start": "tsx index.ts",
13
+ "prepublishOnly": "pnpm run build",
14
+ "build": "rm -rf dist && tsc -p tsconfig.json",
15
+ "prestart": "npm run build",
16
+ "cli": "pnpm run build && node dist/cli.js",
17
+ "test": "jest --coverage",
18
+ "clean": "rm -rf dist node_modules/.cache",
19
+ "test:coverage": "jest --coverage",
20
+ "lint": "eslint .",
21
+ "lint:fix": "eslint . --fix",
22
+ "db:url": "npx ts-node get-db-url.ts",
23
+ "migrate": "DATABASE_URL=$(npm run db:url --silent 2>/dev/null | tr -d '\\n') npx prisma migrate dev",
24
+ "serve": "DATABASE_URL=$(npm run db:url --silent 2>/dev/null | tr -d '\\n') ts-node src/cli.ts --serve "
25
+ },
10
26
  "dependencies": {
11
27
  "@monodog/ci-status": "1.1.1",
12
28
  "@monodog/monorepo-scanner": "1.0.7",
@@ -19,24 +35,14 @@
19
35
  "prisma": "^5.22.0"
20
36
  },
21
37
  "devDependencies": {
22
- "typescript": "^5.3.8",
23
38
  "@types/body-parser": "^1.19.5",
24
39
  "@types/cors": "^2.8.17",
25
40
  "@types/dotenv": "^8.2.0",
26
41
  "@types/express": "^4.17.21",
27
42
  "@types/node": "^20.10.0",
28
- "tsx": "^4.6.0"
29
- },
30
- "scripts": {
31
- "dev": "tsx watch src/index.ts",
32
- "start": "tsx index.ts",
33
- "build": "rm -rf dist && tsc -p tsconfig.json",
34
- "prestart": "npm run build",
35
- "cli": "pnpm run build && node dist/cli.js",
36
- "test": "jest --coverage",
37
- "clean": "rm -rf dist node_modules/.cache",
38
- "test:coverage": "jest --coverage",
39
- "lint": "eslint .",
40
- "lint:fix": "eslint . --fix"
43
+ "cross-env": "^10.1.0",
44
+ "ts-node": "^10.0.0",
45
+ "tsx": "^4.6.0",
46
+ "typescript": "^5.3.8"
41
47
  }
42
- }
48
+ }
@@ -0,0 +1,16 @@
1
+ /*
2
+ Warnings:
3
+
4
+ - You are about to drop the `Post` table. If the table is not empty, all the data it contains will be lost.
5
+ - You are about to drop the `User` table. If the table is not empty, all the data it contains will be lost.
6
+
7
+ */
8
+ -- DropTable
9
+ PRAGMA foreign_keys=off;
10
+ DROP TABLE "Post";
11
+ PRAGMA foreign_keys=on;
12
+
13
+ -- DropTable
14
+ PRAGMA foreign_keys=off;
15
+ DROP TABLE "User";
16
+ PRAGMA foreign_keys=on;
@@ -10,7 +10,7 @@ generator client {
10
10
 
11
11
  datasource db {
12
12
  provider = "sqlite"
13
- url = "file:./monolite.db"
13
+ url = env("DATABASE_URL")
14
14
  }
15
15
 
16
16
  // --- MONOREPO DATA MODEL ---
package/src/cli.ts CHANGED
@@ -13,14 +13,21 @@ import * as fs from 'fs';
13
13
  import * as path from 'path';
14
14
  import { startServer } from './index'; // Assume index.ts exports this function
15
15
 
16
+ import { loadConfig } from './config-loader';
17
+
18
+ const appConfig = loadConfig();
19
+
16
20
  // --- Argument Parsing ---
17
21
 
18
22
  // 1. Get arguments excluding the node executable and script name
19
23
  const args = process.argv.slice(2);
20
24
 
21
25
  // Default settings
26
+ const DEFAULT_PORT = 4000;
22
27
  let serve = false;
23
- let rootPath = process.cwd(); // Default to the current working directory
28
+ let rootPath = path.resolve(appConfig.workspace.root_dir ?? process.cwd()); // Default to the current working directory
29
+ let port = appConfig.server.port ?? DEFAULT_PORT; //Default port
30
+ let host = appConfig.server.host ?? 'localhost'; //Default port
24
31
 
25
32
  // Simple argument parsing loop
26
33
  for (let i = 0; i < args.length; i++) {
@@ -37,7 +44,21 @@ for (let i = 0; i < args.length; i++) {
37
44
  console.error('Error: --root requires a path argument.');
38
45
  process.exit(1);
39
46
  }
40
- } else if (arg === '-h' || arg === '--help') {
47
+ } else if (arg === '--port') {
48
+ // Look at the next argument for the port number
49
+ if (i + 1 < args.length) {
50
+ const portValue = parseInt(args[i + 1], 10);
51
+ if (isNaN(portValue) || portValue <= 0 || portValue > 65535) {
52
+ console.error('Error: --port requires a valid port number (1-65535).');
53
+ process.exit(1);
54
+ }
55
+ port = portValue;
56
+ i++; // Skip the next argument
57
+ } else {
58
+ console.error('Error: --port requires a number argument.');
59
+ process.exit(1);
60
+ }
61
+ } else if (arg === '-h' || arg === '--help') {
41
62
  console.log(`
42
63
  Monodog CLI - Monorepo Analysis Engine
43
64
 
@@ -59,10 +80,13 @@ Example:
59
80
  // --- Execution Logic ---
60
81
 
61
82
  if (serve) {
83
+ console.log(`\nInitializing Configurations...`);
84
+ createConfigFileIfMissing(rootPath ?? process.cwd());
85
+
62
86
  console.log(`Starting Monodog API server...`);
63
87
  console.log(`Analyzing monorepo at root: ${rootPath}`);
64
88
  // Start the Express server and begin analysis
65
- startServer(rootPath);
89
+ startServer(rootPath, port, host);
66
90
  copyPackageToWorkspace(rootPath);
67
91
  } else {
68
92
  // Default mode: print usage or run a default report if no command is specified
@@ -151,3 +175,43 @@ function copyPackageToWorkspace(rootDir: string): void {
151
175
  }
152
176
  }
153
177
 
178
+ function createConfigFileIfMissing(rootPath: string): void {
179
+ // --- CONFIGURATION ---
180
+ const configFileName = 'monodog-conf.json';
181
+ const configFilePath = path.resolve(rootPath, configFileName);
182
+
183
+ // The default content for the configuration file
184
+ const defaultContent = {
185
+ "workspace": {
186
+ "root_dir": "./packages/backend"
187
+ },
188
+ "database": {
189
+ "path": "./monodog.db"
190
+ },
191
+ "server": {
192
+ "host": "0.0.0.0",
193
+ "port": 4000
194
+ }
195
+ };
196
+
197
+ const contentString = JSON.stringify(defaultContent, null, 2);
198
+ // ---------------------
199
+
200
+ console.log(`\n[monodog] Checking for ${configFileName}...`);
201
+
202
+ if (fs.existsSync(configFilePath)) {
203
+ console.log(`[monodog] ${configFileName} already exists. Skipping creation.`);
204
+ } else {
205
+ try {
206
+ // Write the default content to the file
207
+ fs.writeFileSync(configFilePath, contentString, 'utf-8');
208
+ console.log(`[monodog] Successfully generated default ${configFileName} in the workspace root.`);
209
+ console.log('[monodog] Please review and update settings like "host" and "port".');
210
+ } catch (err: unknown) {
211
+ const message = err instanceof Error ? err.message : String(err);
212
+ console.error(`[monodog Error] Failed to generate ${configFileName}:`, message);
213
+ process.exit(1);
214
+ }
215
+ }
216
+ }
217
+
@@ -0,0 +1,88 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+
4
+ // Define a type/interface for your configuration structure
5
+ interface MonodogConfig {
6
+ workspace: {
7
+ root_dir: string;
8
+ };
9
+ database: {
10
+ type: 'postgres' | 'mysql' | 'sqlite';
11
+ host: string;
12
+ port: number;
13
+ user: string;
14
+ path: string; // Used for SQLite path or general data storage path
15
+ };
16
+ server: {
17
+ host: string;
18
+ port: number;
19
+ };
20
+ }
21
+
22
+ // Global variable to hold the loaded config
23
+ let config: MonodogConfig | null = null;
24
+
25
+ /**
26
+ * Loads the monodog-conf.json file from the monorepo root.
27
+ * This should be called only once during application startup.
28
+ * @returns The application configuration object.
29
+ */
30
+ export function loadConfig(): MonodogConfig {
31
+ if (config) {
32
+ return config; // Return cached config if already loaded
33
+ }
34
+
35
+ // 1. Determine the path to the config file
36
+ // We assume the backend package is running from the monorepo root (cwd is root)
37
+ // or that we can navigate up to the root from the current file's location.
38
+ const rootPath = path.resolve(__dirname, '../../../'); // Adjust based on your folder depth if needed
39
+ const configPath = path.resolve(rootPath, 'monodog-conf.json');
40
+
41
+ if (!fs.existsSync(configPath)) {
42
+ console.error(`ERROR: Configuration file not found at ${configPath}`);
43
+ // A missing config is a critical failure, so we exit
44
+ process.exit(1);
45
+ }
46
+
47
+ try {
48
+ // 2. Read and parse the JSON file
49
+ const fileContent = fs.readFileSync(configPath, 'utf-8');
50
+ const parsedConfig = JSON.parse(fileContent) as MonodogConfig;
51
+
52
+ // 3. Optional: Add validation logic here (e.g., check if ports are numbers)
53
+
54
+ // Cache and return
55
+ config = parsedConfig;
56
+ process.stderr.write(`[Config] Loaded configuration from: ${configPath}`);
57
+ return config;
58
+
59
+ } catch (error) {
60
+ console.error("ERROR: Failed to read or parse monodog-conf.json.");
61
+ console.error(error);
62
+ process.exit(1);
63
+ }
64
+ }
65
+
66
+ // --- Example Usage ---
67
+
68
+ // In your main application file (e.g., packages/backend/src/index.ts):
69
+ /*
70
+
71
+ import { loadConfig } from './config-loader';
72
+
73
+ // Load configuration on startup
74
+ const appConfig = loadConfig();
75
+
76
+ // Access the variables easily
77
+ const dbHost = appConfig.database.host;
78
+ const serverPort = appConfig.server.port;
79
+ const workspaceRoot = appConfig.workspace.root_dir;
80
+
81
+ console.log(`\nStarting server on port: ${serverPort}`);
82
+ console.log(`Database connecting to host: ${dbHost}`);
83
+
84
+ // Example server start logic
85
+ // app.listen(serverPort, appConfig.server.host, () => {
86
+ // console.log(`Server running at http://${appConfig.server.host}:${serverPort}`);
87
+ // });
88
+ */
package/src/index.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  import express, { type Request, type Response, type NextFunction } from 'express';
2
2
  import cors from 'cors';
3
3
  import path from 'path';
4
- import fs from 'fs';
5
- // import { glob } from 'glob';
4
+ import fs from 'fs';
6
5
  import { json } from 'body-parser';
7
6
  import {
8
7
  scanner,
@@ -41,11 +40,9 @@ export interface HealthMetric {
41
40
  }
42
41
 
43
42
  const prisma = new PrismaClient();
44
- const DEFAULT_PORT = 4000;
45
43
  // The main function exported and called by the CLI
46
- export function startServer(rootPath: string) {
44
+ export function startServer(rootPath: string, port: number|string, host: string): void {
47
45
  const app = express();
48
- const port = process.env.PORT ? parseInt(process.env.PORT) : DEFAULT_PORT;
49
46
 
50
47
  // --- Middleware ---
51
48
 
@@ -56,18 +53,6 @@ const app = express();
56
53
  });
57
54
  app.use(cors());
58
55
  app.use(json());
59
- // // 2. CORS (Critical for the frontend app to talk to this local server)
60
- // // In a production setup, this would be highly restricted, but for local monorepo tools,
61
- // // we often allow all origins or restrict to a known local hostname/port.
62
- // app.use((_req: Request, res: Response, next: NextFunction) => {
63
- // res.setHeader('Access-Control-Allow-Origin', '*'); // Adjust this in production
64
- // res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
65
- // res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
66
- // next();
67
- // });
68
-
69
- // // 3. JSON body parser
70
- // app.use(express.json());
71
56
 
72
57
  // Health check
73
58
  app.get('/api/health', (_, res) => {
@@ -1244,6 +1229,162 @@ app.put('/api/config/files/:id', async (_req, res) => {
1244
1229
  }
1245
1230
  });
1246
1231
 
1232
+ // Update package configuration with path from frontend - preserving all fields, no backups
1233
+ app.put('/api/packages/update', async (req, res) => {
1234
+ try {
1235
+ const { packageName, config, packagePath } = req.body;
1236
+
1237
+ if (!packageName || !config || !packagePath) {
1238
+ return res.status(400).json({
1239
+ success: false,
1240
+ error: 'Package name, configuration, and package path are required',
1241
+ });
1242
+ }
1243
+
1244
+ console.log('💾 Updating package configuration for:', packageName);
1245
+ console.log('📁 Package path:', packagePath);
1246
+
1247
+ // Validate JSON syntax with better error handling
1248
+ let newConfig;
1249
+ try {
1250
+ newConfig = JSON.parse(config);
1251
+ } catch (error) {
1252
+ console.error('JSON parsing error:', error);
1253
+ return res.status(400).json({
1254
+ success: false,
1255
+ error: 'Invalid JSON configuration',
1256
+ message: `JSON parsing error: ${error instanceof Error ? error.message : 'Invalid format'}`,
1257
+ });
1258
+ }
1259
+
1260
+ const packageJsonPath = path.join(packagePath, 'package.json');
1261
+
1262
+ // Security check: ensure the path is valid
1263
+ if (!fs.existsSync(packagePath)) {
1264
+ return res.status(404).json({
1265
+ success: false,
1266
+ error: 'Package directory not found',
1267
+ });
1268
+ }
1269
+
1270
+ // Check if package.json exists
1271
+ if (!fs.existsSync(packageJsonPath)) {
1272
+ return res.status(404).json({
1273
+ success: false,
1274
+ error: 'package.json not found in the specified directory',
1275
+ });
1276
+ }
1277
+
1278
+ // Read the existing package.json to preserve all fields
1279
+ const existingContent = await fs.promises.readFile(packageJsonPath, 'utf8');
1280
+ let existingConfig;
1281
+ try {
1282
+ existingConfig = JSON.parse(existingContent);
1283
+ } catch (error) {
1284
+ return res.status(500).json({
1285
+ success: false,
1286
+ error: 'Existing package.json contains invalid JSON',
1287
+ message: `Error parsing existing package.json: ${error instanceof Error ? error.message : 'Invalid JSON'}`,
1288
+ });
1289
+ }
1290
+
1291
+ // Merge the new configuration with existing configuration
1292
+ const mergedConfig = {
1293
+ ...existingConfig,
1294
+ name: newConfig.name || existingConfig.name,
1295
+ version: newConfig.version || existingConfig.version,
1296
+ description:
1297
+ newConfig.description !== undefined
1298
+ ? newConfig.description
1299
+ : existingConfig.description,
1300
+ license:
1301
+ newConfig.license !== undefined
1302
+ ? newConfig.license
1303
+ : existingConfig.license,
1304
+ repository: newConfig.repository || existingConfig.repository,
1305
+ scripts: newConfig.scripts || existingConfig.scripts,
1306
+ dependencies: newConfig.dependencies || existingConfig.dependencies,
1307
+ devDependencies:
1308
+ newConfig.devDependencies || existingConfig.devDependencies,
1309
+ peerDependencies:
1310
+ newConfig.peerDependencies || existingConfig.peerDependencies,
1311
+ };
1312
+
1313
+ // Write the merged configuration back
1314
+ const formattedConfig = JSON.stringify(mergedConfig, null, 2);
1315
+ await fs.promises.writeFile(packageJsonPath, formattedConfig, 'utf8');
1316
+
1317
+ // Update the package in the database - use correct field names based on your Prisma schema
1318
+ const updateData: any = {
1319
+ // Use 'lastUpdated' instead of 'updatedAt' based on the error message
1320
+ lastUpdated: new Date(),
1321
+ };
1322
+
1323
+ // Only update fields that exist in your Prisma schema
1324
+ if (newConfig.version) updateData.version = newConfig.version;
1325
+ if (newConfig.description !== undefined)
1326
+ updateData.description = newConfig.description || '';
1327
+ if (newConfig.license !== undefined)
1328
+ updateData.license = newConfig.license || '';
1329
+ if (newConfig.scripts)
1330
+ updateData.scripts = JSON.stringify(newConfig.scripts);
1331
+ if (newConfig.repository)
1332
+ updateData.repository = JSON.stringify(newConfig.repository);
1333
+ if (newConfig.dependencies)
1334
+ updateData.dependencies = JSON.stringify(newConfig.dependencies);
1335
+ if (newConfig.devDependencies)
1336
+ updateData.devDependencies = JSON.stringify(newConfig.devDependencies);
1337
+ if (newConfig.peerDependencies)
1338
+ updateData.peerDependencies = JSON.stringify(newConfig.peerDependencies);
1339
+
1340
+ console.log('📝 Updating database with:', updateData);
1341
+
1342
+ const updatedPackage = await prisma.package.update({
1343
+ where: { name: packageName },
1344
+ data: updateData,
1345
+ });
1346
+
1347
+ // Transform the response
1348
+ const transformedPackage = {
1349
+ ...updatedPackage,
1350
+ maintainers: updatedPackage.maintainers
1351
+ ? JSON.parse(updatedPackage.maintainers)
1352
+ : [],
1353
+ scripts: updatedPackage.scripts ? JSON.parse(updatedPackage.scripts) : {},
1354
+ repository: updatedPackage.repository
1355
+ ? JSON.parse(updatedPackage.repository)
1356
+ : {},
1357
+ dependencies: updatedPackage.dependencies
1358
+ ? JSON.parse(updatedPackage.dependencies)
1359
+ : {},
1360
+ devDependencies: updatedPackage.devDependencies
1361
+ ? JSON.parse(updatedPackage.devDependencies)
1362
+ : {},
1363
+ peerDependencies: updatedPackage.peerDependencies
1364
+ ? JSON.parse(updatedPackage.peerDependencies)
1365
+ : {},
1366
+ };
1367
+
1368
+ // Return success response
1369
+ return res.json({
1370
+ success: true,
1371
+ message: 'Package configuration updated successfully',
1372
+ package: transformedPackage,
1373
+ preservedFields: true,
1374
+ });
1375
+ } catch (error) {
1376
+ console.error('Error updating package configuration:', error);
1377
+
1378
+ // Ensure we always return JSON, even for errors
1379
+ return res.status(500).json({
1380
+ success: false,
1381
+ error: 'Failed to update package configuration',
1382
+ message:
1383
+ error instanceof Error ? error.message : 'Unknown error occurred',
1384
+ });
1385
+ }
1386
+ });
1387
+
1247
1388
  // Error handling middleware
1248
1389
  app.use(
1249
1390
  (
@@ -1269,10 +1410,13 @@ app.use('*', (_, res) => {
1269
1410
  });
1270
1411
  });
1271
1412
 
1272
- const PORT = process.env.PORT || 4000;
1413
+ const PORT = parseInt(port ? port.toString() : '4000');
1414
+
1415
+ app.listen(PORT, host ,async () => {
1416
+ const pcount = await prisma.package.count();
1417
+ console.log(`[Database] Total packages found: ${pcount}`);
1273
1418
 
1274
- app.listen(PORT, () => {
1275
- console.log(`🚀 Backend server running on http://localhost:${PORT}`);
1419
+ console.log(`🚀 Backend server running on http://${host}:${PORT}`);
1276
1420
  console.log(`📊 API endpoints available:`);
1277
1421
  console.log(` - GET /api/health`);
1278
1422
  console.log(` - GET /api/packages/refresh`);
@@ -1291,7 +1435,7 @@ app.listen(PORT, () => {
1291
1435
  }).on('error', (err) => {
1292
1436
  // Handle common errors like EADDRINUSE (port already in use)
1293
1437
  if (err.message.includes('EADDRINUSE')) {
1294
- console.error(`Error: Port ${port} is already in use. Please specify a different port via the PORT environment variable.`);
1438
+ console.error(`Error: Port ${port} is already in use. Please specify a different port via configuration file.`);
1295
1439
  process.exit(1);
1296
1440
  } else {
1297
1441
  console.error('Server failed to start:', err);
package/LICENCE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2024 Lakin Mohapatra
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.
Binary file