@kwaeri/xfmr
Version:
The @kwaeri/xfmr component module of the @kwaeri application framework.
259 lines • 13.8 kB
JavaScript
/*******************************************************************************
* @module kwaeri/xfmr
* @version 0.4.0
* @license
* Copyright © 2014 - 2022 Richard Winters <kirvedx@gmail.com> and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
******************************************************************************/
'use strict';
import rs from 'replacestream';
export default class VersionBump {
/**
* Represents possible operations that could be performed upon a semantic version string
*
* @arg { Object }
*/
operations = {
patch: 0,
minor: 1,
major: 2,
version: 3,
prerelease: 4
};
/**
* Represents a semantic version string that will replace another found within a file stream
*
* @arg { string }
*/
replacementSemver = "";
/**
* Represents one of the possible operations that could be performed upon a semantic version string
*
* @arg { number }
*/
replacementOperation = 0;
/**
* This is a regular expression inteded to parse a JSDoc-based version tag and ac-
* companying value from the header comment of any applicable file.
*
* [NOTES]
*
*
* (?<required> )+ <-- This is the group we want to exist. We're making
* it have to exist for a match to happen, by surrou-
* nding the entire regex with this capture group.
*
* - The matching '( and )' makes a 'group'.
* - The '?<xxx>' inside the opening parenthesis _na-
* mes_ the group (xxx canbe whatever).
* - The '+' at the end means the group must exist 1
* or more times.
*
* * A '?:' inside the opening parenthesis would sig-
* nify NOT to capture anything, typically a nested
* set of parenthesis would then be provided to si-
* gnify _what_ within the non-capturing group, SH-
* OULD be captured, as you'll see in a moment.
*
*
* (?<version> ) <-- Names the version string group
*
*
* (@version:[ ]*) <-- Matches literally "@version:" and possibly 0 or
* more spaces
*
* * We do not use \s, as that includes newlines -
* which we do no want to allow. Therefore, we
* use [ ].
* * The capture (or parenthesis) around this block
* provide us with a group which will contain the
* "@version" and all the spaces that were found
* prior to the major component of our semver stri-
* ng, so that we can build a new, modified, semver
* - without breaking formatting!
*
*
* (?:(?<major>\d+).){1} <-- This captures from 1 or more numbers till a period
* (but does not capture the period)
*
* * The group is named major, the major version com-
* ponent)
*
* (?:(?<minor>\d+).){1} <-- This captures from 1 or more numbers till a period
* again, again 1 time, yet not the period - again.
*
* * The group is named minor, the minor version com-
* ponent)
*
* (?:(?<patch>\d+)){1}))+ <-- This captures from 1 or more numbers till a period
* again, again 1 time, It stops at a white space or
* anything other than a number.
*
* * It's named the patch group (patch version group)
* * It also closes off the version and required gro-
* ups.
* * The "+" following is the "+" of the required gr-
* oups capture, enforcing that everything we've
* covered so far must have happened 1 time, or no
* match will be made at all (one will not be repo-
* rted back, even if part of the components are
* matched).
*
*
* (?:(?<prerelease>-[\+\!\~a-zA-Z0-9]+))?
*
* The above regex follows all which we've covered alrea-
* dy, and states that there is a capture group, named
* 'prerelease', which consists of a hyphen, followed by
* any character from a list which has been provided.
*
* * The hyphen must immediately follow the required
* group in order for this match to succeed.
* * The '[]' denotes a list, which includes \+, \!,
* \~, a-z, A-Z, 0-9. Any other character fails -
* or ends if following an allowed character - the
* match.
* * The '?' means that the entire match can possibl-
* y exist, but does not have to.
* if it does it will be matched, otherwise we wil-
* l still be returned with the required match.
*
* And this is the regex we will use to parse for the semver, within a JSDoc-style
* header, in any file that is passed to this plugin.
*
* The options passed to this plug-in will tell us whether or not we will be repla-
* cing the entire semver string, or bumping a particular component.,
*
* We'll use this regexp in a string.replace function - so that we can actually pr-
* operly replace the string if need be.
*
* @arg { RegExp }
*/
regex = /((:[ ]*)((?:(\d+).){1}(?:(\d+).){1}(?:(\d+)){1}))+(?:(-[\+\!\~a-zA-Z0-9]+))?/gm;
/**
* Holds the default preKey,key, and postKey components of our plug-in's regex.
*
* @arg { Object }
*/
regexPieces = {
preKey: "((",
postKey: "[ ]*)((?:(\\d+).){1}(?:(\\d+).){1}(?:(\\d+)){1}))+(?:(-[\\+\\!\\~a-zA-Z0-9]+))?"
};
/**
* Class constructor
*/
constructor() { }
/**
* Method which operates on a file stream
*
* @param { File|Stream|Buffer } file - A file stream, buffer, or string for which we will operate upon the contents of
* @param { Object } options - An object who's properties are options to this method
*
* @return { Stream } The file stream, with all operations applied
*/
bumpVersion(file, options) {
// If file is null just return with out doing anything:
if (file && file.hasOwnProperty("isNull") && ((typeof file.isNull == "function" && file.isNull()) || file.isNull))
return file;
// If no options are provided, or any acceptable options at least, return with an error:
if (!options || (!options.version && !options.type))
return -3; // -3 indicates that options were wrong or missing
// See if a custom key has been provided:
if (options.key)
this.regex = RegExp(this.regexPieces.preKey + options.key + this.regexPieces.postKey, 'gm');
// Otherwise let's figure out what we're doing here:
if (typeof options.version === 'string') {
// We're manually setting the version string:
this.replacementOperation = this.operations.version;
// Store the version string we're replacing with:
this.replacementSemver = options.version;
}
else {
// If options.type is set to a string, we're bumping a component of the semver:
if (!options.version && options.type && typeof options.type === 'string') {
switch (options.type) {
case 'patch':
this.replacementOperation = this.operations.patch;
break;
case 'minor':
this.replacementOperation = this.operations.minor;
break;
case 'major':
this.replacementOperation = this.operations.major;
break;
case 'prerelease':
this.replacementOperation = this.operations.prerelease;
break;
}
} // Otherwise version may also be set, but to a negative number? Testing?
// If options.version is equal to -1, we're just testing our RegExp:
if (options.version === -1) { // Don't forget that match returns an array, so return the first element!
const matched = String(file.contents).match(this.regex);
if (matched && matched !== null && matched[0])
file.contents = Buffer.from(String(matched[0]));
return file;
}
}
// If file is a stream, leverage replacestream:
if (file && file.hasOwnProperty("isStream") && ((typeof file.isStream == "function" && file.isStream()) || file.isStream))
file.contents = file.contents.pipe(rs(this.regex, this.replaceVersion.bind(this)));
// If file is a buffer, return a new buffer:
if (file && file.hasOwnProperty("isBuffer") && ((typeof file.isBuffer == "function" && file.isBuffer()) || file.isBuffer))
file.contents = Buffer.from(String(file.contents.toString()).replace(this.regex, this.replaceVersion.bind(this)));
if (file && typeof file == "string" && file !== "")
file = file.replace(this.regex, this.replaceVersion.bind(this));
// Return the file
return file;
}
/**
* Method which modifies a JSDoc-based version tag and its accompanying value based upon values supplied to the calling parent.
*
* @param { string } fullMatch - The full match from the regex
* @param { string } group1 - The first nested capture group, includes "@version:" through to the end of the third semver component
* @param { string } group2 - The second nested capture group, includes "@version" through to the semver, but not including any semver components
* @param { string } group3 - The third nested capture group, includes all semver components save prerelease (major, minor, patch)
* @param { string } group4 - The first of 4 capture groups nested 3 levels in, captures the major semver component
* @param { string } group5 - The second of 4 capture groups nested 3 levels in, captures the minor semver component
* @param { string } group6 - The third of 4 capture groups nested 3 levels in, captures the patch semver component
* @param { string } group7 - The fourth of 4 capture groups nested 3 levels in, captures the prerelease semver component (if exists)
*/
replaceVersion(fullMatch, group1, group2, group3, group4, group5, group6, group7) {
// Define a variable with which we can build a return string.
//
// [NOTE] - We'll statically append group2 since its just @version plug the spaces leading
// up to the semver. We need to keep formatting as is, and this is how its done.
let result = group2;
switch (this.replacementOperation) {
case this.operations.patch: // Patch version component
result += group4 + '.' + group5 + '.' + ((parseInt(group6)) + 1);
break;
case this.operations.minor: // Minor version component
result += group4 + '.' + ((parseInt(group5)) + 1) + '.0';
break;
case this.operations.major: // Major version component
result += ((parseInt(group4)) + 1) + '.0.0';
break;
case this.operations.version: // Whole semver string
result += this.replacementSemver; // Simply tac the replacement semver (provided by user) to the result string.
break;
case this.operations.prerelease: // Prerelease version component
result += group4 + '.' + group5 + '.' + group6 + ((group7) ? ((parseInt(group7)) + 1) : '-1'); // A prerelease component may not exist, let's account for that:
break;
}
return result;
}
}
//# sourceMappingURL=version-bump.mjs.map