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.
This commit is contained in:
Harminder Virk
2025-03-12 12:32:19 +05:30
committed by GitHub
parent 267af9f3f6
commit 375c4a5ab1
7 changed files with 94 additions and 3 deletions

View File

@@ -0,0 +1,5 @@
---
"@medusajs/utils": patch
---
feat: use module name as the snapshot name

View File

@@ -59,6 +59,7 @@ export type Filter = {
export async function mikroOrmCreateConnection(
database: ModuleServiceInitializeOptions["database"] & {
connection?: any
snapshotName?: string
filters?: Record<string, Filter>
},
entities: any[],
@@ -99,6 +100,7 @@ export async function mikroOrmCreateConnection(
migrations: {
disableForeignKeys: false,
path: pathToMigrations,
snapshotName: database.snapshotName,
generator: CustomTsMigrationGenerator,
silent: !(
database.debug ??

View File

@@ -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<MigrationsEvents> {
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<MigrationsEvents> {
}
}
/**
* 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<void> {
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

View File

@@ -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<any, any>[]) {
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)
})
})

View File

@@ -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",
},
})
})

View File

@@ -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
)

View File

@@ -61,6 +61,7 @@ export function defineMikroOrmCliConfig(
...(options as any),
entities: entities.filter(Boolean),
migrations: {
snapshotName: `.snapshot-${databaseName}`,
generator: CustomTsMigrationGenerator,
...options.migrations,
},