diff --git a/README.md b/README.md index 12a8ec6..d0e1ec7 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ jobs: | `files` | NO | Multiline list of files (paths) to upload as a [GitHub Release asset](./docs/asset-management.md) | | `versioning` | NO | [Versioning strategy](#versioning-strategies) to apply. MUST be one of `semver` or `calver`. Default: `semver` | | `release-notes` | NO | Path towards a file containing the release notes to include in the GitHub release (Markdown format recommended) | +| `increment-type` | NO | Enforce a specific increment type, please refer to the [Versioning Strategies](./docs/versioning-strategies.md) for more details | ## Outputs diff --git a/action.yml b/action.yml index 7df82f2..d8a2857 100644 --- a/action.yml +++ b/action.yml @@ -41,6 +41,10 @@ inputs: description: 'External Release Notes file to include in the GitHub Release' required: false + increment-type: + description: 'Increment type to apply to the version' + required: false + outputs: created: description: 'Boolean indicating if a release was created' diff --git a/lib/get/index.js b/lib/get/index.js index be9a4be..e5de758 100644 --- a/lib/get/index.js +++ b/lib/get/index.js @@ -12038,7 +12038,7 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.getVersionScheme = exports.compareVersions = exports.incrementVersion = exports.CalVerScheme = exports.SemVerScheme = exports.VersionScheme = void 0; +exports.getIncrementType = exports.getVersionScheme = exports.compareVersions = exports.incrementVersion = exports.CalVerScheme = exports.SemVerScheme = exports.VersionScheme = void 0; const commit_it_1 = __nccwpck_require__(9403); const version_it_1 = __nccwpck_require__(4209); const core = __importStar(__nccwpck_require__(2186)); @@ -12185,24 +12185,70 @@ exports.CalVerScheme = CalVerScheme; * @returns Incremented version */ function incrementVersion(version, incrementType) { + if (!Array.isArray(incrementType)) { + incrementType = [incrementType]; + } if (version instanceof version_it_1.CalVer) { - const newVersion = version.increment(incrementType); - switch (incrementType) { - case "CALENDAR": - if (newVersion.major === version.major && newVersion.minor === version.minor) { - return version.increment("MICRO"); - } - break; - case "MODIFIER": - if (version.modifier === undefined) { - newVersion.modifier = "hotfix.1"; - } + // eslint-disable-next-line no-inner-declarations + function incrementCalVer(version, incrementType, keepModifier) { + const newVersion = version.increment(incrementType); + switch (incrementType) { + case "CALENDAR": + if (newVersion.major === version.major && newVersion.minor === version.minor) { + const newVersion = version.increment("MICRO"); + if (keepModifier) { + newVersion.modifier = version.modifier; + } + return newVersion; + } + break; + case "MODIFIER": + if (version.modifier === undefined) { + newVersion.modifier = "hotfix.1"; + } + break; + } + return newVersion; + } + let newVersion = new version_it_1.CalVer(version.format, version); + let previousIncrement = ""; + for (const increment of incrementType) { + newVersion = incrementCalVer(newVersion, increment, previousIncrement === "MODIFIER"); + previousIncrement = increment; + // TODO: @dev-build-deploy/version-it does not correctly compare CalVer versions with MODIFIERS. + if (newVersion.isGreaterThan(version)) { break; + } } return newVersion; } else if (version instanceof version_it_1.SemVer) { - return version.increment(incrementType); + // eslint-disable-next-line no-inner-declarations + function incrementSemVer(version, incrementType, keepMetadata) { + var _a; + if (incrementType === "PRERELEASE") { + // Apply the pre-release modifier based on the current branch (release: rc.#, default: dev.#) + const prereleaseModifier = branching.getBranch().type === "release" ? "rc." : "dev."; + if (!((_a = version.preRelease) !== null && _a !== void 0 ? _a : "").startsWith(prereleaseModifier)) { + version.preRelease = prereleaseModifier + "0"; + } + } + const newVersion = version.increment(incrementType); + if (keepMetadata) { + newVersion.preRelease = version.preRelease; + } + return newVersion; + } + let newVersion = new version_it_1.SemVer(version); + let previousIncrement = ""; + for (const increment of incrementType) { + newVersion = incrementSemVer(newVersion, increment, ["PRERELEASE", "BUILD"].includes(previousIncrement)); + previousIncrement = increment; + if (newVersion.isGreaterThan(version)) { + break; + } + } + return newVersion; } throw new Error("Cannot increment version of unknown type!"); } @@ -12234,10 +12280,39 @@ function getVersionScheme() { case "calver": return new CalVerScheme(); default: - throw new Error("Unsupported versioning scheme!"); + throw new Error(`Unsupported versioning scheme (${core.getInput("versioning")})!`); } } exports.getVersionScheme = getVersionScheme; +/** + * Retrieves the increment type basedon the provided scheme and string + * + * @param scheme Versioning Scheme + * @param increment String representation of the increment type + * @returns Increment type + */ +function getIncrementType(scheme, increment) { + if (scheme instanceof SemVerScheme) { + switch (increment.toUpperCase()) { + case "MAJOR": + case "MINOR": + case "PATCH": + case "PRERELEASE": + case "BUILD": + return increment.toUpperCase(); + } + } + else if (scheme instanceof CalVerScheme) { + switch (increment.toUpperCase()) { + case "CALENDAR": + case "MICRO": + case "MODIFIER": + return increment.toUpperCase(); + } + } + throw new Error(`Unsupported increment type (${increment})!`); +} +exports.getIncrementType = getIncrementType; /***/ }), diff --git a/lib/main/index.js b/lib/main/index.js index 83e1abc..0e64627 100644 --- a/lib/main/index.js +++ b/lib/main/index.js @@ -18521,20 +18521,33 @@ async function run() { latestRef = initialCommit.hash; latestVersion = versionScheme.initialVersion(); } - const delta = await releasing.getChangesSince(latestRef); - core.info(`ℹ️ Changes since: ${delta.length} commits`); - const commits = filterConventionalCommits(delta); - core.info(`ℹ️ Conventional Commits since: ${commits.length} commits`); - const increment = versionScheme.determineIncrementType(commits); - if (increment === undefined) { - core.info("⚠️ No increment required, skipping..."); - core.endGroup(); - return; + const increments = []; + const commits = []; + if (core.getInput("increment-type")) { + increments.push(...core.getInput("increment-type").split("|").map(inc => versioning.getIncrementType(versionScheme, inc))); + } + else { + const delta = await releasing.getChangesSince(latestRef); + core.info(`ℹ️ Changes since: ${delta.length} commits`); + commits.push(...filterConventionalCommits(delta)); + core.info(`ℹ️ Conventional Commits since: ${commits.length} commits`); + const increment = versionScheme.determineIncrementType(commits); + if (increment === undefined) { + core.info("⚠️ No increment required, skipping..."); + core.endGroup(); + return; + } + increments.push(increment); } core.endGroup(); - const newVersion = versioning.incrementVersion(latestVersion, increment); + core.setOutput("previous-version", latestVersion.toString()); + const newVersion = versioning.incrementVersion(latestVersion, increments); + core.setOutput("incremented-version", newVersion.toString()); + if (!core.getBooleanInput("create-release")) { + return; + } core.startGroup(`📦 Creating GitHub Release...`); - const body = core.getInput("release-notes", undefined) + const body = core.getInput("release-notes") ? await changelog.readChangelogFromFile(core.getInput("release-notes")) : await changelog.generateChangelog(versionScheme, commits); const release = await releasing.createRelease(newVersion, body); @@ -19111,7 +19124,7 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.getVersionScheme = exports.compareVersions = exports.incrementVersion = exports.CalVerScheme = exports.SemVerScheme = exports.VersionScheme = void 0; +exports.getIncrementType = exports.getVersionScheme = exports.compareVersions = exports.incrementVersion = exports.CalVerScheme = exports.SemVerScheme = exports.VersionScheme = void 0; const commit_it_1 = __nccwpck_require__(9403); const version_it_1 = __nccwpck_require__(4209); const core = __importStar(__nccwpck_require__(2186)); @@ -19258,24 +19271,70 @@ exports.CalVerScheme = CalVerScheme; * @returns Incremented version */ function incrementVersion(version, incrementType) { + if (!Array.isArray(incrementType)) { + incrementType = [incrementType]; + } if (version instanceof version_it_1.CalVer) { - const newVersion = version.increment(incrementType); - switch (incrementType) { - case "CALENDAR": - if (newVersion.major === version.major && newVersion.minor === version.minor) { - return version.increment("MICRO"); - } - break; - case "MODIFIER": - if (version.modifier === undefined) { - newVersion.modifier = "hotfix.1"; - } + // eslint-disable-next-line no-inner-declarations + function incrementCalVer(version, incrementType, keepModifier) { + const newVersion = version.increment(incrementType); + switch (incrementType) { + case "CALENDAR": + if (newVersion.major === version.major && newVersion.minor === version.minor) { + const newVersion = version.increment("MICRO"); + if (keepModifier) { + newVersion.modifier = version.modifier; + } + return newVersion; + } + break; + case "MODIFIER": + if (version.modifier === undefined) { + newVersion.modifier = "hotfix.1"; + } + break; + } + return newVersion; + } + let newVersion = new version_it_1.CalVer(version.format, version); + let previousIncrement = ""; + for (const increment of incrementType) { + newVersion = incrementCalVer(newVersion, increment, previousIncrement === "MODIFIER"); + previousIncrement = increment; + // TODO: @dev-build-deploy/version-it does not correctly compare CalVer versions with MODIFIERS. + if (newVersion.isGreaterThan(version)) { break; + } } return newVersion; } else if (version instanceof version_it_1.SemVer) { - return version.increment(incrementType); + // eslint-disable-next-line no-inner-declarations + function incrementSemVer(version, incrementType, keepMetadata) { + var _a; + if (incrementType === "PRERELEASE") { + // Apply the pre-release modifier based on the current branch (release: rc.#, default: dev.#) + const prereleaseModifier = branching.getBranch().type === "release" ? "rc." : "dev."; + if (!((_a = version.preRelease) !== null && _a !== void 0 ? _a : "").startsWith(prereleaseModifier)) { + version.preRelease = prereleaseModifier + "0"; + } + } + const newVersion = version.increment(incrementType); + if (keepMetadata) { + newVersion.preRelease = version.preRelease; + } + return newVersion; + } + let newVersion = new version_it_1.SemVer(version); + let previousIncrement = ""; + for (const increment of incrementType) { + newVersion = incrementSemVer(newVersion, increment, ["PRERELEASE", "BUILD"].includes(previousIncrement)); + previousIncrement = increment; + if (newVersion.isGreaterThan(version)) { + break; + } + } + return newVersion; } throw new Error("Cannot increment version of unknown type!"); } @@ -19307,10 +19366,39 @@ function getVersionScheme() { case "calver": return new CalVerScheme(); default: - throw new Error("Unsupported versioning scheme!"); + throw new Error(`Unsupported versioning scheme (${core.getInput("versioning")})!`); } } exports.getVersionScheme = getVersionScheme; +/** + * Retrieves the increment type basedon the provided scheme and string + * + * @param scheme Versioning Scheme + * @param increment String representation of the increment type + * @returns Increment type + */ +function getIncrementType(scheme, increment) { + if (scheme instanceof SemVerScheme) { + switch (increment.toUpperCase()) { + case "MAJOR": + case "MINOR": + case "PATCH": + case "PRERELEASE": + case "BUILD": + return increment.toUpperCase(); + } + } + else if (scheme instanceof CalVerScheme) { + switch (increment.toUpperCase()) { + case "CALENDAR": + case "MICRO": + case "MODIFIER": + return increment.toUpperCase(); + } + } + throw new Error(`Unsupported increment type (${increment})!`); +} +exports.getIncrementType = getIncrementType; /***/ }), diff --git a/src/action.ts b/src/action.ts index 8393234..e8b1313 100644 --- a/src/action.ts +++ b/src/action.ts @@ -60,26 +60,38 @@ export async function run(): Promise { latestVersion = versionScheme.initialVersion(); } - const delta = await releasing.getChangesSince(latestRef); - core.info(`ℹ️ Changes since: ${delta.length} commits`); - - const commits = filterConventionalCommits(delta); - core.info(`ℹ️ Conventional Commits since: ${commits.length} commits`); + const increments: versioning.VersionIncrement[] = []; + const commits: commitLib.IConventionalCommit[] = []; + if (core.getInput("increment-type")) { + increments.push(...core.getInput("increment-type").split("|").map(inc => versioning.getIncrementType(versionScheme, inc))); + } else { + const delta = await releasing.getChangesSince(latestRef); + core.info(`ℹ️ Changes since: ${delta.length} commits`); + + commits.push(...filterConventionalCommits(delta)); + core.info(`ℹ️ Conventional Commits since: ${commits.length} commits`); + + const increment = versionScheme.determineIncrementType(commits) + if (increment === undefined) { + core.info("⚠️ No increment required, skipping..."); + core.endGroup(); + return; + } + increments.push(increment); + } + core.endGroup(); - const increment = versionScheme.determineIncrementType(commits); + core.setOutput("previous-version", latestVersion.toString()); + const newVersion = versioning.incrementVersion(latestVersion, increments); + core.setOutput("incremented-version", newVersion.toString()); - if (increment === undefined) { - core.info("⚠️ No increment required, skipping..."); - core.endGroup(); + if (!core.getBooleanInput("create-release")) { return; } - core.endGroup(); - - const newVersion = versioning.incrementVersion(latestVersion, increment); core.startGroup(`📦 Creating GitHub Release...`); - const body = core.getInput("release-notes", undefined) + const body = core.getInput("release-notes") ? await changelog.readChangelogFromFile(core.getInput("release-notes")) : await changelog.generateChangelog(versionScheme, commits); diff --git a/src/versioning.ts b/src/versioning.ts index 7f72ed2..5e50c68 100644 --- a/src/versioning.ts +++ b/src/versioning.ts @@ -153,25 +153,76 @@ export class CalVerScheme extends VersionScheme { * @param incrementType Type of increment * @returns Incremented version */ -export function incrementVersion(version: Version, incrementType: VersionIncrement): Version { +export function incrementVersion(version: Version, incrementType: VersionIncrement | VersionIncrement[]): Version { + if (!Array.isArray(incrementType)) { + incrementType = [incrementType]; + } + if (version instanceof CalVer) { - const newVersion = version.increment(incrementType as CalVerIncrement); - switch (incrementType) { - case "CALENDAR": - if (newVersion.major === version.major && newVersion.minor === version.minor) { - return version.increment("MICRO"); - } - break; - case "MODIFIER": - if (version.modifier === undefined) { - newVersion.modifier = "hotfix.1"; - } + // eslint-disable-next-line no-inner-declarations + function incrementCalVer(version: CalVer, incrementType: CalVerIncrement, keepModifier: boolean) { + const newVersion = version.increment(incrementType); + switch (incrementType) { + case "CALENDAR": + if (newVersion.major === version.major && newVersion.minor === version.minor) { + const newVersion = version.increment("MICRO"); + if (keepModifier) { + newVersion.modifier = version.modifier; + } + return newVersion; + } + break; + case "MODIFIER": + if (version.modifier === undefined) { + newVersion.modifier = "hotfix.1"; + } + break; + } + + return newVersion; + } + + let newVersion = new CalVer(version.format, version); + let previousIncrement = ""; + for (const increment of incrementType) { + newVersion = incrementCalVer(newVersion, increment as CalVerIncrement, previousIncrement === "MODIFIER"); + previousIncrement = increment; + + // TODO: @dev-build-deploy/version-it does not correctly compare CalVer versions with MODIFIERS. + if (newVersion.isGreaterThan(version)) { break; + } } return newVersion; } else if (version instanceof SemVer) { - return version.increment(incrementType as SemVerIncrement); + // eslint-disable-next-line no-inner-declarations + function incrementSemVer(version: SemVer, incrementType: SemVerIncrement, keepMetadata: boolean) { + if (incrementType === "PRERELEASE") { + // Apply the pre-release modifier based on the current branch (release: rc.#, default: dev.#) + const prereleaseModifier = branching.getBranch().type === "release" ? "rc." : "dev."; + if (!(version.preRelease ?? "").startsWith(prereleaseModifier)) { + version.preRelease = prereleaseModifier + "0"; + } + } + + const newVersion = version.increment(incrementType as SemVerIncrement); + if (keepMetadata) { + newVersion.preRelease = version.preRelease; + } + return newVersion; + } + + let newVersion = new SemVer(version); + let previousIncrement = ""; + for (const increment of incrementType) { + newVersion = incrementSemVer(newVersion, increment as SemVerIncrement, ["PRERELEASE", "BUILD"].includes(previousIncrement)); + previousIncrement = increment as SemVerIncrement; + if (newVersion.isGreaterThan(version)) { + break; + } + } + return newVersion; } throw new Error("Cannot increment version of unknown type!"); @@ -204,6 +255,35 @@ export function getVersionScheme(): VersionScheme { case "calver": return new CalVerScheme(); default: - throw new Error("Unsupported versioning scheme!"); + throw new Error(`Unsupported versioning scheme (${core.getInput("versioning")})!`); + } +} + +/** + * Retrieves the increment type basedon the provided scheme and string + * + * @param scheme Versioning Scheme + * @param increment String representation of the increment type + * @returns Increment type + */ +export function getIncrementType(scheme: VersionScheme, increment: string): SemVerIncrement | CalVerIncrement { + if (scheme instanceof SemVerScheme) { + switch (increment.toUpperCase()) { + case "MAJOR": + case "MINOR": + case "PATCH": + case "PRERELEASE": + case "BUILD": + return increment.toUpperCase() as SemVerIncrement; + } + } else if (scheme instanceof CalVerScheme) { + switch (increment.toUpperCase()) { + case "CALENDAR": + case "MICRO": + case "MODIFIER": + return increment.toUpperCase() as CalVerIncrement; + } } + + throw new Error(`Unsupported increment type (${increment})!`); } diff --git a/test/versioning.test.ts b/test/versioning.test.ts index 5be485d..dffcb30 100644 --- a/test/versioning.test.ts +++ b/test/versioning.test.ts @@ -255,6 +255,50 @@ describe("Determine bump type (release branch)", () => { }); }); +describe("Increment version with fallback", () => { + beforeAll(() => { + jest.useFakeTimers(); + jest.setSystemTime(new Date(2023, 5, 12)); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + test("SemVer", () => { + jest.spyOn(branching, "getBranch").mockImplementation(() => { + return { type: "default" }; + }); + + expect( + versioning.incrementVersion(new versioning.SemVerScheme().createVersion("0.1.0"), ["PRERELEASE", "MINOR"]).toString() + ).toBe("0.2.0-dev.1") + + expect( + versioning.incrementVersion(new versioning.SemVerScheme().createVersion("0.2.0-dev.1"), ["PRERELEASE", "MINOR"]).toString() + ).toBe("0.2.0-dev.2") + + expect( + versioning.incrementVersion(new versioning.SemVerScheme().createVersion("0.1.0"), ["PRERELEASE", "MAJOR"]).toString() + ).toBe("1.0.0-dev.1") + + expect( + versioning.incrementVersion(new versioning.SemVerScheme().createVersion("0.1.0-dev.1"), ["PRERELEASE", "MAJOR"]).toString() + ).toBe("0.1.0-dev.2") + }) + + test("CalVer", () => { + jest.spyOn(branching, "getBranch").mockImplementation(() => { + return { type: "default" }; + }); + + // NOTE: This is a bug in sorting CalVer in @dev-build-deploy/version-it; it should be 2023.06.1-hotfix.1 + expect( + versioning.incrementVersion(new versioning.CalVerScheme().createVersion("2023.06.0"), ["MODIFIER", "CALENDAR"]).toString() + ).toBe("2023.06.0-hotfix.1") + }); +}); + describe("Increment version (CalVer)", () => { beforeAll(() => { jest.useFakeTimers();