#!/usr/bin/env php $argv[1] ?? 'help']; for ($i = 2; $i < count($argv); $i++) { $a = $argv[$i]; if (str_starts_with($a, '--')) { $eq = strpos($a, '='); if ($eq !== false) { $key = substr($a, 2, $eq - 2); $val = substr($a, $eq + 1); $args[$key] = $val; } else { $key = substr($a, 2); $args[$key] = true; } } else { $args[] = $a; } } return $args; } function loadConfig(array $args): array { // Priority: --config=path.php (must return array), else env vars, else SQLite db.sqlite in project root if (isset($args['config'])) { $path = (string)$args['config']; if (!is_file($path)) { throw new InvalidArgumentException("Config file not found: {$path}"); } $cfg = require $path; if (!is_array($cfg)) { throw new InvalidArgumentException('Config file must return an array'); } return $cfg; } $driver = getenv('DB_DRIVER') ?: null; if ($driver) { $driver = strtolower($driver); $cfg = ['driver' => $driver]; switch ($driver) { case 'mysql': case 'mariadb': $cfg += [ 'host' => getenv('DB_HOST') ?: '127.0.0.1', 'port' => (int)(getenv('DB_PORT') ?: 3306), 'database' => getenv('DB_DATABASE') ?: '', 'username' => getenv('DB_USERNAME') ?: null, 'password' => getenv('DB_PASSWORD') ?: null, 'charset' => getenv('DB_CHARSET') ?: 'utf8mb4', ]; break; case 'pgsql': case 'postgres': case 'postgresql': $cfg += [ 'host' => getenv('DB_HOST') ?: '127.0.0.1', 'port' => (int)(getenv('DB_PORT') ?: 5432), 'database' => getenv('DB_DATABASE') ?: '', 'username' => getenv('DB_USERNAME') ?: null, 'password' => getenv('DB_PASSWORD') ?: null, ]; break; case 'sqlsrv': case 'mssql': $cfg += [ 'host' => getenv('DB_HOST') ?: '127.0.0.1', 'port' => (int)(getenv('DB_PORT') ?: 1433), 'database' => getenv('DB_DATABASE') ?: '', 'username' => getenv('DB_USERNAME') ?: null, 'password' => getenv('DB_PASSWORD') ?: null, ]; break; case 'sqlite': $cfg += [ 'path' => getenv('DB_PATH') ?: (__DIR__ . '/../db.sqlite'), ]; break; default: // fall back later break; } return $cfg; } // Default: SQLite file in project root return [ 'driver' => 'sqlite', 'path' => __DIR__ . '/../db.sqlite', ]; } function migrationsDir(array $args): string { if (isset($args['path'])) return (string)$args['path']; $candidates = [getcwd() . '/database/migrations', __DIR__ . '/../database/migrations', __DIR__ . '/../examples/migrations']; foreach ($candidates as $dir) { if (is_dir($dir)) return $dir; } return __DIR__ . '/../examples/migrations'; } function ensureDir(string $dir): void { if (!is_dir($dir)) { if (!mkdir($dir, 0777, true) && !is_dir($dir)) { throw new RuntimeException('Failed to create directory: ' . $dir); } } } function cmd_help(): void { $help = <<setRegistry($migrations); $applied = $migrator->migrate($migrations); stdout('Applied: ' . json_encode($applied)); break; case 'rollback': $config = loadConfig($args); $conn = ConnectionManager::make($config); $dir = migrationsDir($args); $migrations = MigrationLoader::fromDirectory($dir); $migrator = new Migrator($conn); $migrator->setRegistry($migrations); $steps = isset($args['steps']) ? max(1, (int)$args['steps']) : 1; $rolled = $migrator->rollback($steps); stdout('Rolled back: ' . json_encode($rolled)); break; case 'status': $config = loadConfig($args); $conn = ConnectionManager::make($config); $dir = migrationsDir($args); $migrations = array_keys(MigrationLoader::fromDirectory($dir)); $repo = new \Pairity\Migrations\MigrationsRepository($conn); $ran = $repo->getRan(); $pending = array_values(array_diff($migrations, $ran)); stdout('Ran: ' . json_encode($ran)); stdout('Pending: ' . json_encode($pending)); break; case 'reset': $config = loadConfig($args); $conn = ConnectionManager::make($config); $dir = migrationsDir($args); $migrations = MigrationLoader::fromDirectory($dir); $migrator = new Migrator($conn); $migrator->setRegistry($migrations); $totalRolled = []; while (true) { $rolled = $migrator->rollback(1); if (!$rolled) break; $totalRolled = array_merge($totalRolled, $rolled); } stdout('Reset complete. Rolled back: ' . json_encode($totalRolled)); break; case 'make:migration': $name = $args[0] ?? null; if (!$name) { stderr('Missing migration Name. Usage: pairity make:migration CreateUsersTable [--path=DIR]'); exit(1); } $dir = migrationsDir($args); ensureDir($dir); $ts = date('Y_m_d_His'); $file = $dir . DIRECTORY_SEPARATOR . $ts . '_' . $name . '.php'; $template = <<<'PHP' create('example', function (Blueprint $t) { // $t->increments('id'); // $t->string('name', 255); // }); } public function down(ConnectionInterface $connection): void { // Example: drop table // $schema = SchemaManager::forConnection($connection); // $schema->dropIfExists('example'); } }; PHP; file_put_contents($file, $template); stdout('Created: ' . $file); break; case 'mongo:index:ensure': // Args: DB COLLECTION KEYS_JSON [--unique] $db = $args[0] ?? null; $col = $args[1] ?? null; $keysJson = $args[2] ?? null; if (!$db || !$col || !$keysJson) { stderr('Usage: pairity mongo:index:ensure DB COLLECTION KEYS_JSON [--unique]'); exit(1); } $config = loadConfig($args); $conn = \Pairity\NoSql\Mongo\MongoConnectionManager::make($config); $idx = new \Pairity\NoSql\Mongo\IndexManager($conn, $db, $col); $keys = json_decode($keysJson, true); if (!is_array($keys)) { stderr('Invalid KEYS_JSON (must be object like {"email":1})'); exit(1); } $opts = []; if (!empty($args['unique'])) { $opts['unique'] = true; } $name = $idx->ensureIndex($keys, $opts); stdout('Ensured index: ' . $name); break; case 'mongo:index:drop': // Args: DB COLLECTION NAME $db = $args[0] ?? null; $col = $args[1] ?? null; $name = $args[2] ?? null; if (!$db || !$col || !$name) { stderr('Usage: pairity mongo:index:drop DB COLLECTION NAME'); exit(1); } $config = loadConfig($args); $conn = \Pairity\NoSql\Mongo\MongoConnectionManager::make($config); $idx = new \Pairity\NoSql\Mongo\IndexManager($conn, $db, $col); $idx->dropIndex($name); stdout('Dropped index: ' . $name); break; case 'mongo:index:list': // Args: DB COLLECTION $db = $args[0] ?? null; $col = $args[1] ?? null; if (!$db || !$col) { stderr('Usage: pairity mongo:index:list DB COLLECTION'); exit(1); } $config = loadConfig($args); $conn = \Pairity\NoSql\Mongo\MongoConnectionManager::make($config); $idx = new \Pairity\NoSql\Mongo\IndexManager($conn, $db, $col); $list = $idx->listIndexes(); stdout(json_encode($list)); break; default: cmd_help(); break; } } catch (Throwable $e) { stderr('Error: ' . $e->getMessage()); exit(1); }