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 +502 -298
- package/dist/generator/modeler/generator.js +57 -34
- package/dist/generator/modeler/index.js +3 -1
- package/dist/generator/utils.js +13 -1
- package/package.json +1 -1
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-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
npx prisma-laravel-cli
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
{
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
<
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
//
|
|
29
|
-
const
|
|
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
|
-
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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(
|
|
58
|
+
const enumMeta = enums.find(e => e.name === field.type);
|
|
42
59
|
const phpType = enumMeta
|
|
43
|
-
?
|
|
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
|
-
|
|
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(
|
|
83
|
+
.filter(f => f.kind === "object" &&
|
|
62
84
|
f.relationName &&
|
|
63
85
|
!/@ignore\b/.test(f.documentation ?? ""))
|
|
64
|
-
.map(
|
|
65
|
-
const relatedModel = this.dmmf.datamodel.models.find(
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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/
|
|
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`);
|
package/dist/generator/utils.js
CHANGED
|
@@ -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 ?
|
|
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 = [
|