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 @@ + + + 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 @@ + + +