From 120b10d2422f1668fd6c978d2eb8f5eed6e0474f Mon Sep 17 00:00:00 2001 From: hanseltime Date: Sat, 16 Sep 2023 15:34:50 -0600 Subject: [PATCH] fix: do not use tags for normal prerelease dependency bump --- lib/multiSemanticRelease.js | 2 + lib/updateDeps.js | 50 ++++++++++++++----- test/lib/updateDeps.test.js | 97 ++++++++++++++++++++++++++++++++++--- 3 files changed, 130 insertions(+), 19 deletions(-) diff --git a/lib/multiSemanticRelease.js b/lib/multiSemanticRelease.js index 690da56..ed5cb6d 100644 --- a/lib/multiSemanticRelease.js +++ b/lib/multiSemanticRelease.js @@ -35,6 +35,8 @@ import { createRequire } from "module"; * @param {Package[]} localDeps Array of local dependencies this package relies on. * @param {context|void} context The semantic-release context for this package's release (filled in once semantic-release runs). * @param {undefined|Result|false} result The result of semantic-release (object with lastRelease, nextRelease, commits, releases), false if this package was skipped (no changes or similar), or undefined if the package's release hasn't completed yet. + * @param {Object} _lastRelease The last release object for the package before its current release (set during anaylze-commit) + * @param {Object} _nextRelease The next release object (the release the package is releasing for this cycle) (set during generateNotes) */ /** diff --git a/lib/updateDeps.js b/lib/updateDeps.js index 3628651..f29500c 100644 --- a/lib/updateDeps.js +++ b/lib/updateDeps.js @@ -41,17 +41,41 @@ const getVersionFromTag = (pkg, tag) => { return strMatch && strMatch[0] && semver.valid(strMatch[0]) ? strMatch[0] : null; }; +/** + * Options for NextPreVersion + * + * @typedef {Object} NextPreVersionOptions + * @param {Array|undefined} tags - will use the tags as a reference, overrides useGitTags + * @param {boolean|undefined} useGitTags - if true, will look up all git tags for this prerelease + */ + /** * Resolve next package version on prereleases. * + * Will resolve highest next version of either: + * + * 1. The last release for the package during this multi-release cycle + * 2. (if tag options provided): + * a. the highest increment of the tags array provided + * b. the highest increment of the gitTags for the prerelease + * * @param {Package} pkg Package object. - * @param {Array} tags Override list of tags from specific pkg and branch. + * @param {NextPreVersionOptions|undefined} options additional options * @returns {string|undefined} Next pkg version. * @internal */ -const getNextPreVersion = (pkg, tags) => { +const getNextPreVersion = (pkg, options) => { const tagFilters = [pkg._preRelease]; - const lastVersion = pkg._lastRelease && pkg._lastRelease.version; + // Note: this is only set is a current multi-semantic-release released + const lastVersionForCurrentRelease = pkg._lastRelease && pkg._lastRelease.version; + + // Ensure options don't have a conflict + const normalizedOptions = options || {}; + if (normalizedOptions.tags && normalizedOptions.useGitTags) { + throw new Error("You can only separately provide a set of tags or specify useGitTags!"); + } + let { tags = [] } = normalizedOptions; + const { useGitTags } = normalizedOptions; // Extract tags: // 1. Set filter to extract only package tags @@ -59,7 +83,7 @@ const getNextPreVersion = (pkg, tags) => { // 3. Resolve the versions from the tags // TODO: replace {cwd: '.'} with multiContext.cwd if (pkg.name) tagFilters.push(pkg.name); - if (!tags) { + if (useGitTags) { try { tags = getTags(pkg._branch, { cwd: process.cwd() }, tagFilters); } catch (e) { @@ -69,15 +93,15 @@ const getNextPreVersion = (pkg, tags) => { } } - const lastPreRelTag = getPreReleaseTag(lastVersion); + const lastPreRelTag = getPreReleaseTag(lastVersionForCurrentRelease); const isNewPreRelTag = lastPreRelTag && lastPreRelTag !== pkg._preRelease; const versionToSet = - isNewPreRelTag || !lastVersion + isNewPreRelTag || !lastVersionForCurrentRelease ? `1.0.0-${pkg._preRelease}.1` : _nextPreVersionCases( tags.map((tag) => getVersionFromTag(pkg, tag)).filter((tag) => tag), - lastVersion, + lastVersionForCurrentRelease, pkg._nextType, pkg._preRelease ); @@ -101,23 +125,23 @@ const getPreReleaseTag = (version) => { /** * Resolve next prerelease special cases: highest version from tags or major/minor/patch. * - * @param {Array} tags List of all released tags from package. - * @param {string} lastVersion Last package version released. + * @param {Array} tags - if non-empty, we will use these tags as part fo the comparison + * @param {string} lastVersionForCurrentMultiRelease Last package version released from multi-semantic-release * @param {string} pkgNextType Next type evaluated for the next package type. * @param {string} pkgPreRelease Package prerelease suffix. * @returns {string|undefined} Next pkg version. * @internal */ -const _nextPreVersionCases = (tags, lastVersion, pkgNextType, pkgPreRelease) => { +const _nextPreVersionCases = (tags, lastVersionForCurrentMultiRelease, pkgNextType, pkgPreRelease) => { // Case 1: Normal release on last version and is now converted to a prerelease - if (!semver.prerelease(lastVersion)) { - const { major, minor, patch } = semver.parse(lastVersion); + if (!semver.prerelease(lastVersionForCurrentMultiRelease)) { + const { major, minor, patch } = semver.parse(lastVersionForCurrentMultiRelease); return `${semver.inc(`${major}.${minor}.${patch}`, pkgNextType || "patch")}-${pkgPreRelease}.1`; } // Case 2: Validates version with tags const latestTag = getLatestVersion(tags, { withPrerelease: true }); - return _nextPreHighestVersion(latestTag, lastVersion, pkgPreRelease); + return _nextPreHighestVersion(latestTag, lastVersionForCurrentMultiRelease, pkgPreRelease); }; /** diff --git a/test/lib/updateDeps.test.js b/test/lib/updateDeps.test.js index fa7e8c8..8ad5579 100644 --- a/test/lib/updateDeps.test.js +++ b/test/lib/updateDeps.test.js @@ -1,11 +1,26 @@ -import { - resolveReleaseType, +import { beforeAll, beforeEach, jest } from "@jest/globals"; +jest.unstable_mockModule("../../lib/git.js", () => ({ + getTags: jest.fn(), +})); +let resolveReleaseType, resolveNextVersion, getNextVersion, getNextPreVersion, getPreReleaseTag, getVersionFromTag, -} from "../../lib/updateDeps.js"; + getTags; + +beforeAll(async () => { + ({ getTags } = await import("../../lib/git.js")); + ({ + resolveReleaseType, + resolveNextVersion, + getNextVersion, + getNextPreVersion, + getPreReleaseTag, + getVersionFromTag, + } = await import("../../lib/updateDeps.js")); +}); describe("resolveNextVersion()", () => { // prettier-ignore @@ -201,6 +216,9 @@ describe("getNextVersion()", () => { }); describe("getNextPreVersion()", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); // prettier-ignore const cases = [ [undefined, "patch", "rc", [], "1.0.0-rc.1"], @@ -226,10 +244,77 @@ describe("getNextPreVersion()", () => { _nextType: releaseType, _lastRelease: {version: lastVersion}, _preRelease: preRelease, - _branch: "master", - name: "testing-package" + _branch: "master", + name: "testing-package" + }, + { + tags: lastTags, + } + )).toBe(nextVersion); + }); + it(`${lastVersion} and ${releaseType} ${ + lastTags.length ? "with looked up branch tags " : "" + }gives ${nextVersion}`, () => { + getTags.mockImplementation(() => { + return lastTags; + }); + // prettier-ignore + expect(getNextPreVersion( + { + _nextType: releaseType, + _lastRelease: {version: lastVersion}, + _preRelease: preRelease, + _branch: "master", + name: "testing-package" + }, + { + useGitTags: true, + } + )).toBe(nextVersion); + expect(getTags).toHaveBeenCalledTimes(1); + }); + }); + it("does not allow tags and useGitTags", () => { + expect(() => + getNextPreVersion( + { + _nextType: "patch", + _lastRelease: { version: "1.0.0" }, + _preRelease: "dev", + _branch: "master", + name: "testing-package", + }, + { + useGitTags: true, + tags: [], + } + ) + ).toThrowError("You can only separately provide a set of tags or specify useGitTags!"); + }); + // Simulates us not using tags as criteria + + const noTagCases = [ + // prerelease channels just bump up the pre-release + ["1.0.0-rc.0", "minor", "rc", "1.0.0-rc.1"], + ["1.0.0-dev.0", "major", "dev", "1.0.0-dev.1"], + ["1.0.0-dev.0", "major", "dev", "1.0.0-dev.1"], + ["1.0.1-dev.0", "major", "dev", "1.0.1-dev.1"], + // main channels obey the release type + ["11.0.0", "major", "beta", "12.0.0-beta.1"], + ["1.0.0", "minor", "beta", "1.1.0-beta.1"], + ["1.0.0", "patch", "beta", "1.0.1-beta.1"], + ]; + noTagCases.forEach(([lastVersion, releaseType, preRelease, nextVersion]) => { + it(`${lastVersion} and ${releaseType} for channel ${preRelease} gives ${nextVersion}`, () => { + // prettier-ignore + expect(getNextPreVersion( + { + _nextType: releaseType, + _lastRelease: {version: lastVersion}, + _preRelease: preRelease, + _branch: "master", + name: "testing-package" }, - lastTags )).toBe(nextVersion); }); });