prisma-laravel-migrate 0.0.4 → 0.0.6

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 ADDED
@@ -0,0 +1,329 @@
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
+
package/dist/cli/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { generatorHandler } from "@prisma/generator-helper";
3
2
  import { generateLaravelSchema } from "../generator/migrator/index.js";
3
+ import helperPkg from '@prisma/generator-helper';
4
+ const { generatorHandler } = helperPkg;
4
5
  generatorHandler({
5
6
  onGenerate: generateLaravelSchema,
6
7
  onManifest: () => ({
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { generatorHandler } from "@prisma/generator-helper";
3
2
  import { generateLaravelModels } from "../generator/modeler/index.js";
3
+ import helperPkg from '@prisma/generator-helper';
4
+ const { generatorHandler } = helperPkg;
4
5
  generatorHandler({
5
6
  onGenerate: generateLaravelModels,
6
7
  onManifest: () => ({
@@ -2,13 +2,25 @@ import { existsSync, mkdirSync, readdirSync } from "fs";
2
2
  import path from "path";
3
3
  import { PrismaToLaravelMigrationGenerator } from "./PrismaToLaravelMigrationGenerator.js";
4
4
  import { StubMigrationPrinter } from "../../printer/migrations.js";
5
- import { sortMigrations, writeWithMarkers } from "../../generator/utils.js";
5
+ import { writeWithMarkers } from "../../generator/utils.js";
6
6
  import { fileURLToPath } from "url";
7
+ import { sortMigrations } from "./sort.js";
7
8
  export async function generateLaravelSchema(options) {
8
9
  const { dmmf, generator } = options;
9
10
  // 0) Pull config values (all come in as strings)
10
11
  // Inside generateLaravelSchema()
11
12
  const raw = (generator.config ?? {});
13
+ // 0.a) Load groups from a JS file if provided
14
+ let groups = [];
15
+ if (raw["groups"]) {
16
+ const groupsModulePath = path.resolve(process.cwd(), raw["groups"]);
17
+ const imported = await import(groupsModulePath);
18
+ const exported = imported.default ?? imported;
19
+ if (!Array.isArray(exported)) {
20
+ throw new Error(`Custom groups module must export an array, but got ${typeof exported}`);
21
+ }
22
+ groups = exported;
23
+ }
12
24
  const cfg = {
13
25
  stubPath: raw["stubPath"],
14
26
  overwriteExisting: raw["overwriteExisting"] === "true",
@@ -16,6 +28,8 @@ export async function generateLaravelSchema(options) {
16
28
  outputDir: raw["outputDir"],
17
29
  startMarker: raw["startMarker"] ?? "// <prisma-laravel:start>",
18
30
  endMarker: raw["endMarker"] ?? "// <prisma-laravel:end>",
31
+ stubDir: raw["stubDir"],
32
+ groups,
19
33
  };
20
34
  // 1) Determine and ensure output directory exists
21
35
  const baseOut = cfg.outputDir
@@ -46,10 +60,10 @@ export async function generateLaravelSchema(options) {
46
60
  const __filename = fileURLToPath(import.meta.url);
47
61
  const __dirname = path.dirname(__filename);
48
62
  // 4) Prepare the stub printer
49
- const stubFile = cfg.stubPath
63
+ const fallbackStubFile = cfg.stubPath
50
64
  ? path.resolve(process.cwd(), cfg.stubPath)
51
65
  : path.resolve(__dirname, "../../../stubs/migration.stub");
52
- const printer = new StubMigrationPrinter(stubFile);
66
+ let printer = new StubMigrationPrinter(cfg, fallbackStubFile);
53
67
  // 5) Write each migration file
54
68
  migrations.forEach((mig, idx) => {
55
69
  const timestamp = formatLaravelTimestamp(new Date(), idx + 1);
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Reorders migrations so that any table with foreign‐key dependencies
3
+ * is always migrated *after* the tables it references.
4
+ *
5
+ * @param migrations Array of Migration objects (with tableName & definitions[])
6
+ * @returns New array sorted in dependency order
7
+ * @throws If there’s a cycle in the relationships
8
+ */
9
+ export function sortMigrations(migrations) {
10
+ // 1) Build a map: tableName → Migration
11
+ const migMap = new Map(migrations.map(m => [m.tableName, m]));
12
+ // 2) Collect “true” FKs only (skip back‐relation object fields)
13
+ const rawDeps = new Map();
14
+ for (const { tableName } of migrations) {
15
+ rawDeps.set(tableName, new Set());
16
+ }
17
+ for (const m of migrations) {
18
+ for (const def of m.definitions) {
19
+ // only consider a relationship if:
20
+ // - it exists (def.relationship)
21
+ // - this field is a scalar FK column (def.kind === 'scalar')
22
+ // - it's the owning side (relationFromFields non-empty)
23
+ if (!def.relationship ||
24
+ !def.relationFromFields ||
25
+ def.relationFromFields.length === 0) {
26
+ continue;
27
+ }
28
+ const parent = def.relationship.on;
29
+ if (!migMap.has(parent) || m.tableName == parent)
30
+ continue; // skip external tables
31
+ rawDeps.get(m.tableName).add(parent);
32
+ }
33
+ }
34
+ // 3) Build adjacency (parent → dependents) and in-degree (table → count)
35
+ const adj = new Map();
36
+ const inDegree = new Map();
37
+ for (const tbl of migMap.keys()) {
38
+ adj.set(tbl, []);
39
+ inDegree.set(tbl, 0);
40
+ }
41
+ for (const [child, parents] of rawDeps) {
42
+ for (const parent of parents) {
43
+ if (parent !== child)
44
+ adj.get(parent).push(child);
45
+ inDegree.set(child, inDegree.get(child) + 1);
46
+ }
47
+ }
48
+ // 4) Kahn’s algorithm: enqueue all zero in-degree tables
49
+ const queue = [];
50
+ for (const [tbl, deg] of inDegree) {
51
+ if (deg === 0)
52
+ queue.push(tbl);
53
+ }
54
+ const sorted = [];
55
+ while (queue.length) {
56
+ const tbl = queue.shift();
57
+ sorted.push(migMap.get(tbl));
58
+ for (const child of adj.get(tbl)) {
59
+ const nd = inDegree.get(child) - 1;
60
+ inDegree.set(child, nd);
61
+ if (nd === 0)
62
+ queue.push(child);
63
+ }
64
+ }
65
+ // 5) If not all processed, there is a genuine cycle
66
+ if (sorted.length !== migrations.length) {
67
+ const cycle = migrations
68
+ .map(m => m.tableName)
69
+ .filter(t => !sorted.some(s => s.tableName === t));
70
+ throw new Error(`Cycle detected in migration dependencies: ${cycle.join(' → ')}`);
71
+ }
72
+ console.log('\n📦 Sorted Migration Tables:\n' + sorted.map((item, i) => ` ${i + 1}. ${item.tableName}`).join('\n') + '\n');
73
+ return sorted;
74
+ }
75
+ //# sourceMappingURL=sort.js.map
@@ -9,13 +9,26 @@ export async function generateLaravelModels(options) {
9
9
  // 0) Pull config values
10
10
  // Inside generateLaravelModels()
11
11
  const raw = (generator.config ?? {});
12
+ let groups = [];
13
+ if (raw["groups"]) {
14
+ const groupsModulePath = path.resolve(process.cwd(), raw["groups"]);
15
+ const imported = await import(groupsModulePath);
16
+ const exported = imported.default ?? imported;
17
+ if (!Array.isArray(exported)) {
18
+ throw new Error(`Custom groups module must export an array, but got ${typeof exported}`);
19
+ }
20
+ groups = exported;
21
+ }
12
22
  const cfg = {
13
23
  modelStubPath: raw["modelStubPath"],
14
24
  enumStubPath: raw["enumStubPath"],
15
25
  overwriteExisting: raw["overwriteExisting"] === "true",
16
26
  outputDir: raw["outputDir"],
27
+ outputEnumDir: raw["outputEnumDir"],
17
28
  startMarker: raw["startMarker"] ?? "// <prisma-laravel:start>",
18
29
  endMarker: raw["endMarker"] ?? "// <prisma-laravel:end>",
30
+ stubDir: raw["stubDir"],
31
+ groups,
19
32
  };
20
33
  // 1) Determine and ensure output directories
21
34
  const modelsDir = cfg.outputDir
@@ -24,7 +37,10 @@ export async function generateLaravelModels(options) {
24
37
  if (!existsSync(modelsDir)) {
25
38
  mkdirSync(modelsDir, { recursive: true });
26
39
  }
27
- const enumsDir = path.resolve(process.cwd(), "app/Enums");
40
+ const enumsDir = cfg.outputEnumDir
41
+ ? path.resolve(process.cwd(), cfg.outputEnumDir)
42
+ : path.resolve(process.cwd(), 'app/Enums');
43
+ console.log(enumsDir, cfg, process.cwd());
28
44
  if (!existsSync(enumsDir)) {
29
45
  mkdirSync(enumsDir, { recursive: true });
30
46
  }
@@ -39,7 +55,7 @@ export async function generateLaravelModels(options) {
39
55
  const enumStub = cfg.enumStubPath
40
56
  ? path.resolve(process.cwd(), cfg.enumStubPath)
41
57
  : path.resolve(__dirname, "../../../stubs/enums.stub");
42
- const printer = new StubModelPrinter(modelStub, enumStub);
58
+ const printer = new StubModelPrinter(cfg, modelStub, enumStub);
43
59
  // 3) Generate definitions
44
60
  const schemaGen = new PrismaToLaravelModelGenerator(dmmf);
45
61
  const { models, enums } = schemaGen.generateAll();
@@ -1,6 +1,7 @@
1
1
  import { NativeToMigrationTypeMap } from "./migrator/column-maps.js";
2
2
  import { MigrationTypes } from "./migrator/migrationTypes.js";
3
3
  import { existsSync, readFileSync, writeFileSync } from 'fs';
4
+ import path from "path";
4
5
  /**
5
6
  * Given a Prisma field default, return the PHP code fragment
6
7
  * to append to your migration column definition.
@@ -77,79 +78,6 @@ export function getType(field) {
77
78
  // @ts-ignore
78
79
  return NativeToMigrationTypeMap[key] ?? MigrationTypes.string;
79
80
  }
80
- /**
81
- * Reorders migrations so that any table with foreign‐key dependencies
82
- * is always migrated *after* the tables it references.
83
- *
84
- * @param migrations Array of Migration objects (with tableName & definitions[])
85
- * @returns New array sorted in dependency order
86
- * @throws If there’s a cycle in the relationships
87
- */
88
- export function sortMigrations(migrations) {
89
- // 1) Build a map: tableName → Migration
90
- const migMap = new Map(migrations.map(m => [m.tableName, m]));
91
- // 2) Collect “true” FKs only (skip back‐relation object fields)
92
- const rawDeps = new Map();
93
- for (const { tableName } of migrations) {
94
- rawDeps.set(tableName, new Set());
95
- }
96
- for (const m of migrations) {
97
- for (const def of m.definitions) {
98
- // only consider a relationship if:
99
- // - it exists (def.relationship)
100
- // - this field is a scalar FK column (def.kind === 'scalar')
101
- // - it's the owning side (relationFromFields non-empty)
102
- if (!def.relationship ||
103
- !def.relationFromFields ||
104
- def.relationFromFields.length === 0) {
105
- continue;
106
- }
107
- const parent = def.relationship.on;
108
- if (!migMap.has(parent))
109
- continue; // skip external tables
110
- rawDeps.get(m.tableName).add(parent);
111
- }
112
- }
113
- // 3) Build adjacency (parent → dependents) and in-degree (table → count)
114
- const adj = new Map();
115
- const inDegree = new Map();
116
- for (const tbl of migMap.keys()) {
117
- adj.set(tbl, []);
118
- inDegree.set(tbl, 0);
119
- }
120
- for (const [child, parents] of rawDeps) {
121
- for (const parent of parents) {
122
- adj.get(parent).push(child);
123
- inDegree.set(child, inDegree.get(child) + 1);
124
- }
125
- }
126
- // 4) Kahn’s algorithm: enqueue all zero in-degree tables
127
- const queue = [];
128
- for (const [tbl, deg] of inDegree) {
129
- if (deg === 0)
130
- queue.push(tbl);
131
- }
132
- const sorted = [];
133
- while (queue.length) {
134
- const tbl = queue.shift();
135
- sorted.push(migMap.get(tbl));
136
- for (const child of adj.get(tbl)) {
137
- const nd = inDegree.get(child) - 1;
138
- inDegree.set(child, nd);
139
- if (nd === 0)
140
- queue.push(child);
141
- }
142
- }
143
- // 5) If not all processed, there is a genuine cycle
144
- if (sorted.length !== migrations.length) {
145
- const cycle = migrations
146
- .map(m => m.tableName)
147
- .filter(t => !sorted.some(s => s.tableName === t));
148
- throw new Error(`Cycle detected in migration dependencies: ${cycle.join(' → ')}`);
149
- }
150
- console.log(sorted.map(item => item.tableName));
151
- return sorted;
152
- }
153
81
  export function buildModelContent(model) {
154
82
  const lines = [];
155
83
  // 1) If @guarded is used, emit $guarded instead of $fillable
@@ -236,4 +164,31 @@ export function writeWithMarkers(filePath, fullContent, generated, startMarker,
236
164
  // Otherwise write the full content (which itself can include the markers)
237
165
  writeFileSync(filePath, fullContent, 'utf-8');
238
166
  }
167
+ export function resolveStub(cfg, type, tableName) {
168
+ if (!cfg.stubDir)
169
+ return;
170
+ //---
171
+ const dir = path.resolve(process.cwd(), cfg.stubDir, type);
172
+ // A) 1st: file‐based override: <tableName>.stub
173
+ const fileOverride = path.join(dir, `${tableName}.stub`);
174
+ if (existsSync(fileOverride)) {
175
+ return fileOverride;
176
+ }
177
+ // B) 2nd: group‐based override
178
+ if (cfg.groups) {
179
+ for (const { stubFile, tables } of cfg.groups) {
180
+ if (tables.includes(tableName)) {
181
+ const groupPath = path.join(dir, stubFile);
182
+ if (existsSync(groupPath)) {
183
+ return groupPath;
184
+ }
185
+ }
186
+ }
187
+ }
188
+ // C) Fallback to index.stub
189
+ const defaultPath = path.join(dir, "index.stub");
190
+ if (!existsSync(defaultPath))
191
+ return;
192
+ return defaultPath;
193
+ }
239
194
  //# sourceMappingURL=utils.js.map