├── .gitignore
├── .npmrc
├── .travis.yml
├── README.md
├── __snapshots__
├── changelog-spec.js.snap-shot
└── utils-spec.js.snap-shot
├── images
└── comment.png
├── next-update-travis.sh
├── package.json
└── src
├── github-post-release-spec.js
├── index.js
├── postinstall.js
├── utils-spec.js
└── utils.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .DS_Store
3 | npm-debug.log
4 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=http://registry.npmjs.org/
2 | save-exact=true
3 | progress=false
4 |
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | cache:
3 | directories:
4 | - node_modules
5 | notifications:
6 | email: true
7 | node_js:
8 | - '6'
9 | before_script:
10 | - npm prune
11 | script:
12 | - git --version
13 | - git tag --sort version:refname || true
14 | - git tag
15 | - ./next-update-travis.sh
16 | - npm test
17 | after_success:
18 | - DEBUG=github-post-release npm run semantic-release
19 | branches:
20 | except:
21 | - /^v\d+\.\d+\.\d+$/
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # github-post-release
2 |
3 | > Forms release changelog and posts a note to each referenced issues after semantic release publishes a new module version
4 |
5 | [![NPM][npm-icon] ][npm-url]
6 |
7 | [![Build status][ci-image] ][ci-url]
8 | [![semantic-release][semantic-image] ][semantic-url]
9 | [![js-standard-style][standard-image]][standard-url]
10 | [![next-update-travis badge][nut-badge]][nut-readme]
11 |
12 | ## Problem
13 |
14 | When publishing new version of your NPM package, it would be nice to comment
15 | on each GitHub issue referenced by the semantic commits, letting the user
16 | know that the fix / feature was published.
17 |
18 | This module works as a [semantic-release][sem] [generate notes][gen] plugin.
19 | It both comments on referenced issues and returns the changelog to be
20 | posted on GitHub. Each issue will get a comment like this
21 | ([example](https://github.com/bahmutov/github-post-release/issues/8#issuecomment-313786374))
22 |
23 | 
24 |
25 | [sem]: https://github.com/semantic-release/semantic-release
26 | [gen]: https://github.com/semantic-release/semantic-release#generatenotes
27 |
28 | ## Install and use
29 |
30 | Requires [Node](https://nodejs.org/en/) version 6 or above.
31 |
32 | ```sh
33 | npm install --save-dev github-post-release
34 | ```
35 |
36 | The `postinstall` script will automatically set this module to be the
37 | `generateNotes` plugin for the release. If you want you can do this manually:
38 |
39 | ```json
40 | {
41 | "release": {
42 | "generateNotes": "github-post-release"
43 | }
44 | }
45 | ```
46 |
47 | ## Message types
48 |
49 | By default, the output message for issues will be
50 | "New version has been published to NPM ...". This might not match other
51 | scenarios when this plugin can be used. For example if you deploy a website
52 | and publish release notes to GitHub, you might like different text that does
53 | not mention NPM. There are two built-in message types: "npm", "deploy" and you
54 | can pick on to generate by configuring this plugin like this in `package.json`
55 |
56 | ```json
57 | {
58 | "release": {
59 | "generateNotes": {
60 | "path": "github-post-release",
61 | "type": "deploy"
62 | }
63 | }
64 | }
65 | ```
66 |
67 | ## Debug
68 |
69 | To see more log messages, run this plugin with `DEBUG=github-post-release`
70 | flag. You can even demo the plugin locally (without actual GitHub updates)
71 | using `npm run demo`.
72 |
73 | ## Related
74 |
75 | It was inspired by [semantic-release-github-notifier][notifier],
76 | [release-notes-generator][notes-generator] and uses
77 | [conventional-changelog][conventional-changelog] to generate changelog text
78 | after commenting on issues.
79 |
80 | [notifier]: https://github.com/gitter-badger/semantic-release-github-notifier
81 | [notes-generator]: https://github.com/semantic-release/release-notes-generator/
82 | [conventional-changelog]: https://github.com/conventional-changelog/conventional-changelog#readme
83 |
84 | ### Small print
85 |
86 | Author: Gleb Bahmutov <gleb.bahmutov@gmail.com> © 2017
87 |
88 | * [@bahmutov](https://twitter.com/bahmutov)
89 | * [glebbahmutov.com](https://glebbahmutov.com)
90 | * [blog](https://glebbahmutov.com/blog)
91 |
92 | License: MIT - do anything with the code, but don't blame me if it does not work.
93 |
94 | Support: if you find any problems with this module, email / tweet /
95 | [open issue](https://github.com/bahmutov/github-post-release/issues) on Github
96 |
97 | ## MIT License
98 |
99 | Copyright (c) 2017 Gleb Bahmutov <gleb.bahmutov@gmail.com>
100 |
101 | Permission is hereby granted, free of charge, to any person
102 | obtaining a copy of this software and associated documentation
103 | files (the "Software"), to deal in the Software without
104 | restriction, including without limitation the rights to use,
105 | copy, modify, merge, publish, distribute, sublicense, and/or sell
106 | copies of the Software, and to permit persons to whom the
107 | Software is furnished to do so, subject to the following
108 | conditions:
109 |
110 | The above copyright notice and this permission notice shall be
111 | included in all copies or substantial portions of the Software.
112 |
113 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
114 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
115 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
116 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
117 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
118 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
119 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
120 | OTHER DEALINGS IN THE SOFTWARE.
121 |
122 | [npm-icon]: https://nodei.co/npm/github-post-release.svg?downloads=true
123 | [npm-url]: https://npmjs.org/package/github-post-release
124 | [ci-image]: https://travis-ci.org/bahmutov/github-post-release.svg?branch=master
125 | [ci-url]: https://travis-ci.org/bahmutov/github-post-release
126 | [semantic-image]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
127 | [semantic-url]: https://github.com/semantic-release/semantic-release
128 | [standard-image]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg
129 | [standard-url]: http://standardjs.com/
130 | [nut-badge]: https://img.shields.io/badge/next--update--travis-weekly-green.svg
131 | [nut-readme]: https://github.com/bahmutov/next-update-travis#readme
132 |
--------------------------------------------------------------------------------
/__snapshots__/changelog-spec.js.snap-shot:
--------------------------------------------------------------------------------
1 | exports['forms changelog 1'] = "## New features 👍\n### streams\n* output stdout and stderr, fixes #1, #2 (aaaabbbbccccddddeeeeffff1111222233334444)\n\n## Bug fixes ✅\n### doc\n* updated documentation (aaaabbbbccccddddeeeeffff1111222233334444)\n\n"
2 |
3 | exports['version and public commits 1'] = "\n# 1.0.2 (2000-04-20)\n## New features 👍\n### streams\n* output stdout and stderr, fixes #1, #2 (aaaabbbbccccddddeeeeffff1111222233334444)\n\n## Bug fixes ✅\n### doc\n* updated documentation (aaaabbbbccccddddeeeeffff1111222233334444)\n\n"
4 |
5 | exports['groups 1'] = {
6 | "feat": [
7 | {
8 | "firstLine": "feat(streams): output stdout and stderr, fixes #1, #2",
9 | "type": "feat",
10 | "scope": "streams",
11 | "subject": "output stdout and stderr, fixes #1, #2",
12 | "id": "aaaabbbbccccddddeeeeffff1111222233334444"
13 | }
14 | ],
15 | "fix": [
16 | {
17 | "firstLine": "fix(doc): updated documentation",
18 | "type": "fix",
19 | "scope": "doc",
20 | "subject": "updated documentation",
21 | "id": "aaaabbbbccccddddeeeeffff1111222233334444"
22 | }
23 | ]
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/__snapshots__/utils-spec.js.snap-shot:
--------------------------------------------------------------------------------
1 | exports['finds fixed issue in 1 commit 1'] = [
2 | 2
3 | ]
4 |
5 | exports['finds several closed issues 1'] = [
6 | 2,
7 | 4,
8 | 3
9 | ]
10 |
11 | exports['forms url 1'] = "https://github.com/bahmutov/github-post-release/releases/tag/v1.4.0"
12 |
13 | exports['forms release text 1'] = "github-post-release/releases/tag/v1.4.0"
14 |
15 | exports['forms full message 1'] = "Version `1.4.0` has been published to NPM. The full release note can be found at [repo/releases/tag/v1.4.0](https://github.com/user/repo/releases/tag/v1.4.0).\n\n**Tip:** safely upgrade dependency my-module-name in your project using [next-update](https://github.com/bahmutov/next-update)"
16 |
17 | exports['forms full publish message 1'] = "Version `1.4.0` has been published to NPM. The full release note can be found at [repo/releases/tag/v1.4.0](https://github.com/user/repo/releases/tag/v1.4.0).\n\n**Tip:** safely upgrade dependency my-module-name in your project using [next-update](https://github.com/bahmutov/next-update)"
18 |
19 | exports['forms full deploy message 1'] = "Version `1.4.0` has been deployed. The full release note can be found at [repo/releases/tag/v1.4.0](https://github.com/user/repo/releases/tag/v1.4.0)."
20 |
21 |
--------------------------------------------------------------------------------
/images/comment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bahmutov/github-post-release/d5f0623d0edeaf756c4075e61641f621c545b13e/images/comment.png
--------------------------------------------------------------------------------
/next-update-travis.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | if [ "$TRAVIS_EVENT_TYPE" = "cron" ]; then
6 | if [ "$GH_TOKEN" = "" ]; then
7 | echo ""
8 | echo "⛔️ Cannot find environment variable GH_TOKEN ⛔️"
9 | echo "Please set it up for this script to be able"
10 | echo "to push results to GitHub"
11 | echo "ℹ️ The best way is to use semantic-release to set it up"
12 | echo ""
13 | echo " https://github.com/semantic-release/semantic-release"
14 | echo ""
15 | echo "npm i -g semantic-release-cli"
16 | echo "semantic-release-cli setup"
17 | echo ""
18 | exit 1
19 | fi
20 |
21 | echo "Upgrading dependencies using next-update"
22 | npm i -g next-update
23 |
24 | # you can edit options to allow only some updates
25 | # --allow major | minor | patch
26 | # --latest true | false
27 | # see all options by installing next-update
28 | # and running next-update -h
29 | next-update --allow minor --latest false
30 |
31 | git status
32 | # if package.json is modified we have
33 | # new upgrades
34 | if git diff --name-only | grep package.json > /dev/null; then
35 | echo "There are new versions of dependencies 💪"
36 | git add package.json
37 | echo "----------- package.json diff -------------"
38 | git diff --staged
39 | echo "-------------------------------------------"
40 | git config --global user.email "next-update@ci.com"
41 | git config --global user.name "next-update"
42 | git commit -m "chore(deps): upgrade dependencies using next-update"
43 | # push back to GitHub using token
44 | git remote remove origin
45 | # TODO read origin from package.json
46 | # or use github api module github
47 | # like in https://github.com/semantic-release/semantic-release/blob/caribou/src/post.js
48 | git remote add origin https://next-update:$GH_TOKEN@github.com/bahmutov/github-post-release.git
49 | git push origin HEAD:master
50 | else
51 | echo "No new versions found ✋"
52 | fi
53 | else
54 | echo "Not a cron job, normal test"
55 | fi
56 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "github-post-release",
3 | "description": "Forms release changelog and posts a note to each referenced issues after semantic release publishes a new module version",
4 | "version": "0.0.0-development",
5 | "author": "Gleb Bahmutov ",
6 | "bugs": "https://github.com/bahmutov/github-post-release/issues",
7 | "config": {
8 | "pre-git": {
9 | "commit-msg": "simple",
10 | "pre-commit": [
11 | "npm prune",
12 | "npm run deps",
13 | "npm test",
14 | "git add src/*.js",
15 | "npm run ban"
16 | ],
17 | "pre-push": [
18 | "npm run secure",
19 | "npm run license",
20 | "npm run ban -- --all",
21 | "npm run size"
22 | ],
23 | "post-commit": [],
24 | "post-merge": []
25 | }
26 | },
27 | "engines": {
28 | "node": ">=4"
29 | },
30 | "files": [
31 | "src/*.js",
32 | "!src/*-spec.js"
33 | ],
34 | "homepage": "https://github.com/bahmutov/github-post-release#readme",
35 | "keywords": [
36 | "changelog",
37 | "github",
38 | "plugin",
39 | "semantic",
40 | "semantic-release"
41 | ],
42 | "license": "MIT",
43 | "main": "src/",
44 | "publishConfig": {
45 | "registry": "http://registry.npmjs.org/"
46 | },
47 | "repository": {
48 | "type": "git",
49 | "url": "https://github.com/bahmutov/github-post-release.git"
50 | },
51 | "release": {
52 | "generateNotes": {
53 | "path": ".",
54 | "type": "npm"
55 | },
56 | "analyzeCommits": "simple-commit-message"
57 | },
58 | "scripts": {
59 | "ban": "ban",
60 | "deps": "deps-ok && dependency-check --no-dev . --entry src/postinstall.js",
61 | "issues": "git-issues",
62 | "license": "license-checker --production --onlyunknown --csv",
63 | "pretty": "prettier-standard 'src/*.js'",
64 | "prelint": "npm run pretty",
65 | "lint": "standard --verbose --fix src/*.js",
66 | "pretest": "npm run lint",
67 | "secure": "nsp check",
68 | "size": "t=\"$(npm pack .)\"; wc -c \"${t}\"; tar tvf \"${t}\"; rm \"${t}\";",
69 | "test": "npm run unit",
70 | "unit": "mocha src/*-spec.js",
71 | "semantic-release": "semantic-release pre && npm publish && semantic-release post",
72 | "demo": "DEBUG=github-post-release node .",
73 | "postinstall": "node src/postinstall.js"
74 | },
75 | "devDependencies": {
76 | "ban-sensitive-files": "1.9.0",
77 | "dependency-check": "2.9.1",
78 | "deps-ok": "1.2.1",
79 | "git-issues": "1.3.1",
80 | "license-checker": "13.1.0",
81 | "mocha": "3.5.3",
82 | "mockdate": "2.0.2",
83 | "next-update-travis": "1.7.1",
84 | "nsp": "2.8.1",
85 | "pre-git": "3.15.3",
86 | "prettier-standard": "6.0.0",
87 | "semantic-release": "^6.3.6",
88 | "snap-shot": "2.17.0",
89 | "standard": "10.0.3"
90 | },
91 | "dependencies": {
92 | "am-i-a-dependency": "1.1.2",
93 | "bluebird": "3.5.1",
94 | "check-more-types": "2.24.0",
95 | "commit-closes": "1.0.1",
96 | "common-tags": "1.4.0",
97 | "debug": "3.1.0",
98 | "github": "9.3.1",
99 | "github-url-from-git": "1.5.0",
100 | "lazy-ass": "1.6.0",
101 | "new-public-commits": "1.3.1",
102 | "parse-github-repo-url": "1.4.1",
103 | "pluralize": "6.0.0",
104 | "ramda": "0.25.0",
105 | "simple-changelog": "1.1.3",
106 | "simple-commit-message": "3.3.2"
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/github-post-release-spec.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const la = require('lazy-ass')
4 | const is = require('check-more-types')
5 | const join = require('path').join
6 | const R = require('ramda')
7 |
8 | /* global describe, it */
9 | const githubPostRelease = require('.')
10 | const pkg = require(join(__dirname, '..', 'package.json'))
11 |
12 | describe('github-post-release', () => {
13 | it('is a function', () => {
14 | la(is.fn(githubPostRelease))
15 | })
16 |
17 | it('forms changelog', done => {
18 | const pluginConfig = {}
19 | const json = R.clone(pkg)
20 | json.version = '1000.0.0'
21 | const config = {
22 | pkg: json,
23 | debug: true
24 | }
25 |
26 | githubPostRelease(pluginConfig, config, (err, log) => {
27 | la(!err, 'there was an error', err)
28 | la(is.unemptyString(log), 'missing log', log)
29 | console.log(log)
30 | done()
31 | })
32 | })
33 | })
34 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const bluebird = require('bluebird')
4 | const url = require('url')
5 | const GitHubApi = require('github')
6 | const parseGithubUrl = require('parse-github-repo-url')
7 | const debug = require('debug')('github-post-release')
8 | const newPublicCommits = require('new-public-commits').newPublicCommits
9 | const R = require('ramda')
10 | const pluralize = require('pluralize')
11 | const join = require('path').join
12 | const utils = require('./utils')
13 | const formChangelog = require('simple-changelog')
14 | const la = require('lazy-ass')
15 | const is = require('check-more-types')
16 |
17 | // :: -> [issue numbers]
18 | function getClosedIssues () {
19 | return newPublicCommits().then(utils.commitsToIssues)
20 | }
21 |
22 | function getGitHub (githubUrl, token) {
23 | if (!token) {
24 | throw new Error('Missing gh token')
25 | }
26 | const githubConfig = githubUrl ? url.parse(githubUrl) : {}
27 |
28 | let protocol = (githubConfig.protocol || '').split(':')[0] || null
29 | if (protocol === 'git+https') {
30 | debug('switching github protocol from %s to https', protocol)
31 | protocol = 'https'
32 | }
33 |
34 | let host = githubConfig.hostname
35 | if (host === 'github.com') {
36 | // https://github.com/mikedeboer/node-github/issues/544
37 | debug('using api.github.com host')
38 | host = 'api.github.com'
39 | }
40 |
41 | const config = {
42 | version: '3.0.0',
43 | port: githubConfig.port,
44 | protocol,
45 | host
46 | }
47 | debug('github config')
48 | debug(config)
49 |
50 | const github = new GitHubApi(config)
51 |
52 | github.authenticate({
53 | type: 'token',
54 | token: token
55 | })
56 |
57 | const createComment = bluebird.promisify(github.issues.createComment)
58 | return createComment
59 | }
60 |
61 | function getGitHubToken () {
62 | return process.env.GH_TOKEN
63 | }
64 |
65 | function commentOnIssues (repoUrl, message, debugMode, issues) {
66 | if (!issues) {
67 | return Promise.resolve()
68 | }
69 | if (R.isEmpty(issues)) {
70 | debug('no issues to comment on')
71 | return Promise.resolve()
72 | }
73 |
74 | const createComment = debugMode
75 | ? () => Promise.resolve()
76 | : getGitHub(repoUrl, getGitHubToken())
77 | const parsed = parseGithubUrl(repoUrl)
78 | const owner = parsed[0]
79 | const repo = parsed[1]
80 | debug(
81 | 'commenting on %d %s: %j',
82 | issues.length,
83 | pluralize('issues', issues.length),
84 | issues
85 | )
86 |
87 | const onPosted = number => () => {
88 | console.log('posted comment for issue', number)
89 | }
90 |
91 | const onFailed = number => err => {
92 | console.error('Could not comment on issue', number)
93 | console.error(err)
94 | }
95 |
96 | const commentPromises = issues.map(number =>
97 | createComment({ owner, repo, number, body: message })
98 | .then(onPosted(number))
99 | .catch(onFailed(number))
100 | )
101 |
102 | return bluebird.all(commentPromises)
103 | }
104 |
105 | // should call "callback" function with (err, changelog)
106 | function githubPostRelease (pluginConfig, config, callback) {
107 | // debug('custom plugin config', pluginConfig)
108 | // debug('config parameter', config)
109 |
110 | // normalize message type
111 | let messageType = pluginConfig.type || 'npm'
112 | if (messageType === 'publish') {
113 | messageType = 'npm'
114 | }
115 | la(
116 | is.unemptyString(messageType),
117 | 'missing message type to form',
118 | pluginConfig
119 | )
120 | debug('message type "%s"', messageType)
121 |
122 | const pkg = config.pkg
123 | const repoUrl = pkg.repository.url
124 | const parsedRepo = parseGithubUrl(repoUrl)
125 |
126 | debug('published version %s', pkg.version)
127 | debug('repo url %s', repoUrl)
128 | debug('debug mode?', config.debug)
129 |
130 | const onSuccess = changelog => {
131 | debug('✅ all done, with issue message:')
132 | debug(message)
133 | debug('changelog:')
134 | debug(changelog)
135 | callback(null, changelog)
136 | }
137 |
138 | const onFailure = err => {
139 | console.error('🔥 failed with error')
140 | console.error(err)
141 | callback(err)
142 | }
143 |
144 | const commentingFailed = err => {
145 | console.error('😟 commenting on related issues failed')
146 | console.error(err)
147 | }
148 |
149 | const generateChangeLog = () => {
150 | debug('generate changelog for release version %s', pkg.version)
151 | return formChangelog(pkg.version)
152 | }
153 |
154 | const owner = parsedRepo[0]
155 | const repo = parsedRepo[1]
156 | const message = utils.formMessage(
157 | messageType,
158 | owner,
159 | repo,
160 | pkg.name,
161 | pkg.version
162 | )
163 |
164 | getClosedIssues()
165 | .then(R.partial(commentOnIssues, [repoUrl, message, config.debug]))
166 | .catch(commentingFailed)
167 | .then(generateChangeLog)
168 | .then(onSuccess, onFailure)
169 | }
170 |
171 | module.exports = githubPostRelease
172 |
173 | if (!module.parent) {
174 | console.log('demo run')
175 | const pkg = require(join(__dirname, '..', 'package.json'))
176 | const generateNotes = R.path(['release', 'generateNotes'])(pkg)
177 | const config = R.is(Object, generateNotes) ? generateNotes : {}
178 | debug('config')
179 | debug(config)
180 |
181 | githubPostRelease(config, { pkg: pkg }, (err, changelog) => {
182 | console.log('finished with')
183 | console.log('err?', err)
184 | console.log('changelog\n' + changelog)
185 | })
186 | }
187 |
--------------------------------------------------------------------------------
/src/postinstall.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | 'use strict'
4 |
5 | const debug = require('debug')('github-post-release')
6 | const amIaDependency = require('am-i-a-dependency')
7 | const isForced = process.argv.some(a => a === '--force')
8 |
9 | if (!amIaDependency() && !isForced) {
10 | // top level install (we are running `npm i` in this project)
11 | debug('we are installing own dependencies')
12 | process.exit(0)
13 | }
14 |
15 | debug('installing this module as a dependency')
16 |
17 | const path = require('path')
18 | const fs = require('fs')
19 |
20 | function clientPackageJsonFilename () {
21 | return path.join(process.cwd(), '..', '..', 'package.json')
22 | }
23 |
24 | function alreadyInstalled () {
25 | const filename = clientPackageJsonFilename()
26 | const pkg = require(filename)
27 | if (!pkg.release) {
28 | return false
29 | }
30 | if (!pkg.release.generateNotes) {
31 | return false
32 | }
33 | return true
34 | }
35 |
36 | function addPlugin () {
37 | const filename = clientPackageJsonFilename()
38 | const pkg = require(filename)
39 | if (!pkg.release) {
40 | pkg.release = {}
41 | }
42 | pkg.release.generateNotes = 'github-post-release'
43 | const text = JSON.stringify(pkg, null, 2) + '\n'
44 | fs.writeFileSync(filename, text, 'utf8')
45 | console.log('✅ set generate notes plugin in', filename)
46 | }
47 |
48 | if (alreadyInstalled()) {
49 | debug('plugin generateNotes already set')
50 | process.exit(0)
51 | }
52 |
53 | console.log('⚠️ Installing release generateNotes plugin github-post-release')
54 | addPlugin()
55 |
--------------------------------------------------------------------------------
/src/utils-spec.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | /* global describe, it */
4 | const snapshot = require('snap-shot')
5 |
6 | describe('utils', () => {
7 | describe('utils.', () => {
8 | const utils = require('./utils')
9 |
10 | it('forms url', () => {
11 | snapshot(
12 | utils.formReleaseUrl('bahmutov', 'github-post-release', 'v1.4.0')
13 | )
14 | })
15 | })
16 |
17 | describe('formReleaseText', () => {
18 | const utils = require('./utils')
19 |
20 | it('forms release text', () => {
21 | snapshot(utils.formReleaseText('github-post-release', 'v1.4.0'))
22 | })
23 | })
24 |
25 | describe('formMessage', () => {
26 | const utils = require('./utils')
27 |
28 | it('forms full message', () => {
29 | snapshot(
30 | utils.formMessage('npm', 'user', 'repo', 'my-module-name', '1.4.0')
31 | )
32 | })
33 |
34 | it('forms full publish message', () => {
35 | snapshot(
36 | utils.formMessage('publish', 'user', 'repo', 'my-module-name', '1.4.0')
37 | )
38 | })
39 |
40 | it('forms full deploy message', () => {
41 | snapshot(
42 | utils.formMessage('deploy', 'user', 'repo', 'my-module-name', '1.4.0')
43 | )
44 | })
45 | })
46 |
47 | describe('commitsToIssues', () => {
48 | const utils = require('./utils')
49 |
50 | it('finds fixed issue in 1 commit', () => {
51 | const commits = [
52 | {
53 | id: 'b0be94741486803ab26bb0397d8992e45bed1acd',
54 | message: 'fix(ggit): bring ggit fix #2',
55 | body: ''
56 | }
57 | ]
58 | const issues = utils.commitsToIssues(commits)
59 | snapshot(issues)
60 | })
61 |
62 | it('finds several closed issues', () => {
63 | const commits = [
64 | {
65 | id: 'b0be94741486803ab26bb0397d8992e45bed1acd',
66 | message: 'fix(ggit): bring ggit fix #2',
67 | body: 'and resolves #4'
68 | },
69 | {
70 | id: 'b0be94741486803ab26b50397d8992e45bed1acd',
71 | message: 'fix(ggit): subject',
72 | body: 'closed #2, #3'
73 | }
74 | ]
75 | const issues = utils.commitsToIssues(commits)
76 | snapshot(issues)
77 | })
78 | })
79 | })
80 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | const debug = require('debug')('github-post-release')
2 | const R = require('ramda')
3 | const commitCloses = require('commit-closes')
4 | const pluralize = require('pluralize')
5 | const tags = require('common-tags')
6 | const la = require('lazy-ass')
7 | const is = require('check-more-types')
8 |
9 | function hasIssues (issues) {
10 | return issues.length > 0
11 | }
12 |
13 | function findIssues (commit) {
14 | return commitCloses(commit.message, commit.body)
15 | }
16 |
17 | function commitsToIssues (commits) {
18 | debug(
19 | 'have %d semantic %s',
20 | commits.length,
21 | pluralize('commit', commits.length)
22 | )
23 | if (!commits.length) {
24 | return []
25 | }
26 | debug(commits)
27 |
28 | const closedIssues = R.flatten(commits.map(findIssues).filter(hasIssues))
29 | debug('semantic commits close the following issues')
30 | debug(closedIssues)
31 | const uniqueIssues = R.uniq(closedIssues)
32 | debug('unique closed issues', uniqueIssues)
33 | return uniqueIssues
34 | }
35 |
36 | function formReleaseUrl (user, repo, tag) {
37 | return `https://github.com/${user}/${repo}/releases/tag/${tag}`
38 | }
39 |
40 | function formReleaseText (repo, tag) {
41 | return `${repo}/releases/tag/${tag}`
42 | }
43 |
44 | function formNpmMessage (owner, repo, name, version) {
45 | la(arguments.length === 4, 'invalid arguments', arguments)
46 |
47 | const vTag = `v${version}`
48 | const releaseText = formReleaseText(repo, vTag)
49 | const releaseUrl = formReleaseUrl(owner, repo, vTag)
50 | const nextUpdateUrl = 'https://github.com/bahmutov/next-update'
51 | const message = tags.stripIndent`
52 | Version \`${version}\` has been published to NPM. The full release note can be found at [${releaseText}](${releaseUrl}).
53 |
54 | **Tip:** safely upgrade dependency ${name} in your project using [next-update](${nextUpdateUrl})
55 | `
56 | return message
57 | }
58 |
59 | function formDeployMessage (owner, repo, name, version) {
60 | la(arguments.length === 4, 'invalid arguments', arguments)
61 |
62 | const vTag = `v${version}`
63 | const releaseText = formReleaseText(repo, vTag)
64 | const releaseUrl = formReleaseUrl(owner, repo, vTag)
65 | const message = tags.stripIndent`
66 | Version \`${version}\` has been deployed. The full release note can be found at [${releaseText}](${releaseUrl}).
67 | `
68 | return message
69 | }
70 |
71 | const isValidType = is.oneOf(['npm', 'publish', 'deploy'])
72 |
73 | function formMessage (type, owner, repo, name, version) {
74 | la(isValidType(type), 'invalid message type', type)
75 | const form = {
76 | npm: formNpmMessage,
77 | publish: formNpmMessage,
78 | deploy: formDeployMessage
79 | }
80 | const action = form[type]
81 | la(is.fn(action), 'cannot find action for type', type)
82 | return action(owner, repo, name, version)
83 | }
84 |
85 | module.exports = {
86 | commitsToIssues: commitsToIssues,
87 | formReleaseUrl: formReleaseUrl,
88 | formReleaseText: formReleaseText,
89 | formMessage: formMessage
90 | }
91 |
--------------------------------------------------------------------------------