@famgia/omnify-laravel 0.0.113 → 0.0.115

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@famgia/omnify-laravel",
3
- "version": "0.0.113",
3
+ "version": "0.0.115",
4
4
  "description": "Laravel migration and TypeScript type generator for omnify-schema",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -25,9 +25,9 @@
25
25
  "README.md"
26
26
  ],
27
27
  "dependencies": {
28
- "@famgia/omnify-types": "0.0.102",
29
- "@famgia/omnify-core": "0.0.104",
30
- "@famgia/omnify-atlas": "0.0.98"
28
+ "@famgia/omnify-types": "0.0.104",
29
+ "@famgia/omnify-atlas": "0.0.100",
30
+ "@famgia/omnify-core": "0.0.106"
31
31
  },
32
32
  "scripts": {
33
33
  "build": "tsup",
@@ -32,7 +32,7 @@ Route::get('/users', [UserController::class, 'index']);
32
32
  | `api.php` | `/api` | `/users` | `/users` |
33
33
  | `web.php` | (none) | `/dashboard` | `/dashboard` |
34
34
 
35
- **Full guide:** `.claude/guides/laravel/openapi.md`
35
+ **Full guide:** `.claude/omnify/guides/laravel/openapi.md`
36
36
 
37
37
  ---
38
38
 
@@ -7,7 +7,7 @@ alwaysApply: false
7
7
  # Testing Rules (PEST)
8
8
 
9
9
  > **Agent:** Act as **@tester** agent
10
- > - Read `.claude/guides/laravel/testing.md` for full guide
10
+ > - Read `.claude/omnify/guides/laravel/testing.md` for full guide
11
11
 
12
12
  ## How to Run Tests
13
13
 
@@ -6,7 +6,8 @@ alwaysApply: false
6
6
 
7
7
  # Laravel Rules
8
8
 
9
- > **Documentation:** `.claude/guides/laravel/`
9
+ > **Documentation:** `.claude/omnify/guides/laravel/`
10
+ > **Team Migration Guide:** `.claude/omnify/guides/laravel/migrations-team.md`
10
11
  > **Specific Rules:** See also `laravel-controller.mdc`, `laravel-resource.mdc`, `laravel-request.mdc`, `laravel-testing.mdc`, `migrations-workflow.mdc`, `omnify-migrations.mdc`
11
12
 
12
13
  ## ⛔ CRITICAL: DO NOT EDIT
@@ -76,6 +77,32 @@ See `migrations-workflow.mdc` for complete guide.
76
77
  | Reusable action | Action class |
77
78
  | Async task | Job |
78
79
 
80
+ ## Authentication Models
81
+
82
+ When `authenticatable: true` is set in schema, model extends `Authenticatable`:
83
+
84
+ ```yaml
85
+ # schemas/auth/User.yaml
86
+ options:
87
+ authenticatable: true
88
+ authenticatableLoginIdField: email
89
+ authenticatablePasswordField: password
90
+ authenticatableGuardName: web # Optional: for multi-guard
91
+ ```
92
+
93
+ **Required `config/auth.php` for custom guards:**
94
+
95
+ ```php
96
+ 'guards' => [
97
+ 'admin' => ['driver' => 'session', 'provider' => 'admins'],
98
+ ],
99
+ 'providers' => [
100
+ 'admins' => ['driver' => 'eloquent', 'model' => App\Models\Admin::class],
101
+ ],
102
+ ```
103
+
104
+ **See:** `.claude/omnify/guides/omnify/schema-guide.md` → "Authenticatable Option - Detailed Guide"
105
+
79
106
  ## Pre-Edit Checklist
80
107
 
81
108
  **BEFORE editing any file, MUST:**
@@ -108,4 +135,4 @@ public function store(): JsonResponse
108
135
  | "Improve" unrelated code | Change only what's needed |
109
136
  | Build for future "just in case" | YAGNI - build when needed |
110
137
 
111
- **Full examples:** `.claude/guides/laravel/architecture.md`
138
+ **Full examples:** `.claude/omnify/guides/laravel/architecture.md`
@@ -0,0 +1,588 @@
1
+ # Laravel Authentication with Omnify
2
+
3
+ > Complete guide for implementing authentication using Omnify-generated models.
4
+
5
+ ## Overview
6
+
7
+ When you set `authenticatable: true` in your schema, Omnify generates a Laravel model that:
8
+
9
+ 1. Extends `Illuminate\Foundation\Auth\User` (alias `Authenticatable`)
10
+ 2. Includes the `Notifiable` trait
11
+ 3. Auto-hides password and remember_token fields
12
+ 4. Works with Laravel's built-in auth system
13
+
14
+ ## Schema Configuration
15
+
16
+ ### Basic User Schema
17
+
18
+ ```yaml
19
+ name: User
20
+ displayName:
21
+ ja: ユーザー
22
+ en: User
23
+ options:
24
+ timestamps: true
25
+ softDelete: true
26
+ authenticatable: true
27
+ authenticatableLoginIdField: email # Field for login
28
+ authenticatablePasswordField: password # Field for password
29
+ # authenticatableGuardName: web # Optional: guard name
30
+
31
+ properties:
32
+ email:
33
+ type: Email
34
+ unique: true
35
+ password:
36
+ type: Password
37
+ name:
38
+ type: String
39
+ ```
40
+
41
+ ### Multi-Guard Setup (Admin + User)
42
+
43
+ For separate admin and user authentication:
44
+
45
+ **User Schema:**
46
+ ```yaml
47
+ name: User
48
+ options:
49
+ authenticatable: true
50
+ authenticatableGuardName: web
51
+
52
+ properties:
53
+ email:
54
+ type: Email
55
+ unique: true
56
+ password:
57
+ type: Password
58
+ name:
59
+ type: String
60
+ ```
61
+
62
+ **Admin Schema:**
63
+ ```yaml
64
+ name: Admin
65
+ options:
66
+ authenticatable: true
67
+ authenticatableGuardName: admin # Different guard
68
+
69
+ properties:
70
+ email:
71
+ type: Email
72
+ unique: true
73
+ password:
74
+ type: Password
75
+ name:
76
+ type: String
77
+ role:
78
+ type: EnumRef
79
+ enum: AdminRole
80
+ ```
81
+
82
+ ## Laravel Configuration
83
+
84
+ ### config/auth.php
85
+
86
+ ```php
87
+ <?php
88
+
89
+ return [
90
+ 'defaults' => [
91
+ 'guard' => 'web',
92
+ 'passwords' => 'users',
93
+ ],
94
+
95
+ 'guards' => [
96
+ 'web' => [
97
+ 'driver' => 'session',
98
+ 'provider' => 'users',
99
+ ],
100
+ 'admin' => [ // For Admin model
101
+ 'driver' => 'session',
102
+ 'provider' => 'admins',
103
+ ],
104
+ 'api' => [ // For API authentication
105
+ 'driver' => 'sanctum',
106
+ 'provider' => 'users',
107
+ ],
108
+ ],
109
+
110
+ 'providers' => [
111
+ 'users' => [
112
+ 'driver' => 'eloquent',
113
+ 'model' => App\Models\User::class,
114
+ ],
115
+ 'admins' => [ // For Admin model
116
+ 'driver' => 'eloquent',
117
+ 'model' => App\Models\Admin::class,
118
+ ],
119
+ ],
120
+
121
+ 'passwords' => [
122
+ 'users' => [
123
+ 'provider' => 'users',
124
+ 'table' => 'password_reset_tokens',
125
+ 'expire' => 60,
126
+ 'throttle' => 60,
127
+ ],
128
+ 'admins' => [ // For Admin password reset
129
+ 'provider' => 'admins',
130
+ 'table' => 'password_reset_tokens',
131
+ 'expire' => 60,
132
+ 'throttle' => 60,
133
+ ],
134
+ ],
135
+
136
+ 'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
137
+ ];
138
+ ```
139
+
140
+ ## Generated Model Structure
141
+
142
+ ### Base Model (Auto-generated, DO NOT EDIT)
143
+
144
+ ```php
145
+ // app/Models/Base/UserBaseModel.php
146
+ <?php
147
+
148
+ namespace App\Models\Base;
149
+
150
+ use Illuminate\Foundation\Auth\User as Authenticatable;
151
+ use Illuminate\Notifications\Notifiable;
152
+
153
+ /**
154
+ * DO NOT EDIT - This file is auto-generated by Omnify.
155
+ * Any changes will be overwritten on next generation.
156
+ */
157
+ class UserBaseModel extends Authenticatable
158
+ {
159
+ use Notifiable;
160
+ use HasLocalizedDisplayName;
161
+
162
+ protected $table = 'users';
163
+ protected $primaryKey = 'id';
164
+ public $timestamps = true;
165
+
166
+ protected $fillable = [
167
+ 'email',
168
+ 'password',
169
+ 'name',
170
+ ];
171
+
172
+ protected $hidden = [
173
+ 'password',
174
+ 'remember_token',
175
+ ];
176
+
177
+ protected function casts(): array
178
+ {
179
+ return [
180
+ 'email_verified_at' => 'datetime',
181
+ 'password' => 'hashed',
182
+ ];
183
+ }
184
+ }
185
+ ```
186
+
187
+ ### Your Model (Customizable)
188
+
189
+ ```php
190
+ // app/Models/User.php
191
+ <?php
192
+
193
+ namespace App\Models;
194
+
195
+ use App\Models\Base\UserBaseModel;
196
+
197
+ class User extends UserBaseModel
198
+ {
199
+ // Add your custom methods here
200
+
201
+ public function posts(): HasMany
202
+ {
203
+ return $this->hasMany(Post::class);
204
+ }
205
+
206
+ public function profile(): HasOne
207
+ {
208
+ return $this->hasOne(UserProfile::class);
209
+ }
210
+
211
+ // Custom scopes
212
+ public function scopeActive($query)
213
+ {
214
+ return $query->where('status', 'active');
215
+ }
216
+
217
+ // Custom accessors
218
+ public function getFullNameAttribute(): string
219
+ {
220
+ return $this->name;
221
+ }
222
+ }
223
+ ```
224
+
225
+ ## Authentication Controllers
226
+
227
+ ### Login Controller
228
+
229
+ ```php
230
+ <?php
231
+
232
+ namespace App\Http\Controllers\Auth;
233
+
234
+ use App\Http\Controllers\Controller;
235
+ use App\Http\Requests\Auth\LoginRequest;
236
+ use Illuminate\Http\JsonResponse;
237
+ use Illuminate\Support\Facades\Auth;
238
+
239
+ class LoginController extends Controller
240
+ {
241
+ public function store(LoginRequest $request): JsonResponse
242
+ {
243
+ $request->authenticate();
244
+ $request->session()->regenerate();
245
+
246
+ return response()->json([
247
+ 'user' => Auth::user(),
248
+ 'message' => 'ログインしました',
249
+ ]);
250
+ }
251
+
252
+ public function destroy(): JsonResponse
253
+ {
254
+ Auth::guard('web')->logout();
255
+ request()->session()->invalidate();
256
+ request()->session()->regenerateToken();
257
+
258
+ return response()->json([
259
+ 'message' => 'ログアウトしました',
260
+ ]);
261
+ }
262
+ }
263
+ ```
264
+
265
+ ### Login Request
266
+
267
+ ```php
268
+ <?php
269
+
270
+ namespace App\Http\Requests\Auth;
271
+
272
+ use Illuminate\Auth\Events\Lockout;
273
+ use Illuminate\Foundation\Http\FormRequest;
274
+ use Illuminate\Support\Facades\Auth;
275
+ use Illuminate\Support\Facades\RateLimiter;
276
+ use Illuminate\Support\Str;
277
+ use Illuminate\Validation\ValidationException;
278
+
279
+ class LoginRequest extends FormRequest
280
+ {
281
+ public function authorize(): bool
282
+ {
283
+ return true;
284
+ }
285
+
286
+ public function rules(): array
287
+ {
288
+ return [
289
+ 'email' => ['required', 'string', 'email'],
290
+ 'password' => ['required', 'string'],
291
+ ];
292
+ }
293
+
294
+ public function authenticate(): void
295
+ {
296
+ $this->ensureIsNotRateLimited();
297
+
298
+ if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
299
+ RateLimiter::hit($this->throttleKey());
300
+
301
+ throw ValidationException::withMessages([
302
+ 'email' => __('auth.failed'),
303
+ ]);
304
+ }
305
+
306
+ RateLimiter::clear($this->throttleKey());
307
+ }
308
+
309
+ protected function ensureIsNotRateLimited(): void
310
+ {
311
+ if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
312
+ return;
313
+ }
314
+
315
+ event(new Lockout($this));
316
+
317
+ $seconds = RateLimiter::availableIn($this->throttleKey());
318
+
319
+ throw ValidationException::withMessages([
320
+ 'email' => trans('auth.throttle', [
321
+ 'seconds' => $seconds,
322
+ 'minutes' => ceil($seconds / 60),
323
+ ]),
324
+ ]);
325
+ }
326
+
327
+ protected function throttleKey(): string
328
+ {
329
+ return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip());
330
+ }
331
+ }
332
+ ```
333
+
334
+ ## API Authentication (Laravel Sanctum)
335
+
336
+ ### Install Sanctum
337
+
338
+ ```bash
339
+ composer require laravel/sanctum
340
+ php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
341
+ php artisan migrate
342
+ ```
343
+
344
+ ### Add to User Model
345
+
346
+ ```php
347
+ // app/Models/User.php
348
+ use Laravel\Sanctum\HasApiTokens;
349
+
350
+ class User extends UserBaseModel
351
+ {
352
+ use HasApiTokens; // Add this trait
353
+
354
+ // ...
355
+ }
356
+ ```
357
+
358
+ ### API Login Controller
359
+
360
+ ```php
361
+ <?php
362
+
363
+ namespace App\Http\Controllers\Api\Auth;
364
+
365
+ use App\Http\Controllers\Controller;
366
+ use App\Models\User;
367
+ use Illuminate\Http\JsonResponse;
368
+ use Illuminate\Http\Request;
369
+ use Illuminate\Support\Facades\Hash;
370
+ use Illuminate\Validation\ValidationException;
371
+
372
+ class TokenController extends Controller
373
+ {
374
+ public function store(Request $request): JsonResponse
375
+ {
376
+ $request->validate([
377
+ 'email' => 'required|email',
378
+ 'password' => 'required',
379
+ 'device_name' => 'required',
380
+ ]);
381
+
382
+ $user = User::where('email', $request->email)->first();
383
+
384
+ if (! $user || ! Hash::check($request->password, $user->password)) {
385
+ throw ValidationException::withMessages([
386
+ 'email' => [__('auth.failed')],
387
+ ]);
388
+ }
389
+
390
+ return response()->json([
391
+ 'token' => $user->createToken($request->device_name)->plainTextToken,
392
+ 'user' => $user,
393
+ ]);
394
+ }
395
+
396
+ public function destroy(Request $request): JsonResponse
397
+ {
398
+ $request->user()->currentAccessToken()->delete();
399
+
400
+ return response()->json(['message' => 'Token revoked']);
401
+ }
402
+ }
403
+ ```
404
+
405
+ ## Routes Configuration
406
+
407
+ ### Web Routes (routes/web.php)
408
+
409
+ ```php
410
+ use App\Http\Controllers\Auth\LoginController;
411
+
412
+ Route::middleware('guest')->group(function () {
413
+ Route::post('/login', [LoginController::class, 'store']);
414
+ });
415
+
416
+ Route::middleware('auth')->group(function () {
417
+ Route::post('/logout', [LoginController::class, 'destroy']);
418
+ });
419
+ ```
420
+
421
+ ### API Routes (routes/api.php)
422
+
423
+ ```php
424
+ use App\Http\Controllers\Api\Auth\TokenController;
425
+
426
+ Route::post('/tokens', [TokenController::class, 'store']);
427
+
428
+ Route::middleware('auth:sanctum')->group(function () {
429
+ Route::delete('/tokens', [TokenController::class, 'destroy']);
430
+
431
+ Route::get('/user', function (Request $request) {
432
+ return $request->user();
433
+ });
434
+ });
435
+ ```
436
+
437
+ ## Multi-Guard Middleware
438
+
439
+ ### Admin-Only Routes
440
+
441
+ ```php
442
+ Route::middleware('auth:admin')->prefix('admin')->group(function () {
443
+ Route::get('/dashboard', [AdminDashboardController::class, 'index']);
444
+ });
445
+ ```
446
+
447
+ ### Admin Login Controller
448
+
449
+ ```php
450
+ public function store(AdminLoginRequest $request): JsonResponse
451
+ {
452
+ if (! Auth::guard('admin')->attempt($request->only('email', 'password'))) {
453
+ throw ValidationException::withMessages([
454
+ 'email' => __('auth.failed'),
455
+ ]);
456
+ }
457
+
458
+ $request->session()->regenerate();
459
+
460
+ return response()->json([
461
+ 'admin' => Auth::guard('admin')->user(),
462
+ ]);
463
+ }
464
+ ```
465
+
466
+ ## Common Patterns
467
+
468
+ ### Check Authentication
469
+
470
+ ```php
471
+ // In controller
472
+ if (Auth::check()) {
473
+ $user = Auth::user();
474
+ }
475
+
476
+ // In middleware
477
+ Route::middleware('auth')->group(function () {
478
+ // Protected routes
479
+ });
480
+
481
+ // In Blade
482
+ @auth
483
+ Welcome, {{ Auth::user()->name }}
484
+ @endauth
485
+ ```
486
+
487
+ ### Get Current User
488
+
489
+ ```php
490
+ // Via Auth facade
491
+ $user = Auth::user();
492
+ $userId = Auth::id();
493
+
494
+ // Via request
495
+ $user = $request->user();
496
+
497
+ // Via helper
498
+ $user = auth()->user();
499
+ ```
500
+
501
+ ### Password Hashing
502
+
503
+ ```php
504
+ // Hash password (automatic in Omnify models via cast)
505
+ $user->password = 'plain-text'; // Auto-hashed by 'hashed' cast
506
+ $user->save();
507
+
508
+ // Manual hashing if needed
509
+ use Illuminate\Support\Facades\Hash;
510
+ $hashed = Hash::make('plain-text');
511
+
512
+ // Verify password
513
+ if (Hash::check('plain-text', $user->password)) {
514
+ // Correct
515
+ }
516
+ ```
517
+
518
+ ## Testing Authentication
519
+
520
+ ```php
521
+ use App\Models\User;
522
+ use Illuminate\Foundation\Testing\RefreshDatabase;
523
+
524
+ class AuthTest extends TestCase
525
+ {
526
+ use RefreshDatabase;
527
+
528
+ public function test_user_can_login(): void
529
+ {
530
+ $user = User::factory()->create([
531
+ 'email' => 'test@example.com',
532
+ 'password' => 'password',
533
+ ]);
534
+
535
+ $response = $this->postJson('/login', [
536
+ 'email' => 'test@example.com',
537
+ 'password' => 'password',
538
+ ]);
539
+
540
+ $response->assertOk();
541
+ $this->assertAuthenticated();
542
+ }
543
+
544
+ public function test_authenticated_user_can_access_protected_route(): void
545
+ {
546
+ $user = User::factory()->create();
547
+
548
+ $response = $this->actingAs($user)
549
+ ->getJson('/api/user');
550
+
551
+ $response->assertOk();
552
+ }
553
+
554
+ public function test_guest_cannot_access_protected_route(): void
555
+ {
556
+ $response = $this->getJson('/api/user');
557
+
558
+ $response->assertUnauthorized();
559
+ }
560
+ }
561
+ ```
562
+
563
+ ## Future: Trait-based Authentication
564
+
565
+ > **Coming Soon:** In future versions, Omnify may support a trait-based approach:
566
+ >
567
+ > ```php
568
+ > use Illuminate\Database\Eloquent\Model;
569
+ > use Illuminate\Auth\Authenticatable;
570
+ > use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
571
+ >
572
+ > class UserBaseModel extends Model implements AuthenticatableContract
573
+ > {
574
+ > use Authenticatable; // Trait instead of extending class
575
+ > }
576
+ > ```
577
+ >
578
+ > This will provide more flexibility in model inheritance while maintaining
579
+ > full authentication support.
580
+
581
+ ## Summary
582
+
583
+ | Option | Type | Default | When to Use |
584
+ |--------|------|---------|-------------|
585
+ | `authenticatable` | boolean | `false` | User can login |
586
+ | `authenticatableLoginIdField` | string | `email` | Custom login field |
587
+ | `authenticatablePasswordField` | string | `password` | Custom password field |
588
+ | `authenticatableGuardName` | string | (none) | Multi-guard setup |
@@ -0,0 +1,376 @@
1
+ # Migration Team Workflow Guide
2
+
3
+ > Best practices for managing database migrations in team development environments.
4
+
5
+ ## Overview
6
+
7
+ Omnify follows **international standards** for database migration management:
8
+
9
+ 1. **Schema as Source of Truth** - YAML schemas define the database structure
10
+ 2. **Simple File Tracking** - Lock file tracks migrations for regeneration
11
+ 3. **CI Validation** - Automated checks ensure sync between schema and migrations
12
+ 4. **Git for Conflict Resolution** - Standard Git workflow handles merge conflicts
13
+
14
+ ## Migration Tracking System
15
+
16
+ ### Lock File Structure
17
+
18
+ The `.omnify.lock` file tracks all generated migrations:
19
+
20
+ ```json
21
+ {
22
+ "version": 2,
23
+ "migrations": [
24
+ {
25
+ "fileName": "2026_01_13_100000_create_users_table.php",
26
+ "timestamp": "2026_01_13_100000",
27
+ "tableName": "users",
28
+ "type": "create",
29
+ "generatedAt": "2026-01-13T10:00:00Z",
30
+ "schemas": ["User"],
31
+ "checksum": "sha256..."
32
+ }
33
+ ]
34
+ }
35
+ ```
36
+
37
+ ### Key Fields
38
+
39
+ | Field | Purpose |
40
+ |-------|---------|
41
+ | `fileName` | Full migration filename |
42
+ | `timestamp` | Timestamp prefix for regeneration |
43
+ | `tableName` | Table name for lookup |
44
+ | `type` | Migration type (create/alter/drop/pivot) |
45
+ | `checksum` | SHA-256 hash for integrity verification |
46
+
47
+ ## CI/CD Integration
48
+
49
+ ### GitHub Actions Example
50
+
51
+ ```yaml
52
+ # .github/workflows/migration-check.yml
53
+ name: Migration Check
54
+
55
+ on: [push, pull_request]
56
+
57
+ jobs:
58
+ validate:
59
+ runs-on: ubuntu-latest
60
+ steps:
61
+ - uses: actions/checkout@v4
62
+
63
+ - name: Setup Node.js
64
+ uses: actions/setup-node@v4
65
+ with:
66
+ node-version: '20'
67
+
68
+ - name: Install dependencies
69
+ run: npm ci
70
+
71
+ - name: Check migrations sync
72
+ run: npx omnify generate --check
73
+
74
+ - name: Test migrations (optional)
75
+ run: |
76
+ php artisan migrate:fresh --force
77
+ php artisan migrate:status
78
+ ```
79
+
80
+ ### CI Check Mode
81
+
82
+ ```bash
83
+ # Check if migrations are in sync (for CI)
84
+ npx omnify generate --check
85
+
86
+ # Exit codes:
87
+ # 0 = All migrations in sync ✅
88
+ # 1 = Schema changes detected or migration issues ❌
89
+ ```
90
+
91
+ Output example:
92
+ ```
93
+ Omnify v1.0.113 - Generating Outputs
94
+
95
+ ✔ Loading schemas from schemas
96
+ ✔ Validating schemas...
97
+ ✔ Checking for changes...
98
+
99
+ CI Check Mode Results:
100
+ Schemas: 12
101
+ Tracked migrations: 15
102
+ Migrations on disk: 15
103
+ Schema changes: 0
104
+
105
+ ✅ All migrations in sync
106
+ ```
107
+
108
+ ## Team Development Workflow
109
+
110
+ ### Standard Workflow
111
+
112
+ ```
113
+ ┌─────────────────────────────────────────────────────────────┐
114
+ │ 1. Edit YAML Schema │
115
+ │ schemas/module/Model.yaml │
116
+ └─────────────────────────────────────────────────────────────┘
117
+
118
+
119
+ ┌─────────────────────────────────────────────────────────────┐
120
+ │ 2. Generate Migrations │
121
+ │ npx omnify generate │
122
+ └─────────────────────────────────────────────────────────────┘
123
+
124
+
125
+ ┌─────────────────────────────────────────────────────────────┐
126
+ │ 3. Commit Both │
127
+ │ git add schemas/ database/migrations/ .omnify.lock │
128
+ │ git commit -m "feat: add phone field to User" │
129
+ └─────────────────────────────────────────────────────────────┘
130
+
131
+
132
+ ┌─────────────────────────────────────────────────────────────┐
133
+ │ 4. CI Validates │
134
+ │ GitHub Actions runs: npx omnify generate --check │
135
+ └─────────────────────────────────────────────────────────────┘
136
+
137
+
138
+ ┌─────────────────────────────────────────────────────────────┐
139
+ │ 5. Merge & Deploy │
140
+ │ php artisan migrate │
141
+ └─────────────────────────────────────────────────────────────┘
142
+ ```
143
+
144
+ ### Parallel Development
145
+
146
+ When multiple developers work on different schemas:
147
+
148
+ ```
149
+ Developer A Developer B
150
+ │ │
151
+ Edit User.yaml Edit Product.yaml
152
+ (add phone) (add sku)
153
+ │ │
154
+ omnify generate omnify generate
155
+ │ │
156
+ Creates: Creates:
157
+ alter_users_add_phone.php alter_products_add_sku.php
158
+ │ │
159
+ └──────────┬───────────────────┘
160
+
161
+ Git Merge
162
+
163
+ Both migrations exist ✅
164
+ (different tables, no conflict)
165
+ ```
166
+
167
+ ### Same Schema Conflict
168
+
169
+ When multiple developers edit the SAME schema:
170
+
171
+ ```
172
+ Developer A Developer B
173
+ │ │
174
+ Edit User.yaml Edit User.yaml
175
+ (add phone) (add address)
176
+ │ │
177
+ omnify generate omnify generate
178
+ │ │
179
+ └──────────┬───────────────────┘
180
+
181
+ Git Merge
182
+
183
+ ⚠️ YAML Conflict!
184
+
185
+ ┌──────────┴──────────┐
186
+ │ │
187
+ Resolve YAML Keep both migrations
188
+ (merge both fields) (Laravel runs both)
189
+ │ │
190
+ └──────────┬──────────┘
191
+
192
+ Result:
193
+ User.yaml has phone + address
194
+ Both migrations exist
195
+ Laravel runs in timestamp order ✅
196
+ ```
197
+
198
+ ## Handling Common Scenarios
199
+
200
+ ### Scenario 1: Accidentally Deleted Migration
201
+
202
+ **Problem:** Migration file deleted but lock file still tracks it.
203
+
204
+ **Detection:**
205
+ ```bash
206
+ npx omnify generate
207
+
208
+ ⚠️ Migration file issues detected:
209
+ Missing files (1):
210
+ - 2026_01_13_100000_create_users_table.php
211
+ ```
212
+
213
+ **Solutions:**
214
+ ```bash
215
+ # Option 1: Restore from git
216
+ git checkout -- database/migrations/omnify/
217
+
218
+ # Option 2: Reset and regenerate (destructive!)
219
+ npx omnify reset --migrations
220
+ npx omnify generate --force
221
+ ```
222
+
223
+ ### Scenario 2: Stale Migration from Old Branch
224
+
225
+ **Problem:** Branch created 1 month ago, merged today with old timestamp.
226
+
227
+ **Detection:**
228
+ ```bash
229
+ npx omnify generate
230
+
231
+ ⚠️ Stale migrations detected (old timestamp, not in lock file):
232
+ - 2026_01_04_100000_add_phone_to_users_table.php
233
+ These may be from merged branches. Review before running migrate.
234
+ ```
235
+
236
+ **Action:**
237
+ 1. Review the migration - is it independent or dependent?
238
+ 2. If independent: Keep as-is, Laravel runs in timestamp order
239
+ 3. If dependent: Consider renaming with new timestamp
240
+
241
+ ### Scenario 3: CI Fails with "Schema changes detected"
242
+
243
+ **Problem:** Someone edited schema but forgot to run generate.
244
+
245
+ **Detection:**
246
+ ```bash
247
+ npx omnify generate --check
248
+
249
+ ❌ Schema changes detected - run "npx omnify generate" to update migrations
250
+ ```
251
+
252
+ **Solution:**
253
+ ```bash
254
+ # Locally
255
+ npx omnify generate
256
+ git add .
257
+ git commit --amend # or new commit
258
+ git push --force # or regular push
259
+ ```
260
+
261
+ ## Best Practices
262
+
263
+ ### DO ✅
264
+
265
+ 1. **Always commit schema + migrations together**
266
+ ```bash
267
+ git add schemas/ database/migrations/omnify/ .omnify.lock
268
+ git commit -m "feat: add field to schema"
269
+ ```
270
+
271
+ 2. **Use CI to validate migrations**
272
+ ```bash
273
+ npx omnify generate --check
274
+ ```
275
+
276
+ 3. **Pull and migrate before starting new work**
277
+ ```bash
278
+ git pull
279
+ php artisan migrate
280
+ ```
281
+
282
+ 4. **Review stale migration warnings**
283
+ - Check dependencies before merging old branches
284
+
285
+ ### DON'T ❌
286
+
287
+ 1. **Don't manually edit migrations in `omnify/` folder**
288
+ - They will be overwritten!
289
+
290
+ 2. **Don't ignore CI failures**
291
+ - Always run `npx omnify generate` if CI fails
292
+
293
+ 3. **Don't commit migrations without schemas**
294
+ - Schema is source of truth
295
+
296
+ 4. **Don't skip stale warnings without review**
297
+ - Old timestamps might run before dependent migrations
298
+
299
+ ## Migration Validation
300
+
301
+ ### Check Commands
302
+
303
+ ```bash
304
+ # Full check (for CI)
305
+ npx omnify generate --check
306
+
307
+ # Generate with stale warnings (default)
308
+ npx omnify generate
309
+
310
+ # Generate without stale warnings
311
+ npx omnify generate --no-warn-stale
312
+
313
+ # Force regenerate everything
314
+ npx omnify generate --force
315
+ ```
316
+
317
+ ### Validation Checks
318
+
319
+ | Check | Description | Exit Code |
320
+ |-------|-------------|-----------|
321
+ | Schema sync | Schema matches generated migrations | 1 if mismatch |
322
+ | Missing files | Migration files exist on disk | 1 if missing |
323
+ | Modified files | Files match stored checksum | Warning only |
324
+ | Stale files | Old timestamp, not tracked | Warning only |
325
+
326
+ ## Troubleshooting
327
+
328
+ ### "Missing migration files"
329
+
330
+ ```bash
331
+ # Check what's missing
332
+ npx omnify generate --check
333
+
334
+ # Restore from git
335
+ git checkout -- database/migrations/omnify/
336
+
337
+ # Or reset completely
338
+ npx omnify reset --migrations
339
+ npx omnify generate --force
340
+ ```
341
+
342
+ ### "Checksum mismatch"
343
+
344
+ Someone manually edited an auto-generated file.
345
+
346
+ ```bash
347
+ # Option 1: Regenerate
348
+ npx omnify generate --force
349
+
350
+ # Option 2: Reset lock file entry
351
+ # (manually edit .omnify.lock to remove the entry)
352
+ ```
353
+
354
+ ### "Stale migrations"
355
+
356
+ Old branch merged with old timestamps.
357
+
358
+ ```bash
359
+ # Usually safe to ignore if migrations are independent
360
+ # But review the migration order:
361
+
362
+ php artisan migrate:status
363
+ # Check: Will the old migration run before something it depends on?
364
+ ```
365
+
366
+ ## Summary
367
+
368
+ | Workflow Step | Command |
369
+ |---------------|---------|
370
+ | Edit schema | Edit `schemas/*.yaml` |
371
+ | Generate | `npx omnify generate` |
372
+ | Commit | `git add schemas/ database/migrations/ .omnify.lock` |
373
+ | CI Check | `npx omnify generate --check` |
374
+ | Deploy | `php artisan migrate` |
375
+ | Restore | `git checkout -- database/migrations/omnify/` |
376
+ | Reset | `npx omnify reset --migrations` |