init project
This commit is contained in:
+116
@@ -0,0 +1,116 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { z } from 'zod';
|
||||
import { camelCase, pascalCase } from '../../lib/strings.js';
|
||||
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
|
||||
import { requireLaravel } from './requireLaravel.js';
|
||||
const RELATION_TYPES = [
|
||||
'belongsTo',
|
||||
'hasOne',
|
||||
'hasMany',
|
||||
'belongsToMany',
|
||||
'hasManyThrough',
|
||||
'morphTo',
|
||||
'morphMany',
|
||||
'morphOne',
|
||||
];
|
||||
function findModelFile(root, model) {
|
||||
const candidates = [
|
||||
path.join(root, 'app', 'Models', `${model}.php`),
|
||||
path.join(root, 'app', `${model}.php`),
|
||||
];
|
||||
return candidates.find((file) => fs.existsSync(file)) ?? null;
|
||||
}
|
||||
function relationMethodSource(methodName, relation, relatedModel) {
|
||||
const related = `\\App\\Models\\${relatedModel}::class`;
|
||||
return `
|
||||
public function ${methodName}()
|
||||
{
|
||||
return $this->${relation}(${related});
|
||||
}
|
||||
`;
|
||||
}
|
||||
function insertMethodBeforeClosingBrace(source, method) {
|
||||
const trimmed = method.trimEnd() + '\n';
|
||||
if (source.includes(`function ${trimmed.match(/function\s+(\w+)/)?.[1] ?? ''}(`)) {
|
||||
return source;
|
||||
}
|
||||
const lastBrace = source.lastIndexOf('}');
|
||||
if (lastBrace === -1) {
|
||||
throw new Error('Could not find closing class brace in model file.');
|
||||
}
|
||||
return `${source.slice(0, lastBrace)}${trimmed}${source.slice(lastBrace)}`;
|
||||
}
|
||||
export function registerGenerateEloquentRelationTool(server) {
|
||||
server.registerTool('generate_eloquent_relation', {
|
||||
title: 'Generate Eloquent Relation',
|
||||
description: 'Add an Eloquent relationship method to a model (belongsTo, hasMany, etc.), optionally also adding the inverse relation on the related model.',
|
||||
inputSchema: {
|
||||
model: z.string().min(1).describe('Model class name, e.g. "Post".'),
|
||||
relation: z.enum(RELATION_TYPES).describe('Eloquent relation type.'),
|
||||
related: z.string().min(1).describe('Related model class name, e.g. "User".'),
|
||||
method: z.string().optional().describe('Method name on the source model. Defaults from relation type.'),
|
||||
inverse: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.default(false)
|
||||
.describe('If true, also add a sensible inverse method on the related model.'),
|
||||
},
|
||||
}, async ({ model, relation, related, method, inverse }) => {
|
||||
try {
|
||||
const laravel = requireLaravel();
|
||||
const modelName = pascalCase(model);
|
||||
const relatedName = pascalCase(related);
|
||||
const modelFile = findModelFile(laravel.root, modelName);
|
||||
if (!modelFile) {
|
||||
return errorResult(`Model file not found for "${modelName}" under app/Models or app/.`);
|
||||
}
|
||||
const defaultMethod = relation === 'belongsTo' || relation === 'hasOne' || relation === 'morphTo' || relation === 'morphOne'
|
||||
? camelCase(relatedName)
|
||||
: `${camelCase(relatedName)}s`;
|
||||
const methodName = method?.trim() || defaultMethod;
|
||||
const before = fs.readFileSync(modelFile, 'utf8');
|
||||
const next = insertMethodBeforeClosingBrace(before, relationMethodSource(methodName, relation, relatedName));
|
||||
fs.writeFileSync(modelFile, next);
|
||||
const updated = [modelFile];
|
||||
let inverseMethod = null;
|
||||
let inverseFile = null;
|
||||
if (inverse) {
|
||||
const inverseMap = {
|
||||
belongsTo: 'hasMany',
|
||||
hasMany: 'belongsTo',
|
||||
hasOne: 'belongsTo',
|
||||
belongsToMany: 'belongsToMany',
|
||||
morphMany: 'morphTo',
|
||||
morphOne: 'morphTo',
|
||||
};
|
||||
const inverseRelation = inverseMap[relation];
|
||||
if (inverseRelation) {
|
||||
inverseFile = findModelFile(laravel.root, relatedName);
|
||||
if (!inverseFile) {
|
||||
return errorResult(`Added ${methodName}() on ${modelName}, but related model "${relatedName}" was not found for the inverse.`);
|
||||
}
|
||||
inverseMethod =
|
||||
inverseRelation === 'belongsTo' || inverseRelation === 'hasOne' || inverseRelation === 'morphTo'
|
||||
? camelCase(modelName)
|
||||
: `${camelCase(modelName)}s`;
|
||||
const relatedSource = fs.readFileSync(inverseFile, 'utf8');
|
||||
fs.writeFileSync(inverseFile, insertMethodBeforeClosingBrace(relatedSource, relationMethodSource(inverseMethod, inverseRelation, modelName)));
|
||||
updated.push(inverseFile);
|
||||
}
|
||||
}
|
||||
return jsonResult({
|
||||
model: modelName,
|
||||
related: relatedName,
|
||||
relation,
|
||||
method: methodName,
|
||||
inverseMethod,
|
||||
files: updated,
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
return errorResult(toErrorMessage(error));
|
||||
}
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=generateEloquentRelation.js.map
|
||||
Reference in New Issue
Block a user