forge-sql-orm-cli 2.1.16 → 2.1.18

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.
@@ -1,8 +1,8 @@
1
1
  import "reflect-metadata";
2
2
  import fs from "fs";
3
3
  import path from "path";
4
-
5
- import { execSync } from "child_process";
4
+ import mysql from "mysql2/promise";
5
+ import { RowDataPacket } from "mysql2";
6
6
 
7
7
  /**
8
8
  * Options for migration creation
@@ -11,6 +11,16 @@ export interface CreateMigrationOptions {
11
11
  output: string;
12
12
  entitiesPath: string;
13
13
  force?: boolean;
14
+ host?: string;
15
+ port?: number;
16
+ user?: string;
17
+ password?: string;
18
+ dbName?: string;
19
+ }
20
+
21
+ interface CreateTableRow extends RowDataPacket {
22
+ Table: string;
23
+ "Create Table": string;
14
24
  }
15
25
 
16
26
  /**
@@ -35,29 +45,37 @@ export const loadMigrationVersion = async (migrationPath: string): Promise<numbe
35
45
  }
36
46
  };
37
47
 
48
+ /**
49
+ * Regular expressions for adding IF NOT EXISTS to SQL statements
50
+ * Note: MySQL/TiDB does not support IF NOT EXISTS for ALTER TABLE ADD CONSTRAINT
51
+ */
52
+ const SQL_KIND_REGEX = /CREATE (?!.*IF NOT EXISTS)(UNIQUE INDEX|INDEX|TABLE) /gim;
53
+
54
+ /**
55
+ * Inserts IF NOT EXISTS into CREATE statements.
56
+ * Only adds IF NOT EXISTS to CREATE TABLE, CREATE INDEX, and CREATE UNIQUE INDEX.
57
+ * Does not add IF NOT EXISTS to ALTER TABLE statements as MySQL/TiDB doesn't support it.
58
+ * @param content - The SQL content.
59
+ * @returns The SQL content with IF NOT EXISTS added.
60
+ */
61
+ function insertNotExists(content: string): string {
62
+ SQL_KIND_REGEX.lastIndex = 0;
63
+
64
+ // Add IF NOT EXISTS to CREATE TABLE, CREATE INDEX, CREATE UNIQUE INDEX
65
+ // Note: ALTER TABLE ADD CONSTRAINT and ALTER TABLE ADD INDEX don't support IF NOT EXISTS in MySQL/TiDB
66
+ content = content.replace(SQL_KIND_REGEX, "CREATE $1 IF NOT EXISTS ");
67
+
68
+ return content;
69
+ }
70
+
38
71
  /**
39
72
  * Cleans SQL statements by removing unnecessary database options.
40
73
  * @param sql - The raw SQL statement.
41
74
  * @returns The cleaned SQL statement.
42
75
  */
43
76
  export function cleanSQLStatement(sql: string): string {
44
- // Add IF NOT EXISTS to CREATE TABLE statements
45
- sql = sql.replace(/create\s+table\s+(\w+)/gi, "create table if not exists $1");
46
-
47
- // Add IF NOT EXISTS to CREATE INDEX statements
48
- sql = sql.replace(/create\s+index\s+(\w+)/gi, "create index if not exists $1");
49
-
50
- // Add IF NOT EXISTS to ADD INDEX statements
51
- sql = sql.replace(
52
- /alter\s+table\s+(\w+)\s+add\s+index\s+(\w+)/gi,
53
- "alter table $1 add index if not exists $2",
54
- );
55
-
56
- // Add IF NOT EXISTS to ADD CONSTRAINT statements
57
- sql = sql.replace(
58
- /alter\s+table\s+(\w+)\s+add\s+constraint\s+(\w+)/gi,
59
- "alter table $1 add constraint if not exists $2",
60
- );
77
+ // Add IF NOT EXISTS to relevant statements
78
+ sql = insertNotExists(sql);
61
79
 
62
80
  // Remove unnecessary database options
63
81
  return sql.replace(/\s+default\s+character\s+set\s+utf8mb4\s+engine\s*=\s*InnoDB;?/gi, "").trim();
@@ -143,27 +161,51 @@ ${callLines.join("\n")}
143
161
  }
144
162
 
145
163
  /**
146
- * Extracts only the relevant SQL statements for migration.
147
- * @param schema - The full database schema as SQL.
148
- * @returns Filtered list of SQL statements.
164
+ * Gets list of tables from the database
165
+ * @param connection - MySQL connection
166
+ * @returns Array of table names
149
167
  */
