Skip to content

Commit

Permalink
feat(version): bump prerelease versions from conventional commits (#409)
Browse files Browse the repository at this point in the history
  • Loading branch information
ghiscoding committed Nov 22, 2022
1 parent 03e8157 commit dad936e
Show file tree
Hide file tree
Showing 16 changed files with 172 additions and 4 deletions.
10 changes: 10 additions & 0 deletions packages/cli/schemas/lerna-schema.json
Expand Up @@ -560,6 +560,9 @@
"conventionalGraduate": {
"$ref": "#/$defs/commandOptions/shared/conventionalGraduate"
},
"conventionalBumpPrerelease": {
"$ref": "#/$defs/commandOptions/version/conventionalBumpPrerelease"
},
"conventionalPrerelease": {
"$ref": "#/$defs/commandOptions/version/conventionalPrerelease"
},
Expand Down Expand Up @@ -889,6 +892,9 @@
"amend": {
"$ref": "#/$defs/commandOptions/version/amend"
},
"conventionalBumpPrerelease": {
"$ref": "#/$defs/commandOptions/version/conventionalBumpPrerelease"
},
"conventionalPrerelease": {
"$ref": "#/$defs/commandOptions/version/conventionalPrerelease"
},
Expand Down Expand Up @@ -1307,6 +1313,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"
}
Empty file.
@@ -0,0 +1,5 @@
{
"name": "package-1",
"repository": "lerna/conventional-commits-independent",
"version": "1.0.0-alpha.0"
}
Empty file.
@@ -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.
@@ -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 @@ -203,6 +204,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

0 comments on commit dad936e

Please sign in to comment.