Skip to content

Commit

Permalink
feat(version): bump prerelease versions from conventional commits (#3362
Browse files Browse the repository at this point in the history
)
  • Loading branch information
amorscher authored Nov 8, 2022
1 parent 0f785e4 commit 2288b3a
Show file tree
Hide file tree
Showing 16 changed files with 218 additions and 3 deletions.
16 changes: 16 additions & 0 deletions commands/version/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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-bump-prerelease`](#--conventional-bump-prerelease)
- [`--create-release <type>`](#--create-release-type)
- [`--exact`](#--exact)
- [`--force-publish`](#--force-publish)
Expand Down Expand Up @@ -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-bump-prerelease`

```sh
lerna version --conventional-commits --conventional-prerelease --conventional-bump-prerelease
```

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 <type>`

```sh
Expand Down
27 changes: 27 additions & 0 deletions commands/version/__tests__/version-conventional-commits.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,33 @@ 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-bump-prerelease"
);

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");
Expand Down
4 changes: 4 additions & 0 deletions commands/version/command.js
Original file line number Diff line number Diff line change
Expand Up @@ -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-bump-prerelease": {
describe: "Bumps prerelease versions if conventional commits requires it.",
type: "boolean",
},
"changelog-preset": {
describe: "Custom conventional-changelog preset.",
type: "string",
Expand Down
3 changes: 2 additions & 1 deletion commands/version/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -394,6 +394,7 @@ class VersionCommand extends Command {
rootPath,
tagPrefix: this.tagPrefix,
prereleaseId: getPrereleaseId(node),
conventionalBumpPrerelease,
})
)
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"command": {
"publish": {
"conventionalCommits": true
}
},
"version": "independent"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "conventional-commits-independent",
"repository": "lerna/conventional-commits-independent",
"version": "0.0.0-root"
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "package-1",
"repository": "lerna/conventional-commits-independent",
"version": "1.0.0-alpha.0"
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "package-2",
"repository": "lerna/conventional-commits-independent",
"version": "1.0.0-beta.0",
"dependencies": {
"package-1": "^1.0.0"
}
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "package-3",
"repository": "lerna/conventional-commits-independent",
"version": "1.0.0-beta.0",
"dependencies": {
"package-1": "^1.0.0"
}
}
74 changes: 74 additions & 0 deletions core/conventional-commits/__tests__/conventional-commits.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,80 @@ 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);
Expand Down
8 changes: 6 additions & 2 deletions core/conventional-commits/lib/recommend-version.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ 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 = {
Expand Down Expand Up @@ -59,7 +63,7 @@ function recommendVersion(pkg, type, { changelogPreset, rootPath, tagPrefix, pre
let releaseType = data.releaseType || "patch";

if (prereleaseId) {
const shouldBump = shouldBumpPrerelease(releaseType, pkg.version);
const shouldBump = conventionalBumpPrerelease || shouldBumpPrerelease(releaseType, pkg.version);
const prereleaseType = shouldBump ? `pre${releaseType}` : "prerelease";
log.verbose(type, "increment %s by %s", pkg.version, prereleaseType);
resolve(semver.inc(pkg.version, prereleaseType, prereleaseId));
Expand Down
10 changes: 10 additions & 0 deletions core/lerna/schemas/lerna-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -988,6 +988,9 @@
"conventionalPrerelease": {
"$ref": "#/$defs/commandOptions/version/conventionalPrerelease"
},
"conventionalBumpPrerelease": {
"$ref": "#/$defs/commandOptions/version/conventionalBumpPrerelease"
},
"changelogPreset": {
"$ref": "#/$defs/commandOptions/version/changelogPreset"
},
Expand Down Expand Up @@ -1290,6 +1293,9 @@
"conventionalPrerelease": {
"$ref": "#/$defs/commandOptions/version/conventionalPrerelease"
},
"conventionalBumpPrerelease": {
"$ref": "#/$defs/commandOptions/version/conventionalBumpPrerelease"
},
"changelogPreset": {
"$ref": "#/$defs/commandOptions/version/changelogPreset"
},
Expand Down Expand Up @@ -1680,6 +1686,10 @@
],
"description": "During `lerna version`, version changed packages as prereleases when using --conventional-commits."
},
"conventionalBumpPrerelease": {
"type": "boolean",
"description": "During `lerna version`, bumps version of changed prereleased packages when using --conventional-commits."
},
"changelogPreset": {
"type": "string",
"description": "For `lerna version`, the custom conventional-changelog preset."
Expand Down
45 changes: 45 additions & 0 deletions e2e/tests/lerna-version/conventional-commits.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,51 @@ describe("lerna-version-conventional-commits", () => {
`);
});

it("should correctly generate and bump prerelease versions when using --conventional-prerelease and --conventional-bump-prerelease", async () => {
await fixture.createInitialGitCommit();

await fixture.lerna("create package-a -y");
await fixture.exec("git add --all");
await fixture.exec("git commit -m 'feat: add package-a'");

await fixture.lerna("create package-b -y");
await fixture.exec("git add --all");
await fixture.exec("git commit -m 'feat: add package-b'");

await fixture.exec("git push origin test-main");

// Initial versioning with two packages created
await fixture.lerna("version --conventional-commits --conventional-prerelease -y", {
silenceError: true,
});

// Update and version just package-a
await fixture.exec("echo update_package_a > packages/package-a/new_file.txt");
await fixture.exec("git add --all");
await fixture.exec("git commit -m 'fix: update package-a'");

// Bump a prerelease version
const output = await fixture.lerna("version --conventional-commits --conventional-bump-prerelease -y", {
silenceError: true,
});

expect(output.combinedOutput).toMatchInlineSnapshot(`
lerna notice cli v999.9.9-e2e.0
lerna info current version 0.1.0-alpha.0
lerna info Looking for changed packages since v0.1.0-alpha.0
lerna info getChangelogConfig Successfully resolved preset "conventional-changelog-angular"
Changes:
- package-a: 0.1.0-alpha.0 => 0.1.1-alpha.0
lerna info auto-confirmed
lerna info execute Skipping releases
lerna info git Pushing tags...
lerna success version finished
`);
});

describe("independent packages", () => {
it("should correctly generate changelog and version information when releasing packages independently", async () => {
await fixture.createInitialGitCommit();
Expand Down

0 comments on commit 2288b3a

Please sign in to comment.