150
- export const extractCreateStatements = (schema: string): string[] => {
151
- // Split by statement-breakpoint and semicolon
152
- const statements = schema
153
- .split(/--> statement-breakpoint|;/)
154
- .map((s) => s.trim())
155
- .filter((s) => s.length > 0);
156
-
157
- return statements.filter(
158
- (stmt) =>
159
- stmt.toLowerCase().startsWith("create table") ||
160
- stmt.toLowerCase().startsWith("alter table") ||
161
- stmt.toLowerCase().includes("add index") ||
162
- stmt.toLowerCase().includes("create index") ||
163
- stmt.toLowerCase().includes("add unique index") ||
164
- stmt.toLowerCase().includes("add constraint"),
165
- );
166
- };
168
+ async function getTables(connection: mysql.Connection): Promise<string[]> {
169
+ const [rows] = await connection.execute<any[]>("SHOW TABLES");
170
+ return rows.map((row) => Object.values(row)[0] as string);
171
+ }
172
+
173
+ /**
174
+ * Gets CREATE TABLE statement for a specific table
175
+ * @param connection - MySQL connection
176
+ * @param tableName - Name of the table
177
+ * @returns CREATE TABLE statement
178
+ */
179
+ async function getCreateTableStatement(
180
+ connection: mysql.Connection,
181
+ tableName: string,
182
+ ): Promise<string | null> {
183
+ const [rows] = await connection.execute<CreateTableRow[]>(`SHOW CREATE TABLE \`${tableName}\``);
184
+ const result = rows as CreateTableRow[];
185
+ if (result.length > 0 && result[0]["Create Table"]) {
186
+ return result[0]["Create Table"];
187
+ }
188
+ return null;
189
+ }
190
+
191
+ /**
192
+ * Gets all CREATE TABLE statements from the database
193
+ * @param connection - MySQL connection
194
+ * @returns Array of CREATE TABLE statements
195
+ */
196
+ async function getAllCreateTableStatements(connection: mysql.Connection): Promise<string[]> {
197
+ const tables = await getTables(connection);
198
+ const statements: string[] = [];
199
+
200
+ for (const table of tables) {
201
+ const createTable = await getCreateTableStatement(connection, table);
202
+ if (createTable) {
203
+ statements.push(createTable);
204
+ }
205
+ }
206
+
207
+ return statements;
208
+ }
167
209
 
168
210
  /**
169
211
  * Creates a full database migration.
@@ -186,29 +228,45 @@ export const createMigration = async (options: CreateMigrationOptions) => {
186
228
  }
187
229
  }
188
230
 
189
- // Generate SQL using drizzle-kit
190
- await execSync(
191
- `npx drizzle-kit generate --name=init --dialect mysql --out ${options.output} --schema ${options.entitiesPath}`,
192
- { encoding: "utf-8" },
193
- );
194
- const initSqlFile = path.join(options.output, "0000_init.sql");
195
- const sql = fs.readFileSync(initSqlFile, "utf-8");
196
-
197
- // Extract and clean statements
198
- const createStatements = extractCreateStatements(sql);
199
-
200
- // Generate and save migration files
201
- const migrationFile = generateMigrationFile(createStatements, 1);
202
- saveMigrationFiles(migrationFile, 1, options.output);
203
-
204
- fs.rmSync(initSqlFile, { force: true });
205
- console.log(`✅ Removed SQL file: ${initSqlFile}`);
206
- // Remove meta directory after processing
207
- let metaDir = path.join(options.output, "meta");
208
- fs.rmSync(metaDir, { recursive: true, force: true });
209
- console.log(`✅ Removed: ${metaDir}`);
210
- console.log(`✅ Migration successfully created!`);
211
- process.exit(0);
231
+ // Validate database connection parameters
232
+ if (!options.host || !options.port || !options.user || !options.password || !options.dbName) {
233
+ console.error(
234
+ `❌ Error: Database connection parameters are required (host, port, user, password, dbName)`,
235
+ );
236
+ process.exit(1);
237
+ }
238
+
239
+ // Create database connection
240
+ const connection = await mysql.createConnection({
241
+ host: options.host,
242
+ port: options.port,
243
+ user: options.user,
244
+ password: options.password,
245
+ database: options.dbName,
246
+ });
247
+
248
+ try {
249
+ console.log(`✅ Connected to database: ${options.dbName}`);
250
+
251
+ // Get all CREATE TABLE statements from the database
252
+ console.log(`📋 Fetching CREATE TABLE statements from database...`);
253
+ const createStatements = await getAllCreateTableStatements(connection);
254
+
255
+ if (createStatements.length === 0) {
256
+ console.warn(`⚠️ Warning: No tables found in the database.`);
257
+ } else {
258
+ console.log(`✅ Found ${createStatements.length} table(s)`);
259
+ }
260
+
261
+ // Generate and save migration files
262
+ const migrationFile = generateMigrationFile(createStatements, 1);
263
+ saveMigrationFiles(migrationFile, 1, options.output);
264
+
265
+ console.log(`✅ Migration successfully created!`);
266
+ process.exit(0);
267
+ } finally {
268
+ await connection.end();
269
+ }
212
270
  } catch (error) {
213
271
  console.error(`❌ Error during migration creation:`, error);
214
272
  process.exit(1);