mlgym-deploy 3.3.16 → 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.
- package/index.js +66 -19
- 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.
|
|
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
|
-
|
|
1004
|
-
|
|
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
|
-
|
|
1026
|
-
|
|
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
|
|
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()
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
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 (!
|
|
1217
|
+
if (!hasExpose) {
|
|
1182
1218
|
issues.push({
|
|
1183
|
-
issue: 'Dockerfile does not EXPOSE
|
|
1184
|
-
fix: 'Add "EXPOSE
|
|
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'];
|
|
@@ -2208,9 +2251,11 @@ async function deployProject(args) {
|
|
|
2208
2251
|
log.info(` Fix: ${issue.fix}`);
|
|
2209
2252
|
});
|
|
2210
2253
|
|
|
2211
|
-
//
|
|
2212
|
-
|
|
2213
|
-
|
|
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