prisma-laravel-migrate 0.0.6 → 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,329 +1,503 @@
1
- # Prisma Laravel Migrate
2
-
3
- A generator toolkit that translates your **Prisma schema** into Laravel-compatible
4
- **Database Migrations**, **Eloquent Models**, and **Enum classes**.
5
- Everything is written in strict TypeScript and fully customizable through stubs, grouping, and marker-based injection.
6
-
7
- ---
8
-
9
- ## 📦 Installation
10
-
11
- ```bash
12
- npm install prisma-laravel-migrate --save-dev
13
-
14
- (Requires prisma in the same project.)
15
-
16
-
17
- ---
18
-
19
- 🛠️ Prisma Generator Setup
20
-
21
- Insert two generator blocks in schema.prisma:
22
-
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 // set true to skip writing files
31
- groups = "./prisma/group-stubs.js" // optional group mapping
32
- }
33
-
34
- generator modeler {
35
- provider = "prisma-laravel-models"
36
- stubDir = "./prisma/stubs"
37
- output = "app/Models"
38
- outputDir = "app/Models" // takes precedence
39
- outputEnumDir = "app/Enums" // enums folder (optional)
40
- startMarker = "// <prisma-laravel:start>"
41
- endMarker = "// <prisma-laravel:end>"
42
- noEmit = false
43
- groups = "./prisma/group-stubs.js"
44
- }
45
-
46
- Field Reference
47
-
48
- Key Notes
49
-
50
- output / outputDir Destination folder; outputDir overrides output.
51
- outputEnumDir (modeler) folder for PHP enum classes.
52
- stubDir Root stubs folder (migration/, model/, enum/).
53
- startMarker / endMarker Region markers the generator will update.
54
- groups Path to JS module exporting stub-group mappings.
55
- noEmit If true, generator parses but writes no files.
56
-
57
-
58
-
59
- ---
60
-
61
- 📁 Stub Folder Layout
62
-
63
- Running
64
-
65
- npx prisma-laravel-cli init --schema=prisma/schema.prisma
66
-
67
- creates:
68
-
69
- prisma/stubs/
70
- ├── migration/index.stub
71
- ├── model/index.stub
72
- ├── model/simple-model.stub
73
- └── enum/index.stub
74
-
75
- Copy log (example):
76
-
77
- ➡️ Copied migration.stub stubs/migration/index.stub
78
- ➡️ Copied model.stub → stubs/model/index.stub
79
- ➡️ Copied enums.stub → stubs/enum/index.stub
80
- ➡️ Copied simple-model.stub → stubs/model/simple-model.stub
81
-
82
- Override a single table / enum by adding
83
- stubs/<type>/<name>.stub (e.g. stubs/model/users.stub).
84
-
85
-
86
- ---
87
-
88
- 🔧 CLI Commands
89
-
90
- Command Purpose
91
-
92
- init Injects generator blocks & scaffold stub folders.
93
- customize Create per-table stub overrides.
94
- gen Run prisma generate and then Laravel generators.
95
-
96
-
97
- init
98
-
99
- npx prisma-laravel-cli init --schema=prisma/schema.prisma
100
-
101
- customize
102
-
103
- # create migration+model overrides for users & accounts
104
- npx prisma-laravel-cli customize -t migration,model -n users,accounts
105
-
106
- # create enum overrides
107
- npx prisma-laravel-cli customize -t enum -n UserStatus,RoleType
108
-
109
- migration & model may be combined; enum must be separate.
110
-
111
- gen
112
-
113
- # run prisma generate then Laravel generation
114
- npx prisma-laravel-cli gen --config=prisma/laravel.config.js
115
-
116
- # skip prisma generate step
117
- npx prisma-laravel-cli gen --config=prisma/laravel.config.js --skipGenerate
118
-
119
- prisma/laravel.config.js example:
120
-
121
- module.exports = {
122
- migrator: {
123
- outputDir: 'database/migrations',
124
- stubDir: 'prisma/stubs',
125
- groups: './prisma/group-stubs.js',
126
- },
127
- modeler: {
128
- outputDir: 'app/Models',
129
- outputEnumDir: 'app/Enums',
130
- stubDir: 'prisma/stubs',
131
- groups: './prisma/group-stubs.js',
132
- },
133
- };
134
-
135
-
136
- ---
137
-
138
- 🧩 Grouping Stubs
139
-
140
- prisma/group-stubs.js
141
-
142
- module.exports = [
143
- { stubFile: 'auth.stub', tables: ['users','accounts','password_resets'] },
144
- { stubFile: 'billing.stub', tables: ['invoices','transactions'] },
145
- ];
146
-
147
- Resolution order
148
-
149
- 1. stubs/<type>/<table>.stub
150
-
151
-
152
- 2. Matching group stub (stubFile)
153
-
154
-
155
- 3. stubs/<type>/index.stub
156
-
157
-
158
-
159
-
160
- ---
161
-
162
- Stub Customization Notes
163
-
164
- Stubs are JavaScript template literals. Escape \` and \${ } if you need them literally.
165
-
166
- Keep the ${content} placeholder inside the marker block if you want the generator to keep injecting its dynamic chunk.
167
-
168
- > Full Custom Model Stubs
169
- If you plan to hand-craft a model stub completely—removing both the ${content} placeholder and the // <prisma-laravel:start> / // <prisma-laravel:end> markers—set
170
- noEmit = true for the modeler generator (or exclude that table via custom rules).
171
- Otherwise, the generator has nowhere to inject its code and will skip the file.
172
- Leaving the markers + ${content} lets you customise everything around the generated region while the tool continues to maintain fillable lists, casts, and relations automatically.
173
-
174
-
175
-
176
-
177
- ---
178
-
179
- 📑 Default Stub Templates
180
-
181
- Enum
182
-
183
- <?php
184
-
185
- namespace App\\Enums;
186
-
187
- enum ${enumDef.name}: string
188
- {
189
- // <prisma-laravel:start>
190
- ${enumDef.values.map(v => ` case ${v} = '${v}';`).join('\\n')}
191
- // <prisma-laravel:end>
192
- }
193
-
194
- Migration
195
-
196
- <?php
197
-
198
- use Illuminate\\Database\\Migrations\\Migration;
199
- use Illuminate\\Database\\Schema\\Blueprint;
200
- use Illuminate\\Support\\Facades\\Schema;
201
-
202
- return new class extends Migration
203
- {
204
- public function up(): void
205
- {
206
- Schema::create('${tableName}', function (Blueprint $table) {
207
- // <prisma-laravel:start>
208
- ${columns}
209
- // <prisma-laravel:end>
210
- });
211
- }
212
-
213
- public function down(): void
214
- {
215
- Schema::dropIfExists('${tableName}');
216
- }
217
- };
218
-
219
-
220
- ---
221
-
222
- 🏗️ Complex Model Stub Example
223
-
224
- <?php
225
-
226
- namespace App\\Models;
227
-
228
- ${model.imports}
229
- use Illuminate\\Database\\Eloquent\\Model;
230
- use Illuminate\\Database\\Eloquent\\Relations\\{ BelongsTo, HasMany, BelongsToMany };
231
-
232
- class ${model.className} extends Model
233
- {
234
- protected $table = '${model.tableName}';
235
-
236
- /* ---------- Mass Assignment ---------- */
237
- protected $fillable = [
238
- ${model.properties.filter(p => p.fillable).map(p => ` '${p.name}',`).join('\\n')}
239
- ];
240
- protected $guarded = [
241
- ${(model.guarded ?? []).map(n => ` '${n}',`).join('\\n')}
242
- ];
243
-
244
- /* ---------- Hidden / Casts ---------- */
245
- protected $hidden = [
246
- ${model.properties.filter(p => p.hidden).map(p => ` '${p.name}',`).join('\\n')}
247
- ];
248
- protected $casts = [
249
- ${model.properties.filter(p => p.cast).map(p => ` '${p.name}' => '${p.cast}',`).join('\\n')}
250
- ${model.properties.filter(p => p.enumRef).map(p => ` '${p.name}' => ${p.enumRef}::class,`).join('\\n')}
251
- ];
252
-
253
- /* ---------- Eager Loading ---------- */
254
- protected $with = [
255
- ${(model.with ?? []).map(r => ` '${r}',`).join('\\n')}
256
- ];
257
-
258
- /* ---------- Interfaces ---------- */
259
- public array $interfaces = [
260
- ${Object.entries(model.interfaces).map(([k,i]) => ` '${k}' => {${i.import ? ` import: '${i.import}',` : ''} type: '${i.type}' },`).join('\\n')}
261
- ];
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
- ---
276
-
277
- 📚 Interfaces Metadata Example
278
-
279
- Useful for fumeapp/modeltyper integration:
280
-
281
- public array $interfaces = [
282
- 'props' => [
283
- 'import' => \"@typings/service-forms\",
284
- 'type' => 'ServiceProps',
285
- ],
286
- 'services' => [
287
- 'type' => 'Array<SMMService>',
288
- ],
289
- ];
290
-
291
- Type definition:
292
- Record<string, { import?: string; type: string }>.
293
-
294
-
295
- ---
296
-
297
- 🚀 Enum Casting
298
-
299
- The generator automatically casts Prisma enums:
300
-
301
- protected $casts = [
302
- 'status' => StatusEnum::class,
303
- ];
304
-
305
- Enums are saved to outputEnumDir if configured.
306
-
307
-
308
- ---
309
-
310
- 💡 Tips
311
-
312
- Combine migration & model in the same customize command when table names align.
313
-
314
- Escape template characters in stub files.
315
-
316
- Use noEmit: true for dry-runs or CI validation.
317
-
318
-
319
-
320
- ---
321
-
322
- 📜 License
323
-
324
- MIT Happy scaffolding! 🎉
325
-
326
- ---
327
-
328
- Copy that entire block into **README.md** and you’ll have the corrected, full documentation—including the note about fully custom model stubs.
329
-
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 markerbased 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
+
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.6",
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",