Implements a new `/core:migrate` command to provide a set of helpers for working with Laravel migrations in a monorepo environment. The new command includes the following subcommands: - `/core:migrate create <name>`: Creates a new migration file. - `/core:migrate run`: Runs all pending migrations. - `/core:migrate rollback`: Rolls back the last migration. - `/core:migrate fresh`: Drops all tables and re-runs all migrations. - `/core:migrate status`: Shows the status of all migrations. - `/core:migrate from-model <ModelName>`: Generates a new migration by analyzing an existing Laravel model. Key Features: - **Smart Migration Generation**: The `from-model` command uses a robust PHP script with Reflection to accurately parse model properties and relationships, generating a complete schema definition. - **Multi-Tenant Awareness**: New migrations automatically include a `workspace_id` foreign key to support multi-tenant architectures. - **Module Support**: The `create` and `from-model` commands accept `--path` and `--model-path` arguments, allowing them to be used with different modules in a monorepo. - **Automatic Indexing**: The `from-model` command automatically adds database indexes to foreign key columns.
93 lines
3.3 KiB
PHP
93 lines
3.3 KiB
PHP
<?php
|
|
|
|
// Find the project's vendor/autoload.php to bootstrap the application's classes
|
|
function find_autoload($dir) {
|
|
if (file_exists($dir . '/vendor/autoload.php')) {
|
|
return $dir . '/vendor/autoload.php';
|
|
}
|
|
if (realpath($dir) === '/') {
|
|
return false;
|
|
}
|
|
return find_autoload(dirname($dir));
|
|
}
|
|
|
|
$autoload_path = find_autoload(getcwd());
|
|
if (!$autoload_path) {
|
|
echo json_encode(['error' => 'Could not find vendor/autoload.php. Ensure script is run from within a Laravel project.']);
|
|
exit(1);
|
|
}
|
|
require_once $autoload_path;
|
|
|
|
if ($argc < 2) {
|
|
echo json_encode(['error' => 'Model file path is required.']);
|
|
exit(1);
|
|
}
|
|
|
|
$modelPath = $argv[1];
|
|
if (!file_exists($modelPath)) {
|
|
echo json_encode(['error' => "Model file not found at $modelPath"]);
|
|
exit(1);
|
|
}
|
|
|
|
// Convert file path to a class name (e.g., app/Models/User.php -> App\Models\User)
|
|
$className = str_replace('.php', '', $modelPath);
|
|
$className = ucfirst($className);
|
|
$className = str_replace('/', '\\', $className);
|
|
|
|
|
|
if (!class_exists($className)) {
|
|
echo json_encode(['error' => "Class '$className' could not be found. Check the path and namespace."]);
|
|
exit(1);
|
|
}
|
|
|
|
try {
|
|
$reflectionClass = new ReflectionClass($className);
|
|
$modelInstance = $reflectionClass->newInstanceWithoutConstructor();
|
|
|
|
// 1. Get columns from the $fillable property
|
|
$fillableProperties = $reflectionClass->getDefaultProperties()['fillable'] ?? [];
|
|
|
|
$columns = [];
|
|
foreach ($fillableProperties as $prop) {
|
|
$type = 'string'; // Default type
|
|
if (str_ends_with($prop, '_at')) $type = 'timestamp';
|
|
elseif (str_starts_with($prop, 'is_') || str_starts_with($prop, 'has_')) $type = 'boolean';
|
|
elseif (str_ends_with($prop, '_id')) $type = 'foreignId';
|
|
elseif (in_array($prop, ['description', 'content', 'body', 'details', 'notes'])) $type = 'text';
|
|
|
|
$columns[] = ['name' => $prop, 'type' => $type, 'index' => ($type === 'foreignId')];
|
|
}
|
|
|
|
// 2. Get foreign keys from BelongsTo relationships
|
|
$methods = $reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC);
|
|
foreach ($methods as $method) {
|
|
if ($method->getNumberOfRequiredParameters() > 0) continue;
|
|
|
|
$returnType = $method->getReturnType();
|
|
if ($returnType && $returnType instanceof ReflectionNamedType) {
|
|
if (str_ends_with($returnType->getName(), 'BelongsTo')) {
|
|
// A BelongsTo relation implies a foreign key column on *this* model's table
|
|
$relationName = $method->getName();
|
|
$foreignKey = Illuminate\Support\Str::snake($relationName) . '_id';
|
|
|
|
// Avoid adding duplicates if already found via $fillable
|
|
$exists = false;
|
|
foreach ($columns as $column) {
|
|
if ($column['name'] === $foreignKey) {
|
|
$exists = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!$exists) {
|
|
$columns[] = ['name' => $foreignKey, 'type' => 'foreignId', 'index' => true];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
echo json_encode(['columns' => $columns], JSON_PRETTY_PRINT);
|
|
|
|
} catch (ReflectionException $e) {
|
|
echo json_encode(['error' => "Reflection error: " . $e->getMessage()]);
|
|
exit(1);
|
|
}
|