prisma-laravel-migrate 0.0.13 → 0.0.15
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 +345 -26
- package/dist/cli/cli.js +245 -0
- package/dist/cli/{index.js → migrator.index.js} +1 -1
- package/dist/diff-writer/writer.js +41 -22
- package/dist/generator/migrator/column-definition.js +1 -1
- package/dist/generator/migrator/index.js +39 -21
- package/dist/generator/migrator/rules.js +1 -1
- package/dist/generator/modeler/generator.js +26 -0
- package/dist/generator/modeler/index.js +43 -21
- package/dist/index.js +9 -207
- package/dist/printer/migrations.js +29 -26
- package/dist/printer/models.js +4 -1
- package/dist/utils/build.js +87 -0
- package/dist/utils/loadSharedCfg.js +18 -0
- package/dist/{generator → utils}/utils.js +8 -59
- package/package.json +9 -5
- package/dist/generator/migrator/column-definition-types.js +0 -2
- package/dist/global.js +0 -2
- package/dist/test.js +0 -2
- package/dist/tsconfig.tsbuildinfo +0 -1
- /package/dist/{generator/migrator → utils}/sort.js +0 -0
package/README.md
CHANGED
|
@@ -14,45 +14,181 @@ npm install prisma-laravel-migrate --save-dev
|
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
---
|
|
17
|
+
🛠️ Configuration layers
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
The generators read options in **three tiers (highest → lowest)**:
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
1. **Environment override** – `PRISMA_LARAVEL_CFG=/path/to/laravel.config.js`
|
|
22
|
+
2. **Shared project file** – **`prisma/laravel.config.js`** (auto‑loaded if present)
|
|
23
|
+
3. **`generator … { … }` blocks** in `schema.prisma`
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
A key in tier ① shadows the same key in ② and ③; tier ② shadows tier ③.
|
|
26
|
+
|
|
27
|
+
### 📁 Shared project file — `prisma/laravel.config.js`
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
/** Global to all Prisma‑Laravel generators */
|
|
31
|
+
module.exports = {
|
|
32
|
+
/* -- table decoration ------------------------------- */
|
|
33
|
+
tablePrefix: "tx_",
|
|
34
|
+
tableSuffix: "_arch",
|
|
35
|
+
|
|
36
|
+
/* -- default stub root ------------------------------ */
|
|
37
|
+
stubDir: "prisma/stubs",
|
|
26
38
|
|
|
27
|
-
|
|
28
|
-
|
|
39
|
+
/* -- global dry‑run --------------------------------- */
|
|
40
|
+
noEmit: false,
|
|
41
|
+
|
|
42
|
+
/* -- override default outputs ----------------------- */
|
|
43
|
+
output: {
|
|
44
|
+
migrations: "database/migrations",
|
|
45
|
+
models: "app/Models",
|
|
46
|
+
enums: "app/Enums"
|
|
47
|
+
},
|
|
29
48
|
|
|
30
|
-
|
|
31
|
-
|
|
49
|
+
/* -- per‑generator overrides ------------------------ */
|
|
50
|
+
migrate: {
|
|
51
|
+
groups: "./prisma/migrate-groups.js",
|
|
52
|
+
rules : "./prisma/custom-rules.js"
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
modeler: {
|
|
56
|
+
groups: [
|
|
57
|
+
{ stubFile: "audit.stub", tables: ["logs","audit_trails"] }
|
|
58
|
+
],
|
|
59
|
+
outputEnumDir: "app/Enums",
|
|
60
|
+
overwriteExisting: true
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
<details>
|
|
66
|
+
<summary>Type reference</summary>
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
export interface Rule {
|
|
70
|
+
test(
|
|
71
|
+
def: ColumnDefinition,
|
|
72
|
+
allDefs: ColumnDefinition[],
|
|
73
|
+
dmmf: DMMF.Document
|
|
74
|
+
): boolean;
|
|
75
|
+
render(
|
|
76
|
+
def: ColumnDefinition,
|
|
77
|
+
allDefs: ColumnDefinition[],
|
|
78
|
+
dmmf: DMMF.Document
|
|
79
|
+
): Render;
|
|
80
|
+
}
|
|
81
|
+
/* ------------------------------------------------------------
|
|
82
|
+
* Re-usable stub-group description
|
|
83
|
+
* ---------------------------------------------------------- */
|
|
84
|
+
export interface StubGroupConfig {
|
|
85
|
+
/** Path relative to stubDir/<type>/ (e.g. "auth.stub") */
|
|
86
|
+
stubFile: string;
|
|
87
|
+
tables: string[]; // ["users","accounts",…] or enum names
|
|
32
88
|
}
|
|
33
89
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
90
|
+
/* ------------------------------------------------------------
|
|
91
|
+
* Per-generator overrides (migration / modeler)
|
|
92
|
+
* ---------------------------------------------------------- */
|
|
93
|
+
export interface LaravelGeneratorConfig {
|
|
94
|
+
|
|
95
|
+
/** Override stubDir only for this generator */
|
|
96
|
+
stubDir?: string;
|
|
97
|
+
|
|
98
|
+
/** Where the generated PHP goes (overrides block) */
|
|
99
|
+
outputDir?: string;
|
|
100
|
+
|
|
101
|
+
overwriteExisting?: boolean;
|
|
37
102
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Stub grouping:
|
|
105
|
+
* • string – path to a JS module exporting StubGroupConfig[]
|
|
106
|
+
* • array – the group definitions themselves
|
|
107
|
+
*/
|
|
108
|
+
groups?: string | StubGroupConfig[];
|
|
41
109
|
|
|
42
|
-
|
|
43
|
-
|
|
110
|
+
/** Skip file emission for *this* generator only */
|
|
111
|
+
noEmit?: boolean;
|
|
44
112
|
}
|
|
113
|
+
|
|
114
|
+
/* ------------------------------------------------------------
|
|
115
|
+
* Top-level shared config (visible to all generators)
|
|
116
|
+
* ---------------------------------------------------------- */
|
|
117
|
+
export interface LaravelSharedConfig {
|
|
118
|
+
/** Table name decoration */
|
|
119
|
+
tablePrefix?: string;
|
|
120
|
+
tableSuffix?: string;
|
|
121
|
+
|
|
122
|
+
/** Default stub root (migration/, model/, enum/) */
|
|
123
|
+
stubDir?: string;
|
|
124
|
+
|
|
125
|
+
/** Global “don’t write files” switch */
|
|
126
|
+
noEmit?: boolean;
|
|
127
|
+
|
|
128
|
+
/** Override default output folders */
|
|
129
|
+
output?: {
|
|
130
|
+
migrations?: string;
|
|
131
|
+
models?: string;
|
|
132
|
+
enums?: string;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/** Per-generator fine-tuning */
|
|
136
|
+
migrate?: Partial<MigratorConfigOverride>;
|
|
137
|
+
modeler?: Partial<ModelConfigOverride>;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
/* --- Migrator-specific extra keys ---------------------------------------- */
|
|
142
|
+
export interface MigratorConfigOverride extends LaravelGeneratorConfig {
|
|
143
|
+
/**
|
|
144
|
+
* Custom migration rules:
|
|
145
|
+
* • string – path to JS module exporting Rule[]
|
|
146
|
+
* • Rule[] – rules array inline
|
|
147
|
+
*/
|
|
148
|
+
rules?: string | Rule[];
|
|
149
|
+
|
|
150
|
+
stubPath?: string;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
export interface ModelConfigOverride extends LaravelGeneratorConfig {
|
|
155
|
+
modelStubPath?: string;
|
|
156
|
+
enumStubPath?: string;
|
|
157
|
+
/** Extra folder for enums (modeler only) */
|
|
158
|
+
outputEnumDir?: string;
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
</details>
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## 🛠️ Prisma Generator Setup (quick)
|
|
167
|
+
|
|
168
|
+
Even with the shared file you may still keep minimal blocks in `schema.prisma`:
|
|
169
|
+
|
|
170
|
+
```prisma
|
|
171
|
+
generator migrate {
|
|
172
|
+
provider = "prisma-laravel-migrations"
|
|
173
|
+
stubDir = "./prisma/stubs"
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
generator modeler {
|
|
177
|
+
provider = "prisma-laravel-models"
|
|
178
|
+
stubDir = "./prisma/stubs"
|
|
45
179
|
```
|
|
46
180
|
|
|
47
181
|
### Field Reference
|
|
48
182
|
|
|
49
|
-
| Key
|
|
50
|
-
|
|
|
51
|
-
| `outputDir / output`
|
|
52
|
-
| `outputEnumDir`
|
|
53
|
-
| `stubDir`
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
183
|
+
| Key | Notes |
|
|
184
|
+
| ---------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
185
|
+
| `outputDir / output` | Destination folder (`outputDir` overrides `output`). |
|
|
186
|
+
| `outputEnumDir` | (modeler) directory for generated enum classes. |
|
|
187
|
+
| `stubDir` | Root stub folder (`migration/`, `model/`, `enum/`). |
|
|
188
|
+
| `tablePrefix` | String prepended to every generated **physical** table name. |
|
|
189
|
+
| `tableSuffix` | String appended to every generated **physical** table name. |
|
|
190
|
+
| `groups` | JS module *or* inline array that maps stub files to table groups. |
|
|
191
|
+
| `noEmit` | If `true`, generator parses and validates but **does not write** any files (dry-run / CI mode). |
|
|
56
192
|
|
|
57
193
|
---
|
|
58
194
|
|
|
@@ -408,6 +544,12 @@ your Eloquent model.
|
|
|
408
544
|
| `@ignore` | Relation field | Skips generating the relationship method |
|
|
409
545
|
| `@with` (no args) | Relation field | Adds that single relation to `$with` |
|
|
410
546
|
| `@with(rel1,rel2,…)` | Model only | Adds listed relations to `$with` |
|
|
547
|
+
| **NEW** `@trait:Full\Namespace\MyTrait` | Model only | Adds `use MyTrait;` inside the class |
|
|
548
|
+
| **NEW** `@implements:Full\Interface as Alias`| Model only | Adds the interface (with alias) to the class’s `implements` list |
|
|
549
|
+
| **NEW** `@observer:App\Observers\FooObserver`| Model only | Generates a `boot()` method that calls `static::observe(FooObserver::class);` |
|
|
550
|
+
| **NEW** `@factory:FooFactory` | Model only | Adds `use HasFactory;` and `protected static string $factory = FooFactory::class;` |
|
|
551
|
+
| **NEW** `@touch{col1,col2}` | Model only | Generates `protected $touches = ['col1','col2'];` |
|
|
552
|
+
| **NEW** `@appends{attr1,attr2}` | Model only | Generates `protected $appends = ['attr1','attr2'];` plus `$this->getAttrAttribute()` stubs |
|
|
411
553
|
|
|
412
554
|
> **Syntax options**
|
|
413
555
|
> • Inline:
|
|
@@ -418,7 +560,15 @@ your Eloquent model.
|
|
|
418
560
|
> `/// @fillable{name,balance}`
|
|
419
561
|
> • Model eager‑load:
|
|
420
562
|
> `/// @with(posts,roles)`
|
|
421
|
-
|
|
563
|
+
> • **Traits / implements**:
|
|
564
|
+
> `/// @trait:Illuminate\Auth\Authenticatable`
|
|
565
|
+
> `/// @implements:Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract`
|
|
566
|
+
> • **Observers / factory**:
|
|
567
|
+
> `/// @observer:App\Observers\UserObserver`
|
|
568
|
+
> `/// @factory:UserFactory`
|
|
569
|
+
> • **Touches / appends**:
|
|
570
|
+
> `/// @touch{company,profile}`
|
|
571
|
+
> `/// @appends{full_name,age}`
|
|
422
572
|
---
|
|
423
573
|
|
|
424
574
|
#### Example
|
|
@@ -467,6 +617,71 @@ protected $with = ['posts','comments'];
|
|
|
467
617
|
`@ignore` prevents the `company()` relation method.
|
|
468
618
|
Combine multiple inline directives; they’re processed left‑to‑right.
|
|
469
619
|
|
|
620
|
+
|
|
621
|
+
#### Example: Combined Directives
|
|
622
|
+
|
|
623
|
+
```prisma
|
|
624
|
+
/// @fillable{name,balance}
|
|
625
|
+
/// @hidden{secretToken}
|
|
626
|
+
/// @guarded{password,apiToken}
|
|
627
|
+
/// @trait:Illuminate\Auth\Authenticatable
|
|
628
|
+
/// @implements:Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract
|
|
629
|
+
/// @observer:App\Observers\UserObserver
|
|
630
|
+
/// @factory:UserFactory
|
|
631
|
+
/// @touch{company}
|
|
632
|
+
/// @appends{full_name}
|
|
633
|
+
model User {
|
|
634
|
+
id Int @id @default(autoincrement())
|
|
635
|
+
email String @unique /// @hidden @fillable
|
|
636
|
+
password String /// @hidden @guarded
|
|
637
|
+
balance Decimal @default(0.0) /// @cast{decimal:2}
|
|
638
|
+
profile Json? /// @type{ import:'@types/forms', type:'ProfileDTO' }
|
|
639
|
+
company Company? @relation(fields:[companyId], references:[id]) /// @ignore
|
|
640
|
+
companyId Int?
|
|
641
|
+
posts Post[] /// @with
|
|
642
|
+
}
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
**Generated output (simplified)**
|
|
646
|
+
|
|
647
|
+
```php
|
|
648
|
+
use Illuminate\Auth\Authenticatable;
|
|
649
|
+
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
650
|
+
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
|
|
651
|
+
use App\Observers\UserObserver;
|
|
652
|
+
|
|
653
|
+
class User extends Model implements AuthenticatableContract
|
|
654
|
+
{
|
|
655
|
+
use HasFactory, Authenticatable;
|
|
656
|
+
|
|
657
|
+
protected $fillable = ['name','balance','email'];
|
|
658
|
+
protected $hidden = ['secretToken','password'];
|
|
659
|
+
protected $guarded = ['password','apiToken'];
|
|
660
|
+
|
|
661
|
+
protected $casts = ['balance' => 'decimal:2'];
|
|
662
|
+
protected $touches = ['company'];
|
|
663
|
+
protected $appends = ['full_name'];
|
|
664
|
+
protected static string $factory = UserFactory::class;
|
|
665
|
+
|
|
666
|
+
protected static function boot()
|
|
667
|
+
{
|
|
668
|
+
parent::boot();
|
|
669
|
+
static::observe(UserObserver::class);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
public function getFullNameAttribute()
|
|
673
|
+
{
|
|
674
|
+
// TODO
|
|
675
|
+
return $this->attributes['full_name'] ?? null;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
public function posts()
|
|
679
|
+
{
|
|
680
|
+
return $this->hasMany(Post::class);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
```
|
|
684
|
+
|
|
470
685
|
---
|
|
471
686
|
|
|
472
687
|
## 💡 Tips
|
|
@@ -477,6 +692,110 @@ Combine multiple inline directives; they’re processed left‑to‑right.
|
|
|
477
692
|
|
|
478
693
|
---
|
|
479
694
|
|
|
695
|
+
## 📚 Programmatic API (ES / TypeScript)
|
|
696
|
+
|
|
697
|
+
Use the library directly in a script or build tool instead of the CLI.
|
|
698
|
+
|
|
699
|
+
```ts
|
|
700
|
+
import {
|
|
701
|
+
generateLaravelSchema,
|
|
702
|
+
generateLaravelModels,
|
|
703
|
+
sortMigrations,
|
|
704
|
+
} from 'prisma-laravel-migrate';
|
|
705
|
+
import { readFileSync, writeFileSync } from 'fs';
|
|
706
|
+
import { getDMMF } from '@prisma/sdk';
|
|
707
|
+
|
|
708
|
+
(async () => {
|
|
709
|
+
/* 1. Load schema & build DMMF */
|
|
710
|
+
const schemaPath = 'prisma/schema.prisma';
|
|
711
|
+
const datamodel = readFileSync(schemaPath, 'utf8');
|
|
712
|
+
const dmmf = await getDMMF({ datamodel });
|
|
713
|
+
|
|
714
|
+
/* 2. Run generators entirely in-memory */
|
|
715
|
+
const migrations = generateLaravelSchema({
|
|
716
|
+
dmmf,
|
|
717
|
+
schemaPath, // ← always pass this
|
|
718
|
+
generator : { config: {} } as any,
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
const { models, enums } = generateLaravelModels({
|
|
722
|
+
dmmf,
|
|
723
|
+
schemaPath,
|
|
724
|
+
generator : { config: {} } as any,
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
/* 3. Inspect or write output */
|
|
728
|
+
sortMigrations(migrations).forEach(m => {
|
|
729
|
+
writeFileSync(`./out/${m.tableName}.php`, m.statements.join('\n'), 'utf8');
|
|
730
|
+
});
|
|
731
|
+
})();
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
### Custom migration rules in code
|
|
735
|
+
|
|
736
|
+
```ts
|
|
737
|
+
import { Rule } from 'prisma-laravel-migrate';
|
|
738
|
+
|
|
739
|
+
const softDeleteRule: Rule = {
|
|
740
|
+
test: d => d.name === 'deleted_at' && d.migrationType === 'timestamp',
|
|
741
|
+
render: () => ({
|
|
742
|
+
column : 'deleted_at',
|
|
743
|
+
snippet: ["$table->timestamp('deleted_at')->nullable();"],
|
|
744
|
+
}),
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
generateLaravelSchema({
|
|
748
|
+
dmmf,
|
|
749
|
+
schemaPath: 'prisma/schema.prisma',
|
|
750
|
+
generator : { config: { rules: [softDeleteRule] } } as any,
|
|
751
|
+
});
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
### Public exports
|
|
755
|
+
|
|
756
|
+
| Export | Purpose |
|
|
757
|
+
| -------------------------------- | --------------------------------------------------- |
|
|
758
|
+
| `generateLaravelSchema` | Build migration objects (and optionally write files)|
|
|
759
|
+
| `generateLaravelModels` | Build model + enum definitions |
|
|
760
|
+
| `sortMigrations` | Topologically sort migrations by FK dependencies |
|
|
761
|
+
| `Rule` | Type helper for custom migration shortcuts |
|
|
762
|
+
| _types_ (`column-definition-types`, `laravel-config`) | Full TypeScript typings |
|
|
763
|
+
|
|
764
|
+
```ts
|
|
765
|
+
import {
|
|
766
|
+
ColumnDefinition,
|
|
767
|
+
LaravelSharedConfig,
|
|
768
|
+
MigratorConfigOverride,
|
|
769
|
+
} from 'prisma-laravel-migrate';
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
> **Heads-up:**
|
|
773
|
+
> `generateLaravelSchema` and `generateLaravelModels` **write files by default**
|
|
774
|
+
> (honouring the `outputDir` settings).
|
|
775
|
+
> If you only want the in-memory objects—e.g. to capture the returned
|
|
776
|
+
> `migrations`, `models`, or `enums` arrays—set
|
|
777
|
+
> `noEmit: true` in either
|
|
778
|
+
>
|
|
779
|
+
> * the per-call `generator.config` object:
|
|
780
|
+
> ```ts
|
|
781
|
+
> generateLaravelSchema({
|
|
782
|
+
> dmmf,
|
|
783
|
+
> schemaPath,
|
|
784
|
+
> generator: { config: { noEmit: true } } as any,
|
|
785
|
+
> });
|
|
786
|
+
> ```
|
|
787
|
+
> * **or** in `prisma/laravel.config.js`:
|
|
788
|
+
> ```js
|
|
789
|
+
> module.exports = {
|
|
790
|
+
> migrate: { noEmit: true },
|
|
791
|
+
> modeler: { noEmit: true },
|
|
792
|
+
> };
|
|
793
|
+
> ```
|
|
794
|
+
> This prevents any files from being created or overwritten while still
|
|
795
|
+
> returning the fully-populated data structures for custom processing.
|
|
796
|
+
|
|
797
|
+
---
|
|
798
|
+
|
|
480
799
|
## 📜 License
|
|
481
800
|
|
|
482
801
|
MIT — Happy scaffolding! 🎉
|
package/dist/cli/cli.js
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import * as dmf from '@prisma/sdk';
|
|
7
|
+
import { generateLaravelSchema } from '../generator/migrator/index.js';
|
|
8
|
+
import { generateLaravelModels } from '../generator/modeler/index.js';
|
|
9
|
+
import { existsSync, readFileSync } from 'fs';
|
|
10
|
+
import { spawn } from 'child_process';
|
|
11
|
+
const cli = new Command();
|
|
12
|
+
cli
|
|
13
|
+
.name('prisma-laravel-cli')
|
|
14
|
+
.description('Initialize and customize Prisma→Laravel generators & stubs')
|
|
15
|
+
.version('0.1.0');
|
|
16
|
+
function generatorBlock(base, // singular
|
|
17
|
+
stubDirRel, extras = []) {
|
|
18
|
+
const name = `${base}s`; // migrations / models
|
|
19
|
+
const provider = `prisma-laravel-${name}s`; // prisma-laravel-migrations
|
|
20
|
+
const extra = extras.length ? "\n " + extras.join("\n ") : "";
|
|
21
|
+
return `
|
|
22
|
+
generator ${name} {
|
|
23
|
+
provider = "${provider}"
|
|
24
|
+
stubDir = "${stubDirRel}"${extra}
|
|
25
|
+
}
|
|
26
|
+
`;
|
|
27
|
+
}
|
|
28
|
+
//
|
|
29
|
+
// init
|
|
30
|
+
//
|
|
31
|
+
cli
|
|
32
|
+
.command("init")
|
|
33
|
+
.description("Inject generators into schema.prisma and scaffold stubs/")
|
|
34
|
+
.option("-s, --schema <path>", "Prisma schema file", "prisma/schema.prisma")
|
|
35
|
+
.action(async (opts) => {
|
|
36
|
+
/* 1. Paths ------------------------------------------------------ */
|
|
37
|
+
const schemaPath = path.resolve(process.cwd(), opts.schema);
|
|
38
|
+
const schemaDir = path.dirname(schemaPath); // prisma/
|
|
39
|
+
const userStubs = path.join(schemaDir, "stubs"); // prisma/stubs
|
|
40
|
+
const stubDirRel = "./" + path.relative(schemaDir, userStubs).replace(/\\/g, "/"); // "./stubs"
|
|
41
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
42
|
+
const pkgStubs = path.resolve(__dirname, "../stubs"); // bundled stubs
|
|
43
|
+
/* 2. Load schema.prisma ---------------------------------------- */
|
|
44
|
+
let schema = await fs.readFile(schemaPath, "utf-8");
|
|
45
|
+
const hasGen = (base) => new RegExp(`generator\\s+${base}s\\s*\\{`).test(schema);
|
|
46
|
+
/* 3. Inject generator blocks if missing ------------------------ */
|
|
47
|
+
if (!hasGen("migration")) {
|
|
48
|
+
schema += generatorBlock("migration", stubDirRel, [
|
|
49
|
+
'outputDir = "database/migrations"',
|
|
50
|
+
]);
|
|
51
|
+
console.log("➡️ Added migrations generator");
|
|
52
|
+
}
|
|
53
|
+
if (!hasGen("model")) {
|
|
54
|
+
schema += generatorBlock("model", stubDirRel, [
|
|
55
|
+
'outputDir = "app/Models"',
|
|
56
|
+
'outputEnumDir = "app/Enums"',
|
|
57
|
+
]);
|
|
58
|
+
console.log("➡️ Added models generator");
|
|
59
|
+
}
|
|
60
|
+
await fs.writeFile(schemaPath, schema, "utf-8");
|
|
61
|
+
console.log(`✅ Updated ${schemaPath}`);
|
|
62
|
+
/* 4. Copy default stub files ---------------------------------- */
|
|
63
|
+
const stubTypes = ["migration", "model", "enum"];
|
|
64
|
+
for (const type of stubTypes) {
|
|
65
|
+
const targetDir = path.join(userStubs, type);
|
|
66
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
67
|
+
/* index.stub */
|
|
68
|
+
const src = path.join(pkgStubs, `${type}.stub`);
|
|
69
|
+
const dst = path.join(targetDir, "index.stub");
|
|
70
|
+
try {
|
|
71
|
+
await fs.access(dst);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
await fs.copyFile(src, dst);
|
|
75
|
+
console.log(`➡️ Copied ${type}.stub → stubs/${type}/index.stub`);
|
|
76
|
+
}
|
|
77
|
+
/* simple-model.stub */
|
|
78
|
+
if (type === "model") {
|
|
79
|
+
const src2 = path.join(pkgStubs, "simple-model.stub");
|
|
80
|
+
const dst2 = path.join(targetDir, "simple-model.stub");
|
|
81
|
+
try {
|
|
82
|
+
await fs.access(dst2);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
await fs.copyFile(src2, dst2);
|
|
86
|
+
console.log("➡️ Copied simple-model.stub → stubs/model/simple-model.stub");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/* 5. Create laravel.config.js if absent ------------------------ */
|
|
91
|
+
const cfgPath = path.join(schemaDir, "prisma-laravel.config.js");
|
|
92
|
+
try {
|
|
93
|
+
await fs.access(cfgPath);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
const cfgTemplate = `
|
|
97
|
+
// prisma/prisma-laravel.config.js
|
|
98
|
+
module.exports = {
|
|
99
|
+
tablePrefix: "", // e.g. "tx_"
|
|
100
|
+
tableSuffix: "", // e.g. "_arch"
|
|
101
|
+
stubDir: "${stubDirRel}",
|
|
102
|
+
// migrate: { noEmit: false },
|
|
103
|
+
// modeler: { noEmit: false }
|
|
104
|
+
};
|
|
105
|
+
`;
|
|
106
|
+
await fs.writeFile(cfgPath, cfgTemplate.trimStart(), "utf-8");
|
|
107
|
+
console.log("➡️ Created laravel.config.js");
|
|
108
|
+
}
|
|
109
|
+
console.log("🎉 Initialization complete!");
|
|
110
|
+
});
|
|
111
|
+
//
|
|
112
|
+
// customize
|
|
113
|
+
//
|
|
114
|
+
cli
|
|
115
|
+
.command('customize')
|
|
116
|
+
.alias('c')
|
|
117
|
+
.description('Scaffold per-table stub files from index.stub')
|
|
118
|
+
.option('-s, --schema <path>', 'Prisma schema file', 'prisma/schema.prisma')
|
|
119
|
+
.option('-t, --types <list>', 'Comma-separated: migration,model,enum', (val) => val.split(',').map(s => s.trim().toLowerCase()), [])
|
|
120
|
+
.option('-n, --names <list>', 'Comma-separated base names', (val) => val.split(',').map(s => s.trim()), [])
|
|
121
|
+
.action(async (opts) => {
|
|
122
|
+
const want = opts.types;
|
|
123
|
+
const bases = opts.names;
|
|
124
|
+
if (!want.length)
|
|
125
|
+
throw new Error('Specify at least one type with -t');
|
|
126
|
+
if (!bases.length)
|
|
127
|
+
throw new Error('Specify at least one name with -n');
|
|
128
|
+
// enums stand alone
|
|
129
|
+
if (want.includes('enum') && want.length > 1) {
|
|
130
|
+
throw new Error('`enum` cannot be combined with other types');
|
|
131
|
+
}
|
|
132
|
+
const schemaDir = path.dirname(path.resolve(process.cwd(), opts.schema));
|
|
133
|
+
const stubRoot = path.join(schemaDir, 'stubs');
|
|
134
|
+
const doBoth = want.includes('migration') && want.includes('model');
|
|
135
|
+
for (const t of want) {
|
|
136
|
+
if (t === 'enum') {
|
|
137
|
+
const dir = path.join(stubRoot, 'enum');
|
|
138
|
+
const idx = path.join(dir, 'index.stub');
|
|
139
|
+
await fs.mkdir(dir, { recursive: true });
|
|
140
|
+
for (const name of bases) {
|
|
141
|
+
const dst = path.join(dir, `${name}.stub`);
|
|
142
|
+
try {
|
|
143
|
+
await fs.access(dst);
|
|
144
|
+
console.log(`🟡 Skip enum/${name}.stub`);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
await fs.copyFile(idx, dst);
|
|
148
|
+
console.log(`✅ Created enum/${name}.stub`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
for (const kind of doBoth ? ['migration', 'model'] : [t]) {
|
|
154
|
+
const dir = path.join(stubRoot, kind);
|
|
155
|
+
const idx = path.join(dir, 'index.stub');
|
|
156
|
+
await fs.mkdir(dir, { recursive: true });
|
|
157
|
+
for (const base of bases) {
|
|
158
|
+
const dst = path.join(dir, `${base}.stub`);
|
|
159
|
+
try {
|
|
160
|
+
await fs.access(dst);
|
|
161
|
+
console.log(`🟡 Skip ${kind}/${base}.stub`);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
await fs.copyFile(idx, dst);
|
|
165
|
+
console.log(`✅ Created ${kind}/${base}.stub`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
console.log('🎉 Customize complete!');
|
|
171
|
+
});
|
|
172
|
+
//
|
|
173
|
+
// proxy to Prisma generate
|
|
174
|
+
//
|
|
175
|
+
cli
|
|
176
|
+
.command('gen')
|
|
177
|
+
.description('Run Prisma generate, or skip it (--skipGenerate), then run Laravel generators')
|
|
178
|
+
.option('--config <path>', 'Path to prisma-laravel config file')
|
|
179
|
+
.option('--skipGenerate', 'Only run the Laravel generators (no Prisma generate)')
|
|
180
|
+
.action(async (opts) => {
|
|
181
|
+
const configPath = opts.config
|
|
182
|
+
? path.resolve(process.cwd(), opts.config)
|
|
183
|
+
: path.resolve(process.cwd(), 'prisma-laravel.config.js');
|
|
184
|
+
if (!existsSync(configPath)) {
|
|
185
|
+
console.error(`❌ Config file not found: ${configPath}`);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
const cfgMod = await import(configPath);
|
|
189
|
+
const cfg = cfgMod.default ?? cfgMod;
|
|
190
|
+
if (!cfg.generator?.config) {
|
|
191
|
+
console.error('❌ `generator.config` is required in your config.');
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
const schemaPrismaPath = cfg.schemaPath
|
|
195
|
+
? path.resolve(process.cwd(), cfg.schemaPath)
|
|
196
|
+
: path.resolve(process.cwd(), 'prisma/schema.prisma');
|
|
197
|
+
if (!existsSync(schemaPrismaPath)) {
|
|
198
|
+
console.error(`❌ Schema not found: ${schemaPrismaPath}`);
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
const run = async () => {
|
|
202
|
+
const datamodel = readFileSync(schemaPrismaPath, 'utf-8');
|
|
203
|
+
const sdk = dmf.default ?? dmf;
|
|
204
|
+
const { dmmf } = await sdk.getDMMF({ datamodel });
|
|
205
|
+
await generateLaravelSchema({
|
|
206
|
+
dmmf,
|
|
207
|
+
//@ts-ignore
|
|
208
|
+
generator: { config: cfg.generator.config },
|
|
209
|
+
otherGenerators: [],
|
|
210
|
+
schemaPath: schemaPrismaPath,
|
|
211
|
+
datasources: [],
|
|
212
|
+
datamodel,
|
|
213
|
+
version: '',
|
|
214
|
+
});
|
|
215
|
+
await generateLaravelModels({
|
|
216
|
+
dmmf,
|
|
217
|
+
//@ts-ignore
|
|
218
|
+
generator: { config: cfg.generator.config },
|
|
219
|
+
otherGenerators: [],
|
|
220
|
+
schemaPath: schemaPrismaPath,
|
|
221
|
+
datasources: [],
|
|
222
|
+
datamodel,
|
|
223
|
+
version: '',
|
|
224
|
+
});
|
|
225
|
+
};
|
|
226
|
+
if (opts.skipGenerate) {
|
|
227
|
+
await run();
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
const prisma = spawn('npx', ['prisma', 'generate'], {
|
|
231
|
+
stdio: 'inherit',
|
|
232
|
+
shell: true,
|
|
233
|
+
});
|
|
234
|
+
prisma.on('exit', (code) => {
|
|
235
|
+
if (code !== 0)
|
|
236
|
+
process.exit(code);
|
|
237
|
+
run().catch(e => {
|
|
238
|
+
console.error('❌ Gen failed:', e.message ?? e);
|
|
239
|
+
process.exit(1);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
cli.parse(process.argv);
|
|
245
|
+
//# sourceMappingURL=cli.js.map
|