forge-sql-orm-cli 2.1.14 → 2.1.16

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 CHANGED
@@ -9,6 +9,7 @@ import { execSync } from "child_process";
9
9
  import mysql from "mysql2/promise";
10
10
  import { getTableMetadata, generateDropTableStatements } from "forge-sql-orm";
11
11
  import { UniqueConstraintBuilder } from "drizzle-orm/mysql-core/unique-constraint";
12
+ import { v4 } from "uuid";
12
13
  function replaceMySQLTypes(schemaContent) {
13
14
  const imports = `import { forgeDateTimeString, forgeTimeString, forgeDateString, forgeTimestampString } from "forge-sql-orm";
14
15
 
@@ -248,27 +249,152 @@ const createMigration = async (options) => {
248
249
  process.exit(1);
249
250
  }
250
251
  };
252
+ function buildDefault(preMigration) {
253
+ const def = preMigration.defaultValue;
254
+ const type = preMigration.type.toLowerCase();
255
+ if (def === void 0 || def === null) {
256
+ return "";
257
+ }
258
+ if (def === "") {
259
+ return `''`;
260
+ }
261
+ const stringTypes = /* @__PURE__ */ new Set([
262
+ "char",
263
+ "varchar",
264
+ "text",
265
+ "tinytext",
266
+ "mediumtext",
267
+ "longtext",
268
+ "enum",
269
+ "set",
270
+ "binary",
271
+ "varbinary",
272
+ "blob"
273
+ ]);
274
+ const numericTypes = /* @__PURE__ */ new Set([
275
+ "tinyint",
276
+ "smallint",
277
+ "mediumint",
278
+ "int",
279
+ "bigint",
280
+ "decimal",
281
+ "float",
282
+ "double",
283
+ "bit"
284
+ ]);
285
+ const isNumericLiteral = /^[+-]?\d+(\.\d+)?$/.test(def);
286
+ if (numericTypes.has(type) && isNumericLiteral) {
287
+ return `${def}`;
288
+ }
289
+ if (stringTypes.has(type)) {
290
+ const escaped = def.replace(/'/g, "''");
291
+ return `'${escaped}'`;
292
+ }
293
+ return `${def}`;
294
+ }
295
+ function generateWarningMessage(tableName, colName, version) {
296
+ return `⚠️ WARNING: Field \`${tableName}\`.\`${colName}\` requires a default value for existing NULL records.
297
+ Action required in migration file: migrationV${version}.ts
298
+ Find the line with: UPDATE \`${tableName}\` SET \`${colName}\` = ?
299
+ Replace '?' with an actual value (e.g., '' for strings, 0 for numbers, '1970-01-01' for dates)
300
+ OR remove this migration if it's not needed.`;
301
+ }
302
+ function handleMissingDefaultValue(preMigration, version, migrationLineList) {
303
+ const warningMsg = generateWarningMessage(preMigration.tableName, preMigration.colName, version);
304
+ console.warn(warningMsg);
305
+ migrationLineList.push(`console.error(${JSON.stringify(warningMsg)});`);
306
+ }
307
+ function getUpdateDefaultValue(preMigration, defaultValue) {
308
+ return defaultValue === "?" ? defaultValue : buildDefault(preMigration);
309
+ }
310
+ function generateUpdateStatement(preMigration, defaultValue) {
311
+ const updateValue = getUpdateDefaultValue(preMigration, defaultValue);
312
+ return `UPDATE \`${preMigration.tableName}\` SET \`${preMigration.colName}\` = ${updateValue} WHERE \`${preMigration.colName}\` IS NULL`;
313
+ }
251
314
  function generateMigrationFile$1(createStatements, version) {
252
315
  const versionPrefix = `v${version}_MIGRATION`;
253
- const migrationLines = createStatements.map((stmt, index) => ` .enqueue("${versionPrefix}${index}", "${stmt}")`).join("\n");
316
+ const migrationLineList = [];
317
+ createStatements.changes.forEach((change, index) => {
318
+ if (!change.premigrationId) {
319
+ migrationLineList.push(
320
+ `
321
+ migrationRunner.enqueue("${versionPrefix}${index}", "${change.change}")`
322
+ );
323
+ return;
324
+ }
325
+ const preMigration = createStatements.preMigrations[change.premigrationId];
326
+ if (!preMigration) {
327
+ migrationLineList.push(
328
+ `
329
+ migrationRunner.enqueue("${versionPrefix}${index}", "${change.change}")`
330
+ );
331
+ return;
332
+ }
333
+ const defaultValue = preMigration.defaultValue === void 0 || preMigration.defaultValue === null ? "?" : preMigration.defaultValue;
334
+ const needsWarning = defaultValue === "?";
335
+ if (preMigration.migrationType === "NEW_FIELD_NOT_NULL") {
336
+ const addColumnStatement = change.change.replace("NOT NULL", "NULL");
337
+ migrationLineList.push(
338
+ `
339
+ migrationRunner.enqueue("${versionPrefix}${index}_NULLABLE", "${addColumnStatement}");`
340
+ );
341
+ if (needsWarning) {
342
+ handleMissingDefaultValue(preMigration, version, migrationLineList);
343
+ }
344
+ const updateStatement = generateUpdateStatement(preMigration, defaultValue);
345
+ migrationLineList.push(
346
+ `
347
+ migrationRunner.enqueue("${versionPrefix}${index}_UPDATE_EXISTS_RECORDS", "${updateStatement}");`
348
+ );
349
+ const defaultClause = defaultValue === "?" ? "" : ` DEFAULT ${buildDefault(preMigration)}`;
350
+ const modifyStatement = `ALTER TABLE \`${preMigration.tableName}\` MODIFY COLUMN IF EXISTS \`${preMigration.colName}\` ${preMigration.type} NOT NULL${defaultClause};`;
351
+ migrationLineList.push(
352
+ `
353
+ migrationRunner.enqueue("${versionPrefix}${index}", "${modifyStatement}");`
354
+ );
355
+ } else if (preMigration.migrationType === "MODIFY_NOT_NULL") {
356
+ if (needsWarning) {
357
+ handleMissingDefaultValue(preMigration, version, migrationLineList);
358
+ }
359
+ const updateStatement = generateUpdateStatement(preMigration, defaultValue);
360
+ migrationLineList.push(
361
+ `
362
+ migrationRunner.enqueue("${versionPrefix}${index}_UPDATE_EXISTS_RECORDS", "${updateStatement}")`
363
+ );
364
+ migrationLineList.push(
365
+ `
366
+ migrationRunner.enqueue("${versionPrefix}${index}", "${change.change}")`
367
+ );
368
+ }
369
+ });
370
+ const migrationLines = migrationLineList.join("\n");
254
371
  return `import { MigrationRunner } from "@forge/sql/out/migration";
255
372
 
256
- export default (migrationRunner: MigrationRunner): MigrationRunner => {
257
- return migrationRunner
373
+ export default (migrationRunner: MigrationRunner): MigrationRunner => {
258
374
  ${migrationLines};
375
+ return migrationRunner;
259
376
  };`;
260
377
  }
