Skip to content

Commit edd70e1

Browse files
committed
Add Wayfinder package support and guidelines
Signed-off-by: Pushpak Chhajed <[email protected]>
1 parent 35ad5a9 commit edd70e1

File tree

3 files changed

+159
-0
lines changed

3 files changed

+159
-0
lines changed

.ai/wayfinder/core.blade.php

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
@php
2+
/** @var \Laravel\Boost\Install\GuidelineAssist $assist */
3+
@endphp
4+
## Laravel Wayfinder
5+
6+
Generates fully-typed TypeScript functions for Laravel controllers and routes. Import endpoints as functions—never hardcode URLs. Provides type safety and automatic synchronization between backend routes and frontend code.
7+
8+
### Setup
9+
10+
- Run `php artisan wayfinder:generate`, install `npm i -D @laravel/vite-plugin-wayfinder`, add `wayfinder()` to `vite.config.js`.
11+
- Add `wayfinder/`, `actions/`, `routes/` to `.gitignore`.
12+
13+
### Development Guidelines
14+
15+
- **Always use `search-docs`** before implementing any Wayfinder features
16+
- Prefer named imports for tree-shaking (e.g., `import { show } from '@/actions/...'`)
17+
- Avoid default controller imports (prevents tree-shaking)
18+
- Run `wayfinder:generate` after route changes if Vite plugin isn't installed
19+
20+
**Features:**
21+
22+
- **Route Objects**: Functions return `{ url, method }` objects — `show(1)` → `{ url: "/posts/1", method: "get" }`
23+
- **URL Extraction**: Use `.url()` to get URL string — `show.url(1)` → `"/posts/1"`
24+
- **HTTP Methods**: Call `.get()`, `.post()`, `.patch()`, `.put()`, `.delete()` for specific methods — `show.head(1)` → `{ url: "/posts/1", method: "head" }`
25+
- **Parameter Binding**: Detects route keys (e.g., `{post:slug}`) and accepts matching object properties — `show("my-post")` or `show({ slug: "my-post" })`
26+
- **Invokable Controllers**: Import and invoke directly as functions — `import StorePost from '@/actions/.../StorePostController'; StorePost()`
27+
- **Named Routes**: Import from `@/routes/` for non-controller routes — `import { show } from '@/routes/post'; show(1)` for route name `post.show`
28+
- **Form Support**: Use `.form()` with `--with-form` flag for HTML form attributes — `<form {...store.form()}>` → `action="/posts" method="post"`
29+
- **Query Parameters**: Pass `{ query: {...} }` in options to append params — `show(1, { query: { page: 1 } })` → `"/posts/1?page=1"`
30+
- **Query Merging**: Use `mergeQuery` to merge with `window.location.search`, set values to `null` to remove — `show(1, { mergeQuery: { page: 2, sort: null } })`
31+
32+
**Basic Usage:**
33+
@verbatim
34+
<code-snippet lang="typescript" name="Wayfinder Basic Usage">
35+
// Import controller methods (tree-shakable)
36+
import { show, store, update } from '@/actions/App/Http/Controllers/PostController'
37+
38+
// Get route object with URL and method
39+
show(1) // { url: "/posts/1", method: "get" }
40+
41+
// Get just the URL
42+
show.url(1) // "/posts/1"
43+
44+
// Use specific HTTP methods
45+
show.get(1) // { url: "/posts/1", method: "get" }
46+
show.head(1) // { url: "/posts/1", method: "head" }
47+
48+
// Import named routes
49+
import { show as postShow } from '@/routes/post' // For route name 'post.show'
50+
postShow(1) // { url: "/posts/1", method: "get" }
51+
</code-snippet>
52+
@endverbatim
53+
54+
@if($assist->roster->uses(\Laravel\Roster\Enums\Packages::INERTIA_LARAVEL) || $assist->roster->uses(\Laravel\Roster\Enums\Packages::INERTIA_REACT) || $assist->roster->uses(\Laravel\Roster\Enums\Packages::INERTIA_VUE) || $assist->roster->uses(\Laravel\Roster\Enums\Packages::INERTIA_SVELTE))
55+
### Wayfinder + Inertia
56+
@if($assist->inertia()->hasFormComponent())
57+
If your application uses the `<Form>` component, you can use Wayfinder to generate form attributes automatically.
58+
@if($assist->roster->uses(\Laravel\Roster\Enums\Packages::INERTIA_REACT))
59+
@boostsnippet("Wayfinder Form Component (React)", "typescript")
60+
<Form {...store.form()}><input name="title" /></Form>
61+
@endboostsnippet
62+
@endif
63+
@if($assist->roster->uses(\Laravel\Roster\Enums\Packages::INERTIA_VUE))
64+
@boostsnippet("Wayfinder Form Component (Vue)", "vue")
65+
<Form v-bind="store.form()"><input name="title" /></Form>
66+
@endboostsnippet
67+
@endif
68+
@endif
69+
@endif
70+

all.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public function packages(): \Laravel\Roster\PackageCollection
5454
'folio' => \Laravel\Roster\Enums\Packages::FOLIO,
5555
'pennant' => \Laravel\Roster\Enums\Packages::PENNANT,
5656
'tailwindcss' => \Laravel\Roster\Enums\Packages::TAILWINDCSS,
57+
'wayfinder' => \Laravel\Roster\Enums\Packages::WAYFINDER,
5758
];
5859

