Skip to content

Commit 4e3fc14

Browse files
authored
Merge pull request #48 from pmowrer/plugins-support
v16+ support
2 parents afe88f5 + 885d03e commit 4e3fc14

11 files changed

Lines changed: 196 additions & 424 deletions

README.md

Lines changed: 9 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,14 @@ Preview the semantic release notes that would result from merging a Github PR.
66

77
![image](https://user-images.githubusercontent.com/356320/33625928-257bc906-d9c7-11e7-9adb-de85726952eb.png)
88

9-
This set of [`semantic-release`](https://github.com/semantic-release/semantic-release) plugins will post a Github PR comment with a preview of the release that would result from merging.
9+
This [`semantic-release`](https://github.com/semantic-release/semantic-release) plugin will post a Github PR comment with a preview of the release that would result from merging.
1010

1111
## Install
1212

1313
```bash
14-
npm install -D semantic-release@~15.9.x semantic-release-github-pr
14+
npm install -D semantic-release semantic-release-github-pr
1515
```
1616

17-
NOTE: The current version of this plugin only supports `semantic-release` versions 15.7.x-15.9.x. The next major version will support the current version of `semantic-release`.
18-
1917
## Usage
2018

2119
```bash
@@ -24,7 +22,7 @@ npx semantic-release-github-pr
2422

2523
It helps to think about `semantic-release-github-pr` as a variation on `semantic-release`'s default behavior, using the latter's plugin system to modify some behaviors:
2624

27-
* If a new release would result from running `semantic-release`, _instead of publishing a new release of a package to `npm`_, it posts a comment with the changelog to matching Github PRs.
25+
* If a new release would result from running `semantic-release`, _instead of publishing a new release of a package_, it posts a comment with the changelog to matching Github PRs.
2826

2927
* It posts a static message when there's no release (for clarity).
3028

@@ -34,54 +32,33 @@ It helps to think about `semantic-release-github-pr` as a variation on `semantic
3432

3533
A PR gets a comment if:
3634

37-
1. The PR's "from" branch matches the current branch (that this command is being run against).
35+
1. The PR's _from_ branch matches the current branch (that this plugin is being run on).
3836

3937
To cover multiple CI scenarios ([see below](#ci)), either of:
4038

4139
1. The PR's [_test_ merge commit](https://developer.github.com/v3/pulls/#response-1) matches the current branch's `git` HEAD.
4240
2. The PR and the current branch have the same `git` HEAD.
4341

44-
2. The PR's base branch matches `master` (default) unless the [`githubPr.branch` configuration option](https://github.com/semantic-release/semantic-release#Release-config) is set.
42+
2. The PR's base branch matches `master` (default) unless otherwise configured via the `baseBranch` option.
4543

4644
## Configuration
4745

4846
It is assumed the user is already fully familiar with `semantic-release` and [`its workflow`](https://github.com/semantic-release/semantic-release#how-does-it-work).
4947

5048
### Github
5149

52-
Github authentication must be configured, exactly the same as for `semantic-relase`'s default [`@semantic-release/github`](https://github.com/semantic-release/github/#github-repository-authentication) plugin.
50+
Github authentication must be configured, exactly the same as for `semantic-relase`'s default [`@semantic-release/github`](https://github.com/semantic-release/github/#github-authentication) plugin. PR comments will be posted by the associated GitHub user.
5351

5452
### Release config
5553

56-
It's possible to configure the expected base branch [when matching a PR](#which-prs-get-a-comment).
57-
58-
E.g., `package.json`:
59-
60-
```json
61-
{
62-
"release": {
63-
"githubPr": {
64-
"branch": "staging"
65-
}
66-
}
67-
}
68-
```
69-
70-
#### Advanced
71-
72-
Due to limitations in how plugins may be composed, `semantic-release-github-pr` must unfortunately hard-code the [`analyzeCommits`](#analyzecommits) and [`generateNotes`](#generatenotes) [plugins](https://github.com/semantic-release/semantic-release/blob/caribou/docs/usage/plugins.md) (see discussion [here](https://github.com/semantic-release/semantic-release/issues/550)).
73-
74-
Users may still want to define a custom versions of these plugins, or want to pass options to the default implementations. To work around this problem, set the desired configuration in the [release plugin config](https://github.com/semantic-release/semantic-release#plugins) inside the `githubPr` key instead.
54+
It's possible to configure the expected base branch [when matching a PR](#which-prs-get-a-comment) via a `baseBranch` option set in the [release config](https://github.com/semantic-release/semantic-release/blob/master/docs/usage/configuration.md#configuration-file).
7555

76-
E.g., `package.json`:
56+
E.g., in a `package.json` release config:
7757

7858
```json
7959
{
8060
"release": {
81-
"githubPr": {
82-
"analyzeCommits": "myCommitsAnalyzer",
83-
"generateNotes": "myGenerateNotes"
84-
}
61+
"baseBranch": "staging"
8562
}
8663
}
8764
```
@@ -109,39 +86,3 @@ Unfortunately, CircleCI only supports building on push, [not when a PR is create
10986
post:
11087
- "[[ $CI_PULL_REQUEST != '' ]] && npx semantic-release-github-pr || exit 0"
11188
```
112-
113-
### Advanced
114-
115-
Running `semantic-release-github-pr` is equivalent to running `semantic-release` with the following [configuration](https://github.com/semantic-release/semantic-release/blob/caribou/docs/usage/configuration.md#configuration.) (as encapsulated in [the `semantic-release-github-pr` command](https://github.com/Updater/semantic-release-github-pr/blob/master/bin/semantic-release-github-pr.js)):
116-
117-
```json
118-
{
119-
"release": {
120-
"verifyConditions": "@semantic-release/github",
121-
"analyzeCommits": "semantic-release-github-pr",
122-
"generateNotes": "semantic-release-github-pr"
123-
}
124-
}
125-
```
126-
127-
### verifyConditions
128-
129-
The `@semantic-release/github` plugin is set as a default.
130-
131-
#### analyzeCommits
132-
133-
Used as a hook to clean up previous changelog PR comments made by the `generateNotes` plugin, keeping it from flooding a PR with (eventually stale) changelog comments over time.
134-
135-
If `semantic-release` determines that there's no new version, this plugin will also post a "no release" comment on a matching PR.
136-
137-
This plugin doesn't actually analyze commits to determine when to make a release, but defers to the plugin it decorates ([`@semantic-release/commit-analyzer`](https://github.com/semantic-release/commit-analyzer/) by default).
138-
139-
See the [`Release config`](#release-config) section for how to configure a custom `analyzeCommits` plugin and/or set options.
140-
141-
#### generateNotes
142-
143-
Creates a comment on matching PRs with the changelog for the release that would result from merging.
144-
145-
This plugin doesn't actually generate the changelog that ends up in the PR comment, but defers to the plugin it decorates ([`@semantic-release/release-notes-generator`](https://github.com/semantic-release/release-notes-generator) by default).
146-
147-
See the [`Release config`](#release-config) section for how to configure a custom `generateNotes` plugin and/or set options.

bin/semantic-release-github-pr.js

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,7 @@ const { getCurrentBranchName } = require('../src/git-utils');
1919
`--no-ci`,
2020
// Set `dry-run` to keep `semantic-release` from publishing an actual release.
2121
`--dry-run`,
22-
`--branch=${branch}`,
23-
// We hard-set our version of `analyze-commits (preventing accidental override)`.
24-
`--analyze-commits=${plugins}`,
25-
26-
// TODO: Used to hard-set our version of `generateNotes` as well, but no
27-
// longer seems possible after it became a "multi plugin" (array)
28-
// configuration in `semantic-release` 15.7.0. Instead, it's soft-set set
29-
// via the shareable config option below (`--extends`). It doesn't matter
30-
// other than it allows the user to break the plugin by (inadvertently)
31-
// overriding our version of `generateNotes`.
32-
33-
// We use `extends` here to pick up a soft-set default for `verifyConditions`,
34-
// allowing users to override it (setting a plugin directly from the CLI
35-
// trumps plugins read from a config file).
22+
`--branches=${branch}`,
3623
`--extends=${plugins}`,
3724
]);
3825

package.json

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"bin": "./bin/semantic-release-github-pr.js",
1717
"license": "MIT",
1818
"peerDependencies": {
19-
"semantic-release": "^15.7.0-15.9.x"
19+
"semantic-release": ">=16.0.0"
2020
},
2121
"dependencies": {
2222
"debug": "^4.1.1",
@@ -25,15 +25,14 @@
2525
"github": "^13.0.0",
2626
"parse-github-url": "^1.0.2",
2727
"ramda": "^0.26.1",
28-
"read-pkg": "^5.2.0",
29-
"semantic-release-plugin-decorators": "^2.0.0"
28+
"read-pkg": "^5.2.0"
3029
},
3130
"devDependencies": {
3231
"husky": "^4.2.1",
3332
"jest": "^25.1.0",
3433
"lint-staged": "^10.0.3",
3534
"prettier": "^1.19.1",
36-
"semantic-release": "15.9.x"
35+
"semantic-release": "^17.0.0"
3736
},
3837
"husky": {
3938
"hooks": {
@@ -45,4 +44,4 @@
4544
"yarn format"
4645
]
4746
}
48-
}
47+
}

src/create-changelog.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ const HEADER = {
1515
*/
1616
const createChangelog = (
1717
{ githubRepo, npmPackage: { name: npmPackageName } },
18-
{ logger, nextRelease: { gitHead, gitTag = null, notes } }
18+
{
19+
logger,
20+
envCi: { commit: gitHead },
21+
nextRelease: { gitTag = null, notes } = {},
22+
}
1923
) => async ({ number, title }) => {
2024
const body =
2125
create(gitHead, npmPackageName, gitTag) +

src/delete-changelog.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const matchStaleComment = (
3636
debug(`Comment is "no release" comment: %o`, isNoRelease);
3737

3838
return (
39-
!matchesGitHead || matchesPackageName || (!skipNoRelease && isNoRelease)
39+
(!matchesGitHead && matchesPackageName) || (!skipNoRelease && isNoRelease)
4040
);
4141
};
4242

@@ -48,7 +48,7 @@ const matchStaleComment = (
4848
*/
4949
const deleteStaleChangelogs = skipNoRelease => (
5050
{ githubRepo, npmPackage: { name: npmPackageName } },
51-
{ logger, nextRelease: { gitHead } }
51+
{ logger, envCi: { commit: gitHead } }
5252
) => async ({ number, title }) => {
5353
const { data: comments } = await githubRepo.getIssueComments({ number });
5454
const isStaleComment = matchStaleComment(

src/git-utils.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
const { pipeP, split } = require('ramda');
21
const execa = require('execa');
32

43
const git = async (args, options = {}) => {

src/index.js

Lines changed: 59 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,104 @@
11
const { compose } = require('ramda');
2-
const {
3-
wrapPlugin,
4-
appendMultiPlugin,
5-
} = require('semantic-release-plugin-decorators');
6-
const pluginDefinitions = require('semantic-release/lib/definitions/plugins');
7-
82
const { parse } = require('./comment-tag');
93
const createChangelog = require('./create-changelog');
104
const deleteStaleChangelogs = require('./delete-changelog');
115
const withGithub = require('./with-github');
12-
const withGitHead = require('./with-git-head');
136
const withNpmPackage = require('./with-npm-package');
147
const withMatchingPullRequests = require('./with-matching-pull-requests');
158

16-
const NAMESPACE = 'githubPr';
17-
189
const decoratePlugin = compose(
1910
withGithub,
20-
withGitHead,
2111
withMatchingPullRequests,
2212
withNpmPackage
2313
);
2414

25-
// Use `analyzeCommits` plugin as a hook to post a "no release" PR comment if
26-
// there isn't a new release. We can't do this in `generateNotes` since it only runs
27-
// if there's a new release.
28-
const analyzeCommits = wrapPlugin(
29-
NAMESPACE,
30-
'analyzeCommits',
31-
plugin => async (pluginConfig, context) => {
15+
// Use the `analyzeCommits` step to post a "no release" PR comment when there
16+
// isn't a new release (we can't do this in `generateNotes` since it only runs
17+
// if there's a new release).
18+
const analyzeCommits = async (pluginConfig, context, results) => {
19+
return Promise.all(results).then(async results => {
3220
const { githubRepo, pullRequests } = pluginConfig;
33-
const nextRelease = await plugin(pluginConfig, context);
21+
const hasNextRelease = results.some(result => !!result);
3422

35-
if (!nextRelease) {
23+
if (!hasNextRelease) {
3624
await pullRequests.forEach(async pr => {
3725
const { number } = pr;
3826
const createChangelogOnPr = createChangelog(pluginConfig, context);
3927
const { data: comments } = await githubRepo.getIssueComments({
4028
number,
4129
});
4230

43-
// Create "no release" comment if there are no other comments posted
44-
// by this set of plugins. We want to avoid duplicating the "no release"
45-
// comment and/or posting it when another package has a release (monorepo).
31+
// Create a "no release" comment.
32+
// We only do this if there are no other comments posted by this plugin,
33+
// avoiding duplication of the "no release" comment and/or incorrectly posting
34+
// it when a different package in the same PR has a release (monorepo).
4635
if (!comments.some(comment => !!parse(comment.body))) {
4736
createChangelogOnPr(pr);
4837
}
4938
});
5039
}
5140

52-
// Clean up stale changelog comments, possibly sparing the "no release"
53-
// comment if this package doesn't have a new release.
41+
// Clean up stale changelog comments from previous runs of this plugin.
5442
await pullRequests.forEach(
55-
deleteStaleChangelogs(!nextRelease)(pluginConfig, context)
43+
deleteStaleChangelogs(!hasNextRelease)(pluginConfig, context)
5644
);
5745

58-
return nextRelease;
59-
},
60-
pluginDefinitions.analyzeCommits.default
61-
);
46+
return;
47+
});
48+
};
6249

63-
// Append a plugin that generates PR comments from the release notes resulting
64-
// from the configured `generateNotes` plugins that run ahead of it.
65-
const generateNotes = appendMultiPlugin(
66-
NAMESPACE,
67-
'generateNotes',
68-
decoratePlugin(async (pluginConfig, context) => {
50+
// Use the "generateNotes" step to post a PR comments with the release notes
51+
// generated for the pending release.
52+
const generateNotes = async (pluginConfig, context, results) => {
53+
return Promise.all(results).then(async results => {
6954
const { pullRequests } = pluginConfig;
7055
const { nextRelease } = context;
7156

7257
await pullRequests.forEach(
73-
// Create "release" comment
58+
// Create a "release" comment.
7459
createChangelog(pluginConfig, context)
7560
);
7661

7762
return nextRelease.notes;
78-
}),
79-
pluginDefinitions.generateNotes.default
80-
);
63+
});
64+
};
65+
66+
const appendStep = (stepName, stepFn) => {
67+
const results = [];
68+
69+
return Array(10)
70+
.fill(null)
71+
.map((value, index) => {
72+
return async (pluginConfig, context) => {
73+
const {
74+
options: { plugins },
75+
} = context;
76+
const pluginName = plugins[index];
77+
78+
if (index === plugins.length) {
79+
return stepFn(pluginConfig, context, results);
80+
}
81+
82+
if (!pluginName) {
83+
return '';
84+
}
85+
86+
const plugin = require(pluginName);
87+
const step = plugin && plugin[stepName];
88+
89+
if (!step) {
90+
return '';
91+
}
92+
93+
const result = step(pluginConfig, context);
94+
results.push(result);
95+
return result;
96+
};
97+
});
98+
};
8199

82100
module.exports = {
83101
verifyConditions: '@semantic-release/github',
84-
analyzeCommits: decoratePlugin(analyzeCommits),
85-
generateNotes,
102+
analyzeCommits: appendStep('analyzeCommits', decoratePlugin(analyzeCommits)),
103+
generateNotes: appendStep('generateNotes', decoratePlugin(generateNotes)),
86104
};

src/with-github.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ const githubInit = require('./github-init');
22
const githubRepo = require('./github-repo');
33
const parseGithubUrl = require('parse-github-url');
44

5-
const withGithub = plugin => (pluginConfig, context) => {
5+
const withGithub = plugin => (...args) => {
6+
const [pluginConfig, context] = args;
67
const github = githubInit(pluginConfig, context);
78
const {
89
options: { repositoryUrl },
@@ -14,7 +15,7 @@ const withGithub = plugin => (pluginConfig, context) => {
1415
...pluginConfig,
1516
githubRepo: githubRepo(github, { owner, repo }),
1617
},
17-
context
18+
...args.slice(1)
1819
);
1920
};
2021

0 commit comments

Comments
 (0)