Skip to content

Commit

Permalink
feat(version): Add --create-release=[gitlab|github] option (#2073)
Browse files Browse the repository at this point in the history
Deprecates `--github-release`, replacing with `--create-release=github`
  • Loading branch information
lbennett-stacki authored and evocateur committed Jun 9, 2019
1 parent b22345b commit 4974b78
Show file tree
Hide file tree
Showing 20 changed files with 547 additions and 112 deletions.
9 changes: 9 additions & 0 deletions commands/__mocks__/@lerna/gitlab-client.js
@@ -0,0 +1,9 @@
"use strict";

const client = {
repos: {
createRelease: jest.fn(),
},
};

module.exports = () => client;
16 changes: 11 additions & 5 deletions commands/version/README.md
Expand Up @@ -51,7 +51,7 @@ Running `lerna version --conventional-commits` without the above flags will rele
- [`--exact`](#--exact)
- [`--force-publish`](#--force-publish)
- [`--git-remote`](#--git-remote-name)
- [`--github-release`](#--github-release)
- [`--create-release`](#--create-release-type)
- [`--ignore-changes`](#--ignore-changes)
- [`--include-merged-tags`](#--include-merged-tags)
- [`--message`](#--message-msg)
Expand Down Expand Up @@ -194,19 +194,25 @@ lerna version --git-remote upstream

When run with this flag, `lerna version` will push the git changes to the specified remote instead of `origin`.

### `--github-release`
### `--create-release <type>`

```sh
lerna version --github-release --conventional-commits
lerna version --conventional-commits --create-release github
lerna version --conventional-commits --create-release gitlab
```

When run with this flag, `lerna version` will create an official GitHub release based on the changed packages. Requires `--conventional-commits` to be passed so that changelogs can be generated.
When run with this flag, `lerna version` will create an official GitHub or GitLab release based on the changed packages. Requires `--conventional-commits` to be passed so that changelogs can be generated.

To authenticate with GitHub, the following environment variables can be defined.

- `GH_TOKEN` (required) - Your GitHub authentication token (under Settings > Developer settings > Personal access tokens).
- `GHE_API_URL` - When using GitHub Enterprise, an absolute URL to the API.
- `GHE_VERSION` - When using GitHub Enterprise, the currently installed GHE version. [Supports the following versions](https://github.com/octokit/plugin-enterprise-rest.js).
-
To authenticate with GitLab, the following environment variables can be defined.

- `GL_TOKEN` (required) - Your GitLab authentication token (under User Settings > Access Tokens).
- `GL_API_URL` - An absolute URL to the API, including the version. (Default: https://gitlab.com/api/v4)

> NOTE: When using this option, you cannot pass [`--no-changelog`](#--no-changelog).
Expand Down Expand Up @@ -288,7 +294,7 @@ lerna version --conventional-commits --no-changelog

When using `conventional-commits`, do not generate any `CHANGELOG.md` files.

> NOTE: When using this option, you cannot pass [`--github-release`](#--github-release).
> NOTE: When using this option, you cannot pass [`--create-release`](#--create-release-type).
### `--no-commit-hooks`

Expand Down
4 changes: 2 additions & 2 deletions commands/version/__tests__/version-github-release.test.js
Expand Up @@ -33,7 +33,7 @@ test("--github-release throws an error if --conventional-commits is not passed",
try {
await lernaVersion(cwd)("--github-release");
} catch (err) {
expect(err.message).toBe("To create a Github Release, you must enable --conventional-commits");
expect(err.message).toBe("To create a release, you must enable --conventional-commits");
expect(client.repos.createRelease).not.toHaveBeenCalled();
}

Expand All @@ -46,7 +46,7 @@ test("--github-release throws an error if --no-changelog also passed", async ()
try {
await lernaVersion(cwd)("--github-release", "--conventional-commits", "--no-changelog");
} catch (err) {
expect(err.message).toBe("To create a Github Release, you cannot pass --no-changelog");
expect(err.message).toBe("To create a release, you cannot pass --no-changelog");
expect(client.repos.createRelease).not.toHaveBeenCalled();
}

Expand Down
120 changes: 120 additions & 0 deletions commands/version/__tests__/version-gitlab-release.test.js
@@ -0,0 +1,120 @@
"use strict";

// local modules _must_ be explicitly mocked
jest.mock("../lib/git-add");
jest.mock("../lib/git-commit");
jest.mock("../lib/git-push");
jest.mock("../lib/git-tag");
jest.mock("../lib/is-anything-committed");
jest.mock("../lib/is-behind-upstream");
jest.mock("../lib/remote-branch-exists");

// mocked modules
const client = require("@lerna/gitlab-client")();
const { recommendVersion } = require("@lerna/conventional-commits");

// helpers
const initFixture = require("@lerna-test/init-fixture")(__dirname);

// test command
const lernaVersion = require("@lerna-test/command-runner")(require("../command"));

test("--create-release=gitlab does not create a release if --no-push is passed", async () => {
const cwd = await initFixture("independent");

await lernaVersion(cwd)("--create-release=gitlab", "--conventional-commits", "--no-push");

expect(client.repos.createRelease).not.toHaveBeenCalled();
});

test("--create-release=gitlab throws an error if --conventional-commits is not passed", async () => {
const cwd = await initFixture("independent");

try {
await lernaVersion(cwd)("--create-release=gitlab");
} catch (err) {
expect(err.message).toBe("To create a release, you must enable --conventional-commits");
expect(client.repos.createRelease).not.toHaveBeenCalled();
}

expect.hasAssertions();
});

test("--create-release=gitlab throws an error if --no-changelog also passed", async () => {
const cwd = await initFixture("independent");

try {
await lernaVersion(cwd)("--create-release=gitlab", "--conventional-commits", "--no-changelog");
} catch (err) {
expect(err.message).toBe("To create a release, you cannot pass --no-changelog");
expect(client.repos.createRelease).not.toHaveBeenCalled();
}

expect.hasAssertions();
});

test("--create-release=gitlab marks a version as a pre-release if it contains a valid part", async () => {
const cwd = await initFixture("normal");

recommendVersion.mockResolvedValueOnce("2.0.0-alpha.1");

await lernaVersion(cwd)("--create-release=gitlab", "--conventional-commits");

expect(client.repos.createRelease).toHaveBeenCalledTimes(1);
expect(client.repos.createRelease).toHaveBeenCalledWith({
owner: "lerna",
repo: "lerna",
tag_name: "v2.0.0-alpha.1",
name: "v2.0.0-alpha.1",
body: "normal",
draft: false,
prerelease: true,
});
});

test("--create-release=gitlab creates a release for every independent version", async () => {
const cwd = await initFixture("independent");
const versionBumps = new Map([
["package-1", "1.0.1"],
["package-2", "2.1.0"],
["package-3", "4.0.0"],
["package-4", "4.1.0"],
["package-5", "5.0.1"],
]);

versionBumps.forEach(bump => recommendVersion.mockResolvedValueOnce(bump));

await lernaVersion(cwd)("--create-release=gitlab", "--conventional-commits");

expect(client.repos.createRelease).toHaveBeenCalledTimes(5);
versionBumps.forEach((version, name) => {
expect(client.repos.createRelease).toHaveBeenCalledWith({
owner: "lerna",
repo: "lerna",
tag_name: `${name}@${version}`,
name: `${name}@${version}`,
body: `${name} - ${version}`,
draft: false,
prerelease: false,
});
});
});

test("--create-release=gitlab creates a single fixed release", async () => {
const cwd = await initFixture("normal");

recommendVersion.mockResolvedValueOnce("1.1.0");

await lernaVersion(cwd)("--create-release=gitlab", "--conventional-commits");

expect(client.repos.createRelease).toHaveBeenCalledTimes(1);
expect(client.repos.createRelease).toHaveBeenCalledWith({
owner: "lerna",
repo: "lerna",
tag_name: "v1.1.0",
name: "v1.1.0",
body: "normal",
draft: false,
prerelease: false,
});
});
18 changes: 15 additions & 3 deletions commands/version/command.js
Expand Up @@ -40,9 +40,10 @@ exports.builder = (yargs, composed) => {
requiresArg: true,
defaultDescription: "origin",
},
"github-release": {
describe: "Create an official GitHub release for every version.",
type: "boolean",
"create-release": {
describe: "Create an official GitHub or GitLab release for every version.",
type: "string",
choices: ["gitlab", "github"],
},
"ignore-changes": {
describe: [
Expand Down Expand Up @@ -171,6 +172,11 @@ exports.builder = (yargs, composed) => {
hidden: true,
type: "boolean",
})
.option("github-release", {
// TODO: remove in next major release
hidden: true,
type: "boolean",
})
.check(argv => {
/* eslint-disable no-param-reassign */
if (argv.ignore) {
Expand Down Expand Up @@ -201,6 +207,12 @@ exports.builder = (yargs, composed) => {
delete argv["skip-git"];
log.warn("deprecated", "--skip-git has been replaced by --no-git-tag-version --no-push");
}

if (argv.githubRelease) {
argv.createRelease = "github";
delete argv.githubRelease;
log.warn("deprecated", "--release has been replaced by --create-release=github");
}
/* eslint-enable no-param-reassign */

return argv;
Expand Down
58 changes: 16 additions & 42 deletions commands/version/index.js
Expand Up @@ -19,7 +19,6 @@ const collectUpdates = require("@lerna/collect-updates");
const { createRunner } = require("@lerna/run-lifecycle");
const runTopologically = require("@lerna/run-topologically");
const ValidationError = require("@lerna/validation-error");
const { createGitHubClient, parseGitRepo } = require("@lerna/github-client");
const prereleaseIdFromVersion = require("@lerna/prerelease-id-from-version");

const getCurrentBranch = require("./lib/get-current-branch");
Expand All @@ -32,6 +31,7 @@ const remoteBranchExists = require("./lib/remote-branch-exists");
const isBreakingChange = require("./lib/is-breaking-change");
const isAnythingCommitted = require("./lib/is-anything-committed");
const makePromptVersion = require("./lib/prompt-version");
const createRelease = require("./lib/create-release");

const { collectPackages, getPackagesForOption } = collectUpdates;

Expand Down Expand Up @@ -75,18 +75,15 @@ class VersionCommand extends Command {
this.pushToRemote = gitTagVersion && amend !== true && push;
// never automatically push to remote when amending a commit

this.createReleases = this.pushToRemote && this.options.githubRelease;
this.createRelease = this.pushToRemote && this.options.createRelease;
this.releaseNotes = [];

if (this.createReleases && this.options.conventionalCommits !== true) {
throw new ValidationError(
"ERELEASE",
"To create a Github Release, you must enable --conventional-commits"
);
if (this.createRelease && this.options.conventionalCommits !== true) {
throw new ValidationError("ERELEASE", "To create a release, you must enable --conventional-commits");
}

if (this.createReleases && this.options.changelog === false) {
throw new ValidationError("ERELEASE", "To create a Github Release, you cannot pass --no-changelog");
if (this.createRelease && this.options.changelog === false) {
throw new ValidationError("ERELEASE", "To create a release, you cannot pass --no-changelog");
}

this.gitOpts = {
Expand Down Expand Up @@ -269,10 +266,17 @@ class VersionCommand extends Command {
this.logger.info("execute", "Skipping git push");
}

if (this.createReleases) {
tasks.push(() => this.createGitHubReleases());
if (this.createRelease) {
this.logger.info("execute", "Creating releases...");
tasks.push(() =>
createRelease(
this.options.createRelease,
{ tags: this.tags, releaseNotes: this.releaseNotes },
{ gitRemote: this.options.gitRemote, execOpts: this.execOpts }
)
);
} else {
this.logger.info("execute", "Skipping GitHub releases");
this.logger.info("execute", "Skipping releases");
}

return pWaterfall(tasks).then(() => {
Expand Down Expand Up @@ -652,36 +656,6 @@ class VersionCommand extends Command {

return gitPush(this.gitRemote, this.currentBranch, this.execOpts);
}

createGitHubReleases() {
this.logger.info("github", "Creating GitHub releases...");

const client = createGitHubClient();
const repo = parseGitRepo(this.options.gitRemote, this.execOpts);

return Promise.all(
this.releaseNotes.map(({ notes, name }) => {
const tag = name === "fixed" ? this.tags[0] : this.tags.find(t => t.startsWith(`${name}@`));

/* istanbul ignore if */
if (!tag) {
return Promise.resolve();
}

const prereleaseParts = semver.prerelease(tag.replace(`${name}@`, "")) || [];

return client.repos.createRelease({
owner: repo.owner,
repo: repo.name,
tag_name: tag,
name: tag,
body: notes,
draft: false,
prerelease: prereleaseParts.length > 0,
});
})
);
}
}

module.exports.VersionCommand = VersionCommand;
48 changes: 48 additions & 0 deletions commands/version/lib/create-release.js
@@ -0,0 +1,48 @@
"use strict";

const semver = require("semver");

const createGitLabClient = require("@lerna/gitlab-client");
const { createGitHubClient, parseGitRepo } = require("@lerna/github-client");
const ValidationError = require("@lerna/validation-error");

module.exports = createRelease;

function createClient(type) {
switch (type) {
case "gitlab":
return createGitLabClient();
case "github":
return createGitHubClient();
default:
throw new ValidationError("ERELEASE", "Invalid release client type");
}
}

function createRelease(type, { tags, releaseNotes }, { gitRemote, execOpts }) {
const repo = parseGitRepo(gitRemote, execOpts);
const client = createClient(type);

return Promise.all(
releaseNotes.map(({ notes, name }) => {
const tag = name === "fixed" ? tags[0] : tags.find(t => t.startsWith(`${name}@`));

/* istanbul ignore if */
if (!tag) {
return Promise.resolve();
}

const prereleaseParts = semver.prerelease(tag.replace(`${name}@`, "")) || [];

return client.repos.createRelease({
owner: repo.owner,
repo: repo.name,
tag_name: tag,
name: tag,
body: notes,
draft: false,
prerelease: prereleaseParts.length > 0,
});
})
);
}
1 change: 1 addition & 0 deletions commands/version/package.json
Expand Up @@ -40,6 +40,7 @@
"@lerna/command": "file:../../core/command",
"@lerna/conventional-commits": "file:../../core/conventional-commits",
"@lerna/github-client": "file:../../utils/github-client",
"@lerna/gitlab-client": "file:../../utils/gitlab-client",
"@lerna/output": "file:../../utils/output",
"@lerna/prerelease-id-from-version": "file:../../utils/prerelease-id-from-version",
"@lerna/prompt": "file:../../core/prompt",
Expand Down
3 changes: 3 additions & 0 deletions core/project/__fixtures__/extends-deprecated/lerna.json
Expand Up @@ -3,6 +3,9 @@
"commands": {
"bootstrap": {
"hoist": true
},
"version": {
"githubRelease": true
}
},
"npmTag": "next",
Expand Down

0 comments on commit 4974b78

Please sign in to comment.