Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(version): bump prerelease versions from conventional commits #409

Merged
merged 1 commit into from Nov 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/cli/schemas/lerna-schema.json
Expand Up @@ -557,6 +557,9 @@
"conventionalGraduate": {
"$ref": "#/$defs/commandOptions/shared/conventionalGraduate"
},
"conventionalBumpPrerelease": {
"$ref": "#/$defs/commandOptions/version/conventionalBumpPrerelease"
},
"conventionalPrerelease": {
"$ref": "#/$defs/commandOptions/version/conventionalPrerelease"
},
Expand Down Expand Up @@ -886,6 +889,9 @@
"amend": {
"$ref": "#/$defs/commandOptions/version/amend"
},
"conventionalBumpPrerelease": {
"$ref": "#/$defs/commandOptions/version/conventionalBumpPrerelease"
},
"conventionalPrerelease": {
"$ref": "#/$defs/commandOptions/version/conventionalPrerelease"
},
Expand Down Expand Up @@ -1300,6 +1306,10 @@
"type": "boolean",
"description": "During `lerna version`, when true, amend the existing commit instead of generating a new one."
},
"conventionalBumpPrerelease": {
"type": "boolean",
"description": "During `lerna version`, bumps version of changed prereleased packages when using --conventional-commits."
},
"conventionalPrerelease": {
"anyOf": [{ "type": "string" }, { "type": "boolean" }, { "type": "array", "items": { "type": "string" } }],
"description": "During `lerna version`, version changed packages as prereleases when using --conventional-commits."
Expand Down
4 changes: 4 additions & 0 deletions packages/cli/src/cli-commands/cli-version-commands.ts
Expand Up @@ -44,6 +44,10 @@ export default {
describe: 'Amend the existing commit, instead of generating a new one.',
type: 'boolean',
},
'conventional-bump-prerelease': {
describe: 'Bumps prerelease versions if conventional commits requires it.',
type: 'boolean',
},
'conventional-commits': {
describe: 'Use conventional-changelog to determine version bump and generate CHANGELOG.',
type: 'boolean',
Expand Down
@@ -0,0 +1,8 @@
{
"command": {
"publish": {
"conventionalCommits": true
}
},
"version": "independent"
}
@@ -0,0 +1,5 @@
{
"name": "conventional-commits-independent",
"repository": "lerna/conventional-commits-independent",
"version": "0.0.0-root"
}
@@ -0,0 +1,5 @@
{
"name": "package-1",
"repository": "lerna/conventional-commits-independent",
"version": "1.0.0-alpha.0"
}
@@ -0,0 +1,8 @@
{
"name": "package-2",
"repository": "lerna/conventional-commits-independent",
"version": "1.0.0-beta.0",
"dependencies": {
"package-1": "^1.0.0"
}
}
@@ -0,0 +1,8 @@
{
"name": "package-3",
"repository": "lerna/conventional-commits-independent",
"version": "1.0.0-beta.0",
"dependencies": {
"package-1": "^1.0.0"
}
}
Expand Up @@ -93,6 +93,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 Project.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 Project.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 Project.getPackages(cwd);
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/conventional-commits/recommend-version.ts
Expand Up @@ -16,9 +16,9 @@ import { PackageGraphNode } from '../package-graph';
export async function recommendVersion(
pkg: Package | PackageGraphNode,
type: VersioningStrategy,
recommendationOptions: BaseChangelogOptions & { prereleaseId?: string }
recommendationOptions: BaseChangelogOptions & { prereleaseId?: string; conventionalBumpPrerelease?: boolean }
): Promise<string | null> {
const { changelogPreset, rootPath, tagPrefix, prereleaseId } = recommendationOptions;
const { changelogPreset, rootPath, tagPrefix, prereleaseId, conventionalBumpPrerelease } = recommendationOptions;

log.silly(type, 'for %s at %s', pkg.name, pkg.location);

Expand Down Expand Up @@ -65,7 +65,7 @@ export async function recommendVersion(
let releaseType = data.releaseType || 'patch';

if (prereleaseId) {
const shouldBump = shouldBumpPrerelease(releaseType, pkg.version);
const shouldBump = conventionalBumpPrerelease || shouldBumpPrerelease(releaseType, pkg.version);
const prereleaseType: ReleaseType = shouldBump ? `pre${releaseType}` : 'prerelease';
log.verbose(type, 'increment %s by %s - %s', pkg.version, prereleaseType, pkg.name);
resolve(semver.inc(pkg.version, prereleaseType, prereleaseId));
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/models/command-options.ts
@@ -1,3 +1,4 @@
import { boolean } from 'yargs';
import { RemoteClientType } from './interfaces';

export interface ChangedCommandOption {
Expand Down Expand Up @@ -200,6 +201,9 @@ export interface VersionCommandOption {
/** Version changed packages as prereleases when using `--conventional-commits`. */
conventionalPrerelease?: boolean | string;

/** Bumps prerelease versions if conventional commits requires it. This option is only available when using `--conventional-commits`. */
conventionalBumpPrerelease?: boolean;

/** Add a custom message at the top of all "changelog.md" files. This option is only available when using `--conventional-commits` with changelogs. */
changelogHeaderMessage?: string;

Expand Down
16 changes: 16 additions & 0 deletions packages/version/README.md
Expand Up @@ -77,6 +77,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) (new)
- [`--changelog-include-commits-git-author [msg]`](#--changelog-include-commits-git-author-msg) (new)
- [`--changelog-include-commits-client-login [msg]`](#--changelog-include-commits-client-login-msg) (new)
- [`--changelog-header-message <msg>`](#--changelog-header-message-msg) (new)
Expand Down Expand Up @@ -363,6 +364,21 @@ Add a custom message as a prefix to your new version in your "changelog.md" whic
lerna version --conventional-commits --changelog-version-message "My Great New Version Message"
```

### `--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
Expand Up @@ -119,6 +119,31 @@ describe('--conventional-commits', () => {
});
});

it('should call recommended version with conventionalBumpPrerelease set', async () => {
prereleaseVersionBumps.forEach((bump) => (recommendVersion as jest.Mock).mockResolvedValueOnce(bump));
const cwd = await initFixture('prerelease-independent');

await new VersionCommand(
createArgv(cwd, '--conventional-commits', '--conventional-prerelease', '--conventional-bump-prerelease')
);

prereleaseVersionBumps.forEach((version, name) => {
const prereleaseId = (semver as any).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 as jest.Mock).mockResolvedValueOnce(bump));
const cwd = await initFixture('prerelease-independent');
Expand Down
3 changes: 2 additions & 1 deletion packages/version/src/version-command.ts
Expand Up @@ -520,7 +520,7 @@ export class VersionCommand extends Command<VersionCommandOption> {

async 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 @@ -544,6 +544,7 @@ export class VersionCommand extends Command<VersionCommandOption> {
rootPath,
tagPrefix: this.tagPrefix,
prereleaseId: getPrereleaseId(node),
conventionalBumpPrerelease,
}) as any
);

Expand Down