diff --git a/commands/version/README.md b/commands/version/README.md index ce303e30e47..44712c1f396 100644 --- a/commands/version/README.md +++ b/commands/version/README.md @@ -55,6 +55,7 @@ Running `lerna version --conventional-commits` without the above flags will rele - [`--conventional-commits`](#--conventional-commits) - [`--conventional-graduate`](#--conventional-graduate) - [`--conventional-prerelease`](#--conventional-prerelease) + - [`--conventional-bumpprerelease`](#--conventional-bumpprerelease) - [`--create-release `](#--create-release-type) - [`--exact`](#--exact) - [`--force-publish`](#--force-publish) @@ -201,6 +202,21 @@ lerna version --conventional-commits --conventional-prerelease When run with this flag, `lerna version` will release with prerelease versions the specified packages (comma-separated) or all packages using `*`. Releases all unreleased changes as pre(patch/minor/major/release) by prefixing the version recommendation from `conventional-commits` with `pre`, eg. if present changes include a feature commit, the recommended bump will be `minor`, so this flag will result in a `preminor` release. If changes are present for packages that are not specified (if specifying packages), or for packages that are already in prerelease, those packages will be versioned as they normally would using `--conventional-commits`. +### `--conventional-bumpprerelease` + +```sh +lerna version --conventional-commits --conventional-prerelease --conventional-bumpprerelease +``` + +When run with this flag, `lerna version` will release with bumped prerelease versions even if already released packages are prereleases. Releases all unreleased changes as pre(patch/minor/major/release) by prefixing the version recommendation from `conventional-commits` with `pre`, eg. if present changes include a feature commit, the recommended bump will be `minor`, so this flag will result in a `preminor` release. If not used just a prerelease bump will be applied to prereleased packages. + +```sh +Changes: + - major: 1.0.0-alpha.0 => 2.0.0-alpha.0 + - minor: 1.0.0-alpha.0 => 1.1.0-alpha.0 + - patch: 1.0.0-alpha.0 => 1.0.1-alpha.0 +``` + ### `--create-release ` ```sh diff --git a/commands/version/__tests__/version-conventional-commits.test.js b/commands/version/__tests__/version-conventional-commits.test.js index 93a886df6f8..8f76cb01848 100644 --- a/commands/version/__tests__/version-conventional-commits.test.js +++ b/commands/version/__tests__/version-conventional-commits.test.js @@ -91,6 +91,29 @@ describe("--conventional-commits", () => { }); }); + it("should call recommended version with conventionalBumpprerelease set", async () => { + prereleaseVersionBumps.forEach((bump) => recommendVersion.mockResolvedValueOnce(bump)); + const cwd = await initFixture("prerelease-independent"); + + await lernaVersion(cwd)("--conventional-commits", "--conventional-prerelease","--conventional-bumpprerelease"); + + prereleaseVersionBumps.forEach((version, name) => { + const prereleaseId = semver.prerelease(version)[0]; + expect(recommendVersion).toHaveBeenCalledWith(expect.objectContaining({ name }), "independent", { + changelogPreset: undefined, + rootPath: cwd, + tagPrefix: "v", + prereleaseId, + conventionalBumpprerelease:true + }); + expect(updateChangelog).toHaveBeenCalledWith( + expect.objectContaining({ name, version }), + "independent", + { changelogPreset: undefined, rootPath: cwd, tagPrefix: "v" } + ); + }); + }); + it("should graduate prerelease version bumps and generate CHANGELOG", async () => { versionBumps.forEach((bump) => recommendVersion.mockResolvedValueOnce(bump)); const cwd = await initFixture("prerelease-independent"); diff --git a/commands/version/command.js b/commands/version/command.js index d82bd24c856..f40225382c6 100644 --- a/commands/version/command.js +++ b/commands/version/command.js @@ -32,6 +32,10 @@ exports.builder = (yargs, composed) => { describe: "Version changed packages as prereleases when using --conventional-commits.", // type must remain ambiguous because it is overloaded (boolean _or_ string _or_ array) }, + "conventional-bumpprerelease": { + describe: "Bumps a prerelease versions if convetional commits require it.", + type:"boolean" + }, "changelog-preset": { describe: "Custom conventional-changelog preset.", type: "string", diff --git a/commands/version/index.js b/commands/version/index.js index e4fbf24c878..14b8203b1d3 100644 --- a/commands/version/index.js +++ b/commands/version/index.js @@ -367,7 +367,7 @@ class VersionCommand extends Command { recommendVersions(resolvePrereleaseId) { const independentVersions = this.project.isIndependent(); - const { changelogPreset, conventionalGraduate } = this.options; + const { changelogPreset, conventionalGraduate,conventionalBumpprerelease } = this.options; const rootPath = this.project.manifest.location; const type = independentVersions ? "independent" : "fixed"; const prereleasePackageNames = this.getPrereleasePackageNames(); @@ -394,6 +394,7 @@ class VersionCommand extends Command { rootPath, tagPrefix: this.tagPrefix, prereleaseId: getPrereleaseId(node), + conventionalBumpprerelease }) ) ); diff --git a/core/conventional-commits/__tests__/__fixtures__/prerelease-independent/lerna.json b/core/conventional-commits/__tests__/__fixtures__/prerelease-independent/lerna.json new file mode 100644 index 00000000000..84e537a34e5 --- /dev/null +++ b/core/conventional-commits/__tests__/__fixtures__/prerelease-independent/lerna.json @@ -0,0 +1,8 @@ +{ + "command": { + "publish": { + "conventionalCommits": true + } + }, + "version": "independent" +} diff --git a/core/conventional-commits/__tests__/__fixtures__/prerelease-independent/package.json b/core/conventional-commits/__tests__/__fixtures__/prerelease-independent/package.json new file mode 100644 index 00000000000..20bd4b1f45e --- /dev/null +++ b/core/conventional-commits/__tests__/__fixtures__/prerelease-independent/package.json @@ -0,0 +1,5 @@ +{ + "name": "conventional-commits-independent", + "repository": "lerna/conventional-commits-independent", + "version": "0.0.0-root" +} diff --git a/core/conventional-commits/__tests__/__fixtures__/prerelease-independent/packages/package-1/CHANGELOG.md b/core/conventional-commits/__tests__/__fixtures__/prerelease-independent/packages/package-1/CHANGELOG.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/core/conventional-commits/__tests__/__fixtures__/prerelease-independent/packages/package-1/package.json b/core/conventional-commits/__tests__/__fixtures__/prerelease-independent/packages/package-1/package.json new file mode 100644 index 00000000000..319b58baf74 --- /dev/null +++ b/core/conventional-commits/__tests__/__fixtures__/prerelease-independent/packages/package-1/package.json @@ -0,0 +1,5 @@ +{ + "name": "package-1", + "repository": "lerna/conventional-commits-independent", + "version": "1.0.0-alpha.0" +} diff --git a/core/conventional-commits/__tests__/__fixtures__/prerelease-independent/packages/package-2/CHANGELOG.md b/core/conventional-commits/__tests__/__fixtures__/prerelease-independent/packages/package-2/CHANGELOG.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/core/conventional-commits/__tests__/__fixtures__/prerelease-independent/packages/package-2/package.json b/core/conventional-commits/__tests__/__fixtures__/prerelease-independent/packages/package-2/package.json new file mode 100644 index 00000000000..ff6ac81d8f1 --- /dev/null +++ b/core/conventional-commits/__tests__/__fixtures__/prerelease-independent/packages/package-2/package.json @@ -0,0 +1,8 @@ +{ + "name": "package-2", + "repository": "lerna/conventional-commits-independent", + "version": "1.0.0-beta.0", + "dependencies": { + "package-1": "^1.0.0" + } +} diff --git a/core/conventional-commits/__tests__/__fixtures__/prerelease-independent/packages/package-3/CHANGELOG.md b/core/conventional-commits/__tests__/__fixtures__/prerelease-independent/packages/package-3/CHANGELOG.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/core/conventional-commits/__tests__/__fixtures__/prerelease-independent/packages/package-3/package.json b/core/conventional-commits/__tests__/__fixtures__/prerelease-independent/packages/package-3/package.json new file mode 100644 index 00000000000..0abe2ab7f81 --- /dev/null +++ b/core/conventional-commits/__tests__/__fixtures__/prerelease-independent/packages/package-3/package.json @@ -0,0 +1,8 @@ +{ + "name": "package-3", + "repository": "lerna/conventional-commits-independent", + "version": "1.0.0-beta.0", + "dependencies": { + "package-1": "^1.0.0" + } +} diff --git a/core/conventional-commits/__tests__/conventional-commits.test.js b/core/conventional-commits/__tests__/conventional-commits.test.js index 51a96c381e2..c41e8afb2b8 100644 --- a/core/conventional-commits/__tests__/conventional-commits.test.js +++ b/core/conventional-commits/__tests__/conventional-commits.test.js @@ -95,6 +95,69 @@ describe("conventional-commits", () => { expect(bump2).toBe("1.1.0-beta.0"); }); + it("returns package-specific version bumps from prereleases with prereleaseId", async () => { + const cwd = await initFixture("prerelease-independent"); + const [pkg1, pkg2, pkg3] = await getPackages(cwd); + const opts = { changelogPreset: "angular" }; + + // make a change in package-1, package-2 and package-3 + await pkg1.set("changed", 1).serialize(); + await pkg2.set("changed", 2).serialize(); + await pkg3.set("changed", 3).serialize(); + + await gitAdd(cwd, pkg1.manifestLocation); + await gitCommit(cwd, "fix: changed 1"); + + await gitAdd(cwd, pkg2.manifestLocation); + await gitCommit(cwd, "feat: changed 2"); + + await gitAdd(cwd, pkg3.manifestLocation); + await gitCommit(cwd, "feat!: changed\n\nBREAKING CHANGE: changed"); + + const [bump1, bump2, bump3] = await Promise.all([ + recommendVersion(pkg1, "independent", Object.assign(opts, { prereleaseId: "alpha",conventionalBumpprerelease:true})), + recommendVersion(pkg2, "independent", Object.assign(opts, { prereleaseId: "beta",conventionalBumpprerelease:true })), + recommendVersion(pkg3, "independent", Object.assign(opts, { prereleaseId: "beta",conventionalBumpprerelease:true })), + ]); + + // all versions should be bumped + expect(bump1).toBe("1.0.1-alpha.0"); + expect(bump2).toBe("1.1.0-beta.0"); + expect(bump3).toBe("2.0.0-beta.0"); + + }); + + it("returns package-specific prerelease bumps from prereleases with prereleaseId", async () => { + const cwd = await initFixture("prerelease-independent"); + const [pkg1, pkg2, pkg3] = await getPackages(cwd); + const opts = { changelogPreset: "angular" }; + + // make a change in package-1, package-2 and package-3 + await pkg1.set("changed", 1).serialize(); + await pkg2.set("changed", 2).serialize(); + await pkg3.set("changed", 3).serialize(); + + await gitAdd(cwd, pkg1.manifestLocation); + await gitCommit(cwd, "fix: changed 1"); + + await gitAdd(cwd, pkg2.manifestLocation); + await gitCommit(cwd, "feat: changed 2"); + + await gitAdd(cwd, pkg3.manifestLocation); + await gitCommit(cwd, "feat!: changed\n\nBREAKING CHANGE: changed"); + + const [bump1, bump2, bump3] = await Promise.all([ + recommendVersion(pkg1, "independent", Object.assign(opts, { prereleaseId: "alpha"})), + recommendVersion(pkg2, "independent", Object.assign(opts, { prereleaseId: "beta" })), + recommendVersion(pkg3, "independent", Object.assign(opts, { prereleaseId: "beta"})), + ]); + + // we just have a bump in the prerelease + expect(bump1).toBe("1.0.0-alpha.1"); + expect(bump2).toBe("1.0.0-beta.1"); + expect(bump3).toBe("1.0.0-beta.1"); + }); + it("falls back to patch bumps for non-bumping commit types", async () => { const cwd = await initFixture("independent"); const [pkg1, pkg2] = await getPackages(cwd); diff --git a/core/conventional-commits/lib/recommend-version.js b/core/conventional-commits/lib/recommend-version.js index a982adfc92b..30f908ea8e4 100644 --- a/core/conventional-commits/lib/recommend-version.js +++ b/core/conventional-commits/lib/recommend-version.js @@ -12,7 +12,7 @@ module.exports.recommendVersion = recommendVersion; * @param {import("..").VersioningStrategy} type * @param {import("..").BaseChangelogOptions & { prereleaseId?: string }} commandOptions */ -function recommendVersion(pkg, type, { changelogPreset, rootPath, tagPrefix, prereleaseId }) { +function recommendVersion(pkg, type, { changelogPreset, rootPath, tagPrefix, prereleaseId,conventionalBumpprerelease }) { log.silly(type, "for %s at %s", pkg.name, pkg.location); const options = { @@ -26,8 +26,8 @@ function recommendVersion(pkg, type, { changelogPreset, rootPath, tagPrefix, pre options.tagPrefix = tagPrefix; } - const shouldBumpPrerelease = (releaseType, version) => { - if (!semver.prerelease(version)) { + const shouldBumpPrerelease = (releaseType, version,bumpPrerelease) => { + if (!semver.prerelease(version) || bumpPrerelease) { return true; } switch (releaseType) { @@ -59,7 +59,7 @@ function recommendVersion(pkg, type, { changelogPreset, rootPath, tagPrefix, pre let releaseType = data.releaseType || "patch"; if (prereleaseId) { - const shouldBump = shouldBumpPrerelease(releaseType, pkg.version); + const shouldBump = shouldBumpPrerelease(releaseType, pkg.version,conventionalBumpprerelease); const prereleaseType = shouldBump ? `pre${releaseType}` : "prerelease"; log.verbose(type, "increment %s by %s", pkg.version, prereleaseType); resolve(semver.inc(pkg.version, prereleaseType, prereleaseId));