Files
2026-07-31 13:12:54 -04:00

113 lines
5.2 KiB
JavaScript

import fs from 'node:fs';
import path from 'node:path';
import { z } from 'zod';
import { config } from '../../config.js';
import { runCommand } from '../../lib/runCommand.js';
import { kebabCase, pascalCase } from '../../lib/strings.js';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { requireLaravel } from './requireLaravel.js';
function pluralize(word) {
const lower = word.toLowerCase();
if (lower.endsWith('ies'))
return word;
if (lower.endsWith('y') && !/[aeiou]y$/i.test(word))
return `${word.slice(0, -1)}ies`;
if (/(s|x|z|ch|sh)$/i.test(word))
return `${word}es`;
if (lower.endsWith('s'))
return word;
return `${word}s`;
}
export function registerGenerateLaravelResourceTool(server) {
server.registerTool('generate_laravel_resource', {
title: 'Generate Laravel Resource',
description: 'One-shot scaffold of a full Laravel resource: model + migration + factory + seeder + controller, ' +
'then wire an apiResource/resource route. Wraps the relevant `php artisan make:*` calls.',
inputSchema: {
name: z
.string()
.min(1)
.describe('Resource name, e.g. "Post" or "blog_post". Converted to PascalCase for the model/controller.'),
api: z
.boolean()
.optional()
.default(true)
.describe('If true (default), generate an API controller (--api) and register Route::apiResource in routes/api.php. If false, generate a web resource controller and register Route::resource in routes/web.php.'),
migrate: z
.boolean()
.optional()
.default(false)
.describe('If true, run `php artisan migrate` after scaffolding. Requires a configured database.'),
},
}, async ({ name, api, migrate }) => {
try {
const laravel = requireLaravel();
const model = pascalCase(name);
const controller = `${model}Controller`;
const routeName = kebabCase(pluralize(model));
const steps = [];
// Model + migration + factory + seeder in one go.
const modelResult = await runCommand('php', ['artisan', 'make:model', model, '-mfs', '--no-interaction'], { cwd: laravel.root, timeoutMs: config.scaffoldCommandTimeoutMs });
steps.push({ step: 'make:model -mfs', ...modelResult });
const controllerArgs = api
? ['artisan', 'make:controller', controller, '--api', '--model=' + model, '--no-interaction']
: ['artisan', 'make:controller', controller, '--resource', '--model=' + model, '--no-interaction'];
const controllerResult = await runCommand('php', controllerArgs, {
cwd: laravel.root,
timeoutMs: config.scaffoldCommandTimeoutMs,
});
steps.push({ step: 'make:controller', ...controllerResult });
const routesFile = path.join(laravel.root, 'routes', api ? 'api.php' : 'web.php');
const routeLine = api
? `Route::apiResource('${routeName}', \\App\\Http\\Controllers\\${controller}::class);`
: `Route::resource('${routeName}', \\App\\Http\\Controllers\\${controller}::class);`;
const routeWired = wireRoute(routesFile, routeLine, api);
steps.push({ step: 'wire-route', routesFile, routeLine, wired: routeWired });
let migrateResult = null;
if (migrate) {
migrateResult = await runCommand('php', ['artisan', 'migrate', '--no-interaction'], {
cwd: laravel.root,
timeoutMs: config.scaffoldCommandTimeoutMs,
});
steps.push({ step: 'migrate', ...migrateResult });
}
return jsonResult({
model,
controller,
route: `${api ? 'apiResource' : 'resource'} /${routeName}`,
routesFile,
routeWired,
steps,
});
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
function wireRoute(routesFile, routeLine, api) {
fs.mkdirSync(path.dirname(routesFile), { recursive: true });
if (!fs.existsSync(routesFile)) {
const header = api
? `<?php\n\nuse Illuminate\\Support\\Facades\\Route;\n\n`
: `<?php\n\nuse Illuminate\\Support\\Facades\\Route;\n\n`;
fs.writeFileSync(routesFile, header + routeLine + '\n');
return true;
}
const existing = fs.readFileSync(routesFile, 'utf8');
if (existing.includes(routeLine.trim()))
return false;
// Ensure the Route facade is imported if the file already exists without it.
let next = existing;
if (!/use\s+Illuminate\\Support\\Facades\\Route\s*;/.test(next)) {
if (next.startsWith('<?php')) {
next = next.replace('<?php', "<?php\n\nuse Illuminate\\Support\\Facades\\Route;");
}
else {
next = `<?php\n\nuse Illuminate\\Support\\Facades\\Route;\n\n${next}`;
}
}
fs.writeFileSync(routesFile, `${next.trimEnd()}\n${routeLine}\n`);
return true;
}
//# sourceMappingURL=generateLaravelResource.js.map