From 3db084ef6a56e5028d475193b0b8629d512cfea6 Mon Sep 17 00:00:00 2001 From: marcus-sa <8391194+marcus-sa@users.noreply.github.com> Date: Tue, 17 Sep 2024 11:54:13 +0200 Subject: [PATCH] feat(sql): add database when path is provided to migration commands --- packages/core/src/core.ts | 10 ++++ packages/sql/src/cli/base-command.ts | 5 ++ .../sql/src/cli/migration-create-command.ts | 1 + .../sql/src/cli/migration-down-command.ts | 1 + .../sql/src/cli/migration-pending-command.ts | 1 + packages/sql/src/cli/migration-up-command.ts | 1 + .../sql/src/migration/migration-provider.ts | 60 +++++++++++++++---- 7 files changed, 69 insertions(+), 10 deletions(-) diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 450951bfc..22625e766 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -889,3 +889,13 @@ export function assertDefined(value: T): asserts value is NonNullable { throw new Error(`Value is not defined`); } } + +export function isEsm(): boolean { + try { + // @ts-ignore + import.meta; + return true; + } catch { + return false; + } +} diff --git a/packages/sql/src/cli/base-command.ts b/packages/sql/src/cli/base-command.ts index 2bd371d80..9ca109d08 100644 --- a/packages/sql/src/cli/base-command.ts +++ b/packages/sql/src/cli/base-command.ts @@ -5,4 +5,9 @@ export class BaseCommand { * @description Sets the migration directory. */ protected migrationDir: string & Flag = ''; + + /** + * @description Sets the database path + */ + protected path?: string & Flag; } diff --git a/packages/sql/src/cli/migration-create-command.ts b/packages/sql/src/cli/migration-create-command.ts index 6d5470a74..b2791394b 100644 --- a/packages/sql/src/cli/migration-create-command.ts +++ b/packages/sql/src/cli/migration-create-command.ts @@ -51,6 +51,7 @@ export class MigrationCreateController extends BaseCommand implements Command { empty: boolean & Flag = false, ): Promise { if (this.migrationDir) this.provider.setMigrationDir(this.migrationDir); + if (this.path) await this.provider.addDatabase(this.path); if (!this.provider.databases.getDatabases().length) { this.logger.error('No databases detected. Use --path path/to/database.ts'); diff --git a/packages/sql/src/cli/migration-down-command.ts b/packages/sql/src/cli/migration-down-command.ts index a7d58fca4..c1e2f35ce 100644 --- a/packages/sql/src/cli/migration-down-command.ts +++ b/packages/sql/src/cli/migration-down-command.ts @@ -38,6 +38,7 @@ export class MigrationDownCommand extends BaseCommand { fake: boolean & Flag = false, ): Promise { if (this.migrationDir) this.provider.setMigrationDir(this.migrationDir); + if (this.path) await this.provider.addDatabase(this.path); const migrationsPerDatabase = await this.provider.getMigrationsPerDatabase(database); diff --git a/packages/sql/src/cli/migration-pending-command.ts b/packages/sql/src/cli/migration-pending-command.ts index c6d9c05bd..fa9a1fcb1 100644 --- a/packages/sql/src/cli/migration-pending-command.ts +++ b/packages/sql/src/cli/migration-pending-command.ts @@ -38,6 +38,7 @@ export class MigrationPendingCommand extends BaseCommand { database?: string & Flag<{ char: 'db' }>, ): Promise { if (this.migrationDir) this.provider.setMigrationDir(this.migrationDir); + if (this.path) await this.provider.addDatabase(this.path); const migrationsPerDatabase = await this.provider.getMigrationsPerDatabase(database); diff --git a/packages/sql/src/cli/migration-up-command.ts b/packages/sql/src/cli/migration-up-command.ts index bfbe344a4..3d70f88c2 100644 --- a/packages/sql/src/cli/migration-up-command.ts +++ b/packages/sql/src/cli/migration-up-command.ts @@ -44,6 +44,7 @@ export class MigrationUpCommand extends BaseCommand { all: boolean & Flag = false, ): Promise { if (this.migrationDir) this.provider.setMigrationDir(this.migrationDir); + if (this.path) await this.provider.addDatabase(this.path); const migrationsPerDatabase = await this.provider.getMigrationsPerDatabase(database); diff --git a/packages/sql/src/migration/migration-provider.ts b/packages/sql/src/migration/migration-provider.ts index 78d88167c..421714d0a 100644 --- a/packages/sql/src/migration/migration-provider.ts +++ b/packages/sql/src/migration/migration-provider.ts @@ -8,7 +8,7 @@ * You should have received a copy of the MIT License along with this program. */ -import { ClassType } from '@deepkit/core'; +import { ClassType, isEsm } from '@deepkit/core'; import { Database, DatabaseRegistry } from '@deepkit/orm'; import glob from 'fast-glob'; import { basename, join } from 'path'; @@ -51,24 +51,64 @@ export class MigrationProvider { return migrationsPerDatabase; } - async getMigrations(migrationDir: string): Promise { - let migrations: Migration[] = []; - - const files = await glob('**/*.ts', { cwd: migrationDir }); - require('ts-node').register({ + private async registerTsNode() { + const esm = isEsm(); + const { register } = await import('ts-node'); + register({ + esm, compilerOptions: { experimentalDecorators: true, - module: 'undefined' !== typeof require ? 'CommonJS' : 'ESNext', + module: esm ? 'ESNext' : 'CommonJS', }, transpileOnly: true, }); + } + + async addDatabase(path: string): Promise { + await this.registerTsNode(); + + const exports = Object.values((await import(path) || {})); + if (!exports.length) { + throw new Error(`No database found in path ${path}`); + } + + let databaseInstance: Database | undefined; + let foundDatabaseClass: ClassType | undefined; + + for (const value of exports) { + if (typeof value !== 'object' || value == null) continue; + if (value instanceof Database) { + databaseInstance = value; + break; + } + if ('prototype' in value && value.prototype instanceof Database) { + foundDatabaseClass = value as ClassType; + } + } + + if (!databaseInstance) { + if (foundDatabaseClass) { + throw new Error(`Found database class ${foundDatabaseClass.name} in path ${path} but it has to be instantiated an exported. export const database = new ${foundDatabaseClass.name}(/* ... */);`); + } + throw new Error(`No database found in path ${path}`); + } + + this.databases.addDatabaseInstance(databaseInstance); + } + + async getMigrations(migrationDir: string): Promise { + let migrations: Migration[] = []; + + const files = await glob('**/*.ts', { cwd: migrationDir }); + + await this.registerTsNode(); for (const file of files) { const path = join(process.cwd(), migrationDir, file); const name = basename(file.replace('.ts', '')); - const migration = require(path); - if (migration && migration.SchemaMigration) { - const jo = new class extends (migration.SchemaMigration as ClassType) { + const { SchemaMigration } = (await import(path) || {}); + if (SchemaMigration) { + const jo = new class extends (SchemaMigration as ClassType) { constructor() { super(); if (!this.name) this.name = name;