From 375c4a5ab1b2805ef2a3d792327c325fa11740a5 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 12 Mar 2025 12:32:19 +0530 Subject: [PATCH] refactor: use module name as the snapshot name (#11802) Fixes: FRMW-2930 This PR updates the MikroORM config to use the module name as the snapshot name when generating migration files. Otherwise, MikroORM defaults to the database name and every time you update the database name, it will create a new snapshot. Also, we migrate existing snapshot files to be same the new file name to avoid breaking changes. --- .changeset/shy-pugs-cough.md | 5 ++ .../mikro-orm/mikro-orm-create-connection.ts | 2 + packages/core/utils/src/migrations/index.ts | 33 ++++++++++++- .../__tests__/migrations-generate.spec.ts | 46 +++++++++++++++++++ .../mikro-orm-cli-config-builder.spec.ts | 2 + .../migration-scripts/migration-generate.ts | 8 +++- .../mikro-orm-cli-config-builder.ts | 1 + 7 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 .changeset/shy-pugs-cough.md diff --git a/.changeset/shy-pugs-cough.md b/.changeset/shy-pugs-cough.md new file mode 100644 index 0000000000..9b83ee0c8d --- /dev/null +++ b/.changeset/shy-pugs-cough.md @@ -0,0 +1,5 @@ +--- +"@medusajs/utils": patch +--- + +feat: use module name as the snapshot name diff --git a/packages/core/utils/src/dal/mikro-orm/mikro-orm-create-connection.ts b/packages/core/utils/src/dal/mikro-orm/mikro-orm-create-connection.ts index 0a3574997f..99a892b88f 100644 --- a/packages/core/utils/src/dal/mikro-orm/mikro-orm-create-connection.ts +++ b/packages/core/utils/src/dal/mikro-orm/mikro-orm-create-connection.ts @@ -59,6 +59,7 @@ export type Filter = { export async function mikroOrmCreateConnection( database: ModuleServiceInitializeOptions["database"] & { connection?: any + snapshotName?: string filters?: Record }, entities: any[], @@ -99,6 +100,7 @@ export async function mikroOrmCreateConnection( migrations: { disableForeignKeys: false, path: pathToMigrations, + snapshotName: database.snapshotName, generator: CustomTsMigrationGenerator, silent: !( database.debug ?? diff --git a/packages/core/utils/src/migrations/index.ts b/packages/core/utils/src/migrations/index.ts index 3b3c956469..236edd535d 100644 --- a/packages/core/utils/src/migrations/index.ts +++ b/packages/core/utils/src/migrations/index.ts @@ -6,8 +6,9 @@ import { } from "@mikro-orm/migrations" import { defineConfig, PostgreSqlDriver } from "@mikro-orm/postgresql" import { EventEmitter } from "events" -import { access, mkdir, writeFile } from "fs/promises" -import { dirname } from "path" +import { access, mkdir, rename, writeFile } from "fs/promises" +import { dirname, join } from "path" +import { readDir } from "../common" /** * Events emitted by the migrations class @@ -62,6 +63,7 @@ export class Migrations extends EventEmitter { const migrator = connection.getMigrator() try { + await this.migrateSnapshotFile(migrator["snapshotPath"]) await this.ensureSnapshot(migrator["snapshotPath"]) return await migrator.createMigration() } finally { @@ -141,6 +143,33 @@ export class Migrations extends EventEmitter { } } + /** + * Migrates the existing snapshot file of a module to follow to be + * named after the current snapshot file. + * + * If there are multiple snapshot files inside the directory, then + * the first one will be used. + */ + protected async migrateSnapshotFile(snapshotPath: string): Promise { + const entries = await readDir(dirname(snapshotPath), { + ignoreMissing: true, + }) + + /** + * We assume all JSON files are snapshot files in this directory + */ + const snapshotFile = entries.find( + (entry) => entry.isFile() && entry.name.endsWith(".json") + ) + + if (snapshotFile) { + const absoluteName = join(snapshotFile.path, snapshotFile.name) + if (absoluteName !== snapshotPath) { + await rename(absoluteName, snapshotPath) + } + } + } + /** * Generate a default snapshot file if it does not already exists. This * prevent from creating a database to manage the migrations and instead diff --git a/packages/core/utils/src/migrations/integration-tests/__tests__/migrations-generate.spec.ts b/packages/core/utils/src/migrations/integration-tests/__tests__/migrations-generate.spec.ts index 1005cc6e83..b60a29b6d6 100644 --- a/packages/core/utils/src/migrations/integration-tests/__tests__/migrations-generate.spec.ts +++ b/packages/core/utils/src/migrations/integration-tests/__tests__/migrations-generate.spec.ts @@ -134,4 +134,50 @@ describe("Generate migrations", () => { expect(run1.fileName).not.toEqual(run2.fileName) }) + + test("rename existing snapshot file to the new filename", async () => { + await fs.createJson(".snapshot-foo.json", { + tables: [], + namespaces: [], + }) + + function run(entities: DmlEntity[]) { + const config = defineMikroOrmCliConfig(moduleName, { + entities, + dbName: dbName, + migrations: { + path: fs.basePath, + }, + ...pgGodCredentials, + }) + + const migrations = new Migrations(config) + return migrations.generate() + } + + const User = model.define("User", { + id: model.id().primaryKey(), + email: model.text().unique(), + fullName: model.text().nullable(), + }) + + const run1 = await run([User]) + expect(await fs.exists(run1.fileName)) + expect(await fs.exists(".snapshot-foo.json")).toBeFalsy() + expect( + await fs.exists(".snapshot-medusa-my-test-generate.json") + ).toBeTruthy() + + const Car = model.define("Car", { + id: model.id().primaryKey(), + name: model.text(), + }) + + await setTimeout(1000) + + const run2 = await run([User, Car]) + expect(await fs.exists(run2.fileName)) + + expect(run1.fileName).not.toEqual(run2.fileName) + }) }) diff --git a/packages/core/utils/src/modules-sdk/__tests__/mikro-orm-cli-config-builder.spec.ts b/packages/core/utils/src/modules-sdk/__tests__/mikro-orm-cli-config-builder.spec.ts index 2c47ce8d92..998ed3faea 100644 --- a/packages/core/utils/src/modules-sdk/__tests__/mikro-orm-cli-config-builder.spec.ts +++ b/packages/core/utils/src/modules-sdk/__tests__/mikro-orm-cli-config-builder.spec.ts @@ -26,6 +26,7 @@ describe("defineMikroOrmCliConfig", () => { dbName: "medusa-fulfillment", migrations: { generator: expect.any(Function), + snapshotName: ".snapshot-medusa-my-test", }, }) }) @@ -44,6 +45,7 @@ describe("defineMikroOrmCliConfig", () => { password: "", migrations: { generator: expect.any(Function), + snapshotName: ".snapshot-medusa-my-test", }, }) }) diff --git a/packages/core/utils/src/modules-sdk/migration-scripts/migration-generate.ts b/packages/core/utils/src/modules-sdk/migration-scripts/migration-generate.ts index b682be3606..b28ed6e534 100644 --- a/packages/core/utils/src/modules-sdk/migration-scripts/migration-generate.ts +++ b/packages/core/utils/src/modules-sdk/migration-scripts/migration-generate.ts @@ -3,6 +3,7 @@ import { mikroOrmCreateConnection } from "../../dal" import { loadDatabaseConfig } from "../load-module-database-config" import { Migrations } from "../../migrations" import { toMikroOrmEntities } from "../../dml" +import { kebabCase } from "../../common/to-kebab-case" const TERMINAL_SIZE = process.stdout.columns @@ -42,7 +43,12 @@ export function buildGenerateMigrationScript({ const normalizedModels = toMikroOrmEntities(models) const orm = await mikroOrmCreateConnection( - dbData, + { + ...dbData, + snapshotName: `.snapshot-${kebabCase( + moduleName.replace("Service", "") + )}`, + }, normalizedModels, pathToMigrations ) diff --git a/packages/core/utils/src/modules-sdk/mikro-orm-cli-config-builder.ts b/packages/core/utils/src/modules-sdk/mikro-orm-cli-config-builder.ts index 1dd9defde4..f5e41e46e7 100644 --- a/packages/core/utils/src/modules-sdk/mikro-orm-cli-config-builder.ts +++ b/packages/core/utils/src/modules-sdk/mikro-orm-cli-config-builder.ts @@ -61,6 +61,7 @@ export function defineMikroOrmCliConfig( ...(options as any), entities: entities.filter(Boolean), migrations: { + snapshotName: `.snapshot-${databaseName}`, generator: CustomTsMigrationGenerator, ...options.migrations, },