prisma-laravel-migrate 0.0.7 → 0.0.8

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/README.md CHANGED
@@ -1,299 +1,503 @@
1
- # Prisma Laravel Migrate
2
-
3
- A generator plugin that translates your **Prisma schema** into Laravel‑ready
4
- **Database Migrations**, **Eloquent Models**, and **Enum classes**.
5
- Built in strict TypeScript with fully‑customisable stubs, grouping, and marker‑based updates.
6
-
7
- ---
8
-
9
- ## 📦 Installation
10
-
11
- ```bash
12
- npm install prisma-laravel-migrate --save-dev
13
- # requires the Prisma CLI in your project
14
- ```
15
-
16
- ---
17
-
18
- ## 🛠️ Prisma Generator Setup
19
-
20
- Add both generator blocks to **`schema.prisma`**:
21
-
22
- ```prisma
23
- generator migrate {
24
- provider = "prisma-laravel-migrate"
25
- stubDir = "./prisma/stubs"
26
- output = "database/migrations" // fallback
27
- outputDir = "database/migrations" // takes precedence
28
- startMarker = "// <prisma-laravel:start>"
29
- endMarker = "// <prisma-laravel:end>"
30
- noEmit = false
31
- groups = "./prisma/group-stubs.js"
32
- }
33
-
34
- generator modeler {
35
- provider = "prisma-laravel-models"
36
- stubDir = "./prisma/stubs"
37
- output = "app/Models"
38
- outputDir = "app/Models" // overrides output
39
- outputEnumDir = "app/Enums"
40
- startMarker = "// <prisma-laravel:start>"
41
- endMarker = "// <prisma-laravel:end>"
42
- noEmit = false
43
- groups = "./prisma/group-stubs.js"
44
- }
45
- ```
46
-
47
- ### Field Reference
48
-
49
- | Key | Notes |
50
- | --- | --- |
51
- | `outputDir / output` | Destination folder (`outputDir` takes precedence). |
52
- | `outputEnumDir` | (modeler) directory for PHP enum classes. |
53
- | `stubDir` | Root stubs folder (`migration/`, `model/`, `enum/`). |
54
- | `startMarker/endMarker` | Region markers the generator will update. |
55
- | `groups` | JS module exporting stub‑group mappings. |
56
- | `noEmit` | If `true`, generator parses but **writes no** files. |
57
-
58
- ---
59
-
60
- ## 📁 Stub Folder Layout
61
-
62
- ```
63
- prisma/stubs/
64
- ├── migration/index.stub
65
- ├── model/index.stub
66
- ├── model/simple-model.stub
67
- └── enum/index.stub
68
- ```
69
-
70
- Create a table‑specific override with
71
- `stubs/<type>/<table>.stub` (e.g. `stubs/model/users.stub`).
72
-
73
- ---
74
-
75
- ## 🔧 CLI Commands
76
-
77
- | Command | What it does |
78
- | --- | --- |
79
- | `init` | Inject generator blocks & scaffold stub folders |
80
- | `customize` | Create per‑table stub overrides |
81
- | `gen` | Run `prisma generate` then Laravel generators |
82
-
83
- ### init
84
-
85
- ```bash
86
- npx prisma-laravel-cli init --schema=prisma/schema.prisma
87
- ```
88
-
89
- ### customize
90
-
91
- Generate override stubs.
92
-
93
- ```bash
94
- npx prisma-laravel-cli customize \
95
- -t <types> \
96
- -n <names> \
97
- [--force] \
98
- [--config <path>]
99
- ```
100
-
101
- | Flag | Description |
102
- | --- | --- |
103
- | `-t, --type` | **Required.** Comma‑separated list of stub types.<br>Valid values: `migration`, `model`, `enum`. You may combine `migration,model`; `enum` must be used alone. |
104
- | `-n, --names` | **Required.** Comma‑separated table or enum names (e.g. `users,accounts`). |
105
- | `--force` | Overwrite existing stub files. |
106
- | `--config` | Path to an alternate CLI config file (custom stubDir/groups). |
107
-
108
- **Behaviour**
109
-
110
- 1. Checks `<stubDir>/<type>/<name>.stub`.
111
- 2. If missing, copies from `index.stub` → that path and logs:
112
- `➡️ Created stubs/<type>/<name>.stub from index.stub`
113
- 3. If present and `--force` **not** set:
114
- `⏭️ Skipped existing stubs/<type>/<name>.stub`
115
- 4. With `--force`, overwrites and logs creation.
116
-
117
- **Example**
118
-
119
- ```bash
120
- # migration + model overrides for two tables
121
- npx prisma-laravel-cli customize -t migration,model -n users,accounts
122
- ```
123
-
124
- ### gen
125
-
126
- ```bash
127
- # run prisma generate then Laravel generators
128
- npx prisma-laravel-cli gen --config=prisma/laravel.config.js
129
-
130
- # skip prisma generate step
131
- npx prisma-laravel-cli gen --config=prisma/laravel.config.js --skipGenerate
132
- ```
133
-
134
- `prisma/laravel.config.js` example:
135
-
136
- ```js
137
- module.exports = {
138
- migrator: {
139
- outputDir: "database/migrations",
140
- stubDir: "prisma/stubs",
141
- groups: "./prisma/group-stubs.js",
142
- },
143
- modeler: {
144
- outputDir: "app/Models",
145
- outputEnumDir: "app/Enums",
146
- stubDir: "prisma/stubs",
147
- groups: "./prisma/group-stubs.js",
148
- },
149
- };
150
- ```
151
-
152
- ---
153
-
154
- ## Stub Customization Notes
155
-
156
- Stubs are **JS template literals**. Escape \\` and \\${ } if you want them literally.
157
-
158
- > **Full custom model stubs**
159
- > If you plan to hand-craft **all** the internal sections yourself
160
- > (fillable, hidden, casts, etc.), remove the `${content}` placeholder
161
- > but **keep** the `// <prisma-laravel:start>` and
162
- > `// <prisma-laravel:end>` markers so the generator still knows where
163
- > to inject future updates.
164
- > Remove the markers only if you never want the file touched again
165
-
166
- ---
167
-
168
- ## 📑 Default Stub Templates
169
-
170
- <details>
171
- <summary>Enum <code>index.stub</code></summary>
172
-
173
- ```php
174
- <?php
175
-
176
- namespace App\\Enums;
177
-
178
- enum ${enumDef.name}: string
179
- {
180
- // <prisma-laravel:start>
181
- ${enumDef.values.map(v => ` case ${v} = '${v}';`).join('\\n')}
182
- // <prisma-laravel:end>
183
- }
184
- ```
185
-
186
- </details>
187
-
188
- <details>
189
- <summary>Migration <code>index.stub</code></summary>
190
-
191
- ```php
192
- <?php
193
-
194
- use Illuminate\\Database\\Migrations\\Migration;
195
- use Illuminate\\Database\\Schema\\Blueprint;
196
- use Illuminate\\Support\\Facades\\Schema;
197
-
198
- return new class extends Migration
199
- {
200
- public function up(): void
201
- {
202
- Schema::create('${tableName}', function (Blueprint $table) {
203
- // <prisma-laravel:start>
204
- ${columns}
205
- // <prisma-laravel:end>
206
- });
207
- }
208
-
209
- public function down(): void
210
- {
211
- Schema::dropIfExists('${tableName}');
212
- }
213
- };
214
- ```
215
-
216
- </details>
217
-
218
- ---
219
-
220
- ## 🏗️ Complex Model Stub Example
221
-
222
- <details>
223
- <summary>Expand stub</summary>
224
-
225
- ```php
226
- <?php
227
-
228
- namespace App\\Models;
229
-
230
- ${model.imports}
231
- use Illuminate\\Database\\Eloquent\\Model;
232
- use Illuminate\\Database\\Eloquent\\Relations\\{ BelongsTo, HasMany, BelongsToMany };
233
-
234
- class ${model.className} extends Model
235
- {
236
- protected $table = '${model.tableName}';
237
-
238
- /* Mass Assignment */
239
- protected $fillable = [
240
- ${model.properties.filter(p => p.fillable).map(p => ` '${p.name}',`).join('\\n')}
241
- ];
242
- protected $guarded = [
243
- ${(model.guarded ?? []).map(n => ` '${n}',`).join('\\n')}
244
- ];
245
-
246
- /* Hidden & Casts */
247
- protected $hidden = [
248
- ${model.properties.filter(p => p.hidden).map(p => ` '${p.name}',`).join('\\n')}
249
- ];
250
- protected $casts = [
251
- ${model.properties.filter(p => p.cast).map(p => ` '${p.name}' => '${p.cast}',`).join('\\n')}
252
- ${model.properties.filter(p => p.enumRef).map(p => ` '${p.name}' => ${p.enumRef}::class,`).join('\\n')}
253
- ];
254
-
255
- /* Interfaces metadata */
256
- public array $interfaces = [
257
- ${Object.entries(model.interfaces).map(([k,i]) =>
258
- ` '${k}' => {${i.import ? ` import: '${i.import}',` : ''} type: '${i.type}' },`).join('\\n')}
259
- ];
260
- // This structure is useful for packages like fumeapp/modeltyper which
261
- // read interface metadata to build TypeScript helpers.
262
-
263
- /* Relationships */
264
- ${model.relations.map(r => {
265
- const args = [r.modelClass, r.foreignKey ? `'${r.foreignKey}'` : '', r.localKey ? `'${r.localKey}'` : ''].filter(Boolean).join(', ');
266
- return ` public function ${r.name}(): ${r.type.charAt(0).toUpperCase()+r.type.slice(1)}\\n {\\n return $this->${r.type}(${args});\\n }`;
267
- }).join('\\n\\n')}
268
-
269
- // <prisma-laravel:start>
270
- ${content}
271
- // <prisma-laravel:end>
272
- }
273
- ```
274
-
275
- </details>
276
-
277
- ---
278
-
279
- ## 🚀 Enum Casting
280
-
281
- ```php
282
- protected $casts = [
283
- 'status' => StatusEnum::class,
284
- ];
285
- ```
286
-
287
- ---
288
-
289
- ## 💡 Tips
290
-
291
- - Combine `migration` & `model` in one customize command when table names align.
292
- - Use `noEmit: true` for dry‑runs or CI validation.
293
- - Escape template chars in stub files.
294
-
295
- ---
296
-
297
- ## 📜 License
298
-
1
+ # Prisma Laravel Migrate
2
+
3
+ A generator plugin that translates your **Prisma schema** into Laravel‑ready
4
+ **Database Migrations**, **Eloquent Models**, and **Enum classes**.
5
+ Built in strict TypeScript with fully‑customisable stubs, grouping, and marker‑based updates.
6
+
7
+ ---
8
+
9
+ ## 📦 Installation
10
+
11
+ ```bash
12
+ npm install prisma-laravel-migrate --save-dev
13
+ # requires the Prisma CLI in your project
14
+ ```
15
+
16
+ ---
17
+
18
+ ## 🛠️ Prisma Generator Setup
19
+
20
+ Add both generator blocks to **`schema.prisma`**:
21
+
22
+ ```prisma
23
+ generator migrate {
24
+ provider = "prisma-laravel-migrations"
25
+ stubDir = "./prisma/stubs"
26
+ output = "database/migrations" // fallback
27
+ outputDir = "database/migrations" // takes precedence
28
+ startMarker = "// <prisma-laravel:start>"
29
+ endMarker = "// <prisma-laravel:end>"
30
+ noEmit = false
31
+ groups = "./prisma/group-stubs.js"
32
+ }
33
+
34
+ generator modeler {
35
+ provider = "prisma-laravel-models"
36
+ stubDir = "./prisma/stubs"
37
+ output = "app/Models"
38
+ outputDir = "app/Models" // overrides output
39
+ outputEnumDir = "app/Enums"
40
+ startMarker = "// <prisma-laravel:start>"
41
+ endMarker = "// <prisma-laravel:end>"
42
+ noEmit = false
43
+ groups = "./prisma/group-stubs.js"
44
+ }
45
+ ```
46
+
47
+ ### Field Reference
48
+
49
+ | Key | Notes |
50
+ | --- | --- |
51
+ | `outputDir / output` | Destination folder (`outputDir` takes precedence). |
52
+ | `outputEnumDir` | (modeler) directory for PHP enum classes. |
53
+ | `stubDir` | Root stubs folder (`migration/`, `model/`, `enum/`). |
54
+ | `startMarker/endMarker` | Region markers the generator will update. |
55
+ | `groups` | JS module exporting stub‑group mappings. |
56
+ | `noEmit` | If `true`, generator parses but **writes no** files. |
57
+
58
+
59
+ ### 🔀 `groups` – Stub Grouping
60
+
61
+ Use **`groups`** in the generator block to map multiple tables (or enums)
62
+ to a shared stub template:
63
+
64
+ ```prisma
65
+ generator migrate {
66
+ provider = "prisma-laravel-models"
67
+ stubDir = "./prisma/stubs"
68
+ groups = "./prisma/group-stubs.js"
69
+ }
70
+ ```
71
+
72
+ `prisma/group-stubs.js`
73
+
74
+ ```js
75
+ /**
76
+ * Each object links one stub file (relative to stubDir/<type>/)
77
+ * to an array of tables (or enums) that should use it.
78
+ */
79
+ module.exports = [
80
+ {
81
+ // Auth domain
82
+ stubFile: "auth.stub", // stubs/migration/auth.stub
83
+ tables: ["users", "accounts", "password_resets"]
84
+ },
85
+ {
86
+ // Billing domain
87
+ stubFile: "billing.stub", // stubs/migration/billing.stub
88
+ tables: ["invoices", "transactions"]
89
+ }
90
+ ];
91
+ ```
92
+
93
+ **Resolution order**
94
+
95
+ 1. `stubs/<type>/<table>.stub` (table‑specific)
96
+ 2. Matching group stub (`stubFile`)
97
+ 3. `stubs/<type>/index.stub` (default)
98
+
99
+
100
+ ---
101
+
102
+ ## 📁 Stub Folder Layout
103
+
104
+ ```
105
+ prisma/stubs/
106
+ ├── migration/index.stub
107
+ ├── model/index.stub
108
+ ├── model/simple-model.stub
109
+ └── enum/index.stub
110
+ ```
111
+
112
+ Create a table‑specific override with
113
+ `stubs/<type>/<table>.stub` (e.g. `stubs/model/users.stub`).
114
+
115
+ ---
116
+
117
+ ## 🔧 CLI Commands
118
+
119
+ | Command | What it does |
120
+ | --- | --- |
121
+ | `init` | Inject generator blocks & scaffold stub folders |
122
+ | `customize` | Create per‑table stub overrides |
123
+ | `gen` | Run `prisma generate` then Laravel generators |
124
+
125
+ ### init
126
+
127
+ ```bash
128
+ npx prisma-laravel-cli init --schema=prisma/schema.prisma
129
+ ```
130
+
131
+ ### customize
132
+
133
+ Generate override stubs.
134
+
135
+ ```bash
136
+ npx prisma-laravel-cli customize \
137
+ -t <types> \
138
+ -n <names> \
139
+ [--force] \
140
+ [--config <path>]
141
+ ```
142
+
143
+ | Flag | Description |
144
+ | --- | --- |
145
+ | `-t, --type` | **Required.** Comma‑separated list of stub types.<br>Valid values: `migration`, `model`, `enum`. You may combine `migration,model`; `enum` must be used alone. |
146
+ | `-n, --names` | **Required.** Comma‑separated table or enum names (e.g. `users,accounts`). |
147
+ | `--force` | Overwrite existing stub files. |
148
+ | `--config` | Path to an alternate CLI config file (custom stubDir/groups). |
149
+
150
+ **Behaviour**
151
+
152
+ 1. Checks `<stubDir>/<type>/<name>.stub`.
153
+ 2. If missing, copies from `index.stub` → that path and logs:
154
+ `➡️ Created stubs/<type>/<name>.stub from index.stub`
155
+ 3. If present and `--force` **not** set:
156
+ `⏭️ Skipped existing stubs/<type>/<name>.stub`
157
+ 4. With `--force`, overwrites and logs creation.
158
+
159
+ **Example**
160
+
161
+ ```bash
162
+ # migration + model overrides for two tables
163
+ npx prisma-laravel-cli customize -t migration,model -n users,accounts
164
+ ```
165
+
166
+ ### gen
167
+
168
+ ```bash
169
+ # run prisma generate then Laravel generators
170
+ npx prisma-laravel-cli gen --config=prisma/laravel.config.js
171
+
172
+ # skip prisma generate step
173
+ npx prisma-laravel-cli gen --config=prisma/laravel.config.js --skipGenerate
174
+ ```
175
+
176
+ `prisma/laravel.config.js` example:
177
+
178
+ ```js
179
+ module.exports = {
180
+ migrator: {
181
+ outputDir: "database/migrations",
182
+ stubDir: "prisma/stubs",
183
+ groups: "./prisma/group-stubs.js",
184
+ },
185
+ modeler: {
186
+ outputDir: "app/Models",
187
+ outputEnumDir: "app/Enums",
188
+ stubDir: "prisma/stubs",
189
+ groups: "./prisma/group-stubs.js",
190
+ },
191
+ };
192
+ ```
193
+
194
+ ---
195
+
196
+ ## ✨ Stub Customization Notes
197
+
198
+ Stubs are **JS template literals**. Escape \\` and \\${ } if you want them literally.
199
+
200
+ > **Full custom model stubs**
201
+ > If you plan to hand-craft **all** the internal sections yourself
202
+ > (fillable, hidden, casts, etc.), remove the `${content}` placeholder
203
+ > but **keep** the `// <prisma-laravel:start>` and
204
+ > `// <prisma-laravel:end>` markers so the generator still knows where
205
+ > to inject future updates.
206
+ > Remove the markers only if you never want the file touched again
207
+
208
+ ---
209
+
210
+ ## 📑 Default Stub Templates
211
+
212
+ <details>
213
+ <summary>Enum <code>index.stub</code></summary>
214
+
215
+ ```php
216
+ <?php
217
+
218
+ namespace App\\Enums;
219
+
220
+ enum ${enumDef.name}: string
221
+ {
222
+ // <prisma-laravel:start>
223
+ ${enumDef.values.map(v => ` case ${v} = '${v}';`).join('\\n')}
224
+ // <prisma-laravel:end>
225
+ }
226
+ ```
227
+
228
+ </details>
229
+
230
+ <details>
231
+ <summary>Migration <code>index.stub</code></summary>
232
+
233
+ ```php
234
+ <?php
235
+
236
+ use Illuminate\\Database\\Migrations\\Migration;
237
+ use Illuminate\\Database\\Schema\\Blueprint;
238
+ use Illuminate\\Support\\Facades\\Schema;
239
+
240
+ return new class extends Migration
241
+ {
242
+ public function up(): void
243
+ {
244
+ Schema::create('${tableName}', function (Blueprint $table) {
245
+ // <prisma-laravel:start>
246
+ ${columns}
247
+ // <prisma-laravel:end>
248
+ });
249
+ }
250
+
251
+ public function down(): void
252
+ {
253
+ Schema::dropIfExists('${tableName}');
254
+ }
255
+ };
256
+ ```
257
+
258
+ </details>
259
+
260
+ ---
261
+
262
+ ## 🏗️ Complex Model Stub Example
263
+
264
+ <details>
265
+ <summary>Expand stub</summary>
266
+
267
+ ```php
268
+ <?php
269
+
270
+ namespace App\\Models;
271
+
272
+ ${model.imports}
273
+ use Illuminate\\Database\\Eloquent\\Model;
274
+ use Illuminate\\Database\\Eloquent\\Relations\\{ BelongsTo, HasMany, BelongsToMany };
275
+
276
+ class ${model.className} extends Model
277
+ {
278
+ protected $table = '${model.tableName}';
279
+
280
+ /* Mass Assignment */
281
+ protected $fillable = [
282
+ ${model.properties.filter(p => p.fillable).map(p => ` '${p.name}',`).join('\\n')}
283
+ ];
284
+ protected $guarded = [
285
+ ${(model.guarded ?? []).map(n => ` '${n}',`).join('\\n')}
286
+ ];
287
+
288
+ /* Hidden & Casts */
289
+ protected $hidden = [
290
+ ${model.properties.filter(p => p.hidden).map(p => ` '${p.name}',`).join('\\n')}
291
+ ];
292
+ protected $casts = [
293
+ ${model.properties.filter(p => p.cast).map(p => ` '${p.name}' => '${p.cast}',`).join('\\n')}
294
+ ${model.properties.filter(p => p.enumRef).map(p => ` '${p.name}' => ${p.enumRef}::class,`).join('\\n')}
295
+ ];
296
+
297
+ /* Interfaces metadata */
298
+ public array $interfaces = [
299
+ ${Object.entries(model.interfaces).map(([k,i]) =>
300
+ ` '${k}' => {${i.import ? ` import: '${i.import}',` : ''} type: '${i.type}' },`).join('\\n')}
301
+ ];
302
+ // This structure is useful for packages like fumeapp/modeltyper which
303
+ // read interface metadata to build TypeScript helpers.
304
+
305
+ /* Relationships */
306
+ ${model.relations.map(r => {
307
+ const args = [r.modelClass, r.foreignKey ? `'${r.foreignKey}'` : '', r.localKey ? `'${r.localKey}'` : ''].filter(Boolean).join(', ');
308
+ return ` public function ${r.name}(): ${r.type.charAt(0).toUpperCase()+r.type.slice(1)}\\n {\\n return $this->${r.type}(${args});\\n }`;
309
+ }).join('\\n\\n')}
310
+
311
+ // <prisma-laravel:start>
312
+ ${content}
313
+ // <prisma-laravel:end>
314
+ }
315
+ ```
316
+
317
+ </details>
318
+
319
+ ---
320
+
321
+ ## 🚀 Enum Casting
322
+
323
+ ```php
324
+ protected $casts = [
325
+ 'status' => StatusEnum::class,
326
+ ];
327
+ ```
328
+
329
+ ---
330
+
331
+
332
+ ## 🧩 Custom Migration Rules
333
+
334
+ Point the generator’s `rules` field to a JS file exporting an **array** of
335
+ objects that implement the `Rule` interface:
336
+
337
+ ```prisma
338
+ generator migrate {
339
+ provider = "prisma-laravel-migration"
340
+ stubDir = "./prisma/stubs"
341
+ rules = "./prisma/custom-rules.js"
342
+ }
343
+ ```
344
+
345
+ `prisma/custom-rules.js`
346
+
347
+ ```js
348
+ /** @type {import('prisma-laravel-migrate').Rule[]} */
349
+ module.exports = [
350
+ {
351
+ // Always add an `archived` boolean column defaulting to false
352
+ test(def) {
353
+ return def.name === "archived" && def.migrationType === "boolean";
354
+ },
355
+ render() {
356
+ return {
357
+ column: "archived",
358
+ snippet: ["$table->boolean('archived')->default(false);"],
359
+ };
360
+ },
361
+ },
362
+ // add more Rule objects...
363
+ ];
364
+ ```
365
+
366
+ **Rule execution order**
367
+
368
+ 1. Built‑in rules
369
+ 2. Custom rules (executed in array order)
370
+
371
+ ---
372
+
373
+ ### ColumnDefinition quick reference
374
+
375
+ `ColumnDefinition` extends Prisma’s `DMMF.Field`, so all raw Prisma
376
+ properties remain accessible.
377
+
378
+ ```ts
379
+ import { DMMF } from "@prisma/generator-helper";
380
+
381
+ export interface ColumnDefinition extends DMMF.Field {
382
+ migrationType: MigrationType; // e.g. "unsignedBigInteger"
383
+ args?: string[];
384
+ nullable?: boolean;
385
+ unsigned?: boolean;
386
+
387
+ hasDefaultValue: boolean;
388
+ default?: string | number | boolean | null;
389
+
390
+ relationship?: {
391
+ on: string;
392
+ references?: string;
393
+ onDelete?: string;
394
+ onUpdate?: string;
395
+ };
396
+
397
+ ignore?: boolean;
398
+ }
399
+ ```
400
+
401
+ Common checks inside a rule:
402
+
403
+ ```ts
404
+ def.kind // "scalar" | "enum" | "object"
405
+ def.type // original Prisma scalar
406
+ def.migrationType // mapped Laravel builder name
407
+ def.isId
408
+ ```
409
+
410
+ 📝 **Prisma DMMF docs:**
411
+ https://github.com/prisma/prisma/blob/main/packages/prisma-schema-wasm/src/__tests__/snapshot/dmmf.md
412
+
413
+
414
+ ---
415
+
416
+ ### 📝 Comment-Directives in `schema.prisma`
417
+
418
+ Attach these `@` directives either to a **field** (inline or `///` above) **or**
419
+ to the **model** (curly‑brace syntax) to control what the generator writes into
420
+ your Eloquent model.
421
+
422
+ | Directive | Where you can put it | Effect in generated PHP |
423
+ | --- | --- | --- |
424
+ | `@fillable` | Field **or** `@fillable{...}` on model | Adds column(s) to `$fillable` |
425
+ | `@hidden` | Field **or** `@hidden{...}` on model | Adds column(s) to `$hidden` |
426
+ | `@guarded` | Field **or** `@guarded{...}` on model | Adds column(s) to `$guarded` |
427
+ | `@cast{...}` | Field only | Adds custom entry to `$casts` |
428
+ | `@type{ import:'…', type:'…' }` | Field only | Adds entry to `$interfaces` metadata |
429
+ | `@ignore` | Relation field | Skips generating the relationship method |
430
+ | `@with` (no args) | Relation field | Adds that single relation to `$with` |
431
+ | `@with(rel1,rel2,…)` | Model only | Adds listed relations to `$with` |
432
+
433
+ > **Syntax options**
434
+ > • Inline:
435
+ > `balance Decimal /// @fillable @cast{decimal:2}`
436
+ > • Block above field:
437
+ > `/// @hidden`
438
+ > • Model list:
439
+ > `/// @fillable{name,balance}`
440
+ > • Model eager‑load:
441
+ > `/// @with(posts,roles)`
442
+
443
+ ---
444
+
445
+ #### Example
446
+
447
+ ```prisma
448
+ /// @fillable{name,balance}
449
+ /// @hidden{secretToken}
450
+ model Account {
451
+ id Int @id @default(autoincrement())
452
+
453
+ balance Decimal @default(0.0) /// @cast{decimal:2}
454
+
455
+ nickname String /// @fillable @hidden
456
+
457
+ profile Json? /// @type{ import:'@types/forms', type:'ProfileDTO' }
458
+
459
+ company Company? @relation(fields:[companyId], references:[id]) /// @ignore
460
+ companyId Int?
461
+
462
+ posts Post[] /// @with
463
+ }
464
+
465
+ /// @with(posts,comments)
466
+ model User {
467
+ id Int @id @default(autoincrement())
468
+ email String
469
+ posts Post[]
470
+ comments Comment[]
471
+ }
472
+ ```
473
+
474
+ **Generated output**
475
+
476
+ ```php
477
+ protected $fillable = ['name','balance','nickname'];
478
+ protected $hidden = ['secretToken','nickname'];
479
+ protected $casts = ['balance' => 'decimal:2'];
480
+
481
+ public array $interfaces = [
482
+ 'profile' => { import: '@types/forms', type: 'ProfileDTO' },
483
+ ];
484
+
485
+ protected $with = ['posts','comments'];
486
+ ```
487
+
488
+ `@ignore` prevents the `company()` relation method.
489
+ Combine multiple inline directives; they’re processed left‑to‑right.
490
+
491
+ ---
492
+
493
+ ## 💡 Tips
494
+
495
+ - Combine `migration` & `model` in one customize command when table names align.
496
+ - Use `noEmit: true` for dry‑runs or CI validation.
497
+ - Escape template chars in stub files.
498
+
499
+ ---
500
+
501
+ ## 📜 License
502
+
299
503
  MIT — Happy scaffolding! 🎉
