diff --git a/package.json b/package.json new file mode 100644 index 0000000..34e017b --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "baremetalphp-src", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "vite": "^5.0.0", + "@vitejs/plugin-react": "^4.2.0" + } +} \ No newline at end of file diff --git a/resources/js/App.jsx b/resources/js/App.jsx new file mode 100644 index 0000000..4fab2d8 --- /dev/null +++ b/resources/js/App.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './App'; + +const container = document.getElementById('app'); +if (container) { + const component = container.dataset.component || 'App'; + const props = container.dataset.props ? JSON.parse(container.dataset.props) : {}; + + const root = createRoot(container); + root.render(); +} \ No newline at end of file diff --git a/src/Console/Application.php b/src/Console/Application.php index 23cb647..4f8e893 100644 --- a/src/Console/Application.php +++ b/src/Console/Application.php @@ -46,6 +46,7 @@ public function __construct(Container $app) 'frontend:install' => Commands\InstallFrontendCommand::class, 'go:install' => Commands\InstallGoAppServerCommand::class, 'go:serve' => Commands\GoServeCommand::class, + 'seed' => Commands\SeedDatabaseCommand::class, ]; } diff --git a/src/Console/Commands/SeedDatabaseCommand.php b/src/Console/Commands/SeedDatabaseCommand.php new file mode 100644 index 0000000..288fa72 --- /dev/null +++ b/src/Console/Commands/SeedDatabaseCommand.php @@ -0,0 +1,20 @@ +run($seeder); + + echo "Seeded using {$seeder}\n"; + + exit; + } + } +} \ No newline at end of file diff --git a/src/Database/Seeder/Seeder.php b/src/Database/Seeder/Seeder.php index f7718de..cdc68a3 100644 --- a/src/Database/Seeder/Seeder.php +++ b/src/Database/Seeder/Seeder.php @@ -7,8 +7,20 @@ abstract class Seeder { abstract public function run(): void; - protected function call(string $seederClass): void + protected function call(string $seederClass, ?string $seederDirectory = null): void { - (new $seederClass())->run(); + // If class doesn't exist, try to load it via SeederRunner + if (!class_exists($seederClass)) { + $seederDirectory = $seederDirectory ?? base_path('database/seeders'); + $runner = new SeederRunner($seederDirectory); + $runner->run($seederClass); + return; + } + + $seeder = new $seederClass(); + if (!$seeder instanceof Seeder) { + throw new \RuntimeException("{$seederClass} must extend Seeder"); + } + $seeder->run(); } } \ No newline at end of file diff --git a/src/Database/Seeder/SeederRunner.php b/src/Database/Seeder/SeederRunner.php new file mode 100644 index 0000000..39b7e23 --- /dev/null +++ b/src/Database/Seeder/SeederRunner.php @@ -0,0 +1,33 @@ +seederDirectory = $path ?? base_path('database/seeders'); + } + + public function run(string $seederClass = 'DatabaseSeeder'): void + { + $file = $this->seederDirectory . '/' . $seederClass . '.php'; + + if (! file_exists($file)) { + throw new RuntimeException("Seeder file not found: {$file}"); + } + + require_once $file; + + $seeder = new $seederClass(); + + if (! $seeder instanceof Seeder) { + throw new RuntimeException("{$seederClass} must extend Seeder"); + } + + $seeder->run(); + } +} \ No newline at end of file diff --git a/tests/Unit/Database/SeederRunnerTest.php b/tests/Unit/Database/SeederRunnerTest.php new file mode 100644 index 0000000..74baf3a --- /dev/null +++ b/tests/Unit/Database/SeederRunnerTest.php @@ -0,0 +1,162 @@ +seederDir = sys_get_temp_dir() . '/baremetalphp-seeders-' . uniqid('', true); + + if (! is_dir($this->seederDir)) { + mkdir($this->seederDir, 0777, true); + } + } + + protected function tearDown(): void + { + if (is_dir($this->seederDir)) { + foreach (glob($this->seederDir . '/*.php') ?: [] as $file) { + @unlink($file); + } + + @rmdir($this->seederDir); + } + + parent::tearDown(); + } + + public function testSeederRunsTheGivenSeederClass(): void + { + // Arrange: fake DatabaseSeeder that tracks execution. + $className = 'TestSeeder' . uniqid(); + $this->writeSeederFile($className . '.php', <<seederDir); + + // Act + $runner->run($className); + + // Assert + $this->assertTrue($className::$ran); + } + + public function testItCanChainOtherSeedersViaCall(): void + { + // Use unique class names to avoid redeclaration errors + $postSeederClass = 'PostSeeder' . uniqid(); + $dbSeederClass = 'DatabaseSeeder' . uniqid(); + + // Child seeder that will be called by DatabaseSeeder + $this->writeSeederFile($postSeederClass . '.php', <<seederDir; + $this->writeSeederFile($dbSeederClass . '.php', <<call('{$postSeederClass}', '{$seederDir}'); + } +} +PHP +); + + $runner = new SeederRunner($this->seederDir); + + // Act + $runner->run($dbSeederClass); + + // Assert + $this->assertTrue($dbSeederClass::$ran, 'DatabaseSeeder::run should be executed'); + $this->assertTrue($postSeederClass::$ran, 'PostSeeder::run should be executed via call()'); + } + + public function testItThrowsWhenSeederFileDoesNotExist(): void + { + $runner = new SeederRunner($this->seederDir); + + $this->expectException(RuntimeException::class); + + $runner->run('MissingSeeder'); + } + + public function testItThrowsWhenClassDoesNotExtendSeeder(): void + { + // File exists, but class does not extend the base Seeder + $className = 'InvalidSeeder' . uniqid(); + $this->writeSeederFile($className . '.php', <<seederDir); + + $this->expectException(RuntimeException::class); + + $runner->run($className); + } + + /** + * Helper to write a seeder file into the temporary seeder directory. + */ + private function writeSeederFile(string $filename, string $contents): void + { + file_put_contents($this->seederDir . '/' . $filename, $contents); + } +}