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);
+ }
+}