UNPKG

@kwaeri/mysql-migrator

Version:

The @kwaeri/migration component of the @kwaer/cli user-executable framework.

745 lines 37 kB
/** * SPDX-PackageName: kwaeri/mysql-migrator * SPDX-PackageVersion: 0.7.0 * SPDX-FileCopyrightText: © 2014 - 2022 Richard Winters <kirvedx@gmail.com> and contributors * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception OR MIT */ 'use strict'; // INCLUDES import { kdt } from '@kwaeri/developer-tools'; import { MigratorServiceProvider } from '@kwaeri/migrator'; import { Filesystem } from '@kwaeri/filesystem'; import { Configuration } from '@kwaeri/configuration'; import { MySQLDriver } from '@kwaeri/mysql-database-driver'; import { Console } from '@kwaeri/console'; import debug from 'debug'; // DEFINES const _ = new kdt(), output = new Console({ color: false, background: false, decor: [] }); /* Configure Debug module support */ const DEBUG = debug('kue:mysql-migrator'); export const PARAMATERIZATION = { TYPE: { "mysql": true }, LANG: { 'javascript': true }, EXT: { 'javascript': 'js' } }; const DEFAULT_GENERATOR_OPTIONS = { environment: "default", quest: "migrate", specification: "", version: "", args: { // "step-back": "5", }, subCommands: [], stepBack: 0, configuration: { project: { name: "", type: "", tech: "", root: ".", author: { first: "", last: "", fullName: "", email: "" }, copyright: "", copyrightEmail: "", license: { identifier: "", }, repository: "" } } }; /** * The Migrator Class * * The { Migrator } class handles everything that has to do with the migration * system, including; Setup, Management, and Execution of migrations. */ export class MysqlMigrator extends MigratorServiceProvider { /** * @var { string } */ path = 'data/migrations'; /** * @var { string } */ confPath = 'conf'; /** * @var { string } */ file = 'migrations.json'; /** * @var {string } */ version = '0.1.10'; /** * @var { string } */ table = 'nodekit_migrations'; /** * @var { Date } */ start; /** * @var { Date } */ end; /** * @var { Date } */ processStart; /** * @var { Date } */ processEnd; /** * @var { Configuration } */ configuration; /** * @var { any } */ conf; /** * @var { Configuration } */ databaseConfiguration; /** * @var { any } */ databaseConf; /** * @var { string } */ databaseType = 'mysql'; /** * @var */ databaseProvider = null; /** * @var { DatabaseDriver } */ dbo; /** * Class constructor */ constructor(handler, configuration) { super(handler); // Organize all of the uncertainty: this.version = (configuration && configuration.version) ? configuration.version : "0.1.10", this.databaseType = (configuration && configuration.type) ? configuration.type : "mysql", this.table = (configuration && configuration.table) ? configuration.table : "nodekit_migrations"; // Get the current environment: const environment = (configuration && configuration.environment) ? configuration.environment : "default"; // Instantiate a new configuration object to wrap the migrations configuration: this.configuration = new Configuration("conf", "migrations.json", { plainText: false, type: "migrations" }); // Grab the database configuration pre-emptively as well, if it exists: this.databaseConfiguration = new Configuration('conf', `database.${environment}.json`, { plainText: false, type: 'database' }); // Store the databse provider type: this.databaseProvider = (configuration && configuration.databaseProvider) ? configuration.databaseProvider : MySQLDriver; } getServiceProviderSubscriptions(options) { return { commands: { "migrations": false // Specifications for the 'add' command, false if there are none }, required: { "migrations": {} }, optional: { "migrations": { "step-back": { "for": false, // Or false, if it's not related to an option/value, rather only to the specification/command. "flag": false, // True insists that no value is given. Its existance equates to <option>=1, the lack of its "values": null // existence is similar to <option>=0. }, } } }; } getServiceProviderSubscriptionHelpText(options) { return { helpText: { "commands": { "migrations": { "description": "The 'migrations' command automates the handling of migrations for an existing project.", "specifications": { // ⇦ For the specifications //"example-spec": { // "description": "", // "options": { // "required": { // ⇦ Required options are specific to the specification // "example-option": { // ⇦ List options // "description": "", // "values": [] // } // }, // "optional": { // ⇦ Optional options can be // "specification": { // ⇦ For the specification // "example-option": { // ⇦ List options // "description": "", // "values": [] // } // }, // ⇦ For the various required options that allow optional flags // "required-option": { // ⇦ For a required option // "required-value": { // ⇦ For the required option's value, can be 'any' // "example-option": { // ⇦ List options // "description": "", // "values": false // } // }, // } // } // } //} }, "options": { "optional": { "command": { "step-back": { "description": "Specify the number of migrations to undo from those that are applied.", "values": null } }, //"optional-option": { // ⇦ For the optional options of the command // "optional-value": { // ⇦ For the optional options value, can be 'any' // "example-option": { // ⇦ List options // "description": "", // "values": [] // } // } //} } } } } } }; } /** * Method to resettle the { NodeKitProjectGeneratorOptions }. Essentially we * merge NodeKitOptions with FilesystemDescriptor by combining provided * command options with either a stored configuration or sane default. * * @param { NodeKitOptions } options * * @returns { NodeKitOptions } The options object, with the configuration partially populated with user-provided information */ async assembleOptions(options) { // Setting some defaults let returnable, nko, nkcb, nkpb, nkab; //returnable = ( _.extend( defaults, returnable ) as NodeKitProjectGeneratorOptions ); returnable = _.extend(options, DEFAULT_GENERATOR_OPTIONS); DEBUG(`[ASSEMBLE_OPTIONS] Sanitize settings`); //returnable.name = ( returnable.subCommands[0] && returnable.subCommands[0] !== "" ) ? returnable.subCommands[0] : "MyNewMySQLMigration"; //returnable.fileSafeName = Filesystem.getFileSafeName( returnable.name ); //returnable.type = ( returnable.args.type in PARAMATERIZATION.TYPE ) ? returnable.args.type : 'mysql'; returnable.stepBack = (returnable.args['step-back']) ? returnable.args['step-back'] : 0; DEBUG(`[ASSEMBLE_OPTIONS] Get configuration`); //const { confExists, projectConf } = await this.getConfiguration(); //returnable.configuration = projectConf //if( confExists ) //{ // DEBUG( `ASSEMBLE_OPTIONS] Inject 'fileSafeName'to stored project configuration.` ); // returnable.configuration.project.fileSafeName = Filesystem.getFileSafeName( returnable.configuration.project.name ); // returnable.configuration.project.root = ( projectConf.project.root === "" || projectConf.project.root === "." ) ? `${Filesystem.getPathToCWD()}` : projectConf.project.root; //} //else //{ // DEBUG( `ASSEMBLE_OPTIONS] Set project configuration 'tech' to a sane default.` ); // returnable.configuration.project.tech = ( returnable.args.lang in PARAMATERIZATION.LANG ) ? Object.keys( PARAMATERIZATION.LANG )[returnable.args.lang] : "javascript"; //} //returnable.extension = PARAMATERIZATION.EXT[returnable.configuration.project.tech]; //returnable.path = returnable.configuration.project.root; return Promise.resolve(returnable); } /** * A method which asynchronously executes the necessary steps for creating * a migration within a project file structure * * @param { NodeKitOptions } options An object which specifies parameters for this method * * @return { Promise<any> } */ async renderService(options) { try { this.updateProgress('MYSQL_MIGRATOR_RENDER_SERVICE', { progressLevel: 0, notice: `Preparing to ${(options.args.stepBack) ? (options.args.stepBack > 0) ? `revert ${options.args.stepBack}` : `revert` : `apply`}' migrations` }); DEBUG(`[RENDER_MIGRATION_GENERATOR_SERVICE] Resettle options`); options = await this.assembleOptions(options); DEBUG(`[RENDER_MIGRATION_GENERATOR_SERVICE] Resolve 'runCreateMySQLMigrationRoutine()'`); const { result } = await this.migrate(options); this.updateProgress("MYSQL_MIGRATOR_RENDER_SERVICE", { progressLevel: -1 }); return Promise.resolve({ result, type: "run_migrations" }); } catch (error) { return Promise.reject(new Error(`[RENDER_MIGRATION_GENERATOR_SERVICE]: ${error}`)); } } /** * Checks if the migration system has been installed * * @pram { void } * * @return { Promise<undefined|null> } A promise with a boolean value indicating whether the migration system is installed. */ async checkInstall() { // Check that both the filesystem and database portions of the // migration system are installed, if not - return null if ((await this.checkFilesystemInstall()) === null || (await this.checkDatabaseInstall()) !== undefined) return Promise.resolve(null); // Otherwise, pass the check return Promise.resolve(undefined); } /** * Checks the actual database portion of the migration system is installed * * @returns { Promise<undefined } If the check passes */ async checkDatabaseInstall() { // Get essentials for the check: const conf = await this.getConf(true); const dbo = await this.getDbo(); // Run a query to determine if the migrations table exists:: const checkMigrationsDatabaseResult = await dbo // // information_schema queries are handled differently prior to MySQL 8; // If you do not capitalize the column name in the select clause, or set // a lowercase alias for it - you will have inconsistent use of // capitalization across the different MySQL versions. The simplest // solution is to capitalize the column name (i.e. TABLE_NAME) so that // the column name is capitalized in the result when querying both MySQL // 5 and MySQL 8 (which is the default behavior in 8, regardless of // select case). // // Alternatively, you can alias (i.e. TABLE_NAME as table_name) to force // MySQL 8 and MySQL 5 to both use lowercase for a column name: // // https://stackoverflow.com/a/54541147/2041005 // .query(`SELECT TABLE_NAME FROM information_schema.tables ` + `WHERE table_schema = '${conf.database}' ` + `AND table_name = '${this.table}';`); DEBUG(checkMigrationsDatabaseResult); DEBUG(checkMigrationsDatabaseResult.rows); // Check if there was some sort of error with our query: if (!checkMigrationsDatabaseResult || !checkMigrationsDatabaseResult.rows || _.empty(checkMigrationsDatabaseResult.rows)) return Promise.reject(new Error(`[MIGRATOR][CHECK_INSTALL] Error checking for the migrations table. If the migrations table does not exist, delete '${this.file}' in '${this.confPath}' and generate a new migration ('kue add migration --type mysql <MigrationName>') to force install the migrations system.`)); // Check for TABLE_NAME; if the table was specified in lower case in the // query, but unaliased, we'd need to check for both uppercase and lower // here to ensure the check did not fail. if ((checkMigrationsDatabaseResult.rows?.[0]?.TABLE_NAME !== this.table)) return Promise.resolve(null); // And return undefined indicating return Promise.resolve(undefined); } /** * Checks the actual filesystem portion of the migration system is installed * * @returns {Promise<undefined|null>} */ async checkFilesystemInstall() { // If not ⇨ return false (It will generate the migration configuration): if (!(await this.getConf())) return Promise.resolve(null); // Otherwise, return undefined if there were no issues return Promise.resolve(undefined); } /** * Gets the migration or database configuration * * @param { boolean } database True to get the database configuration, false to get the migration configuration * * @returns { Configuration } A {@link Configuration} object */ async getConf(database = false) { const conf = database ? "databaseConf" : "conf"; //try { if (this[conf] === undefined) this[conf] = database ? await this.databaseConfiguration.get() : await this.configuration.get(); // IF there's no migration conf, return null //if( ( !this[conf] || !this[conf].success ) ) ⇦ Updated configuration implementation if (!this[conf]) if (conf === 'conf') return Promise.resolve(null); else return Promise.reject(new Error(`[MYSQL_MIGRATOR][GET_CONF] There was an issue reading the ${(database ? `database ` : ``)}configuration. `)); //} //catch( error ) { // return Promise.reject( error ); //} DEBUG(`'${conf}' configuration successfully fetched:`); DEBUG(this[conf]); // Otherwise, return the conf //return Promise.resolve( this[conf].configuration ); ⇦ Updated configuration implementation return Promise.resolve(this[conf]); } /** * Get the DBO; If `undefined` then attempt to create it, otherwise return * the existing dbo or resolve `null` in the event that this method * fails. * * @returns A new {@link DatabaseDriver} object */ async getDbo() { try { // Get a database object:: if (!this.dbo) { const dbo = new this.databaseProvider(await this.getConf(true)); //this.dbo = new Driver( await this.getConf( true ), this.databaseProvider ).get(); if (!dbo) return Promise.reject(new Error(`There was an issue getting the Database Provider`)); this.dbo = dbo; } return Promise.resolve(this.dbo); } catch (error) { DEBUG(`${error}`); return Promise.reject(`${error}`); } } /** * Installs the migration system by calling the component install routines * * @param { string } content The migration configuration file content * * @return { Promise<T> } A promise that indicates the result of installing the migration system */ async install(content) { // Call the component install routine methods if (await this.installFilesystemComponent(content) === undefined && await this.installDatabaseComponent() === undefined) return Promise.resolve(undefined); // Return a resolved promise:denoting a false result if any // component install routine fails without throwing return Promise.resolve(null); } /** * Handles the migration's filesystem portion of the install * * @param { string } content The migration configuration file content * * @returns { Promise<undefined> } */ async installFilesystemComponent(content) { // Create the migration file: if (!(await this.createFile(this.confPath, this.file, content))) return Promise.reject(new Error(`[MIGRATOR][INSTALL] There was an issue writing the migration configuration to disk. `)); return Promise.resolve(undefined); } /** * Handles the migration's database portion of the install * * @returns { Promise<undefined> } */ async installDatabaseComponent() { // Create the migrations table: const dbo = await this.getDbo(); const createMigrationsTable = await dbo .query(`create table if not exists ${this.table} ` + `( id int(11) not null auto_increment, ` + `date varchar(255), ` + `name varchar(255), ` + `sequel text, ` + `applied int(11), ` + `primary key (id), ` + `unique key unique_date (date) );`); // If the query failed: if (!createMigrationsTable || !createMigrationsTable.rows) return Promise.reject(new Error(`[MIGRATOR][INSTALL] There was an error installing migrations into the project. `)); return Promise.resolve(undefined); } /** * Applies or reverts [a] migration(s) * * @param options Parameters that shape the migration command * * @returns { Promise<MigrationPromise> } */ async migrate(options) { // Mark the start of the migration procedure DEBUG(`Set migration 'start' time`); this.start = new Date(); // Check that the migration system is already installed - if it is NOT // we will need to install it and generate a migration prior to running // any migration(s): DEBUG(`Check migration system installation`); if (await this.checkInstall() === null) return Promise.reject(new Error(`[MIGRATOR][RUN] Unable to verify whether the migration system is installed. If the 'data/migrations' path and/or the 'data/migrations/migrations.json' configuration are missing - generate a migration in order to force installation on your system. `)); // Next we need to get a list of files within the 'data/migrations' directory: DEBUG(`Get list of available migrations`); //let { hierarchy, files, map } = await this.getAvailableMigrations(); let { files, map } = await this.getAvailableMigrations(); // Next we need a list of migrations which have already been applied. DEBUG(`Get list of applied migrations`); let applied = await this.getAppliedMigrations(); // Since we sort by the date and name it should already be sorted appropriately, queue // up the migrations to process, and process them - keeping in mind whether we are // processing forward or stepping back: let processed = 0, workingSet = this.getWorkingSet(files, applied, options.stepBack), total = workingSet.length; if (workingSet.length) { this.updateProgress('MYSQL_MIGRATOR_RENDER_SERVICE', { progressLevel: 0, notice: `Processing migrations...` }); for (let toProcess in workingSet) { // Because of the simplicity in the logic behind applying migrations, we need to // ensure that if we are applying migrations - that we do not attempt to apply // any migrations we iterate over that have already been applied. However, if // we're reverting, we actually needed to ensure that bit - because were ONLY // reverting migrations that have for sure been applied ⇨ So we have to create // a condition where this code flow is entered under appropriate circumstance. // // Check for existence of the migration 'toProcess' in our collection of applied // migrations. If a non-negative index (>= 0) value returns - it's been applied. const isApplied = applied.indexOf(workingSet[toProcess]) >= 0; // Then, apply migrations which have not yet been applied - or revert migrations // that have already been applied: if ((!options.stepBack && !isApplied) || (options.stepBack && isApplied)) { this.processStart = new Date(); // Make this a lil' easier to read const target = map[workingSet[toProcess]]; DEBUG(`target: ${target}`); DEBUG(target); this.updateProgress('MYSQL_MIGRATOR_RENDER_SERVICE', { progressLevel: ((processed * 100) / total), notice: this.printProcessInfo(target.name, (options.stepBack) ? true : false) }); //this.printProcessInfo( target.name, ( options.stepBack ) ? true : false ); DEBUG(`Enqueue migration '${target.name}' at '${Filesystem.getPathToCWD()}/${target.path.toString()}`); await this.enqueueMigration(target, (options.stepBack) ? true : false); // Require and instantiate the migration const applicableMigration = (await import(`${Filesystem.getPathToCWD()}/${target.path.toString()}`)).default, applicableMigrator = new applicableMigration(); // Composition rocks: applicableMigrator.dbo = this.dbo; DEBUG(`${(options.stepBack) ? `Revert ` : `Apply `} migration '${target.name}`); // Run the migration: const migrationProcessed = (!options.stepBack) ? await applicableMigrator.up() : await applicableMigrator.down(); if (!migrationProcessed || !migrationProcessed.result || !migrationProcessed.rows) return Promise.reject(new Error(`[MIGRATION][${target.name}]: There was an issue processing the migration. `)); DEBUG(`Migration '${target.name}' ${(options.stepBack) ? 'reverted' : 'applied'}...`); DEBUG(`Dequeue migration '${target.name}`); await this.dequeueMigration(target, (options.stepBack) ? true : false); this.processEnd = new Date(); this.updateProgress('MYSQL_MIGRATOR_RENDER_SERVICE', { log: this.printProcessInfo(target.name, (options.stepBack) ? true : false, true) }); //this.printProcessInfo( target.name, ( options.stepBack ) ? true : false, true ); processed++; } } this.end = new Date(); this.updateProgress('MYSQL_MIGRATOR_RENDER_SERVICE', { progressLevel: ((processed * 100) / total), notice: this.printServiceInfo(false, options.stepBack, processed) }); //this.printServiceInfo( false, options.stepBack, processed ); } else { this.end = new Date(); this.updateProgress('MYSQL_MIGRATOR_RENDER_SERVICE', { progressLevel: 100, notice: this.printServiceInfo(true) }); //this.printServiceInfo( true ); /* console.log ( output.normalize().buffer( `[` ).color( 'gray' ).decor( ['bright'] ) .buffer( this.end.toLocaleTimeString( "en-us", this.timeOptions ) ) .normalize().buffer( `] ` ).decor( ['bright'] ) .buffer( `${(options.stepBack)? `No applied migrations exist.` : `No migrations exist.`}` ).color( 'cyan' ) .color( 'white' ).buffer( ` after ` ) .color( 'magenta' ).buffer( `${this.end.getTime() - this.start.getTime()} ms` ).normalize().dump() ); */ } // Return some info return Promise.resolve({ result: true, migrations: files }); } async getAvailableMigrations() { const hierarchy = { root: { type: "directory", children: {} } }; hierarchy.root.children = await this.getDirectoryStructure(this.path.toString()); // We know the directory structure so lets loop through each month of each year // and make a list of all migrations: let files = [], map = {}, count = 0; // Loop through each year //for( let year in ( hierarchy.root.children as any ).files ) { for (let year in hierarchy.root.children) { // For each year, loop through each month: //for( let month in ( hierarchy.root.children as any ).files[year].children ) { for (let month in hierarchy.root.children[year].children) { // For each month, add the migrations to our list: //for( let migration in ( hierarchy.root.children as any ).files[year].children[month].children ) { for (let migration in hierarchy.root.children[year].children[month].children) { // Create a simple array to store the migration files in, this is the device // we'll use to sort and ensure the proper sequence of migrations: files.push(migration); // Build a map to keep necessary data available to us: map[files[count]] = { timestamp: migration.split('_')[0], name: migration.split('_')[1].split('.cjs')[0], path: this.path + '/' + year + '/' + month + '/' + migration }; DEBUG(`Migration '${map[files[count]].name}' found at '${map[files[count]].path}'`); count++; } } } // Now sort the keys array so we apply migrations in order!: files = files.sort(this.alphaNumericSort); // return Promise.resolve( { hierarchy, files, map } ); return Promise.resolve({ files, map }); } async getAppliedMigrations() { // Get the database object: const dbo = await this.getDbo(); // Query the migrations table for applied migrations: const appliedMigrations = await dbo .query(`select * from ${this.table} ` + `where applied=1 ` + `order by date asc, name asc;`); // If the query failed: if (!appliedMigrations || !appliedMigrations.rows) return Promise.reject(new Error(`[MIGRATOR][GET_APPLIED_MIGRATIONS] There was an error getting a list of applied migrations from ${this.table} table in the database. `)); // Now we need to take any rows, and rebuild the migration filename to use as a // comparitor so as to ensure we do not rerun the respective migration as we // loop through our list of migrations and apply any unapplied migrations: let applied = []; for (let record in appliedMigrations.rows) { applied.push(`${appliedMigrations.rows[record].date}_${appliedMigrations.rows[record].name}.cjs`); } DEBUG(`[MIGRATOR][GET_APPLIED_MIGRATIONS]: Found '${applied.length}' migrations in '${this.table}'.`); return Promise.resolve(applied); } getWorkingSet(available, applied, stepBack) { console.log(output.normalize().buffer(`[`).color('gray').decor(['bright']) .buffer(new Date().toLocaleTimeString("en-us", this.timeOptions)) .normalize().buffer(`] `).decor(['bright']) .buffer(`${(stepBack) ? `Stepping back ${stepBack} migrations.\n` : 'Processing migrations forward.\n'}`).normalize().dump()); let workingSet = []; if (!stepBack) { // If we're not stepping back the working set is the files we have in our data/migrations // directory, and we'll apply only migrations which haven't already been applied: workingSet = available; } else { // If we ARE stepping back, the working set will be only the migrations we are going to // revert -> so we'll slice the working set such that we only have the number of migrations // that we are stepping back, to work with: workingSet = applied; // Now let's only get the migrations we're going to revert: //workingSet = workingSet.slice( workingSet.length - stepBack, workingSet.length ); workingSet = workingSet.slice(-stepBack); // Finally, let's re-sort them in reverse: workingSet = workingSet.reverse(); } return workingSet; } /** * Stages the migration for processing. * * Checks if the migration has been queued for processing, and if not - queues it. In * the event that it has already been queued, we note that whether we are applying or * reverting the migration. * * @param { any } target The target migration * @param { Number } stepBack The number of migrations to revert * * @returns { Promise<undefined> } A promise that resolves undefined */ async enqueueMigration(target, stepBack = false) { // As part of the queueing process, check whether the migration is already added to // the migrations table and set as unapplied: const migrationIsQueued = await this.dbo .query(`select * from ${this.table} ` + `where date='${target.timestamp}' and name='${target.name}' and applied=0;`); if (!migrationIsQueued || !migrationIsQueued.rows) return Promise.reject(new Error(`[MIGRATION][MIGRATE] There was an issue checking whether a migration had been queued to be applied in the '${this.table}' database table. `)); // If not, we need to queue it in the database first, then process it. When reverting // migrations, this will conveniently never matter: if (migrationIsQueued.rows.length <= 0 && !stepBack) { DEBUG(`Queueing migration '${target.name}'`); const queueMigration = await this.dbo .query(`insert into ${this.table} ` + `(date, name, applied) ` + `values( '${target.timestamp}', '${target.name}', 0);`); if (!queueMigration || !queueMigration.rows) return Promise.reject(new Error(`[MIGRATION][${target.name}] There was an issue queueing the migration in the ${this.table} table in the database. `)); DEBUG(`Migration '${target.name}' staged to apply.`); } else { // If it's not queued and is in fact a reversion, just note it if (migrationIsQueued.rows.length <= 0 && stepBack) DEBUG(`Migration '${target.name}' staged to revert`); // If it's somehow already queued, it can't possibly be a migration being processed // for reversion, because migrations can only be processed for reversion if they've // been applied, already - that's the rule we've created... so if (migrationIsQueued.rows.length > 0) DEBUG(`Migration '${target.name}' previously queued, resuming...`); } return Promise.resolve(undefined); } /** * If migrations are being applied, it marks the queued migration as 'applied'. If * migrations are being reverted, it marks the reverted migration as unapplied. * * @param { any } target The target migration * @param { number } stepBack The number of migrations to revert * * @returns { Promise<undefined> } An undefined promise */ async dequeueMigration(target, stepBack = false) { // We then need to update the table record such that it reflects // that we have applied the migration: const markApplied = await this.dbo .query(`update ${this.table} set applied=${(stepBack) ? '0' : '1'} ` + `where name='${target.name}' and date='${target.timestamp}';`); if (!markApplied || !markApplied.rows) return Promise.reject(new Error(`[MIGRATION][${target.name}]: There was an issue marking a${(stepBack) ? ' reverted migration as unapplied' : 'n applied migration as completed.'}`)); } /** * Prints current process information * * @param { string } target The name of the migration * @param { boolean } stepBack True if the migration is moving backward, false if forward * @param { boolean } finished True if the process is completed */ printProcessInfo(target, stepBack = false, finished = false) { let _o = output.normalize().buffer(`[`).color('gray').decor(['bright']) .buffer(new Date().toLocaleTimeString("en-us", this.timeOptions)) .normalize().buffer(`] `).decor(['bright']); _o = (finished) ? _o.buffer(`${(stepBack) ? `Reverting '` : `Applying '`}`) : _o.buffer(`${(stepBack) ? `Finished reverting '` : `Finished applying '`}`); _o = _o.color('cyan').buffer(`${target}`).color('white').buffer(`' `); if (finished) _o = _o.buffer(`after `).color('magenta') .buffer(`${this?.processEnd?.getTime() - this?.processStart?.getTime()} ms\n`); //console.log ( _o.normalize().dump() ); return _o.normalize().dump(); } /** * Prints current process information * * @param { string } target The name of the migration * @param { boolean } revert True if the migration is moving backward, false if forward * @param { boolean } finished True if the process is completed */ printServiceInfo(emptySet = true, stepBack = false, processed) { let _o = output.normalize().buffer(`[`).color('gray').decor(['bright']) .buffer(this?.end?.toLocaleTimeString("en-us", this.timeOptions)) .normalize().buffer(`] `).decor(['bright']); // If we had a workingSet, we have one potential output: if (!emptySet) _o = _o.buffer(`${(processed && processed > 0) ? `${(stepBack) ? `Successfully reverted ${stepBack} migrations.` : `Successfully applied ${processed} migrations.`}` : `${(stepBack) ? `Nothing to revert,` : `Migrations are up to date.`}`}`); else _o = _o.buffer(`${(stepBack) ? `No applied migrations exist.` : `No migrations exist.`}`); // And follow with a display of lapsed time for the service or migration, accordingly _o = _o.color('white').buffer(` after `) .color('magenta').buffer(`${this?.end?.getTime() - this?.start?.getTime()} ms`).normalize(); //console.log( _o.dump() ); return _o.dump(); } } //# sourceMappingURL=mysql-migrator.mjs.map