261
378
  function filterWithPreviousMigration(newStatements, prevVersion, outputDir) {
262
379
  const prevMigrationPath = path.join(outputDir, `migrationV${prevVersion}.ts`);
263
380
  if (!fs.existsSync(prevMigrationPath)) {
264
- return newStatements.map((s) => s.replace(/\s+/g, " "));
381
+ return {
382
+ changes: newStatements.changes.map((s) => ({
383
+ change: s.change.replace(/\s+/g, " "),
384
+ premigrationId: s.premigrationId
385
+ })),
386
+ preMigrations: newStatements.preMigrations
387
+ };
265
388
  }
266
389
  const prevContent = fs.readFileSync(prevMigrationPath, "utf-8");
267
390
  const prevStatements = prevContent.split("\n").filter((line) => line.includes(".enqueue(")).map((line) => {
268
391
  const match = line.match(/\.enqueue\([^,]+,\s*"([^"]+)"/);
269
392
  return match ? match[1].replace(/\s+/g, " ").trim() : "";
270
393
  });
271
- return newStatements.filter((s) => !prevStatements.includes(s.replace(/\s+/g, " "))).map((s) => s.replace(/\s+/g, " "));
394
+ return {
395
+ preMigrations: newStatements.preMigrations,
396
+ changes: newStatements.changes.filter((s) => !prevStatements.includes(s.change.replace(/\s+/g, " "))).map((s) => ({ change: s.change.replace(/\s+/g, " "), premigrationId: s.premigrationId }))
397
+ };
272
398
  }
