start-vibing-stacks 2.8.0 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +2 -1
- package/dist/setup.js +10 -2
- package/dist/types.d.ts +8 -0
- package/package.json +1 -1
- package/stacks/frontend/react-api/skills/axios-laravel-api/SKILL.md +466 -0
- package/stacks/frontend/react-api/skills/react-api-standards/SKILL.md +509 -0
- package/stacks/frontend/react-inertia/skills/inertia-react/SKILL.md +11 -2
- package/stacks/frontend/react-inertia/skills/react-standards/SKILL.md +9 -2
- package/stacks/php/skills/api-design/SKILL.md +281 -47
- package/stacks/php/skills/api-security/SKILL.md +128 -49
- package/stacks/php/skills/inertia-react/SKILL.md +16 -3
- package/stacks/php/skills/laravel-api-architecture/SKILL.md +650 -0
- package/stacks/php/skills/laravel-inertia-i18n/SKILL.md +11 -3
- package/stacks/php/skills/laravel-patterns/SKILL.md +123 -61
- package/stacks/php/stack.json +19 -6
- package/templates/CLAUDE-php.md +202 -101
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: laravel-patterns
|
|
3
|
-
version:
|
|
3
|
+
version: 2.0.0
|
|
4
|
+
description: Laravel 12 model/controller/service/job patterns — UUIDs, Loggable
|
|
5
|
+
trait, mass-assignment, JSON casts, thin Service-driven controllers, idempotent
|
|
6
|
+
jobs, batch processing. API-first by default (controllers return Resources, NOT
|
|
7
|
+
`Inertia::render()`). Use for any Laravel domain code. Pairs with
|
|
8
|
+
`laravel-api-architecture` (the pipeline) and `laravel-octane` (worker safety).
|
|
4
9
|
---
|
|
5
10
|
|
|
6
|
-
# Laravel Patterns & Standards
|
|
11
|
+
# Laravel 12 Patterns & Standards
|
|
12
|
+
|
|
13
|
+
> **Default architecture is API-first.** Controllers return `JsonResponse` /
|
|
14
|
+
> `JsonResource` consumed by a React+Axios SPA. The full pipeline
|
|
15
|
+
> (Route → Controller → FormRequest → Policy → Service → Resource → JSON) lives
|
|
16
|
+
> in `laravel-api-architecture`. This skill covers the building blocks
|
|
17
|
+
> (Models, Services, Jobs, Caching) that compose into that pipeline.
|
|
18
|
+
>
|
|
19
|
+
> **Do NOT add new `Inertia::render()` controllers.** Inertia is supported only
|
|
20
|
+
> for legacy projects (see `inertia-react` skill, marked LEGACY).
|
|
7
21
|
|
|
8
22
|
## Model Standards
|
|
9
23
|
|
|
@@ -66,75 +80,65 @@ $user = User::create($request->validated());
|
|
|
66
80
|
|
|
67
81
|
## Controller Standards
|
|
68
82
|
|
|
69
|
-
### Thin Controllers
|
|
70
|
-
|
|
71
|
-
Controllers should ONLY handle HTTP concerns. Delegate to Services.
|
|
83
|
+
### Thin API Controllers (default)
|
|
72
84
|
|
|
73
|
-
|
|
85
|
+
Controllers ONLY handle HTTP concerns. Delegate to Services. Validation goes
|
|
86
|
+
through FormRequest, authorization through Policy. Full end-to-end example
|
|
87
|
+
lives in `laravel-api-architecture` — this section is the contract.
|
|
74
88
|
|
|
75
89
|
```php
|
|
76
|
-
|
|
77
|
-
|
|
90
|
+
namespace App\Http\Controllers\Api;
|
|
91
|
+
|
|
92
|
+
use App\Http\Controllers\Controller;
|
|
93
|
+
use App\Http\Requests\Order\StoreOrderRequest;
|
|
94
|
+
use App\Http\Resources\OrderResource;
|
|
95
|
+
use App\Models\Order;
|
|
96
|
+
use App\Services\OrderService;
|
|
97
|
+
use Illuminate\Http\JsonResponse;
|
|
98
|
+
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
|
78
99
|
|
|
79
100
|
class OrderController extends Controller
|
|
80
101
|
{
|
|
81
102
|
public function __construct(
|
|
82
|
-
private readonly OrderService $
|
|
103
|
+
private readonly OrderService $orders,
|
|
83
104
|
) {}
|
|
84
105
|
|
|
85
|
-
public function index(
|
|
106
|
+
public function index(IndexOrderRequest $request): AnonymousResourceCollection
|
|
86
107
|
{
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
$this->orderService->listForUser($request->user())
|
|
90
|
-
),
|
|
91
|
-
]);
|
|
108
|
+
$paginated = $this->orders->listFor($request->user(), $request->validated());
|
|
109
|
+
return OrderResource::collection($paginated);
|
|
92
110
|
}
|
|
93
111
|
|
|
94
|
-
public function store(StoreOrderRequest $request):
|
|
112
|
+
public function store(StoreOrderRequest $request): JsonResponse
|
|
95
113
|
{
|
|
96
|
-
$this->
|
|
97
|
-
|
|
98
|
-
return redirect()
|
|
99
|
-
->route('orders.index')
|
|
100
|
-
->with('success', __('orders.created'));
|
|
114
|
+
$order = $this->orders->create($request->user(), $request->validated());
|
|
115
|
+
return OrderResource::make($order)->response()->setStatusCode(201);
|
|
101
116
|
}
|
|
102
|
-
}
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
#### API Controllers (JSON responses)
|
|
106
|
-
|
|
107
|
-
```php
|
|
108
|
-
class OrderApiController extends Controller
|
|
109
|
-
{
|
|
110
|
-
public function __construct(
|
|
111
|
-
private readonly OrderService $orderService,
|
|
112
|
-
) {}
|
|
113
117
|
|
|
114
|
-
public function
|
|
118
|
+
public function show(Order $order): OrderResource
|
|
115
119
|
{
|
|
116
|
-
$
|
|
117
|
-
|
|
118
|
-
return OrderResource::make($order)
|
|
119
|
-
->response()
|
|
120
|
-
->setStatusCode(201);
|
|
120
|
+
$this->authorize('view', $order);
|
|
121
|
+
return OrderResource::make($order);
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
public function resetAttempts(Order $order):
|
|
124
|
+
public function resetAttempts(Order $order): OrderResource
|
|
124
125
|
{
|
|
125
|
-
$this->
|
|
126
|
-
|
|
127
|
-
return
|
|
126
|
+
$this->authorize('update', $order);
|
|
127
|
+
$this->orders->resetAttempts($order);
|
|
128
|
+
return OrderResource::make($order->fresh());
|
|
128
129
|
}
|
|
129
130
|
}
|
|
130
131
|
```
|
|
131
132
|
|
|
132
133
|
**Rules:**
|
|
133
|
-
- No business logic in controllers
|
|
134
|
-
-
|
|
135
|
-
|
|
136
|
-
-
|
|
137
|
-
|
|
134
|
+
- No business logic in controllers — delegate to Services.
|
|
135
|
+
- One Service per controller method (composition: a controller may use multiple
|
|
136
|
+
services overall, but each method calls one entry point).
|
|
137
|
+
- Use FormRequest for validation; use `$this->authorize()` only on routes that
|
|
138
|
+
rely on RouteModelBinding without a FormRequest.
|
|
139
|
+
- API: always wrap responses in `JsonResource` / `JsonResource::collection`.
|
|
140
|
+
- DI in constructors only — never `app()` / `resolve()` / `new`.
|
|
141
|
+
- Action endpoints get `POST /resource/{id}/action` (NOT generic `PATCH`).
|
|
138
142
|
|
|
139
143
|
### Form Request Validation
|
|
140
144
|
|
|
@@ -143,20 +147,63 @@ class StoreOrderRequest extends FormRequest
|
|
|
143
147
|
{
|
|
144
148
|
public function authorize(): bool
|
|
145
149
|
{
|
|
146
|
-
return $this->user()->can('create', Order::class);
|
|
150
|
+
return $this->user()->can('create', Order::class); // routes to Policy
|
|
147
151
|
}
|
|
148
152
|
|
|
149
153
|
public function rules(): array
|
|
150
154
|
{
|
|
151
155
|
return [
|
|
152
156
|
'product_id' => ['required', 'uuid', 'exists:products,id'],
|
|
153
|
-
'quantity'
|
|
154
|
-
'notes'
|
|
157
|
+
'quantity' => ['required', 'integer', 'min:1', 'max:100'],
|
|
158
|
+
'notes' => ['nullable', 'string', 'max:500'],
|
|
155
159
|
];
|
|
156
160
|
}
|
|
161
|
+
|
|
162
|
+
protected function prepareForValidation(): void
|
|
163
|
+
{
|
|
164
|
+
$this->merge(['notes' => $this->notes ? trim($this->notes) : null]);
|
|
165
|
+
}
|
|
157
166
|
}
|
|
158
167
|
```
|
|
159
168
|
|
|
169
|
+
**Rules:**
|
|
170
|
+
- `authorize()` MUST call a Policy via `$user->can(...)`. Returning `true`
|
|
171
|
+
without policy is allowed ONLY for `index` actions where Service-level
|
|
172
|
+
user scoping is the gate.
|
|
173
|
+
- `rules()` exhaustive: type, length, allowed enum values, FK existence
|
|
174
|
+
(`exists:table,id`), file mime/size where applicable.
|
|
175
|
+
- `prepareForValidation()` for input normalization (trim, lowercase email).
|
|
176
|
+
- One FormRequest per Controller action: `StoreOrderRequest`,
|
|
177
|
+
`UpdateOrderRequest`, `IndexOrderRequest`, etc. — group under
|
|
178
|
+
`app/Http/Requests/Order/`.
|
|
179
|
+
|
|
180
|
+
### Policy (mandatory for protected resources)
|
|
181
|
+
|
|
182
|
+
```php
|
|
183
|
+
namespace App\Policies;
|
|
184
|
+
|
|
185
|
+
use App\Models\Order;
|
|
186
|
+
use App\Models\User;
|
|
187
|
+
|
|
188
|
+
class OrderPolicy
|
|
189
|
+
{
|
|
190
|
+
// Super-admin bypass — return null (NOT false) to fall through
|
|
191
|
+
public function before(User $user, string $ability): ?bool
|
|
192
|
+
{
|
|
193
|
+
return $user->isSuperAdmin() ? true : null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
public function viewAny(User $user): bool { return true; } // scope in Service
|
|
197
|
+
public function view(User $user, Order $o): bool { return $user->isAdmin() || $o->user_id === $user->id; }
|
|
198
|
+
public function create(User $user): bool { return $user->isAdmin() || $user->isUser(); }
|
|
199
|
+
public function update(User $user, Order $o): bool { return $user->isAdmin() || $o->user_id === $user->id; }
|
|
200
|
+
public function delete(User $user, Order $o): bool { return $user->isAdmin(); }
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Rule pattern:** `superadmin` → bypass via `before()`; `admin` → broad access;
|
|
205
|
+
`user` → only own resources (`$model->user_id === $user->id`).
|
|
206
|
+
|
|
160
207
|
## Service Architecture
|
|
161
208
|
|
|
162
209
|
### Service Layer Pattern
|
|
@@ -401,19 +448,34 @@ return [
|
|
|
401
448
|
- Always add to BOTH `lang/en/*.php` and `lang/pt/*.php`
|
|
402
449
|
- Error strings in `lang/*/errors.php`
|
|
403
450
|
|
|
404
|
-
### Translations
|
|
451
|
+
### Translations for API-First Frontend (default)
|
|
405
452
|
|
|
406
|
-
|
|
453
|
+
The React SPA owns its own i18n bundle (e.g. `react-intl`, `i18next`). Backend
|
|
454
|
+
exposes translations via a single API endpoint that the frontend caches:
|
|
407
455
|
|
|
408
456
|
```php
|
|
409
|
-
//
|
|
410
|
-
|
|
411
|
-
'
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
457
|
+
// routes/api.php
|
|
458
|
+
Route::get('/i18n/{locale}', [I18nController::class, 'show'])
|
|
459
|
+
->whereIn('locale', ['en', 'pt']);
|
|
460
|
+
|
|
461
|
+
// app/Http/Controllers/Api/I18nController.php
|
|
462
|
+
public function show(string $locale): JsonResponse
|
|
463
|
+
{
|
|
464
|
+
return response()
|
|
465
|
+
->json(Cache::rememberForever("i18n:{$locale}", function () use ($locale) {
|
|
466
|
+
$bag = [];
|
|
467
|
+
foreach (File::files(lang_path($locale)) as $file) {
|
|
468
|
+
$bag[$file->getFilenameWithoutExtension()] = require $file->getPathname();
|
|
469
|
+
}
|
|
470
|
+
return $bag;
|
|
471
|
+
}))
|
|
472
|
+
->setMaxAge(3600);
|
|
473
|
+
}
|
|
417
474
|
```
|
|
418
475
|
|
|
419
|
-
|
|
476
|
+
**Rules:**
|
|
477
|
+
- Centralize ALL user-facing strings in `lang/{locale}/*.php` (single source of truth).
|
|
478
|
+
- Frontend pulls them once on boot, caches in memory + `localStorage`.
|
|
479
|
+
- Cache invalidation: bump a version number or clear cache on deploy.
|
|
480
|
+
- For projects already using Inertia, see the **legacy** `inertia-react` and
|
|
481
|
+
`laravel-inertia-i18n` skills.
|
package/stacks/php/stack.json
CHANGED
|
@@ -24,22 +24,26 @@
|
|
|
24
24
|
"frameworks": [
|
|
25
25
|
{
|
|
26
26
|
"id": "laravel-octane",
|
|
27
|
-
"name": "Laravel 12 + Octane (RoadRunner) +
|
|
27
|
+
"name": "Laravel 12 + Octane (RoadRunner) + Sanctum SPA API",
|
|
28
28
|
"icon": "🚀",
|
|
29
29
|
"detectFiles": ["artisan", "rr.yaml"],
|
|
30
30
|
"default": true,
|
|
31
31
|
"skills": [
|
|
32
32
|
"laravel-patterns",
|
|
33
|
-
"laravel-octane"
|
|
33
|
+
"laravel-octane",
|
|
34
|
+
"laravel-api-architecture",
|
|
35
|
+
"api-security"
|
|
34
36
|
]
|
|
35
37
|
},
|
|
36
38
|
{
|
|
37
39
|
"id": "laravel",
|
|
38
|
-
"name": "Laravel 12 (standard)",
|
|
40
|
+
"name": "Laravel 12 (standard) + Sanctum SPA API",
|
|
39
41
|
"icon": "🏗️",
|
|
40
42
|
"detectFiles": ["artisan", "bootstrap/app.php"],
|
|
41
43
|
"skills": [
|
|
42
|
-
"laravel-patterns"
|
|
44
|
+
"laravel-patterns",
|
|
45
|
+
"laravel-api-architecture",
|
|
46
|
+
"api-security"
|
|
43
47
|
]
|
|
44
48
|
}
|
|
45
49
|
],
|
|
@@ -50,10 +54,17 @@
|
|
|
50
54
|
],
|
|
51
55
|
"frontendOptions": [
|
|
52
56
|
{
|
|
53
|
-
"id": "react-
|
|
54
|
-
"name": "ReactJS 19 +
|
|
57
|
+
"id": "react-api",
|
|
58
|
+
"name": "ReactJS 19 + Vite + Axios (Sanctum SPA API)",
|
|
55
59
|
"icon": "⚛️",
|
|
56
60
|
"default": true,
|
|
61
|
+
"frameworks": ["laravel", "laravel-octane"],
|
|
62
|
+
"baseSkillsDir": "react"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"id": "react-inertia",
|
|
66
|
+
"name": "ReactJS 19 + Inertia.js (LEGACY)",
|
|
67
|
+
"icon": "🪦",
|
|
57
68
|
"frameworks": ["laravel", "laravel-octane"]
|
|
58
69
|
},
|
|
59
70
|
{
|
|
@@ -84,6 +95,8 @@
|
|
|
84
95
|
"composer-workflow",
|
|
85
96
|
"security-scan-php",
|
|
86
97
|
"api-design",
|
|
98
|
+
"api-security",
|
|
99
|
+
"laravel-api-architecture",
|
|
87
100
|
"openapi-design"
|
|
88
101
|
],
|
|
89
102
|
"requirements": [
|