diff --git a/config/blueprint.php b/config/blueprint.php
index e44082f9..a533b8d8 100644
--- a/config/blueprint.php
+++ b/config/blueprint.php
@@ -170,6 +170,7 @@
'notification' => \Blueprint\Generators\Statements\NotificationGenerator::class,
'resource' => \Blueprint\Generators\Statements\ResourceGenerator::class,
'view' => \Blueprint\Generators\Statements\ViewGenerator::class,
+ 'inertia_page' => \Blueprint\Generators\Statements\InertiaPageGenerator::class,
'policy' => \Blueprint\Generators\PolicyGenerator::class,
],
diff --git a/src/Generators/Statements/InertiaPageGenerator.php b/src/Generators/Statements/InertiaPageGenerator.php
new file mode 100644
index 00000000..0d6c3b43
--- /dev/null
+++ b/src/Generators/Statements/InertiaPageGenerator.php
@@ -0,0 +1,98 @@
+ ['framework' => 'vue', 'extension' => '.vue'],
+ 'react' => ['framework' => 'react', 'extension' => '.jsx'],
+ 'svelte' => ['framework' => 'svelte', 'extension' => '.svelte'],
+ ];
+
+ protected ?array $adapter = null;
+
+ public function output(Tree $tree): array
+ {
+ $this->adapter = $this->getAdapter();
+
+ if (!$this->adapter) {
+ return $this->output;
+ }
+
+ $stub = $this->filesystem->stub('inertia.' . $this->adapter['framework'] . '.stub');
+
+ /**
+ * @var \Blueprint\Models\Controller $controller
+ */
+ foreach ($tree->controllers() as $controller) {
+ foreach ($controller->methods() as $statements) {
+ foreach ($statements as $statement) {
+ if (!$statement instanceof InertiaStatement) {
+ continue;
+ }
+
+ $path = $this->getStatementPath($statement->view());
+
+ if ($this->filesystem->exists($path)) {
+ $this->output['skipped'][] = $path;
+ continue;
+ }
+
+ $this->create($path, $this->populateStub($stub, $statement));
+ }
+ }
+ }
+
+ return $this->output;
+ }
+
+ protected function getAdapter(): ?array
+ {
+ $packagePath = base_path('package.json');
+
+ if (!$this->filesystem->exists($packagePath)) {
+ return null;
+ }
+
+ $contents = $this->filesystem->get($packagePath);
+
+ if (preg_match('/@inertiajs\/(vue3|react|svelte)/i', $contents, $matches)) {
+ $adapterKey = strtolower($matches[1]);
+
+ return $this->adapters[$adapterKey] ?? null;
+ }
+
+ return null;
+ }
+
+ protected function getStatementPath(string $view): string
+ {
+ return 'resources/js/Pages/' . str_replace('.', '/', $view) . $this->adapter['extension'];
+ }
+
+ protected function populateStub(string $stub, InertiaStatement $inertiaStatement): string
+ {
+ $data = $inertiaStatement->data();
+ $props = $this->adapter['framework'] === 'vue' ? json_encode($data) : '{ ' . implode(', ', $data) . ' }';
+ $componentName = $this->adapter['framework'] === 'react' ? Str::afterLast($inertiaStatement->view(), '/') : null;
+
+ return str_replace([
+ '{{ componentName }}',
+ '{{ props }}',
+ '{{ view }}',
+ ], [
+ $componentName,
+ $props,
+ str_replace('/', ' ', $inertiaStatement->view()),
+ ], $stub);
+ }
+}
diff --git a/stubs/inertia.react.stub b/stubs/inertia.react.stub
new file mode 100644
index 00000000..e3da526b
--- /dev/null
+++ b/stubs/inertia.react.stub
@@ -0,0 +1,10 @@
+import { Head } from '@inertiajs/react'
+
+export default function {{ componentName }}({{ props }}) {
+ return (
+
+
+ {{ view }}
+
+ )
+}
diff --git a/stubs/inertia.svelte.stub b/stubs/inertia.svelte.stub
new file mode 100644
index 00000000..0b893089
--- /dev/null
+++ b/stubs/inertia.svelte.stub
@@ -0,0 +1,11 @@
+
+
+
+ {{ view }}
+
+
+
+
{{ view }}
+
diff --git a/stubs/inertia.vue.stub b/stubs/inertia.vue.stub
new file mode 100644
index 00000000..7bd12f92
--- /dev/null
+++ b/stubs/inertia.vue.stub
@@ -0,0 +1,10 @@
+
+
+
+
+ {{ view }}
+
diff --git a/tests/Feature/Generators/Statements/InertiaPageGeneratorTest.php b/tests/Feature/Generators/Statements/InertiaPageGeneratorTest.php
new file mode 100644
index 00000000..32eda257
--- /dev/null
+++ b/tests/Feature/Generators/Statements/InertiaPageGeneratorTest.php
@@ -0,0 +1,153 @@
+subject = new InertiaPageGenerator($this->files);
+
+ $this->blueprint = new Blueprint;
+ $this->blueprint->registerLexer(new \Blueprint\Lexers\ControllerLexer(new StatementLexer));
+ $this->blueprint->registerGenerator($this->subject);
+ }
+
+ #[Test]
+ public function output_writes_nothing_for_empty_tree(): void
+ {
+ $this->filesystem->shouldNotHaveReceived('put');
+
+ $this->assertEquals([], $this->subject->output(new Tree(['controllers' => []])));
+ }
+
+ #[Test]
+ public function output_writes_nothing_without_inertia_statements(): void
+ {
+ $this->filesystem->shouldNotHaveReceived('put');
+
+ $tokens = $this->blueprint->parse($this->fixture('drafts/controllers-only.yaml'));
+ $tree = $this->blueprint->analyze($tokens);
+
+ $this->assertEquals([], $this->subject->output($tree));
+ }
+
+ #[Test]
+ public function output_writes_nothing_when_package_json_is_missing(): void
+ {
+ $this->filesystem->expects('exists')
+ ->with(base_path('package.json'))
+ ->andReturnFalse();
+ $this->filesystem->shouldNotHaveReceived('put');
+
+ $tokens = $this->blueprint->parse($this->fixture('drafts/controllers-only.yaml'));
+ $tree = $this->blueprint->analyze($tokens);
+
+ $this->assertEquals([], $this->subject->output($tree));
+ }
+
+ #[Test]
+ public function output_writes_nothing_when_adapter_is_not_found(): void
+ {
+ $this->filesystem->expects('exists')
+ ->with(base_path('package.json'))
+ ->andReturnTrue();
+ $this->filesystem->expects('get')
+ ->with(base_path('package.json'))
+ ->andReturn('');
+ $this->filesystem->shouldNotHaveReceived('put');
+
+ $tokens = $this->blueprint->parse($this->fixture('drafts/controllers-only.yaml'));
+ $tree = $this->blueprint->analyze($tokens);
+
+ $this->assertEquals([], $this->subject->output($tree));
+ }
+
+ #[Test]
+ #[DataProvider('inertiaAdaptersDataProvider')]
+ public function output_writes_pages_for_inertia_statements($framework, $dependencies, $path, $extension): void
+ {
+ $this->filesystem->expects('exists')
+ ->with(base_path('package.json'))
+ ->andReturnTrue();
+ $this->filesystem->expects('get')
+ ->with(base_path('package.json'))
+ ->andReturn($dependencies);
+ $this->filesystem->expects('stub')
+ ->with("inertia.$framework.stub")
+ ->andReturn($this->stub("inertia.$framework.stub"));
+ $this->filesystem->expects('exists')
+ ->with($path)
+ ->andReturnFalse();
+ $this->filesystem->expects('put')
+ ->with($path, $this->fixture('inertia-pages/customer-show' . $extension));
+
+ $tokens = $this->blueprint->parse($this->fixture('drafts/inertia-render.yaml'));
+ $tree = $this->blueprint->analyze($tokens);
+ $output = $this->subject->output($tree);
+
+ $this->assertContains(
+ $path,
+ $output['created'],
+ );
+ }
+
+ #[Test]
+ #[DataProvider('inertiaAdaptersDataProvider')]
+ public function it_outputs_skipped_pages($framework, $dependencies, $path): void
+ {
+ $this->filesystem->expects('exists')
+ ->with(base_path('package.json'))
+ ->andReturnTrue();
+ $this->filesystem->expects('get')
+ ->with(base_path('package.json'))
+ ->andReturn($dependencies);
+ $this->filesystem->expects('stub')
+ ->with("inertia.$framework.stub")
+ ->andReturn($this->stub("inertia.$framework.stub"));
+ $this->filesystem->expects('exists')
+ ->with($path)
+ ->andReturnTrue();
+ $this->filesystem->expects('put')
+ ->never();
+
+ $tokens = $this->blueprint->parse($this->fixture('drafts/inertia-render.yaml'));
+ $tree = $this->blueprint->analyze($tokens);
+ $ouput = $this->subject->output($tree);
+
+ $this->assertEquals([
+ 'skipped' => [
+ $path,
+ ],
+ ], $ouput);
+ }
+
+ public static function inertiaAdaptersDataProvider(): array
+ {
+ return [
+ ['vue', '"@inertiajs/vue3": "^2.0.0"', 'resources/js/Pages/Customer/Show.vue', '.vue'],
+ ['react', '"@inertiajs/react": "^2.0.0"', 'resources/js/Pages/Customer/Show.jsx', '.jsx'],
+ ['svelte', '"@inertiajs/svelte": "^2.0.0"', 'resources/js/Pages/Customer/Show.svelte', '.svelte'],
+ ];
+ }
+}
diff --git a/tests/fixtures/inertia-pages/customer-show.jsx b/tests/fixtures/inertia-pages/customer-show.jsx
new file mode 100644
index 00000000..436d5a04
--- /dev/null
+++ b/tests/fixtures/inertia-pages/customer-show.jsx
@@ -0,0 +1,10 @@
+import { Head } from '@inertiajs/react'
+
+export default function Show({ customer, customers }) {
+ return (
+
+
+ Customer Show
+
+ )
+}
diff --git a/tests/fixtures/inertia-pages/customer-show.svelte b/tests/fixtures/inertia-pages/customer-show.svelte
new file mode 100644
index 00000000..9f4c20e0
--- /dev/null
+++ b/tests/fixtures/inertia-pages/customer-show.svelte
@@ -0,0 +1,11 @@
+
+
+
+ Customer Show
+
+
+
+
Customer Show
+
diff --git a/tests/fixtures/inertia-pages/customer-show.vue b/tests/fixtures/inertia-pages/customer-show.vue
new file mode 100644
index 00000000..191dccb1
--- /dev/null
+++ b/tests/fixtures/inertia-pages/customer-show.vue
@@ -0,0 +1,10 @@
+
+
+
+
+ Customer Show
+