@@ -13,58 +13,80 @@ export class PrismaToLaravelModelGenerator {
13
13
  values: e.values.map((v) => v.name),
14
14
  }));
15
15
  // 2) Build each ModelDefinition
16
- const models = this.dmmf.datamodel.models.map((model) => {
17
- const tableName = model.dbName ?? model.name;
18
- const className = model.name;
16
+ // 2) Build each ModelDefinition
17
+ const models = this.dmmf.datamodel.models.map(model => {
18
+ /* ── 2.1 Model-level directives ──────────────────────────────── */
19
19
  const modelDoc = model.documentation ?? "";
20
- const guardedMatch = modelDoc.match(/@guarded\{([^}]+)\}/);
21
- const guarded = guardedMatch
22
- ? guardedMatch[1]
23
- .split(",")
24
- .map((s) => s.trim())
25
- .filter(Boolean)
26
- : undefined;
27
- const withList = [];
28
- // 2a) Properties (scalars + enums + @fillable/@hidden/@ignore/@cast/@type)
29
- const properties = model.fields.map((field) => {
20
+ // helper get list from @tag{a,b,c}
21
+ const listFrom = (doc, tag) => {
22
+ const m = doc.match(new RegExp(`@${tag}\\{([^}]+)\\}`));
23
+ return m ? m[1].split(",").map(s => s.trim()).filter(Boolean) : [];
24
+ };
25
+ const modelFillable = listFrom(modelDoc, "fillable");
26
+ const modelHidden = listFrom(modelDoc, "hidden");
27
+ const modelGuarded = listFrom(modelDoc, "guarded");
28
+ // model-level eager-loads @with(rel1,rel2)
29
+ const modelWith = (() => {
30
+ const m = modelDoc.match(/@with([^)]+)/);
31
+ return m ? m[1].split(",").map(s => s.trim()).filter(Boolean) : [];
32
+ })();
33
+ /* ── 2.2 Field processing ────────────────────────────────────── */
34
+ const withList = [...modelWith]; // eager-load bucket
35
+ const guardedSet = new Set(modelGuarded);
36
+ const fillableSet = new Set(modelFillable);
37
+ const hiddenSet = new Set(modelHidden);
38
+ const properties = model.fields.map(field => {
30
39
  const doc = field.documentation ?? "";
31
- const fillable = /@fillable\b/.test(doc);
32
- const hidden = /@hidden\b/.test(doc);
33
- const ignore = /@ignore\b/.test(doc);
34
- // parse a single @cast{...}
40
+ // quick helper for boolean flags
41
+ const flag = (tag) => new RegExp(`@${tag}\\b`).test(doc);
42
+ const fillable = flag("fillable") || fillableSet.has(field.name);
43
+ const hidden = flag("hidden") || hiddenSet.has(field.name);
44
+ const guarded = flag("guarded") || guardedSet.has(field.name);
45
+ const ignore = flag("ignore");
46
+ // eager-load relation field
47
+ if (flag("with") && !withList.includes(field.name)) {
48
+ withList.push(field.name);
49
+ }
50
+ // custom cast
35
51
  const castMatch = doc.match(/@cast\{([^}]+)\}/);
36
- let cast = castMatch ? castMatch[1].trim() : undefined;
37
- const typeMatch = doc.match(/@type\s*(?:import\s*=\s*"([^"]+)")?\s*,?\s*type\s*=\s*"([^"]+)"/);
52
+ const cast = castMatch ? castMatch[1].trim() : undefined;
53
+ // @type{ import:'x', type:'Y' }
54
+ const typeMatch = doc.match(/@type\{\s*(?:import\s*:\s*'([^']+)')?\s*,?\s*type\s*:\s*'([^']+)'\s*\}/);
38
55
  const typeAnnotation = typeMatch
39
56
  ? { import: typeMatch[1], type: typeMatch[2] }
40
57
  : undefined;
41
- const enumMeta = enums.find((e) => e.name === field.type);
58
+ const enumMeta = enums.find(e => e.name === field.type);
42
59
  const phpType = enumMeta
43
- ? `${field.type}`
60
+ ? enumMeta.name
44
61
  : this.mapPrismaToPhpType(field.type);
45
- // with checking
46
- if (doc.includes("@with"))
47
- withList.push(field.name);
48
62
  return {
49
63
  name: field.name,
50
64
  phpType,
51
65
  fillable,
52
66
  hidden,
53
67
  ignore,
68
+ guarded,
54
69
  cast,
55
70
  enumRef: enumMeta?.name,
56
71
  typeAnnotation,
57
72
  };
58
73
  });
59
- // 2b) Relations: skip any field marked @ignore
74
+ /* ── 2.3 Laravel $guarded array (union model + field) ─────────── */
75
+ const guarded = guardedSet.size || properties.some(p => p.guarded)
76
+ ? [
77
+ ...guardedSet,
78
+ ...properties.filter(p => p.guarded).map(p => p.name),
79
+ ]
80
+ : undefined;
81
+ /* ── 2.4 Relations (unchanged except @ignore honoured) ────────── */
60
82
  const relations = model.fields
61
- .filter((f) => f.kind === "object" &&
83
+ .filter(f => f.kind === "object" &&
62
84
  f.relationName &&
63
85
  !/@ignore\b/.test(f.documentation ?? ""))
64
- .map((f) => {
65
- const relatedModel = this.dmmf.datamodel.models.find((m) => m.name === f.type);
86
+ .map(f => {
87
+ const relatedModel = this.dmmf.datamodel.models.find(m => m.name === f.type);
66
88
  const relatedTable = relatedModel.dbName ?? f.type;
67
- const thisTable = tableName;
89
+ const thisTable = model.dbName ?? model.name;
68
90
  const isImplicitM2M = f.isList && (f.relationFromFields?.length ?? 0) === 0;
69
91
  const relType = isImplicitM2M
70
92
  ? "belongsToMany"
@@ -74,7 +96,7 @@ export class PrismaToLaravelModelGenerator {
74
96
  let pivotTable;
75
97
  if (relType === "belongsToMany") {
76
98
  pivotTable = [thisTable, relatedTable]
77
- .map((t) => t.toLowerCase())
99
+ .map(t => t.toLowerCase())
78
100
  .sort()
79
101
  .join("_");
80
102
  }
@@ -87,22 +109,23 @@ export class PrismaToLaravelModelGenerator {
87
109
  pivotTable,
88
110
  };
89
111
  });
90
- // 2c) Interfaces from @type annotations
112
+ /* ── 2.5 Interfaces from @type annotations ────────────────────── */
91
113
  const interfaces = {};
92
114
  for (const prop of properties) {
93
115
  if (prop.typeAnnotation) {
94
116
  interfaces[prop.name] = { ...prop.typeAnnotation };
95
117
  }
96
118
  }
119
+ /* ── 2.6 Final ModelDefinition ────────────────────────────────── */
97
120
  return {
121
+ className: model.name,
122
+ tableName: model.dbName ?? model.name,
98
123
  guarded,
99
- className,
100
- tableName,
101
124
  properties,
102
125
  relations,
103
126
  enums,
104
127
  interfaces,
105
- with: withList.length ? withList : undefined
128
+ with: withList.length ? withList : undefined,
106
129
  };
107
130
  });
108
131
  return { models, enums };
@@ -51,7 +51,7 @@ export async function generateLaravelModels(options) {
51
51
  // 2) Load stubs (allow overrides)
52
52
  const modelStub = cfg.modelStubPath
53
53
  ? path.resolve(process.cwd(), cfg.modelStubPath)
54
- : path.resolve(__dirname, "../../../stubs/simple-model.stub");
54
+ : path.resolve(__dirname, "../../../stubs/model.stub");
55
55
  const enumStub = cfg.enumStubPath
56
56
  ? path.resolve(process.cwd(), cfg.enumStubPath)
57
57
  : path.resolve(__dirname, "../../../stubs/enums.stub");
@@ -67,6 +67,8 @@ export async function generateLaravelModels(options) {
67
67
  }
68
68
  // 5) Write model files
69
69
  for (const model of models) {
70
+ model.imports = model.properties.filter(item => item.enumRef).map(item => `use App\\Enums\\${item.enumRef};`);
71
+ //----
70
72
  const content = buildModelContent(model);
71
73
  const modelPhp = printer.printModel(model, enums, content);
72
74
  const modelFile = path.join(modelsDir, `${model.className}.php`);
@@ -105,9 +105,21 @@ export function buildModelContent(model) {
105
105
  if (model.properties.some(p => p.cast || p.enumRef)) {
106
106
  lines.push(`protected $casts = [\n${model.properties
107
107
  .filter(p => p.cast || p.enumRef)
108
- .map(p => ` '${p.name}' => ${p.enumRef ? `\\App\\Enums\\${p.enumRef}::class` : `'${p.cast}'`}`)
108
+ .map(p => ` '${p.name}' => ${p.enumRef ? `${p.enumRef}::class` : `'${p.cast}'`}`)
109
109
  .join(",\n")}\n ];`);
110
110
  }
111
+ // — Interfaces metadata slot —
112
+ if (model.interfaces && Object.keys(model.interfaces).length) {
113
+ lines.push(` public array $interfaces = [`);
114
+ for (const [key, info] of Object.entries(model.interfaces)) {
115
+ const parts = [];
116
+ if (info.import)
117
+ parts.push(`import: '${info.import}'`);
118
+ parts.push(`type: '${info.type}'`);
119
+ lines.push(` '${key}' => { ${parts.join(', ')} },`);
120
+ }
121
+ lines.push(` ];`);
122
+ }
111
123
  // 4) Relations (unchanged)
112
124
  for (const rel of model.relations) {
113
125
  const args = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prisma-laravel-migrate",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "Generate laravel migrations and/or models using prisma files",
5
5
  "bin": {
6
6
  "prisma-laravel-migrations": "./dist/cli/index.js",