init project
This commit is contained in:
Vendored
+232
@@ -0,0 +1,232 @@
|
||||
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
|
||||
Reference in New Issue
Block a user