forge-sql-orm 1.0.31 → 1.1.31
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 +216 -695
- package/dist/ForgeSQLORM.js +538 -567
- package/dist/ForgeSQLORM.js.map +1 -1
- package/dist/ForgeSQLORM.mjs +536 -554
- package/dist/ForgeSQLORM.mjs.map +1 -1
- package/dist/core/ForgeSQLCrudOperations.d.ts +101 -130
- package/dist/core/ForgeSQLCrudOperations.d.ts.map +1 -1
- package/dist/core/ForgeSQLORM.d.ts +11 -10
- package/dist/core/ForgeSQLORM.d.ts.map +1 -1
- package/dist/core/ForgeSQLQueryBuilder.d.ts +271 -113
- package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
- package/dist/core/ForgeSQLSelectOperations.d.ts +65 -22
- package/dist/core/ForgeSQLSelectOperations.d.ts.map +1 -1
- package/dist/core/SystemTables.d.ts +59 -0
- package/dist/core/SystemTables.d.ts.map +1 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/utils/sqlUtils.d.ts +53 -6
- package/dist/utils/sqlUtils.d.ts.map +1 -1
- package/dist-cli/cli.js +471 -397
- package/dist-cli/cli.js.map +1 -1
- package/dist-cli/cli.mjs +471 -397
- package/dist-cli/cli.mjs.map +1 -1
- package/package.json +21 -22
- package/src/core/ForgeSQLCrudOperations.ts +360 -473
- package/src/core/ForgeSQLORM.ts +38 -79
- package/src/core/ForgeSQLQueryBuilder.ts +250 -133
- package/src/core/ForgeSQLSelectOperations.ts +182 -72
- package/src/core/SystemTables.ts +7 -0
- package/src/index.ts +1 -2
- package/src/utils/sqlUtils.ts +155 -23
- package/dist/core/ComplexQuerySchemaBuilder.d.ts +0 -38
- package/dist/core/ComplexQuerySchemaBuilder.d.ts.map +0 -1
- package/dist/knex/index.d.ts +0 -4
- package/dist/knex/index.d.ts.map +0 -1
- package/src/core/ComplexQuerySchemaBuilder.ts +0 -63
- package/src/knex/index.ts +0 -4
package/dist-cli/cli.js
CHANGED
|
@@ -6,92 +6,138 @@ const inquirer = require("inquirer");
|
|
|
6
6
|
const fs = require("fs");
|
|
7
7
|
const path = require("path");
|
|
8
8
|
require("reflect-metadata");
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
const indexContent = `${imports.join("\n")}
|
|
9
|
+
const child_process = require("child_process");
|
|
10
|
+
const mysql = require("mysql2/promise");
|
|
11
|
+
require("moment");
|
|
12
|
+
const primaryKeys = require("drizzle-orm/mysql-core/primary-keys");
|
|
13
|
+
const indexes = require("drizzle-orm/mysql-core/indexes");
|
|
14
|
+
const checks = require("drizzle-orm/mysql-core/checks");
|
|
15
|
+
const foreignKeys = require("drizzle-orm/mysql-core/foreign-keys");
|
|
16
|
+
const uniqueConstraint = require("drizzle-orm/mysql-core/unique-constraint");
|
|
17
|
+
function replaceMySQLTypes(schemaContent) {
|
|
18
|
+
const imports = `import { mySqlDateTimeString, mySqlTimeString, mySqlDateString, mySqlTimestampString } from "forge-sql-orm";
|
|
20
19
|
|
|
21
|
-
export default [${entityFiles.map((file) => path.basename(file, ".ts")).join(", ")}];
|
|
22
20
|
`;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
let modifiedContent = schemaContent.replace(/datetime\(['"]([^'"]+)['"],\s*{\s*mode:\s*['"]([^'"]+)['"]\s*}\)/g, "mySqlDateTimeString('$1')").replace(/datetime\(['"]([^'"]+)['"]\)/g, "mySqlDateTimeString('$1')").replace(/datetime\(\s*{\s*mode:\s*['"]([^'"]+)['"]\s*}\s*\)/g, "mySqlDateTimeString()").replace(/datetime\(\)/g, "mySqlDateTimeString()").replace(/time\(['"]([^'"]+)['"],\s*{\s*mode:\s*['"]([^'"]+)['"]\s*}\)/g, "mySqlTimeString('$1')").replace(/time\(['"]([^'"]+)['"]\)/g, "mySqlTimeString('$1')").replace(/time\(\s*{\s*mode:\s*['"]([^'"]+)['"]\s*}\s*\)/g, "mySqlTimeString()").replace(/time\(\)/g, "mySqlTimeString()").replace(/date\(['"]([^'"]+)['"],\s*{\s*mode:\s*['"]([^'"]+)['"]\s*}\)/g, "mySqlDateString('$1')").replace(/date\(['"]([^'"]+)['"]\)/g, "mySqlDateString('$1')").replace(/date\(\s*{\s*mode:\s*['"]([^'"]+)['"]\s*}\s*\)/g, "mySqlDateString()").replace(/date\(\)/g, "mySqlDateString()").replace(/timestamp\(['"]([^'"]+)['"],\s*{\s*mode:\s*['"]([^'"]+)['"]\s*}\)/g, "mySqlTimestampString('$1')").replace(/timestamp\(['"]([^'"]+)['"]\)/g, "mySqlTimestampString('$1')").replace(/timestamp\(\s*{\s*mode:\s*['"]([^'"]+)['"]\s*}\s*\)/g, "mySqlTimestampString()").replace(/timestamp\(\)/g, "mySqlTimestampString()");
|
|
22
|
+
if (!modifiedContent.includes("import { mySqlDateTimeString")) {
|
|
23
|
+
modifiedContent = imports + modifiedContent;
|
|
24
|
+
}
|
|
25
|
+
return modifiedContent;
|
|
26
|
+
}
|
|
26
27
|
const generateModels = async (options) => {
|
|
27
28
|
try {
|
|
28
|
-
const
|
|
29
|
-
host
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (
|
|
50
|
-
console.warn(
|
|
51
|
-
`Version field "${property.name}" can be only datetime or integer Table ${m.tableName} but now is "${property.type}"`
|
|
52
|
-
);
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
if (property.primary) {
|
|
56
|
-
console.warn(
|
|
57
|
-
`Version field "${property.name}" can not be primary key Table ${m.tableName}`
|
|
58
|
-
);
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
if (property.nullable) {
|
|
29
|
+
const sql = await child_process.execSync(
|
|
30
|
+
`npx drizzle-kit pull --dialect mysql --url mysql://${options.user}:${options.password}@${options.host}:${options.port}/${options.dbName} --out ${options.output}`,
|
|
31
|
+
{ encoding: "utf-8" }
|
|
32
|
+
);
|
|
33
|
+
const metaDir = path.join(options.output, "meta");
|
|
34
|
+
const additionalMetadata = {};
|
|
35
|
+
if (fs.existsSync(metaDir)) {
|
|
36
|
+
const snapshotFile = path.join(metaDir, "0000_snapshot.json");
|
|
37
|
+
if (fs.existsSync(snapshotFile)) {
|
|
38
|
+
const snapshotData = JSON.parse(fs.readFileSync(snapshotFile, "utf-8"));
|
|
39
|
+
for (const [tableName, tableData] of Object.entries(snapshotData.tables)) {
|
|
40
|
+
const table = tableData;
|
|
41
|
+
const versionField = Object.entries(table.columns).find(
|
|
42
|
+
([_, col]) => col.name.toLowerCase() === options.versionField
|
|
43
|
+
);
|
|
44
|
+
if (versionField) {
|
|
45
|
+
const [_, col] = versionField;
|
|
46
|
+
const fieldType = col.type;
|
|
47
|
+
const isSupportedType = fieldType === "datetime" || fieldType === "timestamp" || fieldType === "int" || fieldType === "number" || fieldType === "decimal";
|
|
48
|
+
if (!col.notNull) {
|
|
49
|
+
console.warn(`Version field "${col.name}" in table ${tableName} is nullable. Versioning may not work correctly.`);
|
|
50
|
+
} else if (!isSupportedType) {
|
|
62
51
|
console.warn(
|
|
63
|
-
`Version field "${
|
|
52
|
+
`Version field "${col.name}" in table ${tableName} has unsupported type "${fieldType}". Only datetime, timestamp, int, and decimal types are supported for versioning. Versioning will be skipped.`
|
|
64
53
|
);
|
|
65
|
-
|
|
54
|
+
} else {
|
|
55
|
+
additionalMetadata[tableName] = {
|
|
56
|
+
tableName,
|
|
57
|
+
versionField: {
|
|
58
|
+
fieldName: col.name
|
|
59
|
+
}
|
|
60
|
+
};
|
|
66
61
|
}
|
|
67
|
-
property.version = true;
|
|
68
62
|
}
|
|
69
63
|
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const versionMetadataContent = `/**
|
|
67
|
+
* This file was auto-generated by forge-sql-orm
|
|
68
|
+
* Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
69
|
+
*
|
|
70
|
+
* DO NOT EDIT THIS FILE MANUALLY
|
|
71
|
+
* Any changes will be overwritten on next generation
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
export * from "./relations";
|
|
76
|
+
export * from "./schema";
|
|
77
|
+
|
|
78
|
+
export interface VersionFieldMetadata {
|
|
79
|
+
fieldName: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface TableMetadata {
|
|
83
|
+
tableName: string;
|
|
84
|
+
versionField: VersionFieldMetadata;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export type AdditionalMetadata = Record<string, TableMetadata>;
|
|
88
|
+
|
|
89
|
+
export const additionalMetadata: AdditionalMetadata = ${JSON.stringify(additionalMetadata, null, 2)};
|
|
90
|
+
`;
|
|
91
|
+
fs.writeFileSync(path.join(options.output, "index.ts"), versionMetadataContent);
|
|
92
|
+
const schemaPath = path.join(options.output, "schema.ts");
|
|
93
|
+
if (fs.existsSync(schemaPath)) {
|
|
94
|
+
const schemaContent = fs.readFileSync(schemaPath, "utf-8");
|
|
95
|
+
const modifiedContent = replaceMySQLTypes(schemaContent);
|
|
96
|
+
fs.writeFileSync(schemaPath, modifiedContent);
|
|
97
|
+
console.log(`✅ Updated schema types in: ${schemaPath}`);
|
|
98
|
+
}
|
|
99
|
+
const migrationDir = path.join(options.output, "migrations");
|
|
100
|
+
if (fs.existsSync(migrationDir)) {
|
|
101
|
+
fs.rmSync(migrationDir, { recursive: true, force: true });
|
|
102
|
+
console.log(`✅ Removed: ${migrationDir}`);
|
|
103
|
+
}
|
|
104
|
+
if (fs.existsSync(metaDir)) {
|
|
105
|
+
const journalFile = path.join(metaDir, "_journal.json");
|
|
106
|
+
if (fs.existsSync(journalFile)) {
|
|
107
|
+
const journalData = JSON.parse(fs.readFileSync(journalFile, "utf-8"));
|
|
108
|
+
for (const entry of journalData.entries) {
|
|
109
|
+
const sqlFile = path.join(options.output, `${entry.tag}.sql`);
|
|
110
|
+
if (fs.existsSync(sqlFile)) {
|
|
111
|
+
fs.rmSync(sqlFile, { force: true });
|
|
112
|
+
console.log(`✅ Removed SQL file: ${entry.tag}.sql`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
fs.rmSync(metaDir, { recursive: true, force: true });
|
|
117
|
+
console.log(`✅ Removed: ${metaDir}`);
|
|
118
|
+
}
|
|
119
|
+
console.log(`✅ Successfully generated models and version metadata`);
|
|
88
120
|
process.exit(0);
|
|
89
121
|
} catch (error) {
|
|
90
|
-
console.error(`❌ Error
|
|
122
|
+
console.error(`❌ Error during model generation:`, error);
|
|
91
123
|
process.exit(1);
|
|
92
124
|
}
|
|
93
125
|
};
|
|
94
|
-
|
|
126
|
+
const loadMigrationVersion$1 = async (migrationPath) => {
|
|
127
|
+
try {
|
|
128
|
+
const migrationCountFilePath = path.resolve(path.join(migrationPath, "migrationCount.ts"));
|
|
129
|
+
if (!fs.existsSync(migrationCountFilePath)) {
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
const { MIGRATION_VERSION } = await import(migrationCountFilePath);
|
|
133
|
+
console.log(`✅ Current migration version: ${MIGRATION_VERSION}`);
|
|
134
|
+
return MIGRATION_VERSION;
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.error(`❌ Error loading migrationCount:`, error);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
function cleanSQLStatement(sql) {
|
|
95
141
|
sql = sql.replace(/create\s+table\s+(\w+)/gi, "create table if not exists $1");
|
|
96
142
|
sql = sql.replace(/create\s+index\s+(\w+)/gi, "create index if not exists $1");
|
|
97
143
|
sql = sql.replace(/alter\s+table\s+(\w+)\s+add\s+index\s+(\w+)/gi, "alter table $1 add index if not exists $2");
|
|
@@ -101,7 +147,7 @@ function cleanSQLStatement$2(sql) {
|
|
|
101
147
|
function generateMigrationFile$2(createStatements, version) {
|
|
102
148
|
const versionPrefix = `v${version}_MIGRATION`;
|
|
103
149
|
const migrationLines = createStatements.map(
|
|
104
|
-
(stmt, index) => ` .enqueue("${versionPrefix}${index}", "${cleanSQLStatement
|
|
150
|
+
(stmt, index) => ` .enqueue("${versionPrefix}${index}", "${cleanSQLStatement(stmt).replace(/\s+/g, " ")}")`
|
|
105
151
|
// eslint-disable-line no-useless-escape
|
|
106
152
|
).join("\n");
|
|
107
153
|
return `import { MigrationRunner } from "@forge/sql/out/migration";
|
|
@@ -143,41 +189,12 @@ export default async (
|
|
|
143
189
|
console.log(`✅ Migration count file updated: ${migrationCountPath}`);
|
|
144
190
|
console.log(`✅ Migration index file created: ${indexFilePath}`);
|
|
145
191
|
}
|
|
146
|
-
const extractCreateStatements
|
|
147
|
-
const statements = schema.split(
|
|
192
|
+
const extractCreateStatements = (schema) => {
|
|
193
|
+
const statements = schema.split(/--> statement-breakpoint|;/).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
148
194
|
return statements.filter(
|
|
149
|
-
(stmt) => stmt.startsWith("create table") || stmt.startsWith("alter table")
|
|
195
|
+
(stmt) => stmt.toLowerCase().startsWith("create table") || stmt.toLowerCase().startsWith("alter table") || stmt.toLowerCase().includes("add index") || stmt.toLowerCase().includes("add unique index") || stmt.toLowerCase().includes("add constraint")
|
|
150
196
|
);
|
|
151
197
|
};
|
|
152
|
-
const loadEntities$2 = async (entitiesPath) => {
|
|
153
|
-
try {
|
|
154
|
-
const indexFilePath = path.resolve(path.join(entitiesPath, "index.ts"));
|
|
155
|
-
if (!fs.existsSync(indexFilePath)) {
|
|
156
|
-
console.error(`❌ Error: index.ts not found in ${indexFilePath}`);
|
|
157
|
-
process.exit(1);
|
|
158
|
-
}
|
|
159
|
-
const { default: entities } = await import(indexFilePath);
|
|
160
|
-
console.log(`✅ Loaded ${entities.length} entities from ${entitiesPath}`);
|
|
161
|
-
return entities;
|
|
162
|
-
} catch (error) {
|
|
163
|
-
console.error(`❌ Error loading index.ts from ${entitiesPath}:`, error);
|
|
164
|
-
process.exit(1);
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
const loadMigrationVersion$1 = async (migrationPath) => {
|
|
168
|
-
try {
|
|
169
|
-
const migrationCountFilePath = path.resolve(path.join(migrationPath, "migrationCount.ts"));
|
|
170
|
-
if (!fs.existsSync(migrationCountFilePath)) {
|
|
171
|
-
return 0;
|
|
172
|
-
}
|
|
173
|
-
const { MIGRATION_VERSION } = await import(migrationCountFilePath);
|
|
174
|
-
console.log(`✅ Current migration version: ${MIGRATION_VERSION}`);
|
|
175
|
-
return MIGRATION_VERSION;
|
|
176
|
-
} catch (error) {
|
|
177
|
-
console.error(`❌ Error loading migrationCount:`, error);
|
|
178
|
-
process.exit(1);
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
198
|
const createMigration = async (options) => {
|
|
182
199
|
try {
|
|
183
200
|
let version = await loadMigrationVersion$1(options.output);
|
|
@@ -190,19 +207,20 @@ const createMigration = async (options) => {
|
|
|
190
207
|
}
|
|
191
208
|
}
|
|
192
209
|
version = 1;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
210
|
+
await child_process.execSync(
|
|
211
|
+
`npx drizzle-kit generate --name=init --dialect mysql --out ${options.output} --schema ${options.entitiesPath}`,
|
|
212
|
+
{ encoding: "utf-8" }
|
|
213
|
+
);
|
|
214
|
+
const initSqlFile = path.join(options.output, "0000_init.sql");
|
|
215
|
+
const sql = fs.readFileSync(initSqlFile, "utf-8");
|
|
216
|
+
const createStatements = extractCreateStatements(sql);
|
|
217
|
+
const migrationFile = generateMigrationFile$2(createStatements, 1);
|
|
218
|
+
saveMigrationFiles$2(migrationFile, 1, options.output);
|
|
219
|
+
fs.rmSync(initSqlFile, { force: true });
|
|
220
|
+
console.log(`✅ Removed SQL file: ${initSqlFile}`);
|
|
221
|
+
let metaDir = path.join(options.output, "meta");
|
|
222
|
+
fs.rmSync(metaDir, { recursive: true, force: true });
|
|
223
|
+
console.log(`✅ Removed: ${metaDir}`);
|
|
206
224
|
console.log(`✅ Migration successfully created!`);
|
|
207
225
|
process.exit(0);
|
|
208
226
|
} catch (error) {
|
|
@@ -210,13 +228,64 @@ const createMigration = async (options) => {
|
|
|
210
228
|
process.exit(1);
|
|
211
229
|
}
|
|
212
230
|
};
|
|
213
|
-
function
|
|
214
|
-
|
|
231
|
+
function getTableMetadata(table) {
|
|
232
|
+
const symbols = Object.getOwnPropertySymbols(table);
|
|
233
|
+
const nameSymbol = symbols.find((s) => s.toString().includes("Name"));
|
|
234
|
+
const columnsSymbol = symbols.find((s) => s.toString().includes("Columns"));
|
|
235
|
+
const extraSymbol = symbols.find((s) => s.toString().includes("ExtraConfigBuilder"));
|
|
236
|
+
const foreignKeysSymbol = symbols.find((s) => s.toString().includes("MySqlInlineForeignKeys)"));
|
|
237
|
+
const builders = {
|
|
238
|
+
indexes: [],
|
|
239
|
+
checks: [],
|
|
240
|
+
foreignKeys: [],
|
|
241
|
+
primaryKeys: [],
|
|
242
|
+
uniqueConstraints: [],
|
|
243
|
+
extras: []
|
|
244
|
+
};
|
|
245
|
+
if (foreignKeysSymbol) {
|
|
246
|
+
const foreignKeys2 = table[foreignKeysSymbol];
|
|
247
|
+
if (foreignKeys2) {
|
|
248
|
+
for (const foreignKey of foreignKeys2) {
|
|
249
|
+
builders.foreignKeys.push(foreignKey);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (extraSymbol) {
|
|
254
|
+
const extraConfigBuilder = table[extraSymbol];
|
|
255
|
+
if (extraConfigBuilder && typeof extraConfigBuilder === "function") {
|
|
256
|
+
const configBuilders = extraConfigBuilder(table);
|
|
257
|
+
let configBuildersArray = [];
|
|
258
|
+
if (!Array.isArray(configBuilders)) {
|
|
259
|
+
configBuildersArray = Object.values(configBuilders);
|
|
260
|
+
} else {
|
|
261
|
+
configBuildersArray = configBuilders;
|
|
262
|
+
}
|
|
263
|
+
configBuildersArray.forEach((builder) => {
|
|
264
|
+
if (builder instanceof indexes.IndexBuilder) {
|
|
265
|
+
builders.indexes.push(builder);
|
|
266
|
+
} else if (builder instanceof checks.CheckBuilder) {
|
|
267
|
+
builders.checks.push(builder);
|
|
268
|
+
} else if (builder instanceof foreignKeys.ForeignKeyBuilder) {
|
|
269
|
+
builders.foreignKeys.push(builder);
|
|
270
|
+
} else if (builder instanceof primaryKeys.PrimaryKeyBuilder) {
|
|
271
|
+
builders.primaryKeys.push(builder);
|
|
272
|
+
} else if (builder instanceof uniqueConstraint.UniqueConstraintBuilder) {
|
|
273
|
+
builders.uniqueConstraints.push(builder);
|
|
274
|
+
}
|
|
275
|
+
builders.extras.push(builder);
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
tableName: nameSymbol ? table[nameSymbol] : "",
|
|
281
|
+
columns: columnsSymbol ? table[columnsSymbol] : {},
|
|
282
|
+
...builders
|
|
283
|
+
};
|
|
215
284
|
}
|
|
216
285
|
function generateMigrationFile$1(createStatements, version) {
|
|
217
286
|
const versionPrefix = `v${version}_MIGRATION`;
|
|
218
287
|
const migrationLines = createStatements.map(
|
|
219
|
-
(stmt, index) => ` .enqueue("${versionPrefix}${index}", "${
|
|
288
|
+
(stmt, index) => ` .enqueue("${versionPrefix}${index}", "${stmt}")`
|
|
220
289
|
// eslint-disable-line no-useless-escape
|
|
221
290
|
).join("\n");
|
|
222
291
|
return `import { MigrationRunner } from "@forge/sql/out/migration";
|
|
@@ -226,6 +295,18 @@ export default (migrationRunner: MigrationRunner): MigrationRunner => {
|
|
|
226
295
|
${migrationLines};
|
|
227
296
|
};`;
|
|
228
297
|
}
|
|
298
|
+
function filterWithPreviousMigration(newStatements, prevVersion, outputDir) {
|
|
299
|
+
const prevMigrationPath = path.join(outputDir, `migrationV${prevVersion}.ts`);
|
|
300
|
+
if (!fs.existsSync(prevMigrationPath)) {
|
|
301
|
+
return newStatements.map((s) => s.replace(/\s+/g, " "));
|
|
302
|
+
}
|
|
303
|
+
const prevContent = fs.readFileSync(prevMigrationPath, "utf-8");
|
|
304
|
+
const prevStatements = prevContent.split("\n").filter((line) => line.includes(".enqueue(")).map((line) => {
|
|
305
|
+
const match = line.match(/\.enqueue\([^,]+,\s*"([^"]+)"/);
|
|
306
|
+
return match ? match[1].replace(/\s+/g, " ").trim() : "";
|
|
307
|
+
});
|
|
308
|
+
return newStatements.filter((s) => !prevStatements.includes(s.replace(/\s+/g, " "))).map((s) => s.replace(/\s+/g, " "));
|
|
309
|
+
}
|
|
229
310
|
function saveMigrationFiles$1(migrationCode, version, outputDir) {
|
|
230
311
|
if (!fs.existsSync(outputDir)) {
|
|
231
312
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
@@ -257,28 +338,8 @@ export default async (
|
|
|
257
338
|
console.log(`✅ Migration file created: ${migrationFilePath}`);
|
|
258
339
|
console.log(`✅ Migration count file updated: ${migrationCountPath}`);
|
|
259
340
|
console.log(`✅ Migration index file created: ${indexFilePath}`);
|
|
341
|
+
return true;
|
|
260
342
|
}
|
|
261
|
-
const extractCreateStatements = (schema) => {
|
|
262
|
-
const statements = schema.split(";").map((s) => s.trim());
|
|
263
|
-
return statements.filter(
|
|
264
|
-
(stmt) => stmt.startsWith("create table") || stmt.startsWith("alter table") && stmt.includes("add index") || stmt.startsWith("alter table") && stmt.includes("add") && !stmt.includes("foreign") || stmt.startsWith("alter table") && stmt.includes("modify") && !stmt.includes("foreign")
|
|
265
|
-
);
|
|
266
|
-
};
|
|
267
|
-
const loadEntities$1 = async (entitiesPath) => {
|
|
268
|
-
try {
|
|
269
|
-
const indexFilePath = path.resolve(path.join(entitiesPath, "index.ts"));
|
|
270
|
-
if (!fs.existsSync(indexFilePath)) {
|
|
271
|
-
console.error(`❌ Error: index.ts not found in ${entitiesPath}`);
|
|
272
|
-
process.exit(1);
|
|
273
|
-
}
|
|
274
|
-
const { default: entities } = await import(indexFilePath);
|
|
275
|
-
console.log(`✅ Loaded ${entities.length} entities from ${entitiesPath}`);
|
|
276
|
-
return entities;
|
|
277
|
-
} catch (error) {
|
|
278
|
-
console.error(`❌ Error loading index.ts from ${entitiesPath}:`, error);
|
|
279
|
-
process.exit(1);
|
|
280
|
-
}
|
|
281
|
-
};
|
|
282
343
|
const loadMigrationVersion = async (migrationPath) => {
|
|
283
344
|
try {
|
|
284
345
|
const migrationCountFilePath = path.resolve(path.join(migrationPath, "migrationCount.ts"));
|
|
@@ -296,9 +357,183 @@ const loadMigrationVersion = async (migrationPath) => {
|
|
|
296
357
|
process.exit(1);
|
|
297
358
|
}
|
|
298
359
|
};
|
|
360
|
+
async function getDatabaseSchema(connection, dbName) {
|
|
361
|
+
const [columns] = await connection.execute(`
|
|
362
|
+
SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY, EXTRA
|
|
363
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
364
|
+
WHERE TABLE_SCHEMA = ?
|
|
365
|
+
`, [dbName]);
|
|
366
|
+
const [indexes2] = await connection.execute(`
|
|
367
|
+
SELECT TABLE_NAME, INDEX_NAME, COLUMN_NAME, NON_UNIQUE
|
|
368
|
+
FROM INFORMATION_SCHEMA.STATISTICS
|
|
369
|
+
WHERE TABLE_SCHEMA = ?
|
|
370
|
+
ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX
|
|
371
|
+
`, [dbName]);
|
|
372
|
+
const [foreignKeys2] = await connection.execute(`
|
|
373
|
+
SELECT
|
|
374
|
+
TABLE_NAME,
|
|
375
|
+
COLUMN_NAME,
|
|
376
|
+
CONSTRAINT_NAME,
|
|
377
|
+
REFERENCED_TABLE_NAME,
|
|
378
|
+
REFERENCED_COLUMN_NAME
|
|
379
|
+
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
|
|
380
|
+
WHERE TABLE_SCHEMA = ?
|
|
381
|
+
AND REFERENCED_TABLE_NAME IS NOT NULL
|
|
382
|
+
`, [dbName]);
|
|
383
|
+
const schema = {};
|
|
384
|
+
columns.forEach((row) => {
|
|
385
|
+
if (!schema[row.TABLE_NAME]) {
|
|
386
|
+
schema[row.TABLE_NAME] = {
|
|
387
|
+
columns: {},
|
|
388
|
+
indexes: {},
|
|
389
|
+
foreignKeys: {}
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
schema[row.TABLE_NAME].columns[row.COLUMN_NAME] = row;
|
|
393
|
+
});
|
|
394
|
+
indexes2.forEach((row) => {
|
|
395
|
+
if (!schema[row.TABLE_NAME].indexes[row.INDEX_NAME]) {
|
|
396
|
+
schema[row.TABLE_NAME].indexes[row.INDEX_NAME] = {
|
|
397
|
+
columns: [],
|
|
398
|
+
unique: !row.NON_UNIQUE
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
schema[row.TABLE_NAME].indexes[row.INDEX_NAME].columns.push(row.COLUMN_NAME);
|
|
402
|
+
});
|
|
403
|
+
foreignKeys2.forEach((row) => {
|
|
404
|
+
if (!schema[row.TABLE_NAME].foreignKeys[row.CONSTRAINT_NAME]) {
|
|
405
|
+
schema[row.TABLE_NAME].foreignKeys[row.CONSTRAINT_NAME] = {
|
|
406
|
+
column: row.COLUMN_NAME,
|
|
407
|
+
referencedTable: row.REFERENCED_TABLE_NAME,
|
|
408
|
+
referencedColumn: row.REFERENCED_COLUMN_NAME
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
return schema;
|
|
413
|
+
}
|
|
414
|
+
function normalizeMySQLType(mysqlType) {
|
|
415
|
+
let normalized = mysqlType.replace(/\([^)]*\)/, "").toLowerCase();
|
|
416
|
+
normalized = normalized.replace(/^mysql/, "");
|
|
417
|
+
return normalized;
|
|
418
|
+
}
|
|
419
|
+
function getForeignKeyName(fk) {
|
|
420
|
+
return fk.getName();
|
|
421
|
+
}
|
|
422
|
+
function getIndexName(index) {
|
|
423
|
+
return index.name;
|
|
424
|
+
}
|
|
425
|
+
function getUniqueConstraintName(uc) {
|
|
426
|
+
return uc.name;
|
|
427
|
+
}
|
|
428
|
+
function getIndexColumns(index) {
|
|
429
|
+
return index.columns.map((col) => col.name);
|
|
430
|
+
}
|
|
431
|
+
function generateSchemaChanges(drizzleSchema, dbSchema, schemaModule) {
|
|
432
|
+
const changes = [];
|
|
433
|
+
for (const [tableName, dbTable] of Object.entries(dbSchema)) {
|
|
434
|
+
const drizzleColumns = drizzleSchema[tableName];
|
|
435
|
+
if (!drizzleColumns) {
|
|
436
|
+
const columns = Object.entries(dbTable.columns).map(([colName, col]) => {
|
|
437
|
+
const type = col.COLUMN_TYPE;
|
|
438
|
+
const nullable = col.IS_NULLABLE === "YES" ? "NULL" : "NOT NULL";
|
|
439
|
+
const autoIncrement = col.EXTRA.includes("auto_increment") ? "AUTO_INCREMENT" : "";
|
|
440
|
+
return `\`${colName}\` ${type} ${nullable} ${autoIncrement}`.trim();
|
|
441
|
+
}).join(",\n ");
|
|
442
|
+
changes.push(`CREATE TABLE if not exists \`${tableName}\` (
|
|
443
|
+
${columns}
|
|
444
|
+
);`);
|
|
445
|
+
for (const [indexName, dbIndex] of Object.entries(dbTable.indexes)) {
|
|
446
|
+
if (indexName === "PRIMARY") {
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
const isForeignKeyIndex = dbIndex.columns.some((colName) => {
|
|
450
|
+
const column = dbTable.columns[colName];
|
|
451
|
+
return column && column.COLUMN_KEY === "MUL" && column.EXTRA.includes("foreign key");
|
|
452
|
+
});
|
|
453
|
+
if (isForeignKeyIndex) {
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
const columns2 = dbIndex.columns.map((col) => `\`${col}\``).join(", ");
|
|
457
|
+
const unique = dbIndex.unique ? "UNIQUE " : "";
|
|
458
|
+
changes.push(`CREATE if not exists ${unique}INDEX \`${indexName}\` ON \`${tableName}\` (${columns2});`);
|
|
459
|
+
}
|
|
460
|
+
for (const [fkName, dbFK] of Object.entries(dbTable.foreignKeys)) {
|
|
461
|
+
changes.push(`ALTER TABLE \`${tableName}\` ADD CONSTRAINT \`${fkName}\` FOREIGN KEY (\`${dbFK.column}\`) REFERENCES \`${dbFK.referencedTable}\` (\`${dbFK.referencedColumn}\`);`);
|
|
462
|
+
}
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
for (const [colName, dbCol] of Object.entries(dbTable.columns)) {
|
|
466
|
+
const drizzleCol = Object.values(drizzleColumns).find((c) => c.name === colName);
|
|
467
|
+
if (!drizzleCol) {
|
|
468
|
+
const type = dbCol.COLUMN_TYPE;
|
|
469
|
+
const nullable = dbCol.IS_NULLABLE === "YES" ? "NULL" : "NOT NULL";
|
|
470
|
+
changes.push(`ALTER TABLE \`${tableName}\` ADD COLUMN \`${colName}\` ${type} ${nullable};`);
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
const normalizedDbType = normalizeMySQLType(dbCol.COLUMN_TYPE);
|
|
474
|
+
const normalizedDrizzleType = normalizeMySQLType(drizzleCol.getSQLType());
|
|
475
|
+
if (normalizedDbType !== normalizedDrizzleType) {
|
|
476
|
+
const type = dbCol.COLUMN_TYPE;
|
|
477
|
+
const nullable = dbCol.IS_NULLABLE === "YES" ? "NULL" : "NOT NULL";
|
|
478
|
+
changes.push(`ALTER TABLE \`${tableName}\` MODIFY COLUMN \`${colName}\` ${type} ${nullable};`);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
const table = Object.values(schemaModule).find((t) => {
|
|
482
|
+
const metadata = getTableMetadata(t);
|
|
483
|
+
return metadata.tableName === tableName;
|
|
484
|
+
});
|
|
485
|
+
if (table) {
|
|
486
|
+
const metadata = getTableMetadata(table);
|
|
487
|
+
for (const [indexName, dbIndex] of Object.entries(dbTable.indexes)) {
|
|
488
|
+
if (indexName === "PRIMARY") {
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
const isForeignKeyIndex = metadata.foreignKeys.some((fk) => getForeignKeyName(fk) === indexName);
|
|
492
|
+
if (isForeignKeyIndex) {
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
const existsUniqIndex = metadata.uniqueConstraints.find((uc) => getUniqueConstraintName(uc) === indexName);
|
|
496
|
+
let drizzleIndex = metadata.indexes.find((i) => getIndexName(i) === indexName);
|
|
497
|
+
if (!drizzleIndex && existsUniqIndex) {
|
|
498
|
+
drizzleIndex = existsUniqIndex;
|
|
499
|
+
}
|
|
500
|
+
if (!drizzleIndex) {
|
|
501
|
+
const columns = dbIndex.columns.map((col) => `\`${col}\``).join(", ");
|
|
502
|
+
const unique = dbIndex.unique ? "UNIQUE " : "";
|
|
503
|
+
changes.push(`CREATE if not exists ${unique}INDEX \`${indexName}\` ON \`${tableName}\` (${columns});`);
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
const dbColumns = dbIndex.columns.join(", ");
|
|
507
|
+
const drizzleColumns2 = getIndexColumns(drizzleIndex).join(", ");
|
|
508
|
+
if (dbColumns !== drizzleColumns2 || dbIndex.unique !== drizzleIndex instanceof uniqueConstraint.UniqueConstraintBuilder) {
|
|
509
|
+
changes.push(`DROP INDEX \`${indexName}\` ON \`${tableName}\`;`);
|
|
510
|
+
const columns = dbIndex.columns.map((col) => `\`${col}\``).join(", ");
|
|
511
|
+
const unique = dbIndex.unique ? "UNIQUE " : "";
|
|
512
|
+
changes.push(`CREATE if not exists ${unique}INDEX \`${indexName}\` ON \`${tableName}\` (${columns});`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
for (const [fkName, dbFK] of Object.entries(dbTable.foreignKeys)) {
|
|
516
|
+
const drizzleFK = metadata.foreignKeys.find((fk) => getForeignKeyName(fk) === fkName);
|
|
517
|
+
if (!drizzleFK) {
|
|
518
|
+
changes.push(`ALTER TABLE \`${tableName}\` ADD CONSTRAINT \`${fkName}\` FOREIGN KEY (\`${dbFK.column}\`) REFERENCES \`${dbFK.referencedTable}\` (\`${dbFK.referencedColumn}\`);`);
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
for (const drizzleForeignKey of metadata.foreignKeys) {
|
|
523
|
+
const isDbFk = Object.keys(dbTable.foreignKeys).find((fk) => fk === getForeignKeyName(drizzleForeignKey));
|
|
524
|
+
if (!isDbFk) {
|
|
525
|
+
const fkName = getForeignKeyName(drizzleForeignKey);
|
|
526
|
+
changes.push(`ALTER TABLE \`${tableName}\` DROP FOREIGN KEY \`${fkName}\`;`);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return changes;
|
|
532
|
+
}
|
|
299
533
|
const updateMigration = async (options) => {
|
|
300
534
|
try {
|
|
301
535
|
let version = await loadMigrationVersion(options.output);
|
|
536
|
+
const prevVersion = version;
|
|
302
537
|
if (version < 1) {
|
|
303
538
|
console.log(
|
|
304
539
|
`⚠️ Initial migration not found. Run "npx forge-sql-orm migrations:create" first.`
|
|
@@ -306,247 +541,81 @@ const updateMigration = async (options) => {
|
|
|
306
541
|
process.exit(0);
|
|
307
542
|
}
|
|
308
543
|
version += 1;
|
|
309
|
-
const
|
|
310
|
-
const orm = mysql.MikroORM.initSync({
|
|
544
|
+
const connection = await mysql.createConnection({
|
|
311
545
|
host: options.host,
|
|
312
546
|
port: options.port,
|
|
313
547
|
user: options.user,
|
|
314
548
|
password: options.password,
|
|
315
|
-
|
|
316
|
-
entities,
|
|
317
|
-
debug: true
|
|
549
|
+
database: options.dbName
|
|
318
550
|
});
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
process.exit(0);
|
|
329
|
-
}
|
|
330
|
-
} catch (error) {
|
|
331
|
-
console.error(`❌ Error during migration update:`, error);
|
|
332
|
-
process.exit(1);
|
|
333
|
-
}
|
|
334
|
-
};
|
|
335
|
-
const PATCHES = [
|
|
336
|
-
// 🗑️ Remove unused dialects (mssql, postgres, sqlite) in MikroORM
|
|
337
|
-
{
|
|
338
|
-
file: "node_modules/@mikro-orm/knex/MonkeyPatchable.d.ts",
|
|
339
|
-
deleteLines: [
|
|
340
|
-
/^.*mssql.*$/gim,
|
|
341
|
-
/^.*MsSql.*$/gim,
|
|
342
|
-
/^\s*Postgres.*$/gm,
|
|
343
|
-
/^.*Sqlite3.*$/gm,
|
|
344
|
-
/^.*BetterSqlite3.*$/gim
|
|
345
|
-
],
|
|
346
|
-
description: "Removing unused dialects from MonkeyPatchable.d.ts"
|
|
347
|
-
},
|
|
348
|
-
{
|
|
349
|
-
file: "node_modules/@mikro-orm/knex/MonkeyPatchable.js",
|
|
350
|
-
deleteLines: [
|
|
351
|
-
/^.*mssql.*$/gim,
|
|
352
|
-
/^.*MsSql.*$/gim,
|
|
353
|
-
/^.*postgres.*$/gim,
|
|
354
|
-
/^.*sqlite.*$/gim,
|
|
355
|
-
/^.*Sqlite.*$/gim
|
|
356
|
-
],
|
|
357
|
-
description: "Removing unused dialects from MonkeyPatchable.js"
|
|
358
|
-
},
|
|
359
|
-
{
|
|
360
|
-
file: "node_modules/@mikro-orm/knex/dialects/index.js",
|
|
361
|
-
deleteLines: [/^.*mssql.*$/gim, /^.*MsSql.*$/gim, /^.*postgresql.*$/gim, /^.*sqlite.*$/gim],
|
|
362
|
-
description: "Removing unused dialects from @mikro-orm/knex/dialects/index.js"
|
|
363
|
-
},
|
|
364
|
-
{
|
|
365
|
-
deleteFolder: "node_modules/@mikro-orm/knex/dialects/mssql",
|
|
366
|
-
description: "Removing mssql dialect from MikroORM"
|
|
367
|
-
},
|
|
368
|
-
{
|
|
369
|
-
deleteFolder: "node_modules/@mikro-orm/knex/dialects/postgresql",
|
|
370
|
-
description: "Removing postgresql dialect from MikroORM"
|
|
371
|
-
},
|
|
372
|
-
{
|
|
373
|
-
deleteFolder: "node_modules/@mikro-orm/knex/dialects/sqlite",
|
|
374
|
-
description: "Removing sqlite dialect from MikroORM"
|
|
375
|
-
},
|
|
376
|
-
{
|
|
377
|
-
deleteFolder: "node_modules/@mikro-orm/mysql/node_modules",
|
|
378
|
-
description: "Removing node_modules from @mikro-orm/mysql"
|
|
379
|
-
},
|
|
380
|
-
{
|
|
381
|
-
deleteFolder: "node_modules/@mikro-orm/knex/node_modules",
|
|
382
|
-
description: "Removing node_modules from @mikro-orm/knex"
|
|
383
|
-
},
|
|
384
|
-
{
|
|
385
|
-
deleteFolder: "node_modules/@mikro-orm/core/node_modules",
|
|
386
|
-
description: "Removing sqlite dialect from MikroORM"
|
|
387
|
-
},
|
|
388
|
-
// 🔄 Fix Webpack `Critical dependency: the request of a dependency is an expression`
|
|
389
|
-
{
|
|
390
|
-
file: "node_modules/@mikro-orm/core/utils/Configuration.js",
|
|
391
|
-
search: /dynamicImportProvider:\s*\/\* istanbul ignore next \*\/\s*\(id\) => import\(id\),/g,
|
|
392
|
-
replace: "dynamicImportProvider: /* istanbul ignore next */ () => Promise.resolve({}),",
|
|
393
|
-
description: "Fixing dynamic imports in MikroORM Configuration"
|
|
394
|
-
},
|
|
395
|
-
{
|
|
396
|
-
file: "node_modules/@mikro-orm/core/utils/Utils.js",
|
|
397
|
-
search: /static dynamicImportProvider = \(id\) => import\(id\);/g,
|
|
398
|
-
replace: "static dynamicImportProvider = () => Promise.resolve({});",
|
|
399
|
-
description: "Fixing dynamic imports in MikroORM Utils.js"
|
|
400
|
-
},
|
|
401
|
-
// 🛑 Remove deprecated `require.extensions` usage
|
|
402
|
-
{
|
|
403
|
-
file: "node_modules/@mikro-orm/core/utils/Utils.js",
|
|
404
|
-
search: /\s\|\|\s*\(require\.extensions\s*&&\s*!!require\.extensions\['\.ts'\]\);\s*/g,
|
|
405
|
-
replace: ";",
|
|
406
|
-
description: "Removing deprecated `require.extensions` check in MikroORM"
|
|
407
|
-
},
|
|
408
|
-
// 🛠️ Patch Knex to remove `Migrator` and `Seeder`
|
|
409
|
-
{
|
|
410
|
-
file: "node_modules/knex/lib/knex-builder/make-knex.js",
|
|
411
|
-
deleteLines: [
|
|
412
|
-
/^const \{ Migrator \} = require\('\.\.\/migrations\/migrate\/Migrator'\);$/gm,
|
|
413
|
-
/^const Seeder = require\('\.\.\/migrations\/seed\/Seeder'\);$/gm
|
|
414
|
-
],
|
|
415
|
-
description: "Removing `Migrator` and `Seeder` requires from make-knex.js"
|
|
416
|
-
},
|
|
417
|
-
{
|
|
418
|
-
file: "node_modules/knex/lib/knex-builder/make-knex.js",
|
|
419
|
-
search: /\sreturn new Migrator\(this\);/g,
|
|
420
|
-
replace: "return null;",
|
|
421
|
-
description: "Replacing `return new Migrator(this);` with `return null;`"
|
|
422
|
-
},
|
|
423
|
-
{
|
|
424
|
-
file: "node_modules/knex/lib/knex-builder/make-knex.js",
|
|
425
|
-
search: /\sreturn new Seeder\(this\);/g,
|
|
426
|
-
replace: "return null;",
|
|
427
|
-
description: "Replacing `return new Seeder(this);` with `return null;`"
|
|
428
|
-
},
|
|
429
|
-
{
|
|
430
|
-
file: "node_modules/knex/lib/dialects/index.js",
|
|
431
|
-
deleteLines: [
|
|
432
|
-
/^.*mssql.*$/gim,
|
|
433
|
-
/^.*MsSql.*$/gim,
|
|
434
|
-
/^.*postgresql.*$/gim,
|
|
435
|
-
/^.*sqlite.*$/gim,
|
|
436
|
-
/^.*oracle.*$/gim,
|
|
437
|
-
/^.*oracledb.*$/gim,
|
|
438
|
-
/^.*pgnative.*$/gim,
|
|
439
|
-
/^.*postgres.*$/gim,
|
|
440
|
-
/^.*redshift.*$/gim,
|
|
441
|
-
/^.*sqlite3.*$/gim,
|
|
442
|
-
/^.*cockroachdb.*$/gim
|
|
443
|
-
],
|
|
444
|
-
description: "Removing unused dialects from @mikro-orm/knex/dialects/index.js"
|
|
445
|
-
},
|
|
446
|
-
{
|
|
447
|
-
file: "node_modules/@mikro-orm/core/utils/Utils.js",
|
|
448
|
-
search: /\s\|\|\s*\(require\.extensions\s*&&\s*!!require\.extensions\['\.ts'\]\);\s*/g,
|
|
449
|
-
replace: ";",
|
|
450
|
-
// Replaces with semicolon to keep syntax valid
|
|
451
|
-
description: "Removing deprecated `require.extensions` check from MikroORM"
|
|
452
|
-
},
|
|
453
|
-
{
|
|
454
|
-
file: "node_modules/@mikro-orm/core/utils/Utils.js",
|
|
455
|
-
search: /^.*extensions.*$/gim,
|
|
456
|
-
replace: "{",
|
|
457
|
-
// Replaces with semicolon to keep syntax valid
|
|
458
|
-
description: "Removing deprecated `require.extensions` check from MikroORM"
|
|
459
|
-
},
|
|
460
|
-
{
|
|
461
|
-
file: "node_modules/@mikro-orm/core/utils/Utils.js",
|
|
462
|
-
search: /^.*package.json.*$/gim,
|
|
463
|
-
replace: "return 0;",
|
|
464
|
-
// Replaces with semicolon to keep syntax valid
|
|
465
|
-
description: "Removing deprecated `require.extensions` check from MikroORM"
|
|
466
|
-
},
|
|
467
|
-
{
|
|
468
|
-
file: "node_modules/@mikro-orm/knex/dialects/mysql/index.js",
|
|
469
|
-
deleteLines: [/^.*MariaDbKnexDialect.*$/gim],
|
|
470
|
-
description: "Removing MariaDbKnexDialect"
|
|
471
|
-
}
|
|
472
|
-
];
|
|
473
|
-
function runPostInstallPatch() {
|
|
474
|
-
console.log("🔧 Applying MikroORM & Knex patches...");
|
|
475
|
-
PATCHES.forEach(
|
|
476
|
-
({ file, search, replace, deleteLines, deleteFile, deleteFolder, description }) => {
|
|
477
|
-
if (file) {
|
|
478
|
-
const filePath = path.resolve(file);
|
|
479
|
-
if (fs.existsSync(filePath)) {
|
|
480
|
-
let content = fs.readFileSync(filePath, "utf8");
|
|
481
|
-
let originalContent = content;
|
|
482
|
-
if (search && replace) {
|
|
483
|
-
if (typeof search === "string" ? content.includes(search) : search.test(content)) {
|
|
484
|
-
content = content.replace(search, replace);
|
|
485
|
-
console.log(`[PATCHED] ${description}`);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
if (deleteLines) {
|
|
489
|
-
deleteLines.forEach((pattern) => {
|
|
490
|
-
content = content.split("\n").filter((line) => !pattern.test(line)).join("\n");
|
|
491
|
-
});
|
|
492
|
-
if (content !== originalContent) {
|
|
493
|
-
console.log(`[CLEANED] Removed matching lines in ${file}`);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
if (content !== originalContent) {
|
|
497
|
-
fs.writeFileSync(filePath, content, "utf8");
|
|
498
|
-
}
|
|
499
|
-
if (content.trim() === "") {
|
|
500
|
-
fs.unlinkSync(filePath);
|
|
501
|
-
console.log(`[REMOVED] ${filePath} (file is now empty)`);
|
|
502
|
-
}
|
|
503
|
-
} else {
|
|
504
|
-
console.warn(`[WARNING] File not found: ${file}`);
|
|
505
|
-
}
|
|
551
|
+
try {
|
|
552
|
+
const dbSchema = await getDatabaseSchema(connection, options.dbName);
|
|
553
|
+
const schemaPath = path.resolve(options.entitiesPath, "schema.ts");
|
|
554
|
+
if (!fs.existsSync(schemaPath)) {
|
|
555
|
+
throw new Error(`Schema file not found at: ${schemaPath}`);
|
|
556
|
+
}
|
|
557
|
+
const schemaModule = await import(schemaPath);
|
|
558
|
+
if (!schemaModule) {
|
|
559
|
+
throw new Error(`Invalid schema file at: ${schemaPath}. Schema must export tables.`);
|
|
506
560
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
561
|
+
const drizzleSchema = {};
|
|
562
|
+
const tables = Object.values(schemaModule);
|
|
563
|
+
tables.forEach((table) => {
|
|
564
|
+
const metadata = getTableMetadata(table);
|
|
565
|
+
if (metadata.tableName) {
|
|
566
|
+
const columns = {};
|
|
567
|
+
Object.entries(metadata.columns).forEach(([name, column]) => {
|
|
568
|
+
columns[name] = {
|
|
569
|
+
type: column.dataType,
|
|
570
|
+
notNull: column.notNull,
|
|
571
|
+
autoincrement: column.autoincrement,
|
|
572
|
+
columnType: column.columnType,
|
|
573
|
+
name: column.name,
|
|
574
|
+
getSQLType: () => column.getSQLType()
|
|
575
|
+
};
|
|
576
|
+
});
|
|
577
|
+
drizzleSchema[metadata.tableName] = columns;
|
|
514
578
|
}
|
|
579
|
+
});
|
|
580
|
+
if (Object.keys(drizzleSchema).length === 0) {
|
|
581
|
+
throw new Error(`No valid tables found in schema at: ${schemaPath}`);
|
|
515
582
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
console.log(`[SKIPPED] ${deleteFolderPath} ${description}`);
|
|
583
|
+
console.log("Found tables:", Object.keys(drizzleSchema));
|
|
584
|
+
const createStatements = filterWithPreviousMigration(generateSchemaChanges(drizzleSchema, dbSchema, schemaModule), prevVersion, options.output);
|
|
585
|
+
if (createStatements.length) {
|
|
586
|
+
const migrationFile = generateMigrationFile$1(createStatements, version);
|
|
587
|
+
if (saveMigrationFiles$1(migrationFile, version, options.output)) {
|
|
588
|
+
console.log(`✅ Migration successfully updated!`);
|
|
523
589
|
}
|
|
590
|
+
process.exit(0);
|
|
591
|
+
} else {
|
|
592
|
+
console.log(`⚠️ No new migration changes detected.`);
|
|
593
|
+
process.exit(0);
|
|
524
594
|
}
|
|
595
|
+
} finally {
|
|
596
|
+
await connection.end();
|
|
525
597
|
}
|
|
526
|
-
)
|
|
527
|
-
|
|
528
|
-
|
|
598
|
+
} catch (error) {
|
|
599
|
+
console.error(`❌ Error during migration update:`, error);
|
|
600
|
+
process.exit(1);
|
|
601
|
+
}
|
|
602
|
+
};
|
|
529
603
|
function generateMigrationUUID(version) {
|
|
530
604
|
const now = /* @__PURE__ */ new Date();
|
|
531
605
|
const timestamp = now.getTime();
|
|
532
606
|
return `MIGRATION_V${version}_${timestamp}`;
|
|
533
607
|
}
|
|
534
|
-
function cleanSQLStatement(sql) {
|
|
535
|
-
return sql.replace(/\s+default\s+character\s+set\s+utf8mb4\s+engine\s*=\s*InnoDB;?/gi, "").trim();
|
|
536
|
-
}
|
|
537
608
|
function generateMigrationFile(createStatements, version) {
|
|
538
609
|
const uniqId = generateMigrationUUID(version);
|
|
539
610
|
const migrationLines = createStatements.map(
|
|
540
|
-
(stmt, index) => ` .enqueue("${uniqId}_${index}", "${
|
|
611
|
+
(stmt, index) => ` .enqueue("${uniqId}_${index}", "${stmt}")`
|
|
541
612
|
// eslint-disable-line no-useless-escape
|
|
542
613
|
).join("\n");
|
|
543
|
-
const clearMigrationsLine = ` .enqueue("${uniqId}", "DELETE FROM __migrations")`;
|
|
544
614
|
return `import { MigrationRunner } from "@forge/sql/out/migration";
|
|
545
615
|
|
|
546
616
|
export default (migrationRunner: MigrationRunner): MigrationRunner => {
|
|
547
617
|
return migrationRunner
|
|
548
|
-
${migrationLines}
|
|
549
|
-
${clearMigrationsLine};
|
|
618
|
+
${migrationLines};
|
|
550
619
|
};`;
|
|
551
620
|
}
|
|
552
621
|
function saveMigrationFiles(migrationCode, version, outputDir) {
|
|
@@ -581,42 +650,54 @@ export default async (
|
|
|
581
650
|
console.log(`✅ Migration count file updated: ${migrationCountPath}`);
|
|
582
651
|
console.log(`✅ Migration index file created: ${indexFilePath}`);
|
|
583
652
|
}
|
|
584
|
-
const extractDropStatements = (schema) => {
|
|
585
|
-
const statements = schema.split(";").map((s) => s.trim());
|
|
586
|
-
return statements.filter((s) => {
|
|
587
|
-
return s.toLowerCase().startsWith("drop");
|
|
588
|
-
});
|
|
589
|
-
};
|
|
590
|
-
const loadEntities = async (entitiesPath) => {
|
|
591
|
-
try {
|
|
592
|
-
const indexFilePath = path.resolve(path.join(entitiesPath, "index.ts"));
|
|
593
|
-
if (!fs.existsSync(indexFilePath)) {
|
|
594
|
-
console.error(`❌ Error: index.ts not found in ${indexFilePath}`);
|
|
595
|
-
process.exit(1);
|
|
596
|
-
}
|
|
597
|
-
const { default: entities } = await import(indexFilePath);
|
|
598
|
-
console.log(`✅ Loaded ${entities.length} entities from ${entitiesPath}`);
|
|
599
|
-
return entities;
|
|
600
|
-
} catch (error) {
|
|
601
|
-
console.error(`❌ Error loading index.ts from ${entitiesPath}:`, error);
|
|
602
|
-
process.exit(1);
|
|
603
|
-
}
|
|
604
|
-
};
|
|
605
653
|
const dropMigration = async (options) => {
|
|
606
654
|
try {
|
|
607
655
|
const version = 1;
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
656
|
+
const schemaPath = path.resolve(options.entitiesPath, "schema.ts");
|
|
657
|
+
if (!fs.existsSync(schemaPath)) {
|
|
658
|
+
throw new Error(`Schema file not found at: ${schemaPath}`);
|
|
659
|
+
}
|
|
660
|
+
const schemaModule = await import(schemaPath);
|
|
661
|
+
if (!schemaModule) {
|
|
662
|
+
throw new Error(`Invalid schema file at: ${schemaPath}. Schema must export tables.`);
|
|
663
|
+
}
|
|
664
|
+
const drizzleSchema = {};
|
|
665
|
+
const tables = Object.values(schemaModule);
|
|
666
|
+
tables.forEach((table) => {
|
|
667
|
+
const symbols = Object.getOwnPropertySymbols(table);
|
|
668
|
+
const nameSymbol = symbols.find((s) => s.toString().includes("Name"));
|
|
669
|
+
const columnsSymbol = symbols.find((s) => s.toString().includes("Columns"));
|
|
670
|
+
const indexesSymbol = symbols.find((s) => s.toString().includes("Indexes"));
|
|
671
|
+
const foreignKeysSymbol = symbols.find((s) => s.toString().includes("ForeignKeys"));
|
|
672
|
+
if (table && nameSymbol && columnsSymbol) {
|
|
673
|
+
drizzleSchema[table[nameSymbol]] = {
|
|
674
|
+
// @ts-ignore
|
|
675
|
+
columns: table[columnsSymbol],
|
|
676
|
+
// @ts-ignore
|
|
677
|
+
indexes: indexesSymbol ? table[indexesSymbol] || {} : {},
|
|
678
|
+
// @ts-ignore
|
|
679
|
+
foreignKeys: foreignKeysSymbol ? table[foreignKeysSymbol] || {} : {}
|
|
680
|
+
};
|
|
681
|
+
}
|
|
616
682
|
});
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
683
|
+
if (Object.keys(drizzleSchema).length === 0) {
|
|
684
|
+
throw new Error(`No valid tables found in schema at: ${schemaPath}`);
|
|
685
|
+
}
|
|
686
|
+
console.log("Found tables:", Object.keys(drizzleSchema));
|
|
687
|
+
const dropStatements = [];
|
|
688
|
+
for (const [tableName, tableInfo] of Object.entries(drizzleSchema)) {
|
|
689
|
+
for (const fk of Object.values(tableInfo.foreignKeys)) {
|
|
690
|
+
const fkName = fk.getName();
|
|
691
|
+
dropStatements.push(`ALTER TABLE \`${tableName}\` DROP FOREIGN KEY \`${fkName}\`;`);
|
|
692
|
+
}
|
|
693
|
+
for (const [indexName, index] of Object.entries(tableInfo.indexes)) {
|
|
694
|
+
if (indexName === "PRIMARY") continue;
|
|
695
|
+
dropStatements.push(`DROP INDEX \`${indexName}\` ON \`${tableName}\`;`);
|
|
696
|
+
}
|
|
697
|
+
dropStatements.push(`DROP TABLE IF EXISTS \`${tableName}\`;`);
|
|
698
|
+
}
|
|
699
|
+
dropStatements.push(`DELETE FROM __migrations;`);
|
|
700
|
+
const migrationFile = generateMigrationFile(dropStatements, version);
|
|
620
701
|
saveMigrationFiles(migrationFile, version, options.output);
|
|
621
702
|
console.log(`✅ Migration successfully created!`);
|
|
622
703
|
process.exit(0);
|
|
@@ -796,12 +877,5 @@ program.command("migrations:drop").description("Generate a migration to drop all
|
|
|
796
877
|
);
|
|
797
878
|
await dropMigration(config);
|
|
798
879
|
});
|
|
799
|
-
program.command("patch:mikroorm").description("Patch MikroORM and Knex dependencies to work properly with Forge").action(async () => {
|
|
800
|
-
console.log("Running MikroORM patch...");
|
|
801
|
-
await runPostInstallPatch();
|
|
802
|
-
await runPostInstallPatch();
|
|
803
|
-
await runPostInstallPatch();
|
|
804
|
-
console.log("✅ MikroORM patch applied successfully!");
|
|
805
|
-
});
|
|
806
880
|
program.parse(process.argv);
|
|
807
881
|
//# sourceMappingURL=cli.js.map
|