5960
if (isset($enumMapping[$packageName])) {

tests/Feature/Install/GuidelineComposerTest.php

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,3 +426,91 @@
426426
->toContain('Run `npm install` to install dependencies')
427427
->toContain('Package manager: npm');
428428
});
429+
430+
test('includes wayfinder guidelines with inertia integration when both packages are present', function (): void {
431+
$packages = new PackageCollection([
432+
new Package(Packages::LARAVEL, 'laravel/framework', '11.0.0'),
433+
new Package(Packages::WAYFINDER, 'laravel/wayfinder', '1.0.0'),
434+
new Package(Packages::INERTIA_REACT, 'inertiajs/inertia-react', '2.1.2'),
435+
new Package(Packages::INERTIA_LARAVEL, 'inertiajs/inertia-laravel', '2.1.2'),
436+
]);
437+
438+
$this->roster->shouldReceive('packages')->andReturn($packages);
439+
440+
$this->roster->shouldReceive('uses')->with(Packages::INERTIA_LARAVEL)->andReturn(true);
441+
$this->roster->shouldReceive('uses')->with(Packages::INERTIA_REACT)->andReturn(true);
442+
$this->roster->shouldReceive('uses')->with(Packages::INERTIA_VUE)->andReturn(false);
443+
$this->roster->shouldReceive('uses')->with(Packages::INERTIA_SVELTE)->andReturn(false);
444+
445+
$this->roster->shouldReceive('usesVersion')
446+
->with(Packages::INERTIA_LARAVEL, '2.1.0', '>=')
447+
->andReturn(true);
448+
$this->roster->shouldReceive('usesVersion')
449+
->with(Packages::INERTIA_REACT, '2.1.0', '>=')
450+
->andReturn(true);
451+
$this->roster->shouldReceive('usesVersion')
452+
->with(Packages::INERTIA_VUE, '2.1.0', '>=')
453+
->andReturn(false);
454+
$this->roster->shouldReceive('usesVersion')
455+
->with(Packages::INERTIA_SVELTE, '2.1.0', '>=')
456+
->andReturn(false);
457+
458+
$this->roster->shouldReceive('usesVersion')
459+
->with(Packages::INERTIA_LARAVEL, '2.1.2', '>=')
460+
->andReturn(true);
461+
$this->roster->shouldReceive('usesVersion')
462+
->with(Packages::INERTIA_REACT, '2.1.2', '>=')
463+
->andReturn(true);
464+
$this->roster->shouldReceive('usesVersion')
465+
->with(Packages::INERTIA_VUE, '2.1.2', '>=')
466+
->andReturn(false);
467+
$this->roster->shouldReceive('usesVersion')
468+
->with(Packages::INERTIA_SVELTE, '2.1.2', '>=')
469+
->andReturn(false);
470+
471+
$guidelines = $this->composer->compose();
472+
473+
expect($guidelines)
474+
->toContain('=== wayfinder/core rules ===')
475+
->toContain('Wayfinder + Inertia')
476+
->toContain('Wayfinder Form Component (React)')
477+
->toContain('<Form {...store.form()}>')
478+
->toContain('## Laravel Wayfinder');
479+
});
480+
481+
test('includes wayfinder guidelines without inertia integration when inertia is not present', function (): void {
482+
$packages = new PackageCollection([
483+
new Package(Packages::LARAVEL, 'laravel/framework', '11.0.0'),
484+
new Package(Packages::WAYFINDER, 'laravel/wayfinder', '1.0.0'),
485+
]);
486+
487+
$this->roster->shouldReceive('packages')->andReturn($packages);
488+
489+
$this->roster->shouldReceive('uses')->with(Packages::INERTIA_LARAVEL)->andReturn(false);
490+
$this->roster->shouldReceive('uses')->with(Packages::INERTIA_REACT)->andReturn(false);
491+
$this->roster->shouldReceive('uses')->with(Packages::INERTIA_VUE)->andReturn(false);
492+
$this->roster->shouldReceive('uses')->with(Packages::INERTIA_SVELTE)->andReturn(false);
493+
494+
$this->roster->shouldReceive('usesVersion')
495+
->with(Packages::INERTIA_LARAVEL, Mockery::any(), '>=')
496+
->andReturn(false);
497+
$this->roster->shouldReceive('usesVersion')
498+
->with(Packages::INERTIA_REACT, Mockery::any(), '>=')
499+
->andReturn(false);
500+
$this->roster->shouldReceive('usesVersion')
501+
->with(Packages::INERTIA_VUE, Mockery::any(), '>=')
502+
->andReturn(false);
503+
$this->roster->shouldReceive('usesVersion')
504+
->with(Packages::INERTIA_SVELTE, Mockery::any(), '>=')
505+
->andReturn(false);
506+
507+
$guidelines = $this->composer->compose();
508+
509+
expect($guidelines)
510+
->toContain('=== wayfinder/core rules ===')
511+
->toContain('## Laravel Wayfinder')
512+
->toContain('import { show } from \'@/actions/')
513+
->not->toContain('Wayfinder + Inertia')
514+
->not->toContain('Wayfinder Form Component');
515+
});
516+

0 commit comments

Comments
 (0)