UNPKG

npm-git-version

Version:

NPM utility that gets next version of application according git branch and tags

507 lines (506 loc) 22.9 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.VersionsExtractor = exports.processArguments = exports.updateBuildNumber = void 0; var git = require("simple-git"); var semver = require("semver"); var extend = require("extend"); var commandLineArgs = require("command-line-args"); var path = require("path"); var moment = require("moment"); var commandLineUsage = require("command-line-usage"); /** * Reads configuration from ENV variables */ function processEnvCfg() { var result = { config: 'ngv.config.json' }; if (process.env.NGV_BRANCH_NAME) { result.branchName = process.env.NGV_BRANCH_NAME; } if (process.env.NGV_BUILD_NUMBER) { result.buildNumber = parseInt(process.env.NGV_BUILD_NUMBER); } if (process.env.NGV_TAG_PREFIX) { result.tagPrefix = process.env.NGV_TAG_PREFIX; } if (process.env.NGV_IGNORE_BRANCH_PREFIX) { result.ignoreBranchPrefix = process.env.NGV_IGNORE_BRANCH_PREFIX; } if (process.env.NGV_PRE) { result.pre = process.env.NGV_PRE.toLowerCase() == "true"; } if (process.env.NGV_SUFFIX) { result.suffix = process.env.NGV_SUFFIX; } if (process.env.NGV_WORKING_DIRECTORY) { result.workingDirectory = process.env.NGV_WORKING_DIRECTORY; } if (process.env.NGV_CURRENT_VERSION) { result.currentVersion = process.env.NGV_CURRENT_VERSION; } if (process.env.NGV_NO_INCREMENT) { result.noIncrement = process.env.NGV_NO_INCREMENT.toLowerCase() == "true"; } if (process.env.NGV_NO_STDOUT) { result.noStdOut = process.env.NGV_NO_STDOUT.toLowerCase() == "true"; } if (process.env.NGV_EXECUTE) { result.execute = process.env.NGV_EXECUTE; } return result; } /** * Updates build number, handling special build number -1 * @param args Arguments that are being processed */ function updateBuildNumber(args) { if (args.buildNumber == -1) { args.buildNumber = parseInt(moment().format("YYYYMMDDHHmmss")); } } exports.updateBuildNumber = updateBuildNumber; /** * Process arguments and returns parsed object */ function processArguments() { var definitions = [ { name: "help", alias: "h", type: Boolean, description: "Displays help for this command line tool." }, { name: "branchName", type: String, description: "Name of branch used for getting version, if not specified current branch will be used, if detached error will be thrown.", typeLabel: "<branchName>", defaultOption: true }, { name: "buildNumber", alias: "b", type: Number, description: "Build number will be used if suffix is specified, defaults to 0, possible to use -1 to autogenerate date time stamp.", typeLabel: "<buildNumber>" }, { name: "tagPrefix", alias: "t", type: String, description: "Tag prefix (RegExp) used for pairing branch and tag for getting version.", typeLabel: "<prefix>" }, { name: "ignoreBranchPrefix", alias: "i", type: String, description: "Branch prefix name that will be ignored (RegExp) and stripped of branch during version paring.", typeLabel: "<ignorePrefix>" }, { name: "pre", alias: "p", type: Boolean, description: "Indication that prerelease version should be returned." }, { name: "suffix", alias: "s", type: String, description: "Suffix that is used when prerelease version is requested, will be used as prerelease (suffix) name.", typeLabel: "<suffix>" }, { name: "currentVersion", alias: "v", type: String, description: "Current version that will be used if it matches branch and tag as source for next version.", typeLabel: "<version>" }, { name: "noIncrement", alias: "r", type: Boolean, description: "Indication whether no new version should be incremented/calculated." }, { name: "workingDirectory", alias: "w", type: String, description: "Working directory where git repository is located.", typeLabel: "<workingDirectory>" }, { name: "noStdOut", alias: "n", type: Boolean, description: "Indication that no stdout should be written in case of success run." }, { name: "config", alias: "c", type: String, description: "Path to configuration file." }, { name: "execute", alias: "e", type: String, description: "Execute command with variable '$GIT_VERSION' available." } ]; var args = commandLineArgs(definitions); var envConfig = processEnvCfg(); var fileConfig = {}; try { var configPath = path.join(process.cwd(), envConfig.config); require.resolve(configPath); fileConfig = require(configPath); } catch (e) { if (!args.noStdOut) { console.log("No config file '" + envConfig.config + "' found."); } } args = extend({}, fileConfig, envConfig, args); updateBuildNumber(args); if (args.help) { console.log(commandLineUsage([ { header: "description", content: [ "Gets version of application based on branch name and existing tags", "", "Command:", "ngv <branchName> [options]" ] }, { header: "options", optionList: definitions } ])); process.exit(0); } return args; } exports.processArguments = processArguments; /** * Extractor that extracts version number for current git repository */ var VersionsExtractor = /** @class */ (function () { //######################### constructor ######################### function VersionsExtractor(_config) { this._config = _config; /** * Stripped branch prefix from branch name */ this._branchPrefix = ""; /** * Indication that tag is on current HEAD */ this._currentTag = false; /** * Indication that version is new for this branch */ this._startingVersion = false; if (this._config.pre && !this._config.suffix) { console.error("Prerelease version was set, but no suffix was provided!"); process.exit(-1); } } Object.defineProperty(VersionsExtractor.prototype, "branchPrefix", { //######################### public properties ######################### /** * Gets stripped branch prefix from branch name */ get: function () { return this._branchPrefix; }, enumerable: false, configurable: true }); Object.defineProperty(VersionsExtractor.prototype, "branchName", { /** * Gets name of current branch */ get: function () { return this._branchName; }, enumerable: false, configurable: true }); Object.defineProperty(VersionsExtractor.prototype, "branchVersion", { /** * Gets name of current branch stripped only to version */ get: function () { return this._branchVersion; }, enumerable: false, configurable: true }); Object.defineProperty(VersionsExtractor.prototype, "version", { /** * Gets computed version for next build */ get: function () { return this._version; }, enumerable: false, configurable: true }); Object.defineProperty(VersionsExtractor.prototype, "lastMatchingVersion", { /** * Gets number of last matching version */ get: function () { return this._lastMatchingVersion; }, enumerable: false, configurable: true }); Object.defineProperty(VersionsExtractor.prototype, "executeCommand", { /** * Command that should be executed after version is computed */ get: function () { return this._config.execute; }, enumerable: false, configurable: true }); Object.defineProperty(VersionsExtractor.prototype, "prereleaseSuffix", { //######################### private properties ######################### /** * Gets prerelease suffix */ get: function () { if (this._config.pre) { return "" + (this._branchPrefix ? this._branchPrefix + "-" : "") + this._config.suffix; } return ""; }, enumerable: false, configurable: true }); //######################### public methods ######################### /** * Process extraction of version */ VersionsExtractor.prototype.process = function () { var _this = this; var result = new Promise(function (resolve, reject) { _this._processResolve = resolve; _this._processReject = reject; }); //Tests whether application is run inside git repository this._git = git(this._config.workingDirectory).status(this._errorHandle(function () { if (!_this._config.noStdOut) { console.log("Git repository available."); } })); this._runProcessing(); return result; }; //######################### private methods ######################### /** * Runs internal git processing */ VersionsExtractor.prototype._runProcessing = function () { return __awaiter(this, void 0, void 0, function () { var _a, _b; return __generator(this, function (_c) { switch (_c.label) { case 0: _a = this; return [4 /*yield*/, this._getBranchName()]; case 1: _a._branchName = _c.sent(); this._processBranchName(); _b = this; return [4 /*yield*/, this._getLastMatchingTag()]; case 2: _b._lastMatchingVersion = _c.sent(); this._computeVersion(); this._applyBuildNumber(); this._processResolve(this); return [2 /*return*/]; } }); }); }; /** * Applies specified build number for prerelease version */ VersionsExtractor.prototype._applyBuildNumber = function () { //release version or current tag is on same commit as HEAD if (!this._config.pre || this._currentTag || this._config.noIncrement) { return; } //no build number or current version if (!this._config.buildNumber && !this._config.currentVersion) { return; } //build number higher priority if (this._config.buildNumber) { this._version = this._version.replace(/\d+$/g, "" + this._config.buildNumber); return; } //current version specified as parameter if (this._config.currentVersion) { var currentVersionSemver = semver.parse(this._version); var originalVersionSemver = semver.parse(this._config.currentVersion); //invalid one of versions if (!currentVersionSemver || !originalVersionSemver) { this._processReject("Unable to parse version '" + this._version + "' or '" + this._config.currentVersion + "'!"); return; } //version are in match use it as base for increment if (currentVersionSemver.major + "." + currentVersionSemver.minor + "." + currentVersionSemver.patch + "-" + currentVersionSemver.prerelease[0] == originalVersionSemver.major + "." + originalVersionSemver.minor + "." + originalVersionSemver.patch + "-" + originalVersionSemver.prerelease[0]) { this._version = semver.inc(this._config.currentVersion, "prerelease", false, this.prereleaseSuffix); } } }; /** * Computes next version for build */ VersionsExtractor.prototype._computeVersion = function () { //use current version without change if (this._config.currentVersion && this._config.noIncrement) { this._version = this._config.currentVersion; return; } //commit and matching tag are same, always use release version if (this._currentTag) { this._version = this._lastMatchingVersion; return; } //new version for current branch, first for current major.minor number if (this._startingVersion) { if (this._config.pre) { this._version = this._lastMatchingVersion + "-" + this.prereleaseSuffix + ".0"; } else { this._version = this._lastMatchingVersion; } return; } //version will not be incremented if (this._config.noIncrement) { return; } var version = this._config.pre ? "prerelease" : "patch"; this._version = semver.inc(this._lastMatchingVersion, version, false, this.prereleaseSuffix); }; /** * Processes branch name and extracts prefix and pure version number */ VersionsExtractor.prototype._processBranchName = function () { this._branchVersion = this._branchName; //no prefix and wrong format if (!this._config.ignoreBranchPrefix && !/^\d+\.\d+$/g.test(this._branchName)) { this._processReject("Wrong branch name '" + this._branchName + "', no prefix specified and branch is not in correct format!"); return; } var regex = new RegExp("^" + this._config.ignoreBranchPrefix, 'gi'); var matches = regex.exec(this._branchName); //prefix present if (matches) { this._branchPrefix = matches[0].replace(/\/$/g, ''); this._branchVersion = this._branchName.replace(regex, ''); } //after prefix strip still wrong format if (!/^\d+\.\d+$/g.test(this._branchVersion)) { this._processReject("Wrong branch name '" + this._branchName + "', probably wrong prefix regex, extracted version '" + this._branchVersion + "' is not ok!"); return; } }; /** * Gets last matching tag */ VersionsExtractor.prototype._getLastMatchingTag = function () { return __awaiter(this, void 0, void 0, function () { var _this = this; return __generator(this, function (_a) { return [2 /*return*/, new Promise(function (resolve) { //gets all tags _this._git.raw([ "log", '--pretty="%D"', "--tags", "--no-walk" ], _this._errorHandle(function (result) { result = result || ''; //splits string into array of decorated revisions names var tmpArray = result.split('\n') .filter(function (itm) { return itm; }) .map(function (itm) { return itm.replace(/^"|"$/g, ''); }); var resultArray = tmpArray; //current HEAD is on commit with existing old tag if (tmpArray.some(function (itm) { return itm.startsWith('HEAD'); })) { resultArray = []; for (var _i = 0, _a = tmpArray.reverse(); _i < _a.length; _i++) { var x = _a[_i]; resultArray.push(x); if (x.startsWith('HEAD')) { resultArray.reverse(); break; } } } //get array of tag names var tags = resultArray .map(function (itm) { return itm.replace(/^.*?tag:\s?(.*?)(?:$|,.*)/g, '$1'); }) .filter(function (itm) { return itm; }); var _loop_1 = function (x) { var matches = new RegExp("^" + _this._config.tagPrefix + "(" + _this._branchVersion + ".*?)$", 'gi') .exec(tags[x]); //tag and branch match if (matches) { var match_1 = matches[1]; var promises = [ new Promise(function (resolveHash) { _this._git.raw([ "rev-list", "-n", "1", tags[x] ], _this._errorHandle(function (tagResult) { resolveHash(tagResult.trim()); })); }), new Promise(function (resolveHash) { _this._git.revparse([ "HEAD" ], _this._errorHandle(function (tagResult) { resolveHash(tagResult.trim()); })); }) ]; Promise.all(promises).then(function (value) { if (value[0] == value[1]) { _this._currentTag = true; } resolve(match_1); }); return { value: void 0 }; } }; for (var x = 0; x < tags.length; x++) { var state_1 = _loop_1(x); if (typeof state_1 === "object") return state_1.value; } _this._startingVersion = true; resolve(_this._branchVersion + ".0"); })); })]; }); }); }; /** * Gets requested branch name */ VersionsExtractor.prototype._getBranchName = function () { return __awaiter(this, void 0, void 0, function () { var _this = this; return __generator(this, function (_a) { if (this._config.branchName) { return [2 /*return*/, this._config.branchName]; } return [2 /*return*/, new Promise(function (resolve) { _this._git.branch(_this._errorHandle(function (result) { if (result.detached) { _this._processReject("Unable to proceed if 'HEAD' is detached and no 'branchName' was specified!"); return; } resolve(result.current); })); })]; }); }); }; /** * Creates handle function with automatic error handling * @param callback Callback called if result was success */ VersionsExtractor.prototype._errorHandle = function (callback) { var _this = this; return function (error, result) { if (error) { _this._processReject(error); return; } callback(result); }; }; return VersionsExtractor; }()); exports.VersionsExtractor = VersionsExtractor;