prisma-laravel-migrate 0.0.3 → 0.0.5

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/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
- import { generateLaravelModels } from "generator/modeler";
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
package/dist/index.js ADDED
@@ -0,0 +1,220 @@
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
+ //
17
+ // init
18
+ //
19
+ cli
20
+ .command('init')
21
+ .description('Inject generators into schema.prisma and scaffold stubs/')
22
+ .option('-s, --schema <path>', 'Prisma schema file', 'prisma/schema.prisma')
23
+ .action(async (opts) => {
24
+ const schemaPath = path.resolve(process.cwd(), opts.schema);
25
+ let schema = await fs.readFile(schemaPath, 'utf-8');
26
+ // ① inject migrations generator
27
+ if (!/generator\s+migrations\s*\{/.test(schema)) {
28
+ schema += `
29
+ generator migrations {
30
+ provider = "prisma-laravel-migrations"
31
+ output = "database/migrations"
32
+ stubDir = "./stubs"
33
+ }
34
+ `;
35
+ console.log('➡️ Added migrations generator');
36
+ }
37
+ // ② inject models generator
38
+ if (!/generator\s+models\s*\{/.test(schema)) {
39
+ schema += `
40
+ generator models {
41
+ provider = "prisma-laravel-models"
42
+ output = "app/Models"
43
+ outputEnumDir = "app/Enums"
44
+ stubDir = "./stubs"
45
+ }
46
+ `;
47
+ console.log('➡️ Added models generator');
48
+ }
49
+ await fs.writeFile(schemaPath, schema, 'utf-8');
50
+ console.log(`✅ Updated ${schemaPath}`);
51
+ // ③ copy your package’s built-in stubs into prisma/stubs/
52
+ const schemaDir = path.dirname(schemaPath);
53
+ const userStubs = path.join(schemaDir, 'stubs');
54
+ const __filename = fileURLToPath(import.meta.url);
55
+ const __dirname = path.dirname(__filename);
56
+ const pkgStubs = path.resolve(__dirname, '../stubs');
57
+ for (const type of ['migration', 'model', 'enums']) {
58
+ const target = path.join(userStubs, type);
59
+ await fs.mkdir(target, { recursive: true });
60
+ // copy <type>.stub → <type>/index.stub
61
+ const src = path.join(pkgStubs, `${type}.stub`);
62
+ const dst = path.join(target, 'index.stub');
63
+ try {
64
+ await fs.access(dst);
65
+ }
66
+ catch {
67
+ await fs.copyFile(src, dst);
68
+ console.log(`➡️ Copied ${type}.stub → stubs/${type}/index.stub`);
69
+ }
70
+ // copy simple-model.stub too
71
+ if (type === 'model') {
72
+ const src2 = path.join(pkgStubs, 'simple-model.stub');
73
+ const dst2 = path.join(target, 'simple-model.stub');
74
+ try {
75
+ await fs.access(dst2);
76
+ }
77
+ catch {
78
+ await fs.copyFile(src2, dst2);
79
+ console.log(`➡️ Copied simple-model.stub → stubs/model/simple-model.stub`);
80
+ }
81
+ }
82
+ }
83
+ console.log('🎉 Initialization complete!');
84
+ });
85
+ //
86
+ // customize
87
+ //
88
+ cli
89
+ .command('customize')
90
+ .alias('c')
91
+ .description('Scaffold per-table stub files from index.stub')
92
+ .option('-s, --schema <path>', 'Prisma schema file', 'prisma/schema.prisma')
93
+ .option('-t, --types <list>', 'Comma-separated: migration,model,enum', (val) => val.split(',').map(s => s.trim().toLowerCase()), [])
94
+ .option('-n, --names <list>', 'Comma-separated base names', (val) => val.split(',').map(s => s.trim()), [])
95
+ .action(async (opts) => {
96
+ const want = opts.types;
97
+ const bases = opts.names;
98
+ if (!want.length)
99
+ throw new Error('Specify at least one type with -t');
100
+ if (!bases.length)
101
+ throw new Error('Specify at least one name with -n');
102
+ // enums stand alone
103
+ if (want.includes('enum') && want.length > 1) {
104
+ throw new Error('`enum` cannot be combined with other types');
105
+ }
106
+ const schemaDir = path.dirname(path.resolve(process.cwd(), opts.schema));
107
+ const stubRoot = path.join(schemaDir, 'stubs');
108
+ const doBoth = want.includes('migration') && want.includes('model');
109
+ for (const t of want) {
110
+ if (t === 'enum') {
111
+ const dir = path.join(stubRoot, 'enum');
112
+ const idx = path.join(dir, 'index.stub');
113
+ await fs.mkdir(dir, { recursive: true });
114
+ for (const name of bases) {
115
+ const dst = path.join(dir, `${name}.stub`);
116
+ try {
117
+ await fs.access(dst);
118
+ console.log(`🟡 Skip enum/${name}.stub`);
119
+ }
120
+ catch {
121
+ await fs.copyFile(idx, dst);
122
+ console.log(`✅ Created enum/${name}.stub`);
123
+ }
124
+ }
125
+ continue;
126
+ }
127
+ for (const kind of doBoth ? ['migration', 'model'] : [t]) {
128
+ const dir = path.join(stubRoot, kind);
129
+ const idx = path.join(dir, 'index.stub');
130
+ await fs.mkdir(dir, { recursive: true });
131
+ for (const base of bases) {
132
+ const dst = path.join(dir, `${base}.stub`);
133
+ try {
134
+ await fs.access(dst);
135
+ console.log(`🟡 Skip ${kind}/${base}.stub`);
136
+ }
137
+ catch {
138
+ await fs.copyFile(idx, dst);
139
+ console.log(`✅ Created ${kind}/${base}.stub`);
140
+ }
141
+ }
142
+ }
143
+ }
144
+ console.log('🎉 Customize complete!');
145
+ });
146
+ //
147
+ // proxy to Prisma generate
148
+ //
149
+ cli
150
+ .command('gen')
151
+ .description('Run Prisma generate, or skip it (--skipGenerate), then run Laravel generators')
152
+ .option('--config <path>', 'Path to prisma-laravel config file')
153
+ .option('--skipGenerate', 'Only run the Laravel generators (no Prisma generate)')
154
+ .action(async (opts) => {
155
+ const configPath = opts.config
156
+ ? path.resolve(process.cwd(), opts.config)
157
+ : path.resolve(process.cwd(), 'prisma-laravel.config.js');
158
+ if (!existsSync(configPath)) {
159
+ console.error(`❌ Config file not found: ${configPath}`);
160
+ process.exit(1);
161
+ }
162
+ const cfgMod = await import(configPath);
163
+ const cfg = cfgMod.default ?? cfgMod;
164
+ if (!cfg.generator?.config) {
165
+ console.error('❌ `generator.config` is required in your config.');
166
+ process.exit(1);
167
+ }
168
+ const schemaPrismaPath = cfg.schemaPath
169
+ ? path.resolve(process.cwd(), cfg.schemaPath)
170
+ : path.resolve(process.cwd(), 'prisma/schema.prisma');
171
+ if (!existsSync(schemaPrismaPath)) {
172
+ console.error(`❌ Schema not found: ${schemaPrismaPath}`);
173
+ process.exit(1);
174
+ }
175
+ const run = async () => {
176
+ const datamodel = readFileSync(schemaPrismaPath, 'utf-8');
177
+ const sdk = dmf.default ?? dmf;
178
+ const { dmmf } = await sdk.getDMMF({ datamodel });
179
+ await generateLaravelSchema({
180
+ dmmf,
181
+ //@ts-ignore
182
+ generator: { config: cfg.generator.config },
183
+ otherGenerators: [],
184
+ schemaPath: schemaPrismaPath,
185
+ datasources: [],
186
+ datamodel,
187
+ version: '',
188
+ });
189
+ await generateLaravelModels({
190
+ dmmf,
191
+ //@ts-ignore
192
+ generator: { config: cfg.generator.config },
193
+ otherGenerators: [],
194
+ schemaPath: schemaPrismaPath,
195
+ datasources: [],
196
+ datamodel,
197
+ version: '',
198
+ });
199
+ };
200
+ if (opts.skipGenerate) {
201
+ await run();
202
+ }
203
+ else {
204
+ const prisma = spawn('npx', ['prisma', 'generate'], {
205
+ stdio: 'inherit',
206
+ shell: true,
207
+ });
208
+ prisma.on('exit', (code) => {
209
+ if (code !== 0)
210
+ process.exit(code);
211
+ run().catch(e => {
212
+ console.error('❌ Gen failed:', e.message ?? e);
213
+ process.exit(1);
214
+ });
215
+ });
216
+ }
217
+ });
218
+ cli.parse(process.argv);
219
+ cli.parse(process.argv);
220
+ //# sourceMappingURL=index.js.map
@@ -1,32 +1,60 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { formatStub, sortMigrations } from '../generator/utils.js';
3
+ import { formatStub, resolveStub } from '../generator/utils.js';
4
+ import { sortMigrations } from '../generator/migrator/sort.js';
4
5
  export class StubMigrationPrinter {
6
+ cfg;
7
+ globalStubPath;
8
+ #currentStubPath = '';
5
9
  tmplFn;
6
- constructor(stubFilePath) {
7
- // Load the stub exactly as you wrote it, with no backticks
8
- const stub = fs.readFileSync(path.resolve(stubFilePath), 'utf-8');
9
- // Build a function that, given tableName and columns, returns
10
- // the evaluated template literal of your stub.
11
- // We inject the stub inside backticks here—your file stays untouched.
12
- this.tmplFn = new Function('tableName', 'columns', 'definitions',
13
- // Wrap stub in backticks so that `${…}` inside it is evaluated now
14
- `return \`${formatStub(stub)}\`;`);
10
+ constructor(
11
+ /** base config for per‐table stub resolution */
12
+ cfg,
13
+ /** optional global override: if set, always use this stub */
14
+ globalStubPath) {
15
+ this.cfg = cfg;
16
+ this.globalStubPath = globalStubPath;
15
17
  }
18
+ /** Switch to the correct stub for this table (or reuse the last one) */
19
+ ensureStub(tableName) {
20
+ // 1) pick the stub path: global override wins, otherwise per‐table/group/index
21
+ // Prioritize per‐table/group/index stubs; only fall back to global override if none found
22
+ const resolved = resolveStub(this.cfg, 'migration', tableName);
23
+ const stubPath = resolved
24
+ ? resolved
25
+ : this.globalStubPath
26
+ ? path.resolve(process.cwd(), this.globalStubPath)
27
+ : (() => { throw new Error(`No stub found for migration '${tableName}'`); })();
28
+ if (stubPath === this.#currentStubPath) {
29
+ return;
30
+ }
31
+ // 2) load and compile
32
+ const raw = fs.readFileSync(path.resolve(stubPath), 'utf-8');
33
+ const escaped = formatStub(raw);
34
+ this.tmplFn = new Function('tableName', 'columns', 'definitions', `return \`${escaped}\`;`);
35
+ this.#currentStubPath = stubPath;
36
+ }
37
+ /**
38
+ * Render a single migration.
39
+ * Returns both the full file and the raw column block.
40
+ */
16
41
  printMigration(mig) {
17
- // Prepare `columns` as a single string, indenting as you like
42
+ // ensure we have the right stub loaded
43
+ this.ensureStub(mig.tableName);
44
+ // prepare the indented columns block
18
45
  const columns = mig.statements
19
- .map(line => ' ' + line) // match your stub’s indentation
46
+ .map(line => ' ' + line)
20
47
  .join('\n');
21
- // Call the compiled template function
22
- return {
23
- fullContent: this.tmplFn(mig.tableName, columns, mig.definitions),
24
- columns
25
- };
48
+ // call the compiled template function
49
+ const fullContent = this.tmplFn(mig.tableName, columns, mig.definitions);
50
+ return { fullContent, columns };
26
51
  }
52
+ /** Helper to render all, sorted and joined with separators */
27
53
  printAll(migs) {
28
- migs = sortMigrations(migs);
29
- return migs.map(m => this.printMigration(m)).join('\n\n');
54
+ const sorted = sortMigrations(migs);
55
+ return sorted
56
+ .map(m => this.printMigration(m).fullContent)
57
+ .join('\n\n');
30
58
  }
31
59
  }
32
60
  //# sourceMappingURL=migrations.js.map