UNPKG

pg-patch

Version:

PostgreSQL patching made easy.

198 lines (170 loc) 8.27 kB
'use strict'; const fsp = require('./fs-promise'); const q = require('q'); const common = require('./common'); const patchData = require('./patch-data'); const actions = common.action; const patchFileTemplateMode = common.patchFileTemplateMode; let FileScanner = function (config) { config = common.determineValue(config, {}); this.actionUpdate = common.determineValue(config.actionUpdate, 'up'); this.actionRollback = common.determineValue(config.actionRollback, 'rb'); this.patchFileTemplate = common.determineValue(config.patchFileTemplate, '^patch-$VERSION-$ACTION(?:-$DESCRIPTION)?\\.(?:sql|js)$'); this.patchDir = common.determineValue(config.patchDir, 'pg-patch'); this.patchFileTemplateMode = patchFileTemplateMode.INVALID; }; let createNumericalRegexGetFn = function (group) { return function (filename) { let match = this.createPatchFileRegex(group).exec(filename); /* istanbul ignore else */ if (match) { match = match[1]; /* istanbul ignore else */ if (match) { return parseInt(match, 10); } } return common.determineValue(match, null); }; }; FileScanner.prototype = { msg: common.msgHandler, createEmptyPatchDataObject: function () { return new patchData(); }, getPatchFileProperties: function (fName) { //TODO: improve handling of invalid templateMode? let version = null, action = null, source = null, target = null, diff = null; if (this.patchFileTemplateMode === patchFileTemplateMode.AV) { version = this.getPatchFileVersion(fName); action = this.getPatchFileAction(fName); } else /* istanbul ignore else */ if (this.patchFileTemplateMode === patchFileTemplateMode.ST) { source = this.getPatchFileSource(fName); target = this.getPatchFileTarget(fName); diff = target - source; if (diff * diff === 1) { if (diff > 0) { action = actions.UPDATE; version = target; } else { action = actions.ROLLBACK; version = source; } } } return { action: action, version: version, description: common.determineValue(this.getPatchFileDescription(fName), null) }; }, scanDirectoryForPatchFiles: function (dirPath, rData) { dirPath = common.determineValue(dirPath, []); rData = common.determineValue(rData, this.createEmptyPatchDataObject()); let currentSubDir = dirPath.join('/'); let currentFullDir = `${this.patchDir ? this.patchDir : '.'}${currentSubDir ? '/' + currentSubDir : ''}`; this.msg('DIR_SCAN:START', currentFullDir); return fsp.readDir(currentFullDir).then(files => { let dirPromises = []; files.forEach(fName => { let pathPromise = fsp.lstat(`${currentFullDir}/${fName}`).then(lstat => { if (lstat.isFile() && this.isPatchFile(fName)) { this.msg('PATCH_FILE:FOUND', fName); let patchFileProps = this.getPatchFileProperties(fName); if (patchFileProps.action && patchFileProps.version) { rData.addData({ dir: currentFullDir, name: fName, type: 'FILE', description: patchFileProps.description, action: patchFileProps.action, version: patchFileProps.version }); } return true; } else if (lstat.isDirectory()) { return this.scanDirectoryForPatchFiles(dirPath.concat([fName]), rData); //dirPromises.push(this.scanDirectoryForPatchFiles(dirPath.concat([fName]), rData)); } }); dirPromises.push(pathPromise); }); return q.all(dirPromises).then(function () { return rData; }); }); }, validatePatchFileTemplate: function () { let template = this.patchFileTemplate; let found = { action: /\$ACTION/g.test(template), version: /\$VERSION/g.test(template), source: /\$SOURCE/g.test(template), target: /\$TARGET/g.test(template) }; if ((found.action || found.version) && (found.source || found.target)) { return q.reject("Invalid patch file template: action/version cannot be mixed with source/target"); } else if (found.action && found.version) { return q(this.patchFileTemplateMode = patchFileTemplateMode.AV); } else if (found.source && found.target) { return q(this.patchFileTemplateMode = patchFileTemplateMode.ST); } else { return q.reject("Invalid patch file template: you need to supply (action AND version) OR (source AND target)"); } }, createPatchFileRegexGroup: function (key, capturing) { let groupSubstitution = { version: '\\d+', action: `${this.actionUpdate}|${this.actionRollback}`, source: `\\d+`, target: `\\d+`, description: `[0-9a-zA-Z\-\_]+` }[key]; return `(${capturing ? '' : '?:'}${groupSubstitution})`; }, createPatchFileRegex: function (key) { let versionTemplatePart, actionTemplatePart, sourceTemplatePart, targetTemplatePart; let descriptionTemplatePart = this.createPatchFileRegexGroup('description', key === 'description'); if (this.patchFileTemplateMode === patchFileTemplateMode.AV) { versionTemplatePart = this.createPatchFileRegexGroup('version', key === 'version'); actionTemplatePart = this.createPatchFileRegexGroup('action', key === 'action'); } else /* istanbul ignore else */ if (this.patchFileTemplateMode === patchFileTemplateMode.ST) { sourceTemplatePart = this.createPatchFileRegexGroup('source', key === 'source'); targetTemplatePart = this.createPatchFileRegexGroup('target', key === 'target'); } let regexString = this.patchFileTemplate.replace(/\$DESCRIPTION/g, descriptionTemplatePart); if (this.patchFileTemplateMode === patchFileTemplateMode.AV) { regexString = regexString .replace(/\$VERSION/g, versionTemplatePart) .replace(/\$ACTION/g, actionTemplatePart); } else /* istanbul ignore else */ if (this.patchFileTemplateMode === patchFileTemplateMode.ST) { regexString = regexString .replace(/\$SOURCE/g, sourceTemplatePart) .replace(/\$TARGET/g, targetTemplatePart); } return new RegExp(regexString, 'ig'); }, getPatchFileAction: function (filename) { let match = this.createPatchFileRegex('action').exec(filename); /* istanbul ignore else */ if (!match) { return null; } match = match[1]; if (match === this.actionUpdate) { return actions.UPDATE; } else /* istanbul ignore else */ if (match === this.actionRollback) { return actions.ROLLBACK; } }, getPatchFileVersion: createNumericalRegexGetFn('version'), getPatchFileSource: createNumericalRegexGetFn('source'), getPatchFileTarget: createNumericalRegexGetFn('target'), getPatchFileDescription: function (filename) { let match = this.createPatchFileRegex('description').exec(filename); return match ? common.determineValue(match[1], null) : null; }, isPatchFile: function (filename) { return this.createPatchFileRegex().test(filename); } }; module.exports = FileScanner;