mlgym-deploy 3.3.26 → 3.3.31

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 +85 -6
  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.23'; // Scala: flexible src structure, copy all .scala files
21
+ const CURRENT_VERSION = '3.3.30'; // Add warnings for slow-starting services (Cassandra, MongoDB, etc)
22
22
  const PACKAGE_NAME = 'mlgym-deploy';
23
23
 
24
24
  // Debug logging configuration - ENABLED BY DEFAULT
@@ -94,10 +94,29 @@ const CONFIG = {
94
94
  };
95
95
 
96
96
  // Helper to load/save authentication
97
+ // Helper to extract email from JWT token
98
+ function extractEmailFromJWT(token) {
99
+ try {
100
+ // JWT format: header.payload.signature
101
+ const parts = token.split('.');
102
+ if (parts.length !== 3) return null;
103
+
104
+ // Decode base64url payload (replace - with + and _ with /)
105
+ const base64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
106
+ const payload = JSON.parse(Buffer.from(base64, 'base64').toString('utf8'));
107
+
108
+ return payload.email || payload.sub || null;
109
+ } catch {
110
+ return null;
111
+ }
112
+ }
113
+
97
114
  async function loadAuth() {
98
115
  // First check environment variable
99
116
  if (process.env.MLGYM_TOKEN) {
100
- return { token: process.env.MLGYM_TOKEN, email: process.env.MLGYM_EMAIL || 'env-token-user' };
117
+ // Try to extract email from JWT token if MLGYM_EMAIL is not set
118
+ const email = process.env.MLGYM_EMAIL || extractEmailFromJWT(process.env.MLGYM_TOKEN) || 'env-token-user';
119
+ return { token: process.env.MLGYM_TOKEN, email };
101
120
  }
102
121
 
103
122
  // Fall back to config file
@@ -550,6 +569,24 @@ async function analyzeProject(local_path = '.') {
550
569
  } catch {}
551
570
  }
552
571
 
572
+ // Check for PHP project
573
+ if (analysis.project_type === 'unknown') {
574
+ try {
575
+ await fs.access(path.join(absolutePath, 'index.php'));
576
+ analysis.project_type = 'php';
577
+ analysis.framework = 'php';
578
+ analysis.detected_files.push('index.php');
579
+ } catch {
580
+ // Also check for composer.json (PHP with Composer)
581
+ try {
582
+ await fs.access(path.join(absolutePath, 'composer.json'));
583
+ analysis.project_type = 'php';
584
+ analysis.framework = 'composer';
585
+ analysis.detected_files.push('composer.json');
586
+ } catch {}
587
+ }
588
+ }
589
+
553
590
  // Check for static HTML project
