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