Files
mcp_web_dev_server/dist/tools/docker/generate.js
T
2026-07-31 13:12:54 -04:00

232 lines
7.9 KiB
JavaScript

import fs from 'node:fs';
import { z } from 'zod';
import { config } from '../../config.js';
import { detectProject, resolvePackage } from '../../lib/frameworks/detect.js';
import { resolveWorkspacePath } from '../../lib/sandbox.js';
import { runCommand } from '../../lib/runCommand.js';
import { requireLaravel } from '../laravel/requireLaravel.js';
import { errorResult, jsonResult, packageSchema, toErrorMessage } from '../shared.js';
function nodeDockerfile() {
return `# Generated by web-dev-mcp
FROM node:24-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:24-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/index.js"]
`;
}
function phpDockerfile(kind) {
const publicDir = kind === 'laravel' ? 'public' : 'public';
return `# Generated by web-dev-mcp for ${kind}
FROM php:8.3-cli-alpine AS vendor
WORKDIR /app
COPY composer.json composer.lock* ./
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \\
&& composer install --no-dev --optimize-autoloader --no-interaction --prefer-dist || composer install --no-interaction --prefer-dist
FROM php:8.3-apache
WORKDIR /var/www/html
RUN docker-php-ext-install pdo pdo_mysql
COPY --from=vendor /app/vendor ./vendor
COPY . .
ENV APACHE_DOCUMENT_ROOT=/var/www/html/${publicDir}
RUN sed -ri -e 's!/var/www/html!\\\${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf \\
&& a2enmod rewrite
EXPOSE 80
`;
}
function nginxNodeCompose() {
return `# Generated by web-dev-mcp
services:
app:
build: .
ports:
- "3000:3000"
environment:
NODE_ENV: production
depends_on:
- redis
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: app
POSTGRES_DB: app
ports:
- "5432:5432"
volumes:
- db_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
db_data:
`;
}
function phpCompose() {
return `# Generated by web-dev-mcp
services:
app:
build: .
ports:
- "8080:80"
environment:
APP_ENV: local
DB_HOST: db
DB_DATABASE: app
DB_USERNAME: app
DB_PASSWORD: app
REDIS_HOST: redis
depends_on:
- db
- redis
db:
image: mysql:8.0
environment:
MYSQL_DATABASE: app
MYSQL_USER: app
MYSQL_PASSWORD: app
MYSQL_ROOT_PASSWORD: root
ports:
- "3306:3306"
volumes:
- db_data:/var/lib/mysql
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
db_data:
`;
}
export function registerGenerateDockerfileTool(server) {
server.registerTool('generate_dockerfile', {
title: 'Generate Dockerfile',
description: 'Write a stack-aware Dockerfile at the workspace (or package) root based on detected framework (Node multi-stage or PHP/Apache for Laravel/CodeIgniter).',
inputSchema: {
package: packageSchema,
overwrite: z.boolean().optional().default(false),
},
}, async ({ package: packageName, overwrite }) => {
try {
const root = packageName
? resolvePackage(config.workspaceRoot, packageName).pkg.absolutePath
: config.workspaceRoot;
const detected = detectProject(root);
const file = resolveWorkspacePath(root, 'Dockerfile');
if (!overwrite && fs.existsSync(file)) {
return errorResult(`"${file}" already exists. Pass overwrite: true to replace it.`);
}
let contents;
let stack;
if (detected.backend?.kind === 'laravel' ||
detected.backend?.kind === 'codeigniter' ||
detected.backend?.kind === 'symfony') {
stack = detected.backend.kind;
contents = phpDockerfile(detected.backend.kind === 'codeigniter' ? 'codeigniter' : 'laravel');
}
else {
const front = detected.frontend?.kind;
stack =
front === 'react' || front === 'vue' || front === 'nuxt'
? `${front}/node`
: detected.backend?.kind === 'django' || detected.backend?.kind === 'rails'
? detected.backend.kind
: 'node';
contents = nodeDockerfile();
}
fs.writeFileSync(file, contents);
return jsonResult({ file, stack, package: packageName ?? null });
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
export function registerGenerateComposeTool(server) {
server.registerTool('generate_compose', {
title: 'Generate docker-compose.yml',
description: 'Write a docker-compose.yml with app + db + redis presets inferred from the detected stack (Postgres for Node, MySQL for PHP).',
inputSchema: {
package: packageSchema,
overwrite: z.boolean().optional().default(false),
},
}, async ({ package: packageName, overwrite }) => {
try {
const root = packageName
? resolvePackage(config.workspaceRoot, packageName).pkg.absolutePath
: config.workspaceRoot;
const detected = detectProject(root);
const file = resolveWorkspacePath(root, 'docker-compose.yml');
if (!overwrite && fs.existsSync(file)) {
return errorResult(`"${file}" already exists. Pass overwrite: true to replace it.`);
}
const isPhp = detected.backend?.kind === 'laravel' ||
detected.backend?.kind === 'codeigniter' ||
detected.backend?.kind === 'symfony';
const contents = isPhp ? phpCompose() : nginxNodeCompose();
fs.writeFileSync(file, contents);
return jsonResult({
file,
stack: isPhp
? detected.backend?.kind
: (detected.frontend?.kind ?? detected.backend?.kind ?? 'node'),
services: ['app', 'db', 'redis'],
package: packageName ?? null,
});
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
export function registerGenerateSailTool(server) {
server.registerTool('generate_sail', {
title: 'Install Laravel Sail',
description: 'Run `php artisan sail:install` in a detected Laravel project. Requires confirm: true because it modifies docker-compose and related files.',
inputSchema: {
with: z
.array(z.string())
.optional()
.describe('Optional Sail services, e.g. ["mysql", "redis", "mailpit"].'),
confirm: z.boolean().optional().describe('Required as true to run sail:install.'),
},
}, async ({ with: services, confirm }) => {
try {
if (confirm !== true) {
return errorResult('generate_sail requires confirm: true.');
}
const laravel = requireLaravel();
const args = ['artisan', 'sail:install', '--no-interaction'];
if (services && services.length > 0) {
args.push(`--with=${services.join(',')}`);
}
const result = await runCommand('php', args, {
cwd: laravel.root,
timeoutMs: config.scaffoldCommandTimeoutMs,
});
return jsonResult({
services: services ?? null,
exitCode: result.exitCode,
failed: result.failed,
output: result.output,
cwd: result.cwd,
});
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
//# sourceMappingURL=generate.js.map