feat(migrations): CLI generate command (#8103)

This commit is contained in:
Adrien de Peretti
2024-07-12 13:12:49 +02:00
committed by GitHub
parent 4c2e9a3239
commit 104b00d4e9
14 changed files with 341 additions and 164 deletions

View File

@@ -6,6 +6,8 @@ import {
} from "@mikro-orm/migrations"
import { MikroORM, MikroORMOptions } from "@mikro-orm/core"
import { PostgreSqlDriver } from "@mikro-orm/postgresql"
import { dirname } from "path"
import { access, mkdir, writeFile } from "fs/promises"
/**
* Events emitted by the migrations class
@@ -56,7 +58,9 @@ export class Migrations extends EventEmitter<MigrationsEvents> {
async generate(): Promise<MigrationResult> {
const connection = await this.#getConnection()
const migrator = connection.getMigrator()
try {
await this.ensureSnapshot(migrator["snapshotPath"])
return await migrator.createMigration()
} finally {
await connection.close(true)
@@ -134,4 +138,35 @@ export class Migrations extends EventEmitter<MigrationsEvents> {
await connection.close(true)
}
}
/**
* Generate a default snapshot file if it does not already exists. This
* prevent from creating a database to manage the migrations and instead
* rely on the snapshot.
*
* @param snapshotPath
* @protected
*/
protected async ensureSnapshot(snapshotPath: string): Promise<void> {
await mkdir(dirname(snapshotPath), { recursive: true })
const doesFileExists = await access(snapshotPath)
.then(() => true)
.catch(() => false)
if (doesFileExists) {
return
}
const emptySnapshotContent = JSON.stringify(
{
tables: [],
namespaces: [],
},
null,
2
)
await writeFile(snapshotPath, emptySnapshotContent, "utf-8")
}
}

View File

@@ -1,7 +1,6 @@
import { join } from "path"
import { setTimeout } from "timers/promises"
import { MetadataStorage } from "@mikro-orm/core"
import { createDatabase, dropDatabase } from "pg-god"
import { Migrations } from "../../index"
import { FileSystem } from "../../../common"
@@ -22,22 +21,12 @@ const pgGodCredentials = {
host: DB_HOST,
}
// TODO: Reenable once flakiness is taken care of
describe.skip("Generate migrations", () => {
describe("Generate migrations", () => {
beforeEach(async () => {
await dropDatabase(
{ databaseName: dbName, errorIfNonExist: false },
pgGodCredentials
)
await fs.cleanup()
await createDatabase({ databaseName: dbName }, pgGodCredentials)
})
afterEach(async () => {
await dropDatabase(
{ databaseName: dbName, errorIfNonExist: false },
pgGodCredentials
)
await fs.cleanup()
MetadataStorage.clear()
}, 300 * 1000)

View File

@@ -1,3 +1,4 @@
export * from "./migration-down"
export * from "./migration-up"
export * from "./migration-generate"
export * from "./seed"

View File

@@ -0,0 +1,63 @@
import { LoaderOptions, Logger, ModulesSdkTypes } from "@medusajs/types"
import { mikroOrmCreateConnection } from "../../dal"
import { loadDatabaseConfig } from "../load-module-database-config"
import { Migrations } from "../../migrations"
import { toMikroOrmEntities } from "../../dml"
const TERMINAL_SIZE = process.stdout.columns
/**
* Utility function to build a migration generation script that will generate the migrations.
* Only used in mikro orm based modules.
* @param moduleName
* @param models
* @param pathToMigrations
*/
export function buildGenerateMigrationScript({
moduleName,
models,
pathToMigrations,
}) {
/**
* This script is only valid for mikro orm managers. If a user provide a custom manager
* he is in charge of running the migrations.
* @param options
* @param logger
* @param moduleDeclaration
*/
return async function ({
options,
logger,
}: Pick<
LoaderOptions<ModulesSdkTypes.ModuleServiceInitializeOptions>,
"options" | "logger"
> = {}) {
logger ??= console as unknown as Logger
console.log(new Array(TERMINAL_SIZE).join("-"))
console.log("")
logger.info(`MODULE: ${moduleName}`)
const dbData = loadDatabaseConfig(moduleName, options)!
const normalizedModels = toMikroOrmEntities(models)
const orm = await mikroOrmCreateConnection(
dbData,
normalizedModels,
pathToMigrations
)
const migrations = new Migrations(orm)
try {
const { fileName } = await migrations.generate()
if (fileName) {
logger.info(`Generated successfully (${fileName}).`)
} else {
logger.info(`Skipped. No changes detected in your models.`)
}
} catch (error) {
logger.error(`Failed with error ${error.message}`, error)
}
}
}