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 +2 -1
- package/dist/cli/models.index.js +3 -2
- package/dist/generator/migrator/index.js +17 -3
- package/dist/generator/migrator/sort.js +75 -0
- package/dist/generator/modeler/index.js +18 -2
- package/dist/generator/utils.js +28 -73
- package/dist/index.js +220 -0
- package/dist/printer/migrations.js +47 -19
- package/dist/printer/models.js +64 -32
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -3
- package/schema.prisma +1630 -7
- package/stubs/simple-model.stub +2 -2
- package/dist/migrations.js +0 -3
- package/dist/models.js +0 -3
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: () => ({
|
package/dist/cli/models.index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
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 {
|
|
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
|
|
63
|
+
const fallbackStubFile = cfg.stubPath
|
|
50
64
|
? path.resolve(process.cwd(), cfg.stubPath)
|
|
51
65
|
: path.resolve(__dirname, "../../../stubs/migration.stub");
|
|
52
|
-
|
|
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 =
|
|
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();
|
package/dist/generator/utils.js
CHANGED
|
@@ -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,
|
|
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(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
this.
|
|
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
|
-
//
|
|
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)
|
|
46
|
+
.map(line => ' ' + line)
|
|
20
47
|
.join('\n');
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
29
|
-
return
|
|
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
|