node-pg-migrate-custom
Version:
Postgresql database migration management tool for node.js
172 lines (171 loc) • 7.87 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Migration = exports.FilenameFormat = exports.getTimestamp = exports.loadMigrationFiles = void 0;
const fs_1 = __importDefault(require("fs"));
const mkdirp_1 = __importDefault(require("mkdirp"));
const path_1 = __importDefault(require("path"));
const lodash_1 = __importDefault(require("lodash"));
const migration_builder_1 = __importDefault(require("./migration-builder"));
const utils_1 = require("./utils");
const { readdir, lstat } = fs_1.default.promises;
const SEPARATOR = '_';
exports.loadMigrationFiles = async (dir, ignorePattern, subDir) => {
const fullDir = subDir ? `${dir}/${subDir}/` : `${dir}/`;
const dirContent = await readdir(fullDir, { withFileTypes: true });
const files = lodash_1.default(await Promise.all(dirContent.map(async (file) => {
const stats = await lstat(`${fullDir}/${file.name}`);
const filePath = subDir ? `${subDir}/${file.name}` : file.name;
if (stats.isDirectory()) {
return exports.loadMigrationFiles(dir, ignorePattern, filePath);
}
return stats.isFile() ? [{ name: file.name, path: filePath }] : [];
})))
.flattenDeep()
.sortBy(['name'])
.value();
const filter = new RegExp(`^(${ignorePattern})$`);
return ignorePattern === undefined ? files : files.filter((i) => !filter.test(i.name));
};
const getSuffixFromFileName = (fileName) => path_1.default.extname(fileName).substr(1);
const getLastSuffix = async (dir, ignorePattern) => {
try {
const files = await exports.loadMigrationFiles(dir, ignorePattern);
return files.length > 0 ? getSuffixFromFileName(files[files.length - 1].name) : undefined;
}
catch (err) {
return undefined;
}
};
exports.getTimestamp = (logger, filename, defIndex) => {
const prefix = filename.split(SEPARATOR)[0];
if (prefix && /^\d+$/.test(prefix)) {
if (prefix.length === 13) {
return Number(prefix);
}
if (prefix && prefix.length === 17) {
const year = prefix.substr(0, 4);
const month = prefix.substr(4, 2);
const date = prefix.substr(6, 2);
const hours = prefix.substr(8, 2);
const minutes = prefix.substr(10, 2);
const seconds = prefix.substr(12, 2);
const ms = prefix.substr(14);
return new Date(`${year}-${month}-${date}T${hours}:${minutes}:${seconds}.${ms}Z`).valueOf();
}
}
return defIndex;
};
const resolveSuffix = async (directory, { language, ignorePattern }) => language || (await getLastSuffix(directory, ignorePattern)) || 'js';
var FilenameFormat;
(function (FilenameFormat) {
FilenameFormat["timestamp"] = "timestamp";
FilenameFormat["utc"] = "utc";
})(FilenameFormat = exports.FilenameFormat || (exports.FilenameFormat = {}));
class Migration {
constructor(db, migrationPath, { up, down }, options, index, typeShorthands, logger = console) {
this.db = db;
this.path = migrationPath;
this.name = path_1.default.basename(migrationPath, path_1.default.extname(migrationPath));
this.timestamp = exports.getTimestamp(logger, this.name, index);
this.up = up;
this.down = down;
this.options = options;
this.typeShorthands = typeShorthands;
this.logger = logger;
}
static async create(name, directory, _language, _ignorePattern, _filenameFormat) {
if (typeof _language === 'string') {
console.warn('This usage is deprecated. Please use this method with options object argument');
}
const options = typeof _language === 'object'
? _language
: { language: _language, ignorePattern: _ignorePattern, filenameFormat: _filenameFormat };
const { filenameFormat = FilenameFormat.timestamp } = options;
mkdirp_1.default.sync(directory);
const now = new Date();
const time = filenameFormat === FilenameFormat.utc ? now.toISOString().replace(/[^\d]/g, '') : now.valueOf();
const templateFileName = 'templateFileName' in options
? path_1.default.resolve(process.cwd(), options.templateFileName)
: path_1.default.resolve(__dirname, `../templates/migration-template.${await resolveSuffix(directory, options)}`);
const suffix = getSuffixFromFileName(templateFileName);
const newFile = `${directory}/${time}${SEPARATOR}${name}.${suffix}`;
await new Promise((resolve, reject) => {
fs_1.default.createReadStream(templateFileName)
.pipe(fs_1.default.createWriteStream(newFile))
.on('close', resolve)
.on('error', reject);
});
return newFile;
}
_getMarkAsRun(action) {
const schema = utils_1.getMigrationTableSchema(this.options);
const { migrationsTable } = this.options;
const { name } = this;
switch (action) {
case this.down:
this.logger.info(`### MIGRATION ${this.name} (DOWN) ###`);
return `DELETE FROM "${schema}"."${migrationsTable}" WHERE name='${name}';`;
case this.up:
this.logger.info(`### MIGRATION ${this.name} (UP) ###`);
return `INSERT INTO "${schema}"."${migrationsTable}" (name, run_on) VALUES ('${name}', NOW());`;
default:
throw new Error('Unknown direction');
}
}
async _apply(action, pgm) {
if (action.length === 2) {
await new Promise((resolve) => action(pgm, resolve));
}
else {
await action(pgm);
}
const sqlSteps = pgm.getSqlSteps();
if (!this.options.skipMetaSave) {
sqlSteps.push(this._getMarkAsRun(action));
}
if (!this.options.singleTransaction && pgm.isUsingTransaction()) {
sqlSteps.unshift('BEGIN;');
sqlSteps.push('COMMIT;');
}
else if (this.options.singleTransaction && !pgm.isUsingTransaction()) {
this.logger.warn('#> WARNING: Need to break single transaction! <');
sqlSteps.unshift('COMMIT;');
sqlSteps.push('BEGIN;');
}
else if (!this.options.singleTransaction || !pgm.isUsingTransaction()) {
this.logger.warn('#> WARNING: This migration is not wrapped in a transaction! <');
}
if (typeof this.logger.debug === 'function') {
this.logger.debug(`${sqlSteps.join('\n')}\n\n`);
}
return sqlSteps.reduce((promise, sql) => promise.then(() => this.options.dryRun || this.db.query(sql)), Promise.resolve());
}
_getAction(direction) {
if (direction === 'down' && this.down === undefined) {
this.down = this.up;
}
const action = this[direction];
if (action === false) {
throw new Error(`User has disabled ${direction} migration on file: ${this.name}`);
}
if (typeof action !== 'function') {
throw new Error(`Unknown value for direction: ${direction}. Is the migration ${this.name} exporting a '${direction}' function?`);
}
return action;
}
apply(direction) {
const pgm = new migration_builder_1.default(this.db, this.typeShorthands, Boolean(this.options.decamelize), this.logger);
const action = this._getAction(direction);
if (this.down === this.up) {
pgm.enableReverseMode();
}
return this._apply(action, pgm);
}
markAsRun(direction) {
return this.db.query(this._getMarkAsRun(this._getAction(direction)));
}
}
exports.Migration = Migration;
;