forge-sql-orm 2.0.12 → 2.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist-cli/cli.mjs DELETED
@@ -1,964 +0,0 @@
1
- #!/usr/bin/env node
2
- import { Command } from "commander";
3
- import dotenv from "dotenv";
4
- import inquirer from "inquirer";
5
- import fs from "fs";
6
- import path from "path";
7
- import "reflect-metadata";
8
- import { execSync } from "child_process";
9
- import mysql from "mysql2/promise";
10
- import moment from "moment";
11
- import { UniqueConstraintBuilder } from "drizzle-orm/mysql-core/unique-constraint";
12
- import "@forge/sql";
13
- import { customType, mysqlTable, timestamp, varchar, bigint } from "drizzle-orm/mysql-core";
14
- import moment$1 from "moment/moment.js";
15
- function replaceMySQLTypes(schemaContent) {
16
- const imports = `import { forgeDateTimeString, forgeTimeString, forgeDateString, forgeTimestampString } from "forge-sql-orm";
17
-
18
- `;
19
- let modifiedContent = schemaContent.replace(/datetime\(['"]([^'"]+)['"],\s*{\s*mode:\s*['"]string['"]\s*}\)/g, "forgeDateTimeString('$1')").replace(/datetime\(['"]([^'"]+)['"]\)/g, "forgeDateTimeString('$1')").replace(/datetime\(\s*{\s*mode:\s*['"]string['"]\s*}\s*\)/g, "forgeDateTimeString()").replace(/time\(['"]([^'"]+)['"],\s*{\s*mode:\s*['"]string['"]\s*}\)/g, "forgeTimeString('$1')").replace(/time\(['"]([^'"]+)['"]\)/g, "forgeTimeString('$1')").replace(/time\(\s*{\s*mode:\s*['"]string['"]\s*}\s*\)/g, "forgeTimeString()").replace(/date\(['"]([^'"]+)['"],\s*{\s*mode:\s*['"]string['"]\s*}\)/g, "forgeDateString('$1')").replace(/date\(['"]([^'"]+)['"]\)/g, "forgeDateString('$1')").replace(/date\(\s*{\s*mode:\s*['"]string['"]\s*}\s*\)/g, "forgeDateString()").replace(/timestamp\(['"]([^'"]+)['"],\s*{\s*mode:\s*['"]string['"]\s*}\)/g, "forgeTimestampString('$1')").replace(/timestamp\(['"]([^'"]+)['"]\)/g, "forgeTimestampString('$1')").replace(/timestamp\(\s*{\s*mode:\s*['"]string['"]\s*}\s*\)/g, "forgeTimestampString()");
20
- if (!modifiedContent.includes("import { forgeDateTimeString")) {
21
- modifiedContent = imports + modifiedContent;
22
- }
23
- return modifiedContent;
24
- }
25
- const generateModels = async (options) => {
26
- try {
27
- const sql = await execSync(
28
- `npx drizzle-kit pull --dialect mysql --url mysql://${options.user}:${options.password}@${options.host}:${options.port}/${options.dbName} --out ${options.output}`,
29
- { encoding: "utf-8" }
30
- );
31
- const metaDir = path.join(options.output, "meta");
32
- const additionalMetadata = {};
33
- if (fs.existsSync(metaDir)) {
34
- const snapshotFile = path.join(metaDir, "0000_snapshot.json");
35
- if (fs.existsSync(snapshotFile)) {
36
- const snapshotData = JSON.parse(fs.readFileSync(snapshotFile, "utf-8"));
37
- for (const [tableName, tableData] of Object.entries(snapshotData.tables)) {
38
- const table = tableData;
39
- const versionField = Object.entries(table.columns).find(
40
- ([_, col]) => col.name.toLowerCase() === options.versionField
41
- );
42
- if (versionField) {
43
- const [_, col] = versionField;
44
- const fieldType = col.type;
45
- const isSupportedType = fieldType === "datetime" || fieldType === "timestamp" || fieldType === "int" || fieldType === "number" || fieldType === "decimal";
46
- if (!col.notNull) {
47
- console.warn(`Version field "${col.name}" in table ${tableName} is nullable. Versioning may not work correctly.`);
48
- } else if (!isSupportedType) {
49
- console.warn(
50
- `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.`
51
- );
52
- } else {
53
- additionalMetadata[tableName] = {
54
- tableName,
55
- versionField: {
56
- fieldName: col.name
57
- }
58
- };
59
- }
60
- }
61
- }
62
- }
63
- }
64
- const versionMetadataContent = `/**
65
- * This file was auto-generated by forge-sql-orm
66
- * Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
67
- *
68
- * DO NOT EDIT THIS FILE MANUALLY
69
- * Any changes will be overwritten on next generation
70
- */
71
-
72
-
73
- export * from "./relations";
74
- export * from "./schema";
75
-
76
- export interface VersionFieldMetadata {
77
- fieldName: string;
78
- }
79
-
80
- export interface TableMetadata {
81
- tableName: string;
82
- versionField: VersionFieldMetadata;
83
- }
84
-
85
- export type AdditionalMetadata = Record<string, TableMetadata>;
86
-
87
- export const additionalMetadata: AdditionalMetadata = ${JSON.stringify(additionalMetadata, null, 2)};
88
- `;
89
- fs.writeFileSync(path.join(options.output, "index.ts"), versionMetadataContent);
90
- const schemaPath = path.join(options.output, "schema.ts");
91
- if (fs.existsSync(schemaPath)) {
92
- const schemaContent = fs.readFileSync(schemaPath, "utf-8");
93
- const modifiedContent = replaceMySQLTypes(schemaContent);
94
- fs.writeFileSync(schemaPath, modifiedContent);
95
- console.log(`✅ Updated schema types in: ${schemaPath}`);
96
- }
97
- const migrationDir = path.join(options.output, "migrations");
98
- if (fs.existsSync(migrationDir)) {
99
- fs.rmSync(migrationDir, { recursive: true, force: true });
100
- console.log(`✅ Removed: ${migrationDir}`);
101
- }
102
- if (fs.existsSync(metaDir)) {
103
- const journalFile = path.join(metaDir, "_journal.json");
104
- if (fs.existsSync(journalFile)) {
105
- const journalData = JSON.parse(fs.readFileSync(journalFile, "utf-8"));
106
- for (const entry of journalData.entries) {
107
- const sqlFile = path.join(options.output, `${entry.tag}.sql`);
108
- if (fs.existsSync(sqlFile)) {
109
- fs.rmSync(sqlFile, { force: true });
110
- console.log(`✅ Removed SQL file: ${entry.tag}.sql`);
111
- }
112
- }
113
- }
114
- fs.rmSync(metaDir, { recursive: true, force: true });
115
- console.log(`✅ Removed: ${metaDir}`);
116
- }
117
- console.log(`✅ Successfully generated models and version metadata`);
118
- process.exit(0);
119
- } catch (error) {
120
- console.error(`❌ Error during model generation:`, error);
121
- process.exit(1);
122
- }
123
- };
124
- const loadMigrationVersion$1 = async (migrationPath) => {
125
- try {
126
- const migrationCountFilePath = path.resolve(path.join(migrationPath, "migrationCount.ts"));
127
- if (!fs.existsSync(migrationCountFilePath)) {
128
- return 0;
129
- }
130
- const { MIGRATION_VERSION } = await import(migrationCountFilePath);
131
- console.log(`✅ Current migration version: ${MIGRATION_VERSION}`);
132
- return MIGRATION_VERSION;
133
- } catch (error) {
134
- console.error(`❌ Error loading migrationCount:`, error);
135
- process.exit(1);
136
- }
137
- };
138
- function cleanSQLStatement(sql) {
139
- sql = sql.replace(/create\s+table\s+(\w+)/gi, "create table if not exists $1");
140
- sql = sql.replace(/create\s+index\s+(\w+)/gi, "create index if not exists $1");
141
- sql = sql.replace(/alter\s+table\s+(\w+)\s+add\s+index\s+(\w+)/gi, "alter table $1 add index if not exists $2");
142
- sql = sql.replace(/alter\s+table\s+(\w+)\s+add\s+constraint\s+(\w+)/gi, "alter table $1 add constraint if not exists $2");
143
- return sql.replace(/\s+default\s+character\s+set\s+utf8mb4\s+engine\s*=\s*InnoDB;?/gi, "").trim();
144
- }
145
- function generateMigrationFile$2(createStatements, version) {
146
- const versionPrefix = `v${version}_MIGRATION`;
147
- const migrationLines = createStatements.map(
148
- (stmt, index) => ` .enqueue("${versionPrefix}${index}", "${cleanSQLStatement(stmt).replace(/\s+/g, " ")}")`
149
- // eslint-disable-line no-useless-escape
150
- ).join("\n");
151
- return `import { MigrationRunner } from "@forge/sql/out/migration";
152
-
153
- export default (migrationRunner: MigrationRunner): MigrationRunner => {
154
- return migrationRunner
155
- ${migrationLines};
156
- };`;
157
- }
158
- function saveMigrationFiles$2(migrationCode, version, outputDir) {
159
- if (!fs.existsSync(outputDir)) {
160
- fs.mkdirSync(outputDir, { recursive: true });
161
- }
162
- const migrationFilePath = path.join(outputDir, `migrationV${version}.ts`);
163
- const migrationCountPath = path.join(outputDir, `migrationCount.ts`);
164
- const indexFilePath = path.join(outputDir, `index.ts`);
165
- fs.writeFileSync(migrationFilePath, migrationCode);
166
- fs.writeFileSync(migrationCountPath, `export const MIGRATION_VERSION = ${version};`);
167
- const indexFileContent = `import { MigrationRunner } from "@forge/sql/out/migration";
168
- import { MIGRATION_VERSION } from "./migrationCount";
169
-
170
- export type MigrationType = (
171
- migrationRunner: MigrationRunner,
172
- ) => MigrationRunner;
173
-
174
- export default async (
175
- migrationRunner: MigrationRunner,
176
- ): Promise<MigrationRunner> => {
177
- for (let i = 1; i <= MIGRATION_VERSION; i++) {
178
- const migrations = (await import(\`./migrationV\${i}\`)) as {
179
- default: MigrationType;
180
- };
181
- migrations.default(migrationRunner);
182
- }
183
- return migrationRunner;
184
- };`;
185
- fs.writeFileSync(indexFilePath, indexFileContent);
186
- console.log(`✅ Migration file created: ${migrationFilePath}`);
187
- console.log(`✅ Migration count file updated: ${migrationCountPath}`);
188
- console.log(`✅ Migration index file created: ${indexFilePath}`);
189
- }
190
- const extractCreateStatements = (schema) => {
191
- const statements = schema.split(/--> statement-breakpoint|;/).map((s) => s.trim()).filter((s) => s.length > 0);
192
- return statements.filter(
193
- (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")
194
- );
195
- };
196
- const createMigration = async (options) => {
197
- try {
198
- let version = await loadMigrationVersion$1(options.output);
199
- if (version > 0) {
200
- if (options.force) {
201
- console.warn(`⚠️ Warning: Migration already exists. Creating new migration with force flag...`);
202
- } else {
203
- console.error(`❌ Error: Migration has already been created. Use --force flag to override.`);
204
- process.exit(1);
205
- }
206
- }
207
- version = 1;
208
- await execSync(
209
- `npx drizzle-kit generate --name=init --dialect mysql --out ${options.output} --schema ${options.entitiesPath}`,
210
- { encoding: "utf-8" }
211
- );
212
- const initSqlFile = path.join(options.output, "0000_init.sql");
213
- const sql = fs.readFileSync(initSqlFile, "utf-8");
214
- const createStatements = extractCreateStatements(sql);
215
- const migrationFile = generateMigrationFile$2(createStatements, 1);
216
- saveMigrationFiles$2(migrationFile, 1, options.output);
217
- fs.rmSync(initSqlFile, { force: true });
218
- console.log(`✅ Removed SQL file: ${initSqlFile}`);
219
- let metaDir = path.join(options.output, "meta");
220
- fs.rmSync(metaDir, { recursive: true, force: true });
221
- console.log(`✅ Removed: ${metaDir}`);
222
- console.log(`✅ Migration successfully created!`);
223
- process.exit(0);
224
- } catch (error) {
225
- console.error(`❌ Error during migration creation:`, error);
226
- process.exit(1);
227
- }
228
- };
229
- const parseDateTime = (value, format) => {
230
- const m = moment(value, format, true);
231
- if (!m.isValid()) {
232
- return moment(value).toDate();
233
- }
234
- return m.toDate();
235
- };
236
- function processForeignKeys(table, foreignKeysSymbol, extraSymbol) {
237
- const foreignKeys = [];
238
- if (foreignKeysSymbol) {
239
- const fkArray = table[foreignKeysSymbol];
240
- if (fkArray) {
241
- fkArray.forEach((fk) => {
242
- if (fk.reference) {
243
- const item = fk.reference(fk);
244
- foreignKeys.push(item);
245
- }
246
- });
247
- }
248
- }
249
- if (extraSymbol) {
250
- const extraConfigBuilder = table[extraSymbol];
251
- if (extraConfigBuilder && typeof extraConfigBuilder === "function") {
252
- const configBuilderData = extraConfigBuilder(table);
253
- if (configBuilderData) {
254
- const configBuilders = Array.isArray(configBuilderData) ? configBuilderData : Object.values(configBuilderData).map(
255
- (item) => item.value || item
256
- );
257
- configBuilders.forEach((builder) => {
258
- if (!builder?.constructor) return;
259
- const builderName = builder.constructor.name.toLowerCase();
260
- if (builderName.includes("foreignkeybuilder")) {
261
- foreignKeys.push(builder);
262
- }
263
- });
264
- }
265
- }
266
- }
267
- return foreignKeys;
268
- }
269
- function getTableMetadata(table) {
270
- const symbols = Object.getOwnPropertySymbols(table);
271
- const nameSymbol = symbols.find((s) => s.toString().includes("Name"));
272
- const columnsSymbol = symbols.find((s) => s.toString().includes("Columns"));
273
- const foreignKeysSymbol = symbols.find((s) => s.toString().includes("ForeignKeys)"));
274
- const extraSymbol = symbols.find((s) => s.toString().includes("ExtraConfigBuilder"));
275
- const builders = {
276
- indexes: [],
277
- checks: [],
278
- foreignKeys: [],
279
- primaryKeys: [],
280
- uniqueConstraints: [],
281
- extras: []
282
- };
283
- builders.foreignKeys = processForeignKeys(table, foreignKeysSymbol, extraSymbol);
284
- if (extraSymbol) {
285
- const extraConfigBuilder = table[extraSymbol];
286
- if (extraConfigBuilder && typeof extraConfigBuilder === "function") {
287
- const configBuilderData = extraConfigBuilder(table);
288
- if (configBuilderData) {
289
- const configBuilders = Array.isArray(configBuilderData) ? configBuilderData : Object.values(configBuilderData).map(
290
- (item) => item.value || item
291
- );
292
- configBuilders.forEach((builder) => {
293
- if (!builder?.constructor) return;
294
- const builderName = builder.constructor.name.toLowerCase();
295
- const builderMap = {
296
- indexbuilder: builders.indexes,
297
- checkbuilder: builders.checks,
298
- primarykeybuilder: builders.primaryKeys,
299
- uniqueconstraintbuilder: builders.uniqueConstraints
300
- };
301
- for (const [type, array] of Object.entries(builderMap)) {
302
- if (builderName.includes(type)) {
303
- array.push(builder);
304
- break;
305
- }
306
- }
307
- builders.extras.push(builder);
308
- });
309
- }
310
- }
311
- }
312
- return {
313
- tableName: nameSymbol ? table[nameSymbol] : "",
314
- columns: columnsSymbol ? table[columnsSymbol] : {},
315
- ...builders
316
- };
317
- }
318
- function generateDropTableStatements(tables) {
319
- const dropStatements = [];
320
- tables.forEach((table) => {
321
- const tableMetadata = getTableMetadata(table);
322
- if (tableMetadata.tableName) {
323
- dropStatements.push(`DROP TABLE IF EXISTS \`${tableMetadata.tableName}\`;`);
324
- }
325
- });
326
- dropStatements.push(`DELETE FROM __migrations;`);
327
- return dropStatements;
328
- }
329
- function generateMigrationFile$1(createStatements, version) {
330
- const versionPrefix = `v${version}_MIGRATION`;
331
- const migrationLines = createStatements.map(
332
- (stmt, index) => ` .enqueue("${versionPrefix}${index}", "${stmt}")`
333
- // eslint-disable-line no-useless-escape
334
- ).join("\n");
335
- return `import { MigrationRunner } from "@forge/sql/out/migration";
336
-
337
- export default (migrationRunner: MigrationRunner): MigrationRunner => {
338
- return migrationRunner
339
- ${migrationLines};
340
- };`;
341
- }
342
- function filterWithPreviousMigration(newStatements, prevVersion, outputDir) {
343
- const prevMigrationPath = path.join(outputDir, `migrationV${prevVersion}.ts`);
344
- if (!fs.existsSync(prevMigrationPath)) {
345
- return newStatements.map((s) => s.replace(/\s+/g, " "));
346
- }
347
- const prevContent = fs.readFileSync(prevMigrationPath, "utf-8");
348
- const prevStatements = prevContent.split("\n").filter((line) => line.includes(".enqueue(")).map((line) => {
349
- const match = line.match(/\.enqueue\([^,]+,\s*"([^"]+)"/);
350
- return match ? match[1].replace(/\s+/g, " ").trim() : "";
351
- });
352
- return newStatements.filter((s) => !prevStatements.includes(s.replace(/\s+/g, " "))).map((s) => s.replace(/\s+/g, " "));
353
- }
354
- function saveMigrationFiles$1(migrationCode, version, outputDir) {
355
- if (!fs.existsSync(outputDir)) {
356
- fs.mkdirSync(outputDir, { recursive: true });
357
- }
358
- const migrationFilePath = path.join(outputDir, `migrationV${version}.ts`);
359
- const migrationCountPath = path.join(outputDir, `migrationCount.ts`);
360
- const indexFilePath = path.join(outputDir, `index.ts`);
361
- fs.writeFileSync(migrationFilePath, migrationCode);
362
- fs.writeFileSync(migrationCountPath, `export const MIGRATION_VERSION = ${version};`);
363
- const indexFileContent = `import { MigrationRunner } from "@forge/sql/out/migration";
364
- import { MIGRATION_VERSION } from "./migrationCount";
365
-
366
- export type MigrationType = (
367
- migrationRunner: MigrationRunner,
368
- ) => MigrationRunner;
369
-
370
- export default async (
371
- migrationRunner: MigrationRunner,
372
- ): Promise<MigrationRunner> => {
373
- for (let i = 1; i <= MIGRATION_VERSION; i++) {
374
- const migrations = (await import(\`./migrationV\${i}\`)) as {
375
- default: MigrationType;
376
- };
377
- migrations.default(migrationRunner);
378
- }
379
- return migrationRunner;
380
- };`;
381
- fs.writeFileSync(indexFilePath, indexFileContent);
382
- console.log(`✅ Migration file created: ${migrationFilePath}`);
383
- console.log(`✅ Migration count file updated: ${migrationCountPath}`);
384
- console.log(`✅ Migration index file created: ${indexFilePath}`);
385
- return true;
386
- }
387
- const loadMigrationVersion = async (migrationPath) => {
388
- try {
389
- const migrationCountFilePath = path.resolve(path.join(migrationPath, "migrationCount.ts"));
390
- if (!fs.existsSync(migrationCountFilePath)) {
391
- console.warn(
392
- `⚠️ Warning: migrationCount.ts not found in ${migrationCountFilePath}, assuming no previous migrations.`
393
- );
394
- return 0;
395
- }
396
- const { MIGRATION_VERSION } = await import(migrationCountFilePath);
397
- console.log(`✅ Current migration version: ${MIGRATION_VERSION}`);
398
- return MIGRATION_VERSION;
399
- } catch (error) {
400
- console.error(`❌ Error loading migrationCount:`, error);
401
- process.exit(1);
402
- }
403
- };
404
- async function getDatabaseSchema(connection, dbName) {
405
- const [columns] = await connection.execute(`
406
- SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY, EXTRA
407
- FROM INFORMATION_SCHEMA.COLUMNS
408
- WHERE TABLE_SCHEMA = ?
409
- `, [dbName]);
410
- const [indexes] = await connection.execute(`
411
- SELECT TABLE_NAME, INDEX_NAME, COLUMN_NAME, NON_UNIQUE
412
- FROM INFORMATION_SCHEMA.STATISTICS
413
- WHERE TABLE_SCHEMA = ?
414
- ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX
415
- `, [dbName]);
416
- const [foreignKeys] = await connection.execute(`
417
- SELECT
418
- TABLE_NAME,
419
- COLUMN_NAME,
420
- CONSTRAINT_NAME,
421
- REFERENCED_TABLE_NAME,
422
- REFERENCED_COLUMN_NAME
423
- FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
424
- WHERE TABLE_SCHEMA = ?
425
- AND REFERENCED_TABLE_NAME IS NOT NULL
426
- `, [dbName]);
427
- const schema = {};
428
- columns.forEach((row) => {
429
- if (!schema[row.TABLE_NAME]) {
430
- schema[row.TABLE_NAME] = {
431
- columns: {},
432
- indexes: {},
433
- foreignKeys: {}
434
- };
435
- }
436
- schema[row.TABLE_NAME].columns[row.COLUMN_NAME] = row;
437
- });
438
- indexes.forEach((row) => {
439
- if (!schema[row.TABLE_NAME].indexes[row.INDEX_NAME]) {
440
- schema[row.TABLE_NAME].indexes[row.INDEX_NAME] = {
441
- columns: [],
442
- unique: !row.NON_UNIQUE
443
- };
444
- }
445
- schema[row.TABLE_NAME].indexes[row.INDEX_NAME].columns.push(row.COLUMN_NAME);
446
- });
447
- foreignKeys.forEach((row) => {
448
- if (!schema[row.TABLE_NAME].foreignKeys[row.CONSTRAINT_NAME]) {
449
- schema[row.TABLE_NAME].foreignKeys[row.CONSTRAINT_NAME] = {
450
- column: row.COLUMN_NAME,
451
- referencedTable: row.REFERENCED_TABLE_NAME,
452
- referencedColumn: row.REFERENCED_COLUMN_NAME
453
- };
454
- }
455
- });
456
- return schema;
457
- }
458
- function normalizeMySQLType(mysqlType) {
459
- let normalized = mysqlType.replace(/\([^)]*\)/, "").toLowerCase();
460
- normalized = normalized.replace(/^mysql/, "");
461
- return normalized;
462
- }
463
- function getForeignKeyName(fk) {
464
- return fk.name;
465
- }
466
- function getIndexName(index) {
467
- return index.name;
468
- }
469
- function getUniqueConstraintName(uc) {
470
- return uc.name;
471
- }
472
- function getIndexColumns(index) {
473
- return index.columns.map((col) => col.name);
474
- }
475
- function compareForeignKey(fk, { columns }) {
476
- const fcolumns = fk.columns.map((c) => c.name);
477
- return fcolumns.sort().join(",") === columns.sort().join(",");
478
- }
479
- function generateSchemaChanges(drizzleSchema, dbSchema, schemaModule) {
480
- const changes = [];
481
- for (const [tableName, dbTable] of Object.entries(dbSchema)) {
482
- const drizzleColumns = drizzleSchema[tableName];
483
- if (!drizzleColumns) {
484
- const columns = Object.entries(dbTable.columns).map(([colName, col]) => {
485
- const type = col.COLUMN_TYPE;
486
- const nullable = col.IS_NULLABLE === "YES" ? "NULL" : "NOT NULL";
487
- const autoIncrement = col.EXTRA.includes("auto_increment") ? "AUTO_INCREMENT" : "";
488
- return `\`${colName}\` ${type} ${nullable} ${autoIncrement}`.trim();
489
- }).join(",\n ");
490
- changes.push(`CREATE TABLE if not exists \`${tableName}\` (
491
- ${columns}
492
- );`);
493
- for (const [indexName, dbIndex] of Object.entries(dbTable.indexes)) {
494
- if (indexName === "PRIMARY") {
495
- continue;
496
- }
497
- const isForeignKeyIndex = dbIndex.columns.some((colName) => {
498
- const column = dbTable.columns[colName];
499
- return column && column.COLUMN_KEY === "MUL" && column.EXTRA.includes("foreign key");
500
- });
501
- if (isForeignKeyIndex) {
502
- continue;
503
- }
504
- const columns2 = dbIndex.columns.map((col) => `\`${col}\``).join(", ");
505
- const unique = dbIndex.unique ? "UNIQUE " : "";
506
- changes.push(`CREATE ${unique}INDEX if not exists \`${indexName}\` ON \`${tableName}\` (${columns2});`);
507
- }
508
- for (const [fkName, dbFK] of Object.entries(dbTable.foreignKeys)) {
509
- changes.push(`ALTER TABLE \`${tableName}\` ADD CONSTRAINT \`${fkName}\` FOREIGN KEY (\`${dbFK.column}\`) REFERENCES \`${dbFK.referencedTable}\` (\`${dbFK.referencedColumn}\`);`);
510
- }
511
- continue;
512
- }
513
- for (const [colName, dbCol] of Object.entries(dbTable.columns)) {
514
- const drizzleCol = Object.values(drizzleColumns).find((c) => c.name === colName);
515
- if (!drizzleCol) {
516
- const type = dbCol.COLUMN_TYPE;
517
- const nullable = dbCol.IS_NULLABLE === "YES" ? "NULL" : "NOT NULL";
518
- changes.push(`ALTER TABLE \`${tableName}\` ADD COLUMN \`${colName}\` ${type} ${nullable};`);
519
- continue;
520
- }
521
- const normalizedDbType = normalizeMySQLType(dbCol.COLUMN_TYPE);
522
- const normalizedDrizzleType = normalizeMySQLType(drizzleCol.getSQLType());
523
- if (normalizedDbType !== normalizedDrizzleType) {
524
- const type = dbCol.COLUMN_TYPE;
525
- const nullable = dbCol.IS_NULLABLE === "YES" ? "NULL" : "NOT NULL";
526
- changes.push(`ALTER TABLE \`${tableName}\` MODIFY COLUMN \`${colName}\` ${type} ${nullable};`);
527
- }
528
- }
529
- const table = Object.values(schemaModule).find((t) => {
530
- const metadata = getTableMetadata(t);
531
- return metadata.tableName === tableName;
532
- });
533
- if (table) {
534
- const metadata = getTableMetadata(table);
535
- for (const [indexName, dbIndex] of Object.entries(dbTable.indexes)) {
536
- if (indexName === "PRIMARY") {
537
- continue;
538
- }
539
- const isForeignKeyIndex = metadata.foreignKeys.some((fk) => getForeignKeyName(fk) === indexName || compareForeignKey(fk, dbIndex));
540
- if (isForeignKeyIndex) {
541
- continue;
542
- }
543
- const existsUniqIndex = metadata.uniqueConstraints.find((uc) => getUniqueConstraintName(uc) === indexName);
544
- let drizzleIndex = metadata.indexes.find((i) => getIndexName(i) === indexName);
545
- if (!drizzleIndex && existsUniqIndex) {
546
- drizzleIndex = existsUniqIndex;
547
- }
548
- if (!drizzleIndex) {
549
- const columns = dbIndex.columns.map((col) => `\`${col}\``).join(", ");
550
- const unique = dbIndex.unique ? "UNIQUE " : "";
551
- changes.push(`CREATE ${unique}INDEX if not exists \`${indexName}\` ON \`${tableName}\` (${columns});`);
552
- continue;
553
- }
554
- const dbColumns = dbIndex.columns.join(", ");
555
- const drizzleColumns2 = getIndexColumns(drizzleIndex).join(", ");
556
- if (dbColumns !== drizzleColumns2 || dbIndex.unique !== drizzleIndex instanceof UniqueConstraintBuilder) {
557
- changes.push(`DROP INDEX \`${indexName}\` ON \`${tableName}\`;`);
558
- const columns = dbIndex.columns.map((col) => `\`${col}\``).join(", ");
559
- const unique = dbIndex.unique ? "UNIQUE " : "";
560
- changes.push(`CREATE ${unique}INDEX if not exists \`${indexName}\` ON \`${tableName}\` (${columns});`);
561
- }
562
- }
563
- for (const [fkName, dbFK] of Object.entries(dbTable.foreignKeys)) {
564
- const drizzleFK = metadata.foreignKeys.find((fk) => getForeignKeyName(fk) === fkName || compareForeignKey(fk, { columns: [dbFK.column] }));
565
- if (!drizzleFK) {
566
- changes.push(`ALTER TABLE \`${tableName}\` ADD CONSTRAINT \`${fkName}\` FOREIGN KEY (\`${dbFK.column}\`) REFERENCES \`${dbFK.referencedTable}\` (\`${dbFK.referencedColumn}\`);`);
567
- continue;
568
- }
569
- }
570
- for (const drizzleForeignKey of metadata.foreignKeys) {
571
- const isDbFk = Object.keys(dbTable.foreignKeys).find((fk) => {
572
- let foreignKey = dbTable.foreignKeys[fk];
573
- return fk === getForeignKeyName(drizzleForeignKey) || compareForeignKey(drizzleForeignKey, { columns: [foreignKey.column] });
574
- });
575
- if (!isDbFk) {
576
- const fkName = getForeignKeyName(drizzleForeignKey);
577
- if (fkName) {
578
- changes.push(`ALTER TABLE \`${tableName}\` DROP FOREIGN KEY \`${fkName}\`;`);
579
- } else {
580
- const columns = drizzleForeignKey?.columns;
581
- const columnNames = columns?.length ? columns.map((c) => c.name).join(", ") : "unknown columns";
582
- console.warn(`⚠️ Drizzle model for table '${tableName}' does not provide a name for FOREIGN KEY constraint on columns: ${columnNames}`);
583
- }
584
- }
585
- }
586
- }
587
- }
588
- return changes;
589
- }
590
- const updateMigration = async (options) => {
591
- try {
592
- let version = await loadMigrationVersion(options.output);
593
- const prevVersion = version;
594
- if (version < 1) {
595
- console.log(
596
- `⚠️ Initial migration not found. Run "npx forge-sql-orm migrations:create" first.`
597
- );
598
- process.exit(0);
599
- }
600
- version += 1;
601
- const connection = await mysql.createConnection({
602
- host: options.host,
603
- port: options.port,
604
- user: options.user,
605
- password: options.password,
606
- database: options.dbName
607
- });
608
- try {
609
- const dbSchema = await getDatabaseSchema(connection, options.dbName);
610
- const schemaPath = path.resolve(options.entitiesPath, "schema.ts");
611
- if (!fs.existsSync(schemaPath)) {
612
- throw new Error(`Schema file not found at: ${schemaPath}`);
613
- }
614
- const schemaModule = await import(schemaPath);
615
- if (!schemaModule) {
616
- throw new Error(`Invalid schema file at: ${schemaPath}. Schema must export tables.`);
617
- }
618
- const drizzleSchema = {};
619
- const tables = Object.values(schemaModule);
620
- tables.forEach((table) => {
621
- const metadata = getTableMetadata(table);
622
- if (metadata.tableName) {
623
- const columns = {};
624
- Object.entries(metadata.columns).forEach(([name, column]) => {
625
- columns[name] = {
626
- type: column.dataType,
627
- notNull: column.notNull,
628
- autoincrement: column.autoincrement,
629
- columnType: column.columnType,
630
- name: column.name,
631
- getSQLType: () => column.getSQLType()
632
- };
633
- });
634
- drizzleSchema[metadata.tableName] = columns;
635
- }
636
- });
637
- if (Object.keys(drizzleSchema).length === 0) {
638
- throw new Error(`No valid tables found in schema at: ${schemaPath}`);
639
- }
640
- console.log("Found tables:", Object.keys(drizzleSchema));
641
- const createStatements = filterWithPreviousMigration(generateSchemaChanges(drizzleSchema, dbSchema, schemaModule), prevVersion, options.output);
642
- if (createStatements.length) {
643
- const migrationFile = generateMigrationFile$1(createStatements, version);
644
- if (saveMigrationFiles$1(migrationFile, version, options.output)) {
645
- console.log(`✅ Migration successfully updated!`);
646
- }
647
- process.exit(0);
648
- } else {
649
- console.log(`⚠️ No new migration changes detected.`);
650
- process.exit(0);
651
- }
652
- } finally {
653
- await connection.end();
654
- }
655
- } catch (error) {
656
- console.error(`❌ Error during migration update:`, error);
657
- process.exit(1);
658
- }
659
- };
660
- customType({
661
- dataType() {
662
- return "datetime";
663
- },
664
- toDriver(value) {
665
- return moment$1(value).format("YYYY-MM-DDTHH:mm:ss.SSS");
666
- },
667
- fromDriver(value) {
668
- const format = "YYYY-MM-DDTHH:mm:ss.SSS";
669
- return parseDateTime(value, format);
670
- }
671
- });
672
- customType({
673
- dataType() {
674
- return "timestamp";
675
- },
676
- toDriver(value) {
677
- return moment$1(value).format("YYYY-MM-DDTHH:mm:ss.SSS");
678
- },
679
- fromDriver(value) {
680
- const format = "YYYY-MM-DDTHH:mm:ss.SSS";
681
- return parseDateTime(value, format);
682
- }
683
- });
684
- customType({
685
- dataType() {
686
- return "date";
687
- },
688
- toDriver(value) {
689
- return moment$1(value).format("YYYY-MM-DD");
690
- },
691
- fromDriver(value) {
692
- const format = "YYYY-MM-DD";
693
- return parseDateTime(value, format);
694
- }
695
- });
696
- customType({
697
- dataType() {
698
- return "time";
699
- },
700
- toDriver(value) {
701
- return moment$1(value).format("HH:mm:ss.SSS");
702
- },
703
- fromDriver(value) {
704
- return parseDateTime(value, "HH:mm:ss.SSS");
705
- }
706
- });
707
- mysqlTable("__migrations", {
708
- id: bigint("id", { mode: "number" }).primaryKey().autoincrement(),
709
- name: varchar("name", { length: 255 }).notNull(),
710
- migratedAt: timestamp("migratedAt").defaultNow().notNull()
711
- });
712
- function generateMigrationUUID(version) {
713
- const now = /* @__PURE__ */ new Date();
714
- const timestamp2 = now.getTime();
715
- return `MIGRATION_V${version}_${timestamp2}`;
716
- }
717
- function generateMigrationFile(createStatements, version) {
718
- const uniqId = generateMigrationUUID(version);
719
- const migrationLines = createStatements.map(
720
- (stmt, index) => ` .enqueue("${uniqId}_${index}", "${stmt}")`
721
- // eslint-disable-line no-useless-escape
722
- ).join("\n");
723
- return `import { MigrationRunner } from "@forge/sql/out/migration";
724
-
725
- export default (migrationRunner: MigrationRunner): MigrationRunner => {
726
- return migrationRunner
727
- ${migrationLines};
728
- };`;
729
- }
730
- function saveMigrationFiles(migrationCode, version, outputDir) {
731
- if (!fs.existsSync(outputDir)) {
732
- fs.mkdirSync(outputDir, { recursive: true });
733
- }
734
- const migrationFilePath = path.join(outputDir, `migrationV${version}.ts`);
735
- const migrationCountPath = path.join(outputDir, `migrationCount.ts`);
736
- const indexFilePath = path.join(outputDir, `index.ts`);
737
- fs.writeFileSync(migrationFilePath, migrationCode);
738
- fs.writeFileSync(migrationCountPath, `export const MIGRATION_VERSION = ${version};`);
739
- const indexFileContent = `import { MigrationRunner } from "@forge/sql/out/migration";
740
- import { MIGRATION_VERSION } from "./migrationCount";
741
-
742
- export type MigrationType = (
743
- migrationRunner: MigrationRunner,
744
- ) => MigrationRunner;
745
-
746
- export default async (
747
- migrationRunner: MigrationRunner,
748
- ): Promise<MigrationRunner> => {
749
- for (let i = 1; i <= MIGRATION_VERSION; i++) {
750
- const migrations = (await import(\`./migrationV\${i}\`)) as {
751
- default: MigrationType;
752
- };
753
- migrations.default(migrationRunner);
754
- }
755
- return migrationRunner;
756
- };`;
757
- fs.writeFileSync(indexFilePath, indexFileContent);
758
- console.log(`✅ Migration file created: ${migrationFilePath}`);
759
- console.log(`✅ Migration count file updated: ${migrationCountPath}`);
760
- console.log(`✅ Migration index file created: ${indexFilePath}`);
761
- }
762
- const dropMigration = async (options) => {
763
- try {
764
- const version = 1;
765
- const schemaPath = path.resolve(options.entitiesPath, "schema.ts");
766
- if (!fs.existsSync(schemaPath)) {
767
- throw new Error(`Schema file not found at: ${schemaPath}`);
768
- }
769
- const schemaModule = await import(schemaPath);
770
- if (!schemaModule) {
771
- throw new Error(`Invalid schema file at: ${schemaPath}. Schema must export tables.`);
772
- }
773
- const tables = Object.values(schemaModule);
774
- if (tables.length === 0) {
775
- throw new Error(`No valid tables found in schema at: ${schemaPath}`);
776
- }
777
- const tableNames = tables.map((table) => {
778
- const metadata = getTableMetadata(table);
779
- return metadata.tableName;
780
- }).filter(Boolean);
781
- console.log("Found tables:", tableNames);
782
- const dropStatements = generateDropTableStatements(tables);
783
- const migrationFile = generateMigrationFile(dropStatements, version);
784
- saveMigrationFiles(migrationFile, version, options.output);
785
- console.log(`✅ Migration successfully created!`);
786
- process.exit(0);
787
- } catch (error) {
788
- console.error(`❌ Error during migration creation:`, error);
789
- process.exit(1);
790
- }
791
- };
792
- const ENV_PATH = path.resolve(process.cwd(), ".env");
793
- dotenv.config({ path: ENV_PATH });
794
- const saveEnvFile = (config) => {
795
- let envContent = "";
796
- const envFilePath = ENV_PATH;
797
- if (fs.existsSync(envFilePath)) {
798
- envContent = fs.readFileSync(envFilePath, "utf8");
799
- }
800
- const envVars = envContent.split("\n").filter((line) => line.trim() !== "" && !line.startsWith("#")).reduce((acc, line) => {
801
- const [key, ...value] = line.split("=");
802
- acc[key] = value.join("=");
803
- return acc;
804
- }, {});
805
- Object.entries(config).forEach(([key, value]) => {
806
- envVars[`FORGE_SQL_ORM_${key.toUpperCase()}`] = value;
807
- });
808
- const updatedEnvContent = Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n");
809
- fs.writeFileSync(envFilePath, updatedEnvContent, { encoding: "utf8" });
810
- console.log("✅ Configuration saved to .env without overwriting other variables.");
811
- };
812
- const askMissingParams = async (config, defaultOutput, customAskMissingParams) => {
813
- const questions = [];
814
- if (!config.host)
815
- questions.push({
816
- type: "input",
817
- name: "host",
818
- message: "Enter database host:",
819
- default: "localhost"
820
- });
821
- if (!config.port)
822
- questions.push({
823
- type: "input",
824
- name: "port",
825
- message: "Enter database port:",
826
- default: "3306",
827
- validate: (input) => !isNaN(parseInt(input, 10))
828
- });
829
- if (!config.user)
830
- questions.push({
831
- type: "input",
832
- name: "user",
833
- message: "Enter database user:",
834
- default: "root"
835
- });
836
- if (!config.password)
837
- questions.push({
838
- type: "password",
839
- name: "password",
840
- message: "Enter database password:",
841
- mask: "*"
842
- });
843
- if (!config.dbName)
844
- questions.push({
845
- type: "input",
846
- name: "dbName",
847
- message: "Enter database name:"
848
- });
849
- if (!config.output)
850
- questions.push({
851
- type: "input",
852
- name: "output",
853
- message: "Enter output path:",
854
- default: defaultOutput
855
- });
856
- if (customAskMissingParams) {
857
- customAskMissingParams(config, questions);
858
- }
859
- if (questions.length > 0) {
860
- const answers = await inquirer.prompt(questions);
861
- return { ...config, ...answers, port: parseInt(config.port ?? answers.port, 10) };
862
- }
863
- return config;
864
- };
865
- const getConfig = async (cmd, defaultOutput, customConfig, customAskMissingParams) => {
866
- let config = {
867
- host: cmd.host || process.env.FORGE_SQL_ORM_HOST,
868
- port: cmd.port ? parseInt(cmd.port, 10) : process.env.FORGE_SQL_ORM_PORT ? parseInt(process.env.FORGE_SQL_ORM_PORT, 10) : void 0,
869
- user: cmd.user || process.env.FORGE_SQL_ORM_USER,
870
- password: cmd.password || process.env.FORGE_SQL_ORM_PASSWORD,
871
- dbName: cmd.dbName || process.env.FORGE_SQL_ORM_DBNAME,
872
- output: cmd.output || process.env.FORGE_SQL_ORM_OUTPUT
873
- };
874
- if (customConfig) {
875
- config = { ...config, ...customConfig() };
876
- }
877
- const conf = await askMissingParams(config, defaultOutput, customAskMissingParams);
878
- if (cmd.saveEnv) {
879
- saveEnvFile(conf);
880
- }
881
- return conf;
882
- };
883
- const program = new Command();
884
- program.version("1.0.0");
885
- program.command("generate:model").description("Generate MikroORM models from the 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 entities").option("--versionField <string>", "Field name for versioning").option("--saveEnv", "Save configuration to .env file").action(async (cmd) => {
886
- const config = await getConfig(
887
- cmd,
888
- "./database/entities",
889
- () => ({
890
- versionField: cmd.versionField || process.env.FORGE_SQL_ORM_VERSIONFIELD
891
- }),
892
- (cfg, questions) => {
893
- if (!cfg.versionField) {
894
- questions.push({
895
- type: "input",
896
- name: "versionField",
897
- message: "Enter the field name for versioning (leave empty to skip):",
898
- default: ""
899
- });
900
- }
901
- }
902
- );
903
- await generateModels(config);
904
- });
905
- 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) => {
906
- const config = await getConfig(
907
- cmd,
908
- "./database/migration",
909
- () => ({
910
- entitiesPath: cmd.entitiesPath || process.env.FORGE_SQL_ORM_ENTITIESPATH,
911
- force: cmd.force || false
912
- }),
913
- (cfg, questions) => {
914
- if (!cfg.entitiesPath)
915
- questions.push({
916
- type: "input",
917
- name: "entitiesPath",
918
- message: "Enter the path to entities:",
919
- default: "./database/entities"
920
- });
921
- }
922
- );
923
- await createMigration(config);
924
- });
925
- program.command("migrations:update").description("Generate a migration to update the database schema.").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) => {
926
- const config = await getConfig(
927
- cmd,
928
- "./database/migration",
929
- () => ({
930
- entitiesPath: cmd.entitiesPath || process.env.FORGE_SQL_ORM_ENTITIESPATH
931
- }),
932
- (cfg, questions) => {
933
- if (!cfg.entitiesPath)
934
- questions.push({
935
- type: "input",
936
- name: "entitiesPath",
937
- message: "Enter the path to entities:",
938
- default: "./database/entities"
939
- });
940
- }
941
- );
942
- await updateMigration(config);
943
- });
944
- 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) => {
945
- const config = await getConfig(
946
- cmd,
947
- "./database/migration",
948
- () => ({
949
- entitiesPath: cmd.entitiesPath || process.env.FORGE_SQL_ORM_ENTITIESPATH
950
- }),
951
- (cfg, questions) => {
952
- if (!cfg.entitiesPath)
953
- questions.push({
954
- type: "input",
955
- name: "entitiesPath",
956
- message: "Enter the path to entities:",
957
- default: "./database/entities"
958
- });
959
- }
960
- );
961
- await dropMigration(config);
962
- });
963
- program.parse(process.argv);
964
- //# sourceMappingURL=cli.mjs.map