UNPKG

relion

Version:

Release workflow helper for Node.js projects.

235 lines (212 loc) 6.95 kB
import checkpoint from '../checkpoint.js'; import { Bumper } from 'conventional-recommended-bump'; import fs from 'fs'; import DotGitignore from 'dotgitignore'; import path from 'path'; import runLifecycleScript from '../run-lifecycle-script.js'; import semver from 'semver'; import writeFile from '../write-file.js'; import { resolveUpdaterObjectFromArgument } from '../updaters/index.js'; let configsToUpdate = {}; const sanitizeQuotesRegex = /['"]+/g; async function Bump(args, newVersion) { // reset the cache of updated config files each // time we perform the version bump step. configsToUpdate = {}; if ( args.releaseAs && !(['major', 'minor', 'patch'].includes(args.releaseAs.toLowerCase()) || semver.valid(args.releaseAs)) ) { throw new Error("releaseAs must be one of 'major', 'minor' or 'patch', or a valid semvar version."); } await runLifecycleScript(args, 'prerelease'); const stdout = await runLifecycleScript(args, 'prebump'); if (stdout?.trim().length) { const prebumpString = stdout.trim().replace(sanitizeQuotesRegex, ''); if (semver.valid(prebumpString)) args.releaseAs = prebumpString; } updateConfigs(args, newVersion); await runLifecycleScript(args, 'postbump'); return newVersion; } Bump.getUpdatedConfigs = function () { return configsToUpdate; }; /** * Get the new version number * @param args * @param version * @returns Promise<string> */ export async function getNewVersion(args, version) { let newVersion = version; if (semver.valid(args.releaseAs)) { const releaseAs = new semver.SemVer(args.releaseAs); if ( isString(args.prerelease) && releaseAs.prerelease.length && releaseAs.prerelease.slice(0, -1).join('.') !== args.prerelease ) { // If both releaseAs and the prerelease identifier are supplied, they must match. The behavior // for a mismatch is undefined, so error out instead. throw new Error('releaseAs and prerelease have conflicting prerelease identifiers'); } else if (isString(args.prerelease) && releaseAs.prerelease.length) { newVersion = releaseAs.version; } else if (isString(args.prerelease)) { newVersion = `${releaseAs.major}.${releaseAs.minor}.${releaseAs.patch}-${args.prerelease}.0`; } else { newVersion = releaseAs.version; } // Check if the previous version is the same version and prerelease, and increment if so if ( isString(args.prerelease) && ['prerelease', null].includes(semver.diff(version, newVersion)) && semver.lte(newVersion, version) ) { newVersion = semver.inc(version, 'prerelease', args.prerelease); } // Append any build info from releaseAs newVersion = semvarToVersionStr(newVersion, releaseAs.build); } else { const release = await bumpVersion(args.releaseAs, version, args); const releaseType = getReleaseType(args.prerelease, release.releaseType, version); newVersion = semver.inc(version, releaseType, args.prerelease); } return newVersion; } /** * Convert a semver object to a full version string including build metadata * @param {string} semverVersion The semvar version string * @param {string[]} semverBuild An array of the build metadata elements, to be joined with '.' * @returns {string} */ function semvarToVersionStr(semverVersion, semverBuild) { return [semverVersion, semverBuild.join('.')].filter(Boolean).join('+'); } function getReleaseType(prerelease, expectedReleaseType, currentVersion) { if (isString(prerelease)) { if (isInPrerelease(currentVersion)) { if ( shouldContinuePrerelease(currentVersion, expectedReleaseType) || getTypePriority(getCurrentActiveType(currentVersion)) > getTypePriority(expectedReleaseType) ) { return 'prerelease'; } } return 'pre' + expectedReleaseType; } else { return expectedReleaseType; } } function isString(val) { return typeof val === 'string'; } /** * if a version is currently in pre-release state, * and if it current in-pre-release type is same as expect type, * it should continue the pre-release with the same type * * @param version * @param expectType * @return {boolean} */ function shouldContinuePrerelease(version, expectType) { return getCurrentActiveType(version) === expectType; } function isInPrerelease(version) { return Array.isArray(semver.prerelease(version)); } const TypeList = ['major', 'minor', 'patch'].reverse(); /** * extract the in-pre-release type in target version * * @param version * @return {string} */ function getCurrentActiveType(version) { const typelist = TypeList; for (let i = 0; i < typelist.length; i++) { if (semver[typelist[i]](version)) { return typelist[i]; } } } /** * calculate the priority of release type, * major - 2, minor - 1, patch - 0 * * @param type * @return {number} */ function getTypePriority(type) { return TypeList.indexOf(type); } async function bumpVersion(releaseAs, currentVersion, args) { if (releaseAs) { return { releaseType: releaseAs, }; } else { const recommendation = await getRecommendedBump(args, currentVersion); if (recommendation) { return recommendation; } else { throw new Error('No recommendation found'); } } } async function getRecommendedBump(args, currentVersion) { const bumper = new Bumper(args.path); bumper.loadPreset({ preMajor: semver.lt(currentVersion, '1.0.0'), ...args.preset, }); bumper.tag({ tagPrefix: args.tagPrefix, lernaPackage: args.lernaPackage, }); bumper.commits({}, args.parserOpts); const recommendation = await bumper.bump(); return recommendation; } /** * attempt to update the version number in provided `bumpFiles` * @param args config object * @param newVersion version number to update to. * @return void */ function updateConfigs(args, newVersion) { const dotgit = DotGitignore(); args.bumpFiles.forEach(async function (bumpFile) { const updater = await resolveUpdaterObjectFromArgument(bumpFile); if (!updater) { return; } const configPath = path.resolve(process.cwd(), updater.filename); try { if (dotgit.ignore(updater.filename)) { console.debug(`Not updating file '${updater.filename}', as it is ignored in Git`); return; } const stat = fs.lstatSync(configPath); if (!stat.isFile()) { console.debug(`Not updating '${updater.filename}', as it is not a file`); return; } const contents = fs.readFileSync(configPath, 'utf8'); const newContents = updater.updater.writeVersion(contents, newVersion); const realNewVersion = updater.updater.readVersion(newContents); checkpoint(args, 'bumping version in ' + updater.filename + ' from %s to %s', [ updater.updater.readVersion(contents), realNewVersion, ]); writeFile(args, configPath, newContents); // flag any config files that we modify the version # for // as having been updated. configsToUpdate[updater.filename] = true; } catch (err) { if (err.code !== 'ENOENT') console.warn(err.message); } }); } export default Bump;