|
| 1 | +const { writeFileSync } = require("fs"); |
| 2 | +const recognizeFormat = require("./recognizeFormat"); |
| 3 | +const semver = require("semver"); |
| 4 | + |
| 5 | +/** |
| 6 | + * Resolve next package version. |
| 7 | + * |
| 8 | + * @param {Package} pkg Package object. |
| 9 | + * @returns {string|undefined} Next pkg version. |
| 10 | + * @internal |
| 11 | + */ |
| 12 | +const getNextVersion = (pkg) => { |
| 13 | + const lastVersion = pkg._lastRelease && pkg._lastRelease.version; |
| 14 | + |
| 15 | + return lastVersion && typeof pkg._nextType === "string" ? semver.inc(lastVersion, pkg._nextType) : "1.0.0"; |
| 16 | +}; |
| 17 | + |
| 18 | +/** |
| 19 | + * Resolve package release type taking into account the cascading dependency update. |
| 20 | + * |
| 21 | + * @param {Package} pkg Package object. |
| 22 | + * @param {string|undefined} bumpStrategy Dependency resolution strategy: override, satisfy, inherit. |
| 23 | + * @param {string|undefined} releaseStrategy Release type triggered by deps updating: patch, minor, major, inherit. |
| 24 | + * @param {Package[]} ignore=[] Packages to ignore (to prevent infinite loops). |
| 25 | + * @returns {string|undefined} Resolved release type. |
| 26 | + * @internal |
| 27 | + */ |
| 28 | +const resolveReleaseType = (pkg, bumpStrategy = "override", releaseStrategy = "patch", ignore = []) => { |
| 29 | + // NOTE This fn also updates pkg deps, so it must be invoked anyway. |
| 30 | + const dependantReleaseType = getDependantRelease(pkg, bumpStrategy, releaseStrategy, ignore); |
| 31 | + |
| 32 | + // Release type found by commitAnalyzer. |
| 33 | + if (pkg._nextType) { |
| 34 | + return pkg._nextType; |
| 35 | + } |
| 36 | + |
| 37 | + if (!dependantReleaseType) { |
| 38 | + return undefined; |
| 39 | + } |
| 40 | + |
| 41 | + pkg._nextType = releaseStrategy === "inherit" ? dependantReleaseType : releaseStrategy; |
| 42 | + |
| 43 | + return pkg._nextType; |
| 44 | +}; |
| 45 | + |
| 46 | +/** |
| 47 | + * Get dependant release type by recursive scanning and updating its deps. |
| 48 | + * |
| 49 | + * @param {Package} pkg The package with local deps to check. |
| 50 | + * @param {string} bumpStrategy Dependency resolution strategy: override, satisfy, inherit. |
| 51 | + * @param {string} releaseStrategy Release type triggered by deps updating: patch, minor, major, inherit. |
| 52 | + * @param {Package[]} ignore Packages to ignore (to prevent infinite loops). |
| 53 | + * @returns {string|undefined} Returns the highest release type if found, undefined otherwise |
| 54 | + * @internal |
| 55 | + */ |
| 56 | +const getDependantRelease = (pkg, bumpStrategy, releaseStrategy, ignore) => { |
| 57 | + const severityOrder = ["patch", "minor", "major"]; |
| 58 | + const { _localDeps, manifest = {} } = pkg; |
| 59 | + const { dependencies = {}, devDependencies = {}, peerDependencies = {}, optionalDependencies = {} } = manifest; |
| 60 | + const scopes = [dependencies, devDependencies, peerDependencies, optionalDependencies]; |
| 61 | + const bumpDependency = (scope, name, nextVersion) => { |
| 62 | + const currentVersion = scope[name]; |
| 63 | + if (!nextVersion || !currentVersion) { |
| 64 | + return; |
| 65 | + } |
| 66 | + const resolvedVersion = resolveNextVersion(currentVersion, nextVersion, releaseStrategy); |
| 67 | + |
| 68 | + if (currentVersion !== resolvedVersion) { |
| 69 | + scope[name] = resolvedVersion; |
| 70 | + |
| 71 | + return true; |
| 72 | + } |
| 73 | + }; |
| 74 | + |
| 75 | + // prettier-ignore |
| 76 | + return _localDeps |
| 77 | + .filter((p) => ignore.indexOf(p) === -1) |
| 78 | + .reduce((releaseType, p) => { |
| 79 | + const name = p.name; |
| 80 | + |
| 81 | + // Has changed if... |
| 82 | + // 1. Any local dep package itself has changed |
| 83 | + // 2. Any local dep package has local deps that have changed. |
| 84 | + const nextType = resolveReleaseType(p, bumpStrategy, releaseStrategy,[...ignore, ..._localDeps]); |
| 85 | + const nextVersion = getNextVersion(p); |
| 86 | + const lastVersion = pkg._lastRelease && pkg._lastRelease.version; |
| 87 | + |
| 88 | + // 3. And this change should correspond to manifest updating rule. |
| 89 | + const requireRelease = [ |
| 90 | + ...scopes.map((scope) => bumpDependency(scope, name, nextVersion)), |
| 91 | + ].some(v => v) || !lastVersion; |
| 92 | + |
| 93 | + return requireRelease && (severityOrder.indexOf(nextType) > severityOrder.indexOf(releaseType)) |
| 94 | + ? nextType |
| 95 | + : releaseType; |
| 96 | + }, undefined); |
| 97 | +}; |
| 98 | + |
| 99 | +/** |
| 100 | + * Resolve next version of dependency. |
| 101 | + * |
| 102 | + * @param {string} currentVersion Current dep version |
| 103 | + * @param {string} nextVersion Next release type: patch, minor, major |
| 104 | + * @param {string|undefined} strategy Resolution strategy: inherit, override, satisfy |
| 105 | + * @returns {string} Next dependency version |
| 106 | + * @internal |
| 107 | + */ |
| 108 | +const resolveNextVersion = (currentVersion, nextVersion, strategy = "override") => { |
| 109 | + if (strategy === "satisfy" && semver.satisfies(nextVersion, currentVersion)) { |
| 110 | + return currentVersion; |
| 111 | + } |
| 112 | + |
| 113 | + if (strategy === "inherit") { |
| 114 | + const sep = "."; |
| 115 | + const nextChunks = nextVersion.split(sep); |
| 116 | + const currentChunks = currentVersion.split(sep); |
| 117 | + // prettier-ignore |
| 118 | + const resolvedChunks = currentChunks.map((chunk, i) => |
| 119 | + nextChunks[i] |
| 120 | + ? chunk.replace(/\d+/, nextChunks[i]) |
| 121 | + : chunk |
| 122 | + ); |
| 123 | + |
| 124 | + return resolvedChunks.join(sep); |
| 125 | + } |
| 126 | + |
| 127 | + // By default next package version would be set as is for the all dependants |
| 128 | + return nextVersion; |
| 129 | +}; |
| 130 | + |
| 131 | +/** |
| 132 | + * Update pkg deps. |
| 133 | + * |
| 134 | + * @param {Package} pkg The package this function is being called on. |
| 135 | + * @param {string} strategy Dependency version updating rule |
| 136 | + * @returns {undefined} |
| 137 | + * @internal |
| 138 | + */ |
| 139 | +const updateManifestDeps = (pkg, strategy) => { |
| 140 | + const { manifest, path } = pkg; |
| 141 | + const { indent, trailingWhitespace } = recognizeFormat(manifest.__contents__); |
| 142 | + |
| 143 | + // Loop through localDeps to verify release consistency. |
| 144 | + pkg._localDeps.forEach((d) => { |
| 145 | + // Get version of dependency. |
| 146 | + const release = d._nextRelease || d._lastRelease; |
| 147 | + |
| 148 | + // Cannot establish version. |
| 149 | + if (!release || !release.version) |
| 150 | + throw Error(`Cannot release because dependency ${d.name} has not been released`); |
| 151 | + }); |
| 152 | + |
| 153 | + // Write package.json back out. |
| 154 | + writeFileSync(path, JSON.stringify(manifest, null, indent) + trailingWhitespace); |
| 155 | +}; |
| 156 | + |
| 157 | +module.exports = { |
| 158 | + getNextVersion, |
| 159 | + updateManifestDeps, |
| 160 | + resolveReleaseType, |
| 161 | + resolveNextVersion, |
| 162 | +}; |
0 commit comments