mlgym-deploy 3.3.15 → 3.3.19

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.
Files changed (2) hide show
  1. package/index.js +67 -20
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -18,7 +18,7 @@ import crypto from 'crypto';
18
18
  const execAsync = promisify(exec);
19
19
 
20
20
  // Current version of this MCP server - INCREMENT FOR WORKFLOW FIXES
21
- const CURRENT_VERSION = '3.3.15'; // Fix: Use expose instead of ports:80 for Traefik compatibility
21
+ const CURRENT_VERSION = '3.3.17'; // Fix: Extract EXPOSE port from Dockerfile and send to backend
22
22
  const PACKAGE_NAME = 'mlgym-deploy';
23
23
 
24
24
  // Debug logging configuration - ENABLED BY DEFAULT
@@ -95,6 +95,12 @@ const CONFIG = {
95
95
 
96
96
  // Helper to load/save authentication
97
97
  async function loadAuth() {
98
+ // First check environment variable
99
+ if (process.env.MLGYM_TOKEN) {
100
+ return { token: process.env.MLGYM_TOKEN, email: process.env.MLGYM_EMAIL || 'env-token-user' };
101
+ }
102
+
103
+ // Fall back to config file
98
104
  try {
99
105
  const data = await fs.readFile(CONFIG.config_file, 'utf8');
100
106
  return JSON.parse(data);
@@ -985,6 +991,30 @@ async function smartDeploy(args) {
985
991
  }
986
992
 
987
993
  // Deployment Strategy Detection (matches CLI logic)
994
+ // Extract EXPOSE port from Dockerfile
995
+ async function extractDockerfilePort(dockerfilePath) {
996
+ try {
997
+ const content = await fs.readFile(dockerfilePath, 'utf8');
998
+ const lines = content.split('\n');
999
+ for (const line of lines) {
1000
+ const trimmed = line.trim();
1001
+ if (trimmed.startsWith('EXPOSE ')) {
1002
+ const portMatch = trimmed.match(/EXPOSE\s+(\d+)/);
1003
+ if (portMatch) {
1004
+ const port = portMatch[1];
1005
+ log.info(`MCP >>> [extractDockerfilePort] Found EXPOSE ${port}`);
1006
+ return port;
1007
+ }
1008
+ }
1009
+ }
1010
+ log.info('MCP >>> [extractDockerfilePort] No EXPOSE directive found, defaulting to 3000');
1011
+ return '3000'; // Default if no EXPOSE found
1012
+ } catch (err) {
1013
+ log.error('MCP >>> [extractDockerfilePort] Error reading Dockerfile:', err.message);
1014
+ return '3000';
1015
+ }
1016
+ }
1017
+
988
1018
  async function detectDeploymentStrategy(projectPath) {
989
1019
  const dockerfilePath = path.join(projectPath, 'Dockerfile');
990
1020
  const composePathYML = path.join(projectPath, 'docker-compose.yml');
@@ -1000,8 +1030,9 @@ async function detectDeploymentStrategy(projectPath) {
1000
1030
 
1001
1031
  // Case 1: Only Dockerfile
1002
1032
  if (hasDockerfile && !hasCompose) {
1003
- log.info('MCP >>> [detectDeploymentStrategy] Strategy: dockerfile (Dockerfile only)');
1004
- return { type: 'dockerfile', reason: 'Dockerfile only' };
1033
+ const port = await extractDockerfilePort(dockerfilePath);
1034
+ log.info(`MCP >>> [detectDeploymentStrategy] Strategy: dockerfile (Dockerfile only), port: ${port}`);
1035
+ return { type: 'dockerfile', reason: 'Dockerfile only', port };
1005
1036
  }
1006
1037
 
1007
1038
  // Case 2: Only docker-compose
@@ -1022,8 +1053,9 @@ async function detectDeploymentStrategy(projectPath) {
1022
1053
  // SIMPLE: Only 1 web service, no other services, uses local Dockerfile
1023
1054
  // → docker-compose is just convenience wrapper
1024
1055
  if (totalServiceCount === 1 && webServiceCount === 1 && usesLocalDockerfile) {
1025
- log.info('MCP >>> [detectDeploymentStrategy] Strategy: dockerfile (single web service)');
1026
- return { type: 'dockerfile', reason: 'docker-compose.yml only has web service (convenience wrapper for Dockerfile)' };
1056
+ const port = await extractDockerfilePort(dockerfilePath);
1057
+ log.info(`MCP >>> [detectDeploymentStrategy] Strategy: dockerfile (single web service), port: ${port}`);
1058
+ return { type: 'dockerfile', reason: 'docker-compose.yml only has web service (convenience wrapper for Dockerfile)', port };
1027
1059
  }
1028
1060
 
1029
1061
  // COMPLEX: Multiple services (web + database, etc.)
@@ -1165,29 +1197,34 @@ function randomizeVolumeNames(content, suffix) {
1165
1197
  function validateDockerfile(content) {
1166
1198
  const lines = content.split('\n');
1167
1199
  const issues = [];
1168
- let hasExpose80 = false;
1200
+ let hasExpose = false;
1201
+ let exposedPort = null;
1169
1202
 
1170
1203
  for (let i = 0; i < lines.length; i++) {
1171
- const trimmed = lines[i].trim().toUpperCase();
1172
-
1173
- if (trimmed.startsWith('EXPOSE')) {
1174
- // Check if it exposes port 80
1175
- if (trimmed.includes('80') && !trimmed.includes('8080') && !trimmed.includes('8443')) {
1176
- hasExpose80 = true;
1204
+ const trimmed = lines[i].trim();
1205
+ const upper = trimmed.toUpperCase();
1206
+
1207
+ if (upper.startsWith('EXPOSE')) {
1208
+ hasExpose = true;
1209
+ // Extract the port number
1210
+ const portMatch = trimmed.match(/EXPOSE\s+(\d+)/i);
1211
+ if (portMatch) {
1212
+ exposedPort = portMatch[1];
1177
1213
  }
1178
1214
  }
1179
1215
  }
1180
1216
 
1181
- if (!hasExpose80) {
1217
+ if (!hasExpose) {
1182
1218
  issues.push({
1183
- issue: 'Dockerfile does not EXPOSE port 80',
1184
- fix: 'Add "EXPOSE 80" to your Dockerfile'
1219
+ issue: 'Dockerfile does not have an EXPOSE directive',
1220
+ fix: 'Add "EXPOSE <port>" to your Dockerfile (e.g., EXPOSE 80 for web servers, EXPOSE 3000 for Node.js)'
1185
1221
  });
1186
1222
  }
1187
1223
 
1188
1224
  return {
1189
1225
  isValid: issues.length === 0,
1190
- issues
1226
+ issues,
1227
+ exposedPort
1191
1228
  };
1192
1229
  }
1193
1230
 
@@ -1357,6 +1394,12 @@ async function initProject(args) {
1357
1394
  projectData.hostname = hostname;
1358
1395
  projectData.local_path = local_path;
1359
1396
 
1397
+ // Send ports_exposes for Dockerfile deployments (v3.3.17+)
1398
+ if (strategy.port) {
1399
+ projectData.ports_exposes = strategy.port;
1400
+ log.info(`MCP >>> [initProject] Sending ports_exposes: ${strategy.port} to backend`);
1401
+ }
1402
+
1360
1403
  // Read docker-compose content if using dockercompose strategy (v3.2.1+)
1361
1404
  if (strategy.type === 'dockercompose') {
1362
1405
  const composeFiles = ['docker-compose.yml', 'docker-compose.yaml'];
@@ -1886,7 +1929,7 @@ class DeploymentWorkflow {
1886
1929
  // Parse the result
1887
1930
  const resultData = JSON.parse(authResult.content[0].text);
1888
1931
 
1889
- if (resultData.status === 'success' || resultData.authenticated) {
1932
+ if (resultData.status === 'success' || resultData.status === 'authenticated' || resultData.authenticated) {
1890
1933
  this.authToken = (await loadAuth()).token;
1891
1934
  this.updateLastStep('completed', 'Authentication successful');
1892
1935
  return { authenticated: true, cached: false };
@@ -2208,9 +2251,11 @@ async function deployProject(args) {
2208
2251
  log.info(` Fix: ${issue.fix}`);
2209
2252
  });
2210
2253
 
2211
- // For Dockerfile, we'll add EXPOSE 80 if missing
2212
- if (!content.includes('EXPOSE 80')) {
2213
- log.info('MCP >>> Auto-fixing Dockerfile: adding EXPOSE 80...');
2254
+ // Only add EXPOSE if there's truly no EXPOSE directive at all
2255
+ // Don't override an existing EXPOSE with a different port
2256
+ const hasAnyExpose = /^\s*EXPOSE\s+\d+/mi.test(content);
2257
+ if (!hasAnyExpose) {
2258
+ log.info('MCP >>> Auto-fixing Dockerfile: adding EXPOSE 80 (no existing EXPOSE found)...');
2214
2259
  const lines = content.split('\n');
2215
2260
 
2216
2261
  // Find the last FROM or WORKDIR line to add EXPOSE after it
@@ -2230,6 +2275,8 @@ async function deployProject(args) {
2230
2275
  fsSync.writeFileSync(dockerfilePath, fixedContent);
2231
2276
 
2232
2277
  log.success('MCP >>> Fixed Dockerfile: added EXPOSE 80');
2278
+ } else {
2279
+ log.info('MCP >>> Dockerfile already has EXPOSE directive, skipping auto-fix');
2233
2280
  }
2234
2281
  } else {
2235
2282
  log.success('MCP >>> Dockerfile is Coolify compliant');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mlgym-deploy",
3
- "version": "3.3.15",
3
+ "version": "3.3.19",
4
4
  "description": "MCP server for MLGym - Complete deployment management: deploy, configure, monitor, and rollback applications",
5
5
  "main": "index.js",
6
6
  "type": "module",