UNPKG

@ostro/database

Version:
320 lines (219 loc) 7.98 kB
const Collection = require('@ostro/support/collection') const fs = require('fs-extra') const path = require('path'); class Migrator { $paths = []; $output = null; constructor($repository, $resolver, $file) { this.$repository = $repository; this.$resolver = $resolver; this.$file = $file; } run($paths = [], $options = []) { return this.getMigrationFiles($paths).then($files => { return this.$repository.getRan().then(async (res) => { let $migrations = await this.pendingMigrations($files, res) return this.runPending($migrations, $options) }) }) } pendingMigrations($files, $ran) { return Collection.make($files) .differenceWith($ran, (file, compare) => { return this.getMigrationName(file) == compare }).values().all(); } async runPending($migrations, $options = []) { if ($migrations.length === 0) { this.note('<info>Nothing to migrate.</info>'); return; } let $batch = await this.$repository.getNextBatchNumber(); let $pretend = $options['pretend'] ? $options['pretend'] : false; let $step = $options['step'] ? $options['step'] : false; for (let $file of $migrations) { await this.runUp($file, $batch, $pretend); if ($step) { $batch++; } } } async runUp($file, $batch, $pretend) { let $migration = this.resolvePath($file); let $name = this.getMigrationName($file); if ($pretend) { return await this.pretendToRun($migration, 'up'); } this.note(`<comment>Migrating:</comment> ${$name}`); let $startTime = Date.now(); await this.runMigration($migration, 'up'); let $runTime = Number((Date.now() - $startTime)); await this.$repository.log($name, $batch); this.note(`<info>Migrated:</info> ${$name} (${$runTime}ms)`); } async rollback($paths = [], $options = []) { let $migrations = await this.getMigrationsForRollback($options); if ($migrations.length === 0) { this.note('<info>Nothing to rollback.</info>'); return []; } return this.rollbackMigrations($migrations, $paths, $options); } async getMigrationsForRollback($options = []) { let $steps = $options['step'] ? $options['step'] : 0 if ($steps > 0) { return await this.$repository.getMigrations($steps); } return this.$repository.getLast(); } async rollbackMigrations($migrations, $paths, $options) { let $rolledBack = []; let $files = await this.getMigrationFiles($paths) for (let $migration of $migrations) { let $file = $files.find((content) => this.getMigrationName(content) == $migration.migration) if (!$file) { this.note(`<fg=red>Migration not found:</> ${$migration.migration}`); continue; } $rolledBack.push($file); await this.runDown( $file, $migration, $options['pretend'] ? $options['pretend'] : false ); } return $rolledBack; } async reset($paths = [], $pretend = false) { let $migrations = (await this.$repository.getRan()).reverse(); if (count($migrations) === 0) { this.note('<info>Nothing to rollback.</info>'); return []; } return this.resetMigrations($migrations, $paths, $pretend); } async resetMigrations($migrations, $paths, $pretend = false) { $migrations = collect($migrations).map(function ($m) { return { 'migration': $m }; }).all(); return await this.rollbackMigrations( $migrations, $paths, { $pretend } ); } async runDown($file, $migration, $pretend) { let $instance = this.resolvePath($file); this.note(`<comment>Rolling back:</comment> ${$migration.migration}`); if ($pretend) { return await this.pretendToRun($instance, 'down'); } let $startTime = Date.now(); await this.runMigration($instance, 'down'); let $runTime = Number((Date.now() - $startTime)); await this.$repository.delete($migration); this.note(`<info>Rolled back:</info> ${$migration.migration} (${$runTime}ms)`); } async runMigration($migration, $method) { if (typeof $migration[$method] == 'function') { await $migration[$method](); } } async pretendToRun($migration, $method) { try { for (let $query of await this.getQueries($migration, $method)) { $name = get_class_name($migration); $reflectionClass = new ReflectionClass($migration); if ($reflectionClass.isAnonymous()) { $name = await this.getMigrationName($reflectionClass.getFileName()); } this.note(`<info>${$name}:</info> ${$query['query']}`); } } catch ($e) { $name = get_class_name($migration); this.note(`<info>${$name}:</info> failed to dump queries. This may be due to changing database columns using Doctrine, which is not supported while pretending to run migrations.`); } } getQueries($migration, $method) { let $db = this.resolveConnection( $migration.getConnection() ); return $db.pretend(function () { if (method_exists($migration, $method)) { $migration[$method](); } }); } resolve($file) { $class = this.getMigrationClass($file); return new $class; } resolvePath($path) { let $migration = require($path); return typeof $migration == 'object' ? $migration : new $migration; } getMigrationClass($migrationName) { } async getMigrationFiles($paths) { let datas = Collection.make($paths).flatMap(async ($path) => { return $path.endsWith('.js') ? [$path] : this.$file.glob($path + path.sep + '*_*.js') }).all() return Promise.all(datas).then(files => { return Collection.make(files).filter().values().flatten().sortBy(($file, $key) => { return $key; }).all() }) } requireFiles($files = []) { for (var i = 0; i < $files.length; i++) { $files[i] = fs.readFile($files[i]).then(res => res.toString()); } return Promise.allSettled($files) } getMigrationName($path = '') { return path.basename($path) } paths() { return this.$paths; } getConnection() { return this.connection; } usingConnection($name) { return Promise.resolve(this.resolveConnection($name)) } setConnection($name) { this.connection = $name; } resolveConnection($connection) { return this.$resolver.connection($connection || this.connection); } getRepository() { return this.$repository; } repositoryExists() { return this.$repository.repositoryExists(); } hasRunAnyMigrations() { return this.repositoryExists().then(res => { if (res == true) { return this.$repository.getRan() } }) } deleteRepository() { return this.$repository.deleteRepository(); } getFilesystem() { return this.$file; } setOutput($output) { this.output = $output; return this; } note($message) { if (this.output) { this.output.writeln($message); } } } module.exports = Migrator