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.
- package/index.js +85 -6
- 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.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
|
-
|
|
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
|
-
|
|
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