From ca167d541dfa969d37a7948309f8d7939510db7f Mon Sep 17 00:00:00 2001 From: igalklebanov Date: Mon, 28 Oct 2024 00:28:26 +0200 Subject: [PATCH] add import prop @ file migration provider. .. add FileMigrationProvider test suite. .. --- src/migration/file-migration-provider.ts | 58 +++++++----- test/node/src/file-migration-provider.test.ts | 89 +++++++++++++++++++ test/node/src/test-setup.ts | 8 +- test/node/tsconfig.json | 10 ++- 4 files changed, 139 insertions(+), 26 deletions(-) create mode 100644 test/node/src/file-migration-provider.test.ts diff --git a/src/migration/file-migration-provider.ts b/src/migration/file-migration-provider.ts index 00e3eebac..6d47dd1f6 100644 --- a/src/migration/file-migration-provider.ts +++ b/src/migration/file-migration-provider.ts @@ -2,7 +2,7 @@ import { isFunction, isObject } from '../util/object-utils.js' import { Migration, MigrationProvider } from './migrator.js' /** - * Reads all migrations from a folder in node.js. + * Reads all migrations from a folder. * * ### Examples * @@ -29,31 +29,45 @@ export class FileMigrationProvider implements MigrationProvider { const files = await this.#props.fs.readdir(this.#props.migrationFolder) for (const fileName of files) { - if ( - fileName.endsWith('.js') || - (fileName.endsWith('.ts') && !fileName.endsWith('.d.ts')) || - fileName.endsWith('.mjs') || - (fileName.endsWith('.mts') && !fileName.endsWith('.d.mts')) - ) { - const migration = await import( - /* webpackIgnore: true */ this.#props.path.join( - this.#props.migrationFolder, - fileName, - ) - ) - const migrationKey = fileName.substring(0, fileName.lastIndexOf('.')) + if (!this.hasExpectedExtension(fileName)) { + this.#props.onDiscarded?.(fileName, 'Extension') + continue + } + + const filePath = this.#props.path.join( + this.#props.migrationFolder, + fileName, + ) + + const migration = this.#props.import + ? await this.#props.import(filePath) + : await import(/* webpackIgnore: true */ filePath) + + const migrationKey = fileName.substring(0, fileName.lastIndexOf('.')) - // Handle esModuleInterop export's `default` prop... - if (isMigration(migration?.default)) { - migrations[migrationKey] = migration.default - } else if (isMigration(migration)) { - migrations[migrationKey] = migration - } + // Handle esModuleInterop export's `default` prop... + if (isMigration(migration?.default)) { + migrations[migrationKey] = migration.default + } else if (isMigration(migration)) { + migrations[migrationKey] = migration + } else { + this.#props.onDiscarded?.(fileName, 'NotMigration') } } return migrations } + + protected hasExpectedExtension(fileName: string): boolean { + return ( + fileName.endsWith('.js') || + (fileName.endsWith('.ts') && !fileName.endsWith('.d.ts')) || + fileName.endsWith('.mjs') || + (fileName.endsWith('.mts') && !fileName.endsWith('.d.mts')) || + fileName.endsWith('.cjs') || + (fileName.endsWith('.cts') && !fileName.endsWith('.d.cts')) + ) + } } function isMigration(obj: unknown): obj is Migration { @@ -70,6 +84,8 @@ export interface FileMigrationProviderPath { export interface FileMigrationProviderProps { fs: FileMigrationProviderFS - path: FileMigrationProviderPath + import?(module: string): Promise migrationFolder: string + onDiscarded?(fileName: string, reason: 'Extension' | 'NotMigration'): void + path: FileMigrationProviderPath } diff --git a/test/node/src/file-migration-provider.test.ts b/test/node/src/file-migration-provider.test.ts new file mode 100644 index 000000000..517215d78 --- /dev/null +++ b/test/node/src/file-migration-provider.test.ts @@ -0,0 +1,89 @@ +import { mkdir, readdir, rm, writeFile } from 'node:fs/promises' +import { join } from 'node:path' +import { require as tsxRequire } from 'tsx/cjs/api' +import { FileMigrationProvider } from '../../..' +import { expect } from './test-setup.js' + +describe('FileMigrationProvider', () => { + ;['js', 'ts', 'mjs', 'cjs', 'mts', 'cts'].forEach((extension) => { + describe(`${extension} files`, () => { + const migrationFolder = `${extension}-migrations` + const migrationFolderPath = join(__dirname, migrationFolder) + let provider: FileMigrationProvider + const migrationName = '123_noop' + + before(async () => { + await mkdir(migrationFolderPath) + await writeFile( + join(migrationFolderPath, `${migrationName}.${extension}`), + extension.endsWith('js') + ? 'exports.up = () => {}' + : 'export const up = () => {}', + ) + + provider = new FileMigrationProvider({ + fs: { readdir }, + import: + extension.endsWith('ts') || extension.startsWith('m') + ? (module: string) => tsxRequire(module, __filename) + : undefined, + migrationFolder: migrationFolderPath, + path: { join }, + }) + }) + + after(async () => { + await rm(migrationFolderPath, { recursive: true }) + }) + + it('should get migration files with this extension', async () => { + const migrations = await provider.getMigrations() + + expect(migrations).to.have.property(migrationName) + }) + }) + }) + // + ;['zip', 'd.ts', 'd.mts', 'd.cts'].forEach((extension) => { + describe(`${extension} files`, () => { + const migrationFolder = `${extension}-migrations` + const migrationFolderPath = join(__dirname, migrationFolder) + let provider: FileMigrationProvider + const migrationFileName = `123_noop.${extension}` + let discarded: { fileName: string; reason: string }[] + + before(async () => { + await mkdir(migrationFolderPath) + await writeFile( + join(migrationFolderPath, migrationFileName), + extension.endsWith('ts') ? 'export {}' : '==asdhjgbaudg1827dg127', + ) + discarded = [] + + provider = new FileMigrationProvider({ + fs: { readdir }, + migrationFolder: migrationFolderPath, + onDiscarded: (fileName, reason) => { + discarded.push({ fileName, reason }) + }, + path: { join }, + }) + }) + + after(async () => { + await rm(migrationFolderPath, { recursive: true }) + }) + + it('should discard files with this extension', async () => { + const migrations = await provider.getMigrations() + + expect(migrations).to.deep.equal({}) + expect(discarded).to.have.length(1) + expect(discarded[0]).to.deep.equal({ + fileName: migrationFileName, + reason: 'Extension', + }) + }) + }) + }) +}) diff --git a/test/node/src/test-setup.ts b/test/node/src/test-setup.ts index 0c1e866e7..0c60b969c 100644 --- a/test/node/src/test-setup.ts +++ b/test/node/src/test-setup.ts @@ -1,10 +1,10 @@ import * as chai from 'chai' -import * as chaiAsPromised from 'chai-as-promised' -import * as chaiSubset from 'chai-subset' -import * as Cursor from 'pg-cursor' +import chaiAsPromised from 'chai-as-promised' +import chaiSubset from 'chai-subset' +import Cursor from 'pg-cursor' import { Pool, PoolConfig } from 'pg' import { createPool } from 'mysql2' -import * as Database from 'better-sqlite3' +import Database from 'better-sqlite3' import * as Tarn from 'tarn' import * as Tedious from 'tedious' import { PoolOptions } from 'mysql2' diff --git a/test/node/tsconfig.json b/test/node/tsconfig.json index 314b0282c..954d4336a 100644 --- a/test/node/tsconfig.json +++ b/test/node/tsconfig.json @@ -1 +1,9 @@ -{"compilerOptions":{"target":"ES2022","lib":["ESNext"],"module":"CommonJS","outDir":"dist","skipLibCheck":true},"default":{"compilerOptions":{"target":"ES2022","lib":["ESNext"],"module":"CommonJS","outDir":"dist","skipLibCheck":true},"default":{"compilerOptions":{"target":"ES2022","lib":["ESNext"],"module":"CommonJS","outDir":"dist","skipLibCheck":true},"default":{"extends":"../../tsconfig-base.json","include":["src/**/*"],"compilerOptions":{"target":"ES2022","lib":["ESNext"],"module":"CommonJS","outDir":"dist","skipLibCheck":true}},"extends":"../../tsconfig-base.json","include":["src/**/*"],"exclude":["src/async-dispose.test.ts"]},"exclude":["src/async-dispose.test.ts"],"extends":"../../tsconfig-base.json","include":["src/**/*"]},"exclude":["src/async-dispose.test.ts","src/async-dispose.test.ts"],"extends":"../../tsconfig-base.json","include":["src/**/*"]} \ No newline at end of file +{ + "extends": "../../tsconfig-base.json", + "include": ["src/**/*"], + "compilerOptions": { + "module": "NodeNext", + "outDir": "dist", + "moduleResolution": "NodeNext" + } +}