273
399
  function saveMigrationFiles$1(migrationCode, version, outputDir) {
274
400
  if (!fs.existsSync(outputDir)) {
@@ -279,22 +405,23 @@ function saveMigrationFiles$1(migrationCode, version, outputDir) {
279
405
  const indexFilePath = path.join(outputDir, `index.ts`);
280
406
  fs.writeFileSync(migrationFilePath, migrationCode);
281
407
  fs.writeFileSync(migrationCountPath, `export const MIGRATION_VERSION = ${version};`);
408
+ const importLines = [];
409
+ const callLines = [];
410
+ for (let i = 1; i <= version; i++) {
411
+ importLines.push(`import migrationV${i} from "./migrationV${i}";`);
412
+ callLines.push(` migrationV${i}(migrationRunner);`);
413
+ }
282
414
  const indexFileContent = `import { MigrationRunner } from "@forge/sql/out/migration";
283
- import { MIGRATION_VERSION } from "./migrationCount";
415
+ ${importLines.join("\n")}
284
416
 
285
417
  export type MigrationType = (
286
418
  migrationRunner: MigrationRunner,
287
419
  ) => MigrationRunner;
288
420
 
289
- export default async (
421
+ export default (
290
422
  migrationRunner: MigrationRunner,
291
- ): Promise<MigrationRunner> => {
292
- for (let i = 1; i <= MIGRATION_VERSION; i++) {
293
- const migrations = (await import(\`./migrationV\${i}\`)) as {
294
- default: MigrationType;
295
- };
296
- migrations.default(migrationRunner);
297
- }
423
+ ): MigrationRunner => {
424
+ ${callLines.join("\n")}
298
425
  return migrationRunner;
299
426
  };`;
300
427
  fs.writeFileSync(indexFilePath, indexFileContent);
@@ -323,7 +450,7 @@ const loadMigrationVersion = async (migrationPath) => {
323
450
  async function getDatabaseSchema(connection, dbName) {
324
451
  const [columns] = await connection.execute(
325
452
  `
326
- SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY, EXTRA
453
+ SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY, EXTRA, COLUMN_DEFAULT
327
454
  FROM INFORMATION_SCHEMA.COLUMNS
328
455
  WHERE TABLE_SCHEMA = ?
329
456
  `,
@@ -406,6 +533,7 @@ function compareForeignKey(fk, { columns }) {
406
533
  }
407
534
  function generateSchemaChanges(drizzleSchema, dbSchema, schemaModule) {
408
535
  const changes = [];
536
+ const preMigrations = {};
409
537
  for (const [tableName, dbTable] of Object.entries(dbSchema)) {
410
538
  const drizzleColumns = drizzleSchema[tableName];
411
539
  if (!drizzleColumns) {
@@ -415,9 +543,9 @@ function generateSchemaChanges(drizzleSchema, dbSchema, schemaModule) {
415
543
  const autoIncrement = col.EXTRA.includes("auto_increment") ? "AUTO_INCREMENT" : "";
416
544
  return `\`${colName}\` ${type} ${nullable} ${autoIncrement}`.trim();
417
545
  }).join(",\n ");
418
- changes.push(`CREATE TABLE if not exists \`${tableName}\` (
546
+ changes.push({ change: `CREATE TABLE if not exists \`${tableName}\` (
419
547
  ${columns}
420
- );`);
548
+ );` });
421
549
  for (const [indexName, dbIndex] of Object.entries(dbTable.indexes)) {
422
550
  if (indexName === "PRIMARY") {
423
551
  continue;
@@ -431,14 +559,14 @@ function generateSchemaChanges(drizzleSchema, dbSchema, schemaModule) {
431
559
  }
432
560
  const columns2 = dbIndex.columns.map((col) => `\`${col}\``).join(", ");
433
561
  const unique = dbIndex.unique ? "UNIQUE " : "";
434
- changes.push(
435
- `CREATE ${unique}INDEX if not exists \`${indexName}\` ON \`${tableName}\` (${columns2});`
436
- );
562
+ changes.push({
563
+ change: `CREATE ${unique}INDEX if not exists \`${indexName}\` ON \`${tableName}\` (${columns2});`
564
+ });
437
565
  }
438
566
  for (const [fkName, dbFK] of Object.entries(dbTable.foreignKeys)) {
439
- changes.push(
440
- `ALTER TABLE \`${tableName}\` ADD CONSTRAINT \`${fkName}\` FOREIGN KEY (\`${dbFK.column}\`) REFERENCES \`${dbFK.referencedTable}\` (\`${dbFK.referencedColumn}\`);`
441
- );
567
+ changes.push({
568
+ change: `ALTER TABLE \`${tableName}\` ADD CONSTRAINT \`${fkName}\` FOREIGN KEY (\`${dbFK.column}\`) REFERENCES \`${dbFK.referencedTable}\` (\`${dbFK.referencedColumn}\`);`
569
+ });
442
570
  }
443
571
  continue;
444
572
  }
@@ -446,18 +574,67 @@ function generateSchemaChanges(drizzleSchema, dbSchema, schemaModule) {
446
574
  const drizzleCol = Object.values(drizzleColumns).find((c) => c.name === colName);
447
575
  if (!drizzleCol) {
448
576
  const type = dbCol.COLUMN_TYPE;
449
- const nullable = dbCol.IS_NULLABLE === "YES" ? "NULL" : "NOT NULL";
450
- changes.push(`ALTER TABLE \`${tableName}\` ADD COLUMN IF NOT EXISTS \`${colName}\` ${type} ${nullable};`);
577
+ const nullable2 = dbCol.IS_NULLABLE === "YES" ? "NULL" : "NOT NULL";
578
+ let premigrationId = nullable2 === "NOT NULL" ? v4() : void 0;
579
+ const defaultValue = dbCol.COLUMN_DEFAULT;
580
+ if (nullable2 === "NOT NULL") {
581
+ premigrationId = v4();
582
+ preMigrations[premigrationId] = {
583
+ tableName,
584
+ dbTable,
585
+ colName,
586
+ type,
587
+ migrationType: "NEW_FIELD_NOT_NULL",
588
+ defaultValue
589
+ };
590
+ }
591
+ changes.push({
592
+ change: `ALTER TABLE \`${tableName}\` ADD COLUMN IF NOT EXISTS \`${colName}\` ${type} ${nullable2} ${defaultValue === void 0 || defaultValue === null ? "" : `DEFAULT ${buildDefault({
593
+ type,
594
+ defaultValue
595
+ })}`};`,
596
+ premigrationId
597
+ });
451
598
  continue;
452
599
  }
453
600
  const normalizedDbType = normalizeMySQLType(dbCol.COLUMN_TYPE);
454
601
  const normalizedDrizzleType = normalizeMySQLType(drizzleCol.getSQLType());
455
- if (normalizedDbType !== normalizedDrizzleType) {
602
+ const nullable = dbCol.IS_NULLABLE === "YES" ? "NULL" : "NOT NULL";
603
+ const dbIsNotNull = nullable === "NOT NULL";
604
+ const drizzleIsNotNull = drizzleCol.notNull;
605
+ const typeChanged = normalizedDbType !== normalizedDrizzleType;
606
+ const nullabilityChanged = dbIsNotNull !== drizzleIsNotNull;
607
+ const hasDrizzleDefault = drizzleCol.default !== null && drizzleCol.default !== void 0;
608
+ const hasDbDefault = dbCol.COLUMN_DEFAULT !== null && dbCol.COLUMN_DEFAULT !== void 0;
609
+ const defaultChanged = hasDrizzleDefault && hasDbDefault && drizzleCol.default !== dbCol.COLUMN_DEFAULT;
610
+ if (typeChanged || nullabilityChanged || defaultChanged) {
456
611
  const type = dbCol.COLUMN_TYPE;
457
- const nullable = dbCol.IS_NULLABLE === "YES" ? "NULL" : "NOT NULL";
458
- changes.push(
459
- `ALTER TABLE \`${tableName}\` MODIFY COLUMN \`${colName}\` IF EXISTS ${type} ${nullable};`
460
- );
612
+ const defaultValue = dbCol.COLUMN_DEFAULT;
613
+ let premigrationId = void 0;
614
+ if (dbIsNotNull && !drizzleIsNotNull) {
615
+ premigrationId = v4();
616
+ preMigrations[premigrationId] = {
617
+ tableName,
618
+ dbTable,
619
+ colName,
620
+ type,
621
+ migrationType: "MODIFY_NOT_NULL",
622
+ defaultValue
623
+ };
624
+ }
625
+ let defaultClause = "";
626
+ if (defaultValue !== void 0 && defaultValue !== null) {
627
+ const defaultValueObj = {
628
+ type,
629
+ defaultValue
630
+ };
631
+ defaultClause = ` DEFAULT ${buildDefault(defaultValueObj)}`;
632
+ }
633
+ const modifyStatement = `ALTER TABLE \`${tableName}\` MODIFY COLUMN IF EXISTS \`${colName}\` ${type} ${nullable}${defaultClause};`;
634
+ changes.push({
635
+ change: modifyStatement,
636
+ premigrationId
637
+ });
461
638
  }
462
639
  }
463
640
  const table = Object.values(schemaModule).find((t) => {
@@ -486,20 +663,20 @@ function generateSchemaChanges(drizzleSchema, dbSchema, schemaModule) {
486
663
  if (!drizzleIndex) {
487
664
  const columns = dbIndex.columns.map((col) => `\`${col}\``).join(", ");
488
665
  const unique = dbIndex.unique ? "UNIQUE " : "";
489
- changes.push(
490
- `CREATE ${unique}INDEX if not exists \`${indexName}\` ON \`${tableName}\` (${columns});`
491
- );
666
+ changes.push({
667
+ change: `CREATE ${unique}INDEX if not exists \`${indexName}\` ON \`${tableName}\` (${columns});`
668
+ });
492
669
  continue;
493
670
  }
494
671
  const dbColumns = dbIndex.columns.join(", ");
495
672
  const drizzleColumns2 = getIndexColumns(drizzleIndex).join(", ");
496
673
  if (dbColumns !== drizzleColumns2 || dbIndex.unique !== drizzleIndex instanceof UniqueConstraintBuilder) {
497
- changes.push(`DROP INDEX \`${indexName}\` ON \`${tableName}\`;`);
674
+ changes.push({ change: `DROP INDEX \`${indexName}\` ON \`${tableName}\`;` });
498
675
  const columns = dbIndex.columns.map((col) => `\`${col}\``).join(", ");
499
676
  const unique = dbIndex.unique ? "UNIQUE " : "";
500
- changes.push(
501
- `CREATE ${unique}INDEX if not exists \`${indexName}\` ON \`${tableName}\` (${columns});`
502
- );
677
+ changes.push({
678
+ change: `CREATE ${unique}INDEX if not exists \`${indexName}\` ON \`${tableName}\` (${columns});`
679
+ });
503
680
  }
504
681
  }
505
682
  for (const [fkName, dbFK] of Object.entries(dbTable.foreignKeys)) {
@@ -507,9 +684,9 @@ function generateSchemaChanges(drizzleSchema, dbSchema, schemaModule) {
507
684
  (fk) => getForeignKeyName(fk) === fkName || compareForeignKey(fk, { columns: [dbFK.column] })
508
685
  );
509
686
  if (!drizzleFK) {
510
- changes.push(
511
- `ALTER TABLE \`${tableName}\` ADD CONSTRAINT \`${fkName}\` FOREIGN KEY (\`${dbFK.column}\`) REFERENCES \`${dbFK.referencedTable}\` (\`${dbFK.referencedColumn}\`);`
512
- );
687
+ changes.push({
688
+ change: `ALTER TABLE \`${tableName}\` ADD CONSTRAINT \`${fkName}\` FOREIGN KEY (\`${dbFK.column}\`) REFERENCES \`${dbFK.referencedTable}\` (\`${dbFK.referencedColumn}\`);`
689
+ });
513
690
  continue;
514
691
  }
515
692
  }
@@ -522,7 +699,9 @@ function generateSchemaChanges(drizzleSchema, dbSchema, schemaModule) {
522
699
  if (drizzleForeignKey) {
523
700
  const fkName = getForeignKeyName(drizzleForeignKey);
524
701
  if (fkName) {
525
- changes.push(`ALTER TABLE \`${tableName}\` DROP FOREIGN KEY \`${fkName}\`;`);
702
+ changes.push({
703
+ change: `ALTER TABLE \`${tableName}\` DROP FOREIGN KEY \`${fkName}\`;`
704
+ });
526
705
  } else {
527
706
  const columns = drizzleForeignKey.columns;
528
707
  const columnNames = columns?.length ? columns.map((c) => c.name).join(", ") : "unknown columns";
@@ -535,7 +714,7 @@ function generateSchemaChanges(drizzleSchema, dbSchema, schemaModule) {
535
714
  }
536
715
  }
537
716
  }
538
- return changes;
717
+ return { changes, preMigrations };
539
718
  }
540
719
  const updateMigration = async (options) => {
541
720
  try {
@@ -578,6 +757,7 @@ const updateMigration = async (options) => {
578
757
  autoincrement: column.autoincrement,
579
758
  columnType: column.columnType,
580
759
  name: column.name,
760
+ default: metadata.columns.email.hasDefault ? String(column.default) : void 0,
581
761
  getSQLType: () => column.getSQLType()
582
762
  };
583
763
  });
@@ -593,7 +773,7 @@ const updateMigration = async (options) => {
593
773
  prevVersion,
594
774
  options.output
595
775
  );
596
- if (createStatements.length) {
776
+ if (createStatements.changes.length) {
597
777
  const migrationFile = generateMigrationFile$1(createStatements, version);
598
778
  if (saveMigrationFiles$1(migrationFile, version, options.output)) {
599
779
  console.log(`✅ Migration successfully updated!`);