mlgym-deploy 2.1.0 → 2.2.0

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 (3) hide show
  1. package/index-v2.js +24 -2
  2. package/index.js +214 -2
  3. package/package.json +1 -1
package/index-v2.js CHANGED
@@ -964,25 +964,44 @@ async function deployProject(sessionId, args) {
964
964
  description: `Deployed via MLGym MCP from ${session.framework} project`,
965
965
  visibility: 'private',
966
966
  enable_deployment: true,
967
- deployment_region: region,
968
967
  webhook_secret: crypto.randomBytes(16).toString('hex')
968
+ // Note: deployment_region is not used by backend, it uses random server selection
969
969
  };
970
970
 
971
+ console.error(`Creating project with deployment enabled for ${session.projectName} in region ${region}`);
971
972
  const result = await apiRequest('POST', '/api/v1/projects', projectData);
972
973
 
973
974
  if (!result.success) {
975
+ console.error(`Project creation failed: ${result.error}`);
976
+ // Provide more detailed error information
974
977
  return {
975
978
  content: [{
976
979
  type: 'text',
977
980
  text: JSON.stringify({
978
981
  status: 'error',
979
- message: `Deployment failed: ${result.error}`
982
+ message: `Deployment failed: ${result.error}`,
983
+ details: {
984
+ project_name: session.projectName,
985
+ region: region,
986
+ framework: session.framework,
987
+ suggestion: 'Please ensure the backend and Coolify services are running properly'
988
+ }
980
989
  }, null, 2)
981
990
  }]
982
991
  };
983
992
  }
984
993
 
985
994
  const project = result.data;
995
+ console.error(`Project created successfully: ${project.name} (ID: ${project.id})`);
996
+ console.error(`SSH URL: ${project.ssh_url_to_repo}`);
997
+
998
+ // Check if deployment was actually created
999
+ const deploymentCreated = project.deployment_status || project.coolify_resource_id;
1000
+ if (deploymentCreated) {
1001
+ console.error(`Deployment resource created in Coolify`);
1002
+ } else {
1003
+ console.error(`Warning: Project created but deployment resource might not be set up`);
1004
+ }
986
1005
 
987
1006
  // Initialize git repository