554
591
  if (analysis.project_type === 'unknown') {
555
592
  try {
@@ -723,6 +760,13 @@ COPY . .
723
760
  EXPOSE 8000
724
761
  CMD ["python", "main.py"]`;
725
762
  }
763
+ } else if (projectType === 'php') {
764
+ dockerfile = `FROM php:8.2-apache
765
+ WORKDIR /var/www/html
766
+ COPY . .
767
+ RUN chown -R www-data:www-data /var/www/html
768
+ EXPOSE 80
769
+ CMD ["apache2-foreground"]`;
726
770
  } else if (projectType === 'static') {
727
771
  dockerfile = `FROM nginx:alpine
728
772
  COPY . /usr/share/nginx/html
@@ -1181,10 +1225,17 @@ async function detectDeploymentStrategy(projectPath) {
1181
1225
  log.info('MCP >>> [detectDeploymentStrategy] Both files found, analyzing compose...');
1182
1226
 
1183
1227
  try {
1184
- const { webServiceCount, usesLocalDockerfile, totalServiceCount } = await analyzeComposeFile(composePath);
1228
+ const { webServiceCount, usesLocalDockerfile, totalServiceCount, slowStartServices } = await analyzeComposeFile(composePath);
1185
1229
 
1186
1230
  log.info(`MCP >>> [analyzeCompose] Total services: ${totalServiceCount}, Web services: ${webServiceCount}, Uses local Dockerfile: ${usesLocalDockerfile}`);
1187
1231
 
1232
+ // Warn about slow-starting services
1233
+ if (slowStartServices && slowStartServices.length > 0) {
1234
+ log.warning(`⚠ WARNING: Detected services with slow startup: ${slowStartServices.join(', ')}`);
1235
+ log.warning(`⏱ These services (Cassandra, MongoDB, Elasticsearch, etc.) may take 2-5 minutes to become healthy.`);
1236
+ log.warning(` Your application will not be accessible until ALL services are running.`);
1237
+ }
1238
+
1188
1239
  // SIMPLE: Only 1 web service, no other services, uses local Dockerfile
1189
1240
  // → docker-compose is just convenience wrapper
1190
1241
  if (totalServiceCount === 1 && webServiceCount === 1 && usesLocalDockerfile) {
@@ -1196,7 +1247,7 @@ async function detectDeploymentStrategy(projectPath) {
1196
1247
  // COMPLEX: Multiple services (web + database, etc.)
1197
1248
  // → MUST use docker-compose for orchestration
1198
1249
  log.info('MCP >>> [detectDeploymentStrategy] Strategy: dockercompose (multi-service)');
1199
- return { type: 'dockercompose', reason: 'Application requires multiple services (web + database/cache/etc)' };
1250
+ return { type: 'dockercompose', reason: 'Application requires multiple services (web + database/cache/etc)', slowStartServices };
1200
1251
  } catch (err) {
1201
1252
  log.error('MCP >>> [detectDeploymentStrategy] Analysis failed, defaulting to dockercompose:', err.message);
1202
1253
  return { type: 'dockercompose', reason: 'Multiple deployment files found' };
@@ -1487,6 +1538,7 @@ async function analyzeComposeFile(composePath) {
1487
1538
  let inServicesSection = false;
1488
1539
  let currentService = '';
1489
1540
  let currentServiceIsWeb = false;
1541
+ let slowStartServices = []; // Track services with known slow startup
1490
1542
 
1491
1543
  for (const line of lines) {
1492
1544
  const trimmed = line.trim();
@@ -1523,6 +1575,15 @@ async function analyzeComposeFile(composePath) {
1523
1575
 
1524
1576
  currentService = trimmed.split(':')[0].trim();
1525
1577
  currentServiceIsWeb = false;
1578
+
1579
+ // Check for services with known slow startup times
1580
+ const serviceLower = currentService.toLowerCase();
1581
+ if (serviceLower.includes('cassandra') || serviceLower.includes('elasticsearch') ||
1582
+ serviceLower.includes('mongodb') || serviceLower.includes('kafka') ||
1583
+ serviceLower.includes('zookeeper') || serviceLower.includes('spark')) {
1584
+ slowStartServices.push(currentService);
1585
+ }
1586
+
1526
1587
  continue;
1527
1588
  }
1528
1589
 
@@ -1535,6 +1596,18 @@ async function analyzeComposeFile(composePath) {
1535
1596
  if (trimmed === 'build: .' || trimmed === 'build: ./') {
1536
1597
  usesLocalDockerfile = true;
1537
1598
  }
1599
+
1600
+ // Also detect slow-start services by image name
1601
+ if (trimmed.startsWith('image:')) {
1602
+ const imageName = trimmed.substring(6).trim().toLowerCase();
1603
+ if (imageName.includes('cassandra') || imageName.includes('elasticsearch') ||
1604
+ imageName.includes('mongodb') || imageName.includes('kafka') ||
1605
+ imageName.includes('zookeeper') || imageName.includes('spark')) {
1606
+ if (!slowStartServices.includes(currentService)) {
1607
+ slowStartServices.push(currentService);
1608
+ }
1609
+ }
1610
+ }
1538
1611
  }
1539
1612
 
1540
1613
  // Count last service
@@ -1545,7 +1618,7 @@ async function analyzeComposeFile(composePath) {
1545
1618
  }
1546
1619
  }
1547
1620
 
1548
- return { webServiceCount, usesLocalDockerfile, totalServiceCount };
1621
+ return { webServiceCount, usesLocalDockerfile, totalServiceCount, slowStartServices };
1549
1622
  }
1550
1623
 
1551
1624
  // Helper to check if file exists
@@ -1694,6 +1767,10 @@ async function initProject(args) {
1694
1767
 
1695
1768
  const project = result.data;
1696
1769
 
1770
+ // Debug: Log the deployment_url received from backend
1771
+ log.debug('MCP >>> [initProject] Backend response - deployment_url:', project.deployment_url);
1772
+ log.debug('MCP >>> [initProject] Backend response - webhook_id:', project.webhook_id);
1773
+
1697
1774
  // Add administrator (chka@stratus5.com) as project member for debugging
1698
1775
  console.error('Adding administrator as project member...');
1699
1776
  try {
@@ -1886,7 +1963,9 @@ async function initProject(args) {
1886
1963
  if (status === 'deployed' || status === 'running') {
1887
1964
  deploymentStatus = {
1888
1965
  status: 'deployed',
1889
- url: statusResult.data.url || `https://${hostname}.ezb.net`,
1966
+ // Use deployment_url from backend response, NOT a fallback hostname pattern
1967
+ // The correct format is: https://{coolify_app_id}.{eu1|eu2|eu3}.ezb.net
1968
+ url: statusResult.data.url || project.deployment_url || null,
1890
1969
  message: 'Application successfully deployed'
1891
1970
  };
1892
1971
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mlgym-deploy",
3
- "version": "3.3.26",
3
+ "version": "3.3.31",
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",