988
1007
  const gitCommands = [
@@ -990,6 +1009,8 @@ async function deployProject(sessionId, args) {
990
1009
  `git remote add origin ${project.ssh_url_to_repo}`,
991
1010
  'git add .',
992
1011
  'git commit -m "Initial deployment via MLGym"',
1012
+ '# Wait 5-10 seconds for SSH key to propagate in GitLab',
1013
+ 'sleep 10',
993
1014
  'git push -u origin main'
994
1015
  ];
995
1016
 
@@ -1014,6 +1035,7 @@ async function deployProject(sessionId, args) {
1014
1035
  ssl: 'auto-provisioned',
1015
1036
  cdn: 'enabled'
1016
1037
  },
1038
+ important_note: '⚠️ SSH key propagation: Please wait 10 seconds before pushing to allow GitLab to activate your SSH key',
1017
1039
  next_steps: {
1018
1040
  message: 'Run these commands in your project directory:',
1019
1041
  commands: gitCommands
package/index.js CHANGED
@@ -2019,10 +2019,47 @@ async function initProject(args) {
2019
2019
  namespace: project.namespace?.path || project.namespace || auth.email.split('@')[0]
2020
2020
  };
2021
2021
 
2022
- // If deployment was enabled, the backend has already created webhook and Coolify resources
2022
+ // If deployment was enabled, create Dockerfile if not exists
2023
2023
  if (enable_deployment) {
2024
2024
  console.error('Deployment enabled - backend has set up webhook and Coolify resources automatically');
2025
2025
 
2026
+ // Check if Dockerfile already exists
2027
+ const dockerfilePath = path.join(local_path, 'Dockerfile');
2028
+ let dockerfileCreated = false;
2029
+
2030
+ try {
2031
+ await fs.access(dockerfilePath);
2032
+ console.error('Dockerfile already exists in project');
2033
+ } catch {
2034
+ // Dockerfile doesn't exist, create one based on detected framework
2035
+ console.error('No Dockerfile found, generating one...');
2036
+
2037
+ try {
2038
+ const framework = await detectFramework(local_path);
2039
+ console.error(`Detected framework: ${framework}`);
2040
+
2041
+ const dockerfileContent = generateDockerfile(framework, name);
2042
+ await fs.writeFile(dockerfilePath, dockerfileContent, 'utf8');
2043
+ dockerfileCreated = true;
2044
+
2045
+ console.error(`Created Dockerfile for ${framework} framework`);
2046
+
2047
+ // If React app, also create nginx.conf
2048
+ if (framework === 'react') {
2049
+ const nginxPath = path.join(local_path, 'nginx.conf');
2050
+ try {
2051
+ await fs.access(nginxPath);
2052
+ } catch {
2053
+ const nginxContent = generateNginxConf();
2054
+ await fs.writeFile(nginxPath, nginxContent, 'utf8');
2055
+ console.error('Created nginx.conf for React app');
2056
+ }
2057
+ }
2058
+ } catch (err) {
2059
+ console.error(`Failed to create Dockerfile: ${err.message}`);
2060
+ }
2061
+ }
2062
+
2026
2063
  response.webhook = {
2027
2064
  secret: webhookSecret,
2028
2065
  status: 'created',
@@ -2032,7 +2069,8 @@ async function initProject(args) {
2032
2069
  response.deployment = {
2033
2070
  status: 'initialized',
2034
2071
  domain: `${name}.eu-central.mlgym.app`,
2035
- message: 'Coolify deployment ready - push to main branch to trigger deployment'
2072
+ message: 'Coolify deployment ready - push to main branch to trigger deployment',
2073
+ dockerfile: dockerfileCreated ? 'Generated automatically based on framework detection' : 'Using existing Dockerfile'
2036
2074
  };
2037
2075
  }
2038
2076
 
@@ -2075,6 +2113,180 @@ function generateWebhookSecret() {
2075
2113
  return secret;
2076
2114
  }
2077
2115
 
2116
+ // Generate Dockerfile based on detected framework
2117
+ function generateDockerfile(framework, projectName) {
2118
+ const dockerfiles = {
2119
+ nextjs: `# Next.js Production Dockerfile
2120
+ FROM node:20-alpine AS builder
2121
+ WORKDIR /app
2122
+ COPY package*.json ./
2123
+ RUN npm ci
2124
+ COPY . .
2125
+ RUN npm run build
2126
+
2127
+ FROM node:20-alpine
2128
+ WORKDIR /app
2129
+ ENV NODE_ENV=production
2130
+ COPY --from=builder /app/public ./public
2131
+ COPY --from=builder /app/.next ./.next
2132
+ COPY --from=builder /app/node_modules ./node_modules
2133
+ COPY --from=builder /app/package.json ./package.json
2134
+ EXPOSE 3000
2135
+ ENV PORT=3000
2136
+ CMD ["npm", "start"]`,
2137
+
2138
+ react: `# React Production Dockerfile
2139
+ FROM node:20-alpine AS builder
2140
+ WORKDIR /app
2141
+ COPY package*.json ./
2142
+ RUN npm ci
2143
+ COPY . .
2144
+ RUN npm run build
2145
+
2146
+ FROM nginx:alpine
2147
+ COPY --from=builder /app/build /usr/share/nginx/html
2148
+ COPY nginx.conf /etc/nginx/conf.d/default.conf
2149
+ EXPOSE 80
2150
+ CMD ["nginx", "-g", "daemon off;"]`,
2151
+
2152
+ express: `# Express.js Dockerfile
2153
+ FROM node:20-alpine
2154
+ WORKDIR /app
2155
+ COPY package*.json ./
2156
+ RUN npm ci
2157
+ COPY . .
2158
+ EXPOSE 3000
2159
+ ENV PORT=3000
2160
+ CMD ["node", "index.js"]`,
2161
+
2162
+ django: `# Django Dockerfile
2163
+ FROM python:3.11-slim
2164
+ WORKDIR /app
2165
+ ENV PYTHONUNBUFFERED=1
2166
+ COPY requirements.txt .
2167
+ RUN pip install --no-cache-dir -r requirements.txt
2168
+ COPY . .
2169
+ RUN python manage.py collectstatic --noinput
2170
+ EXPOSE 8000
2171
+ CMD ["gunicorn", "--bind", "0.0.0.0:8000", "myproject.wsgi:application"]`,
2172
+
2173
+ php: `# PHP Dockerfile
2174
+ FROM php:8.2-apache
2175
+ RUN docker-php-ext-install pdo pdo_mysql
2176
+ COPY . /var/www/html/
2177
+ RUN a2enmod rewrite
2178
+ EXPOSE 80`,
2179
+
2180
+ static: `# Static Site Dockerfile
2181
+ FROM nginx:alpine
2182
+ COPY . /usr/share/nginx/html
2183
+ EXPOSE 80
2184
+ CMD ["nginx", "-g", "daemon off;"]`,
2185
+
2186
+ go: `# Go Dockerfile
2187
+ FROM golang:1.21-alpine AS builder
2188
+ WORKDIR /app
2189
+ COPY go.* ./
2190
+ RUN go mod download
2191
+ COPY . .
2192
+ RUN go build -o main .
2193
+
2194
+ FROM alpine:latest
2195
+ RUN apk --no-cache add ca-certificates
2196
+ WORKDIR /root/
2197
+ COPY --from=builder /app/main .
2198
+ EXPOSE 8080
2199
+ CMD ["./main"]`,
2200
+
2201
+ rust: `# Rust Dockerfile
2202
+ FROM rust:1.75 AS builder
2203
+ WORKDIR /app
2204
+ COPY Cargo.* ./
2205
+ COPY src ./src
2206
+ RUN cargo build --release
2207
+
2208
+ FROM debian:bookworm-slim
2209
+ WORKDIR /app
2210
+ COPY --from=builder /app/target/release/${projectName} .
2211
+ EXPOSE 8080
2212
+ CMD ["./${projectName}"]`
2213
+ };
2214
+
2215
+ return dockerfiles[framework] || dockerfiles.static;
2216
+ }
2217
+
2218
+ // Generate nginx.conf for React apps
2219
+ function generateNginxConf() {
2220
+ return `server {
2221
+ listen 80;
2222
+ location / {
2223
+ root /usr/share/nginx/html;
2224
+ index index.html index.htm;
2225
+ try_files $uri $uri/ /index.html;
2226
+ }
2227
+ }`;
2228
+ }
2229
+
2230
+ // Detect project framework by analyzing files
2231
+ async function detectFramework(localPath) {
2232
+ try {
2233
+ // Check for package.json
2234
+ const packagePath = path.join(localPath, 'package.json');
2235
+ const packageData = await fs.readFile(packagePath, 'utf8');
2236
+ const packageJson = JSON.parse(packageData);
2237
+
2238
+ // Check dependencies
2239
+ const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
2240
+
2241
+ if (deps.next) return 'nextjs';
2242
+ if (deps.react && !deps.next) return 'react';
2243
+ if (deps.express) return 'express';
2244
+ if (deps.vue) return 'vue';
2245
+ if (deps.angular) return 'angular';
2246
+
2247
+ // If has package.json but no recognized framework
2248
+ return 'node';
2249
+ } catch {
2250
+ // No package.json, check for other files
2251
+ }
2252
+
2253
+ // Check for Python
2254
+ try {
2255
+ await fs.access(path.join(localPath, 'requirements.txt'));
2256
+ const content = await fs.readFile(path.join(localPath, 'requirements.txt'), 'utf8');
2257
+ if (content.includes('django')) return 'django';
2258
+ if (content.includes('flask')) return 'flask';
2259
+ return 'python';
2260
+ } catch {}
2261
+
2262
+ // Check for Go
2263
+ try {
2264
+ await fs.access(path.join(localPath, 'go.mod'));
2265
+ return 'go';
2266
+ } catch {}
2267
+
2268
+ // Check for Rust
2269
+ try {
2270
+ await fs.access(path.join(localPath, 'Cargo.toml'));
2271
+ return 'rust';
2272
+ } catch {}
2273
+
2274
+ // Check for PHP
2275
+ try {
2276
+ await fs.access(path.join(localPath, 'composer.json'));
2277
+ return 'php';
2278
+ } catch {}
2279
+
2280
+ // Check for index.php
2281
+ try {
2282
+ await fs.access(path.join(localPath, 'index.php'));
2283
+ return 'php';
2284
+ } catch {}
2285
+
2286
+ // Default to static site
2287
+ return 'static';
2288
+ }
2289
+
2078
2290
  // Tool implementation: Recover User
2079
2291
  async function recoverUser(args) {
2080
2292
  const { email, pin, new_password } = args;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mlgym-deploy",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "MCP server for GitLab Backend - User creation and project deployment",
5
5
  "main": "index.js",
6
6
  "type": "module",