Skip to content

Commit cf51a0f

Browse files
committed
feat!: switch to ESM, fix output colourising, dedupe more code w/ branch-diff
* upgrade all deps, including ESM-only deps * --simple is now default output, including colourising, with --markdown being an opt-in output format. * move process + print logic to separate module for exporting for simplifying branch-diff * add --sha and --reverse options from branch-diff to dedupe some processing code Fixes: #120 Closes: #107 Closes: #119
1 parent c37c159 commit cf51a0f

11 files changed

+230
-239
lines changed

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,15 @@ npm i changelog-maker -g
4444

4545
## Usage
4646

47-
**`changelog-maker [--simple] [--group] [--commit-url=<url/with/{ref}>] [--start-ref=<ref>] [--end-ref=<ref>] [github-user[, github-project]]`**
47+
**`changelog-maker [--plaintext|p] [--markdown|md] [--sha] [--group|-g] [--reverse] [--commit-url=<url/with/{ref}>] [--start-ref=<ref>] [--end-ref=<ref>] [github-user[, github-project]]`**
4848

4949
`github-user` and `github-project` should point to the GitHub repository that can be used to find the `PR-URL` data if just an issue number is provided and will also impact how the PR-URL issue numbers are displayed
5050

51-
* `--simple`: print a simple form, without additional Markdown cruft
51+
* `--plaintext`: print a very simple form, without commit details, implies `--group`
52+
* `--markdown`: print a Markdown formatted from, with links and proper escaping
53+
* `--sha`: print only the list of short-form commit hashes
5254
* `--group`: reorder commits so that they are listed in groups where the `xyz:` prefix of the commit message defines the group. Commits are listed in original order _within_ group.
55+
* `--reverse`: reverse the order of commits when printed, does not work with `--reverse`
5356
* `--commit-url`: pass in a url template which will be used to generate commit URLs for a repository not hosted in Github. `{ref}` is the placeholder that will be replaced with the commit, i.e. `--commit-url=https://gitlab.com/myUser/myRepo/commit/{ref}`
5457
* `--start-ref=<ref>`: use the given git `<ref>` as a starting point rather than the _last tag_. The `<ref>` can be anything commit-ish including a commit sha, tag, branch name. If you specify a `--start-ref` argument the commit log will not be pruned so that version commits and `working on <version>` commits are left in the list.
5558
* `--end-ref=<ref>`: use the given git `<ref>` as a end-point rather than the _now_. The `<ref>` can be anything commit-ish including a commit sha, tag, branch name.

changelog-maker.js

Lines changed: 49 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,26 @@
11
#!/usr/bin/env node
22

3-
'use strict'
4-
5-
const fs = require('fs')
6-
const path = require('path')
7-
const split2 = require('split2')
8-
const list = require('list-stream')
9-
const stripAnsi = require('strip-ansi')
10-
const pkgtoId = require('pkg-to-id')
11-
const commitStream = require('commit-stream')
12-
const gitexec = require('gitexec')
13-
const { commitToOutput, formatType } = require('./commit-to-output')
14-
const groupCommits = require('./group-commits')
15-
const collectCommitLabels = require('./collect-commit-labels')
16-
const { isReleaseCommit, toGroups } = require('./groups')
17-
const pkg = require('./package.json')
18-
const debug = require('debug')(pkg.name)
19-
const argv = require('minimist')(process.argv.slice(2))
20-
21-
// Skip on formatting on Node.js 10.
22-
const formatMarkdown = process.versions.node.startsWith('10.') ? false : import('./format.mjs')
23-
24-
const quiet = argv.quiet || argv.q
3+
import { existsSync, readFileSync } from 'fs'
4+
import { join } from 'path'
5+
import process from 'process'
6+
import stream from 'stream'
7+
import split2 from 'split2'
8+
import pkgtoId from 'pkg-to-id'
9+
import commitStream from 'commit-stream'
10+
import gitexec from 'gitexec'
11+
import _debug from 'debug'
12+
import minimist from 'minimist'
13+
import { processCommits } from './process-commits.js'
14+
import { isReleaseCommit } from './groups.js'
15+
16+
const { pipeline } = stream.promises
17+
const debug = _debug('changelog-maker')
18+
const argv = minimist(process.argv.slice(2))
2519
const help = argv.h || argv.help
26-
const commitUrl = argv['commit-url'] || 'https://github.com/{ghUser}/{ghRepo}/commit/{ref}'
27-
const pkgFile = path.join(process.cwd(), 'package.json')
28-
const pkgData = fs.existsSync(pkgFile) ? require(pkgFile) : {}
20+
const pkgFile = join(process.cwd(), 'package.json')
21+
const pkgData = existsSync(pkgFile) ? JSON.parse(readFileSync(pkgFile)) : {}
2922
const pkgId = pkgtoId(pkgData)
3023

31-
const getFormat = () => {
32-
if (argv.simple || argv.s) {
33-
return formatType.SIMPLE
34-
} else if (argv.plaintext || argv.p) {
35-
return formatType.PLAINTEXT
36-
}
37-
return formatType.MARKDOWN
38-
}
39-
4024
const ghId = {
4125
user: argv._[0] || pkgId.user || 'nodejs',
4226
repo: argv._[1] || (pkgId.name && stripScope(pkgId.name)) || 'node'
@@ -58,14 +42,11 @@ if (help) {
5842
}
5943

6044
function showUsage () {
61-
let usage = fs.readFileSync(path.join(__dirname, 'README.md'), 'utf8')
45+
const usage = readFileSync(new URL('README.md', import.meta.url), 'utf8')
6246
.replace(/[\s\S]+(## Usage\n[\s\S]*)\n## [\s\S]+/m, '$1')
63-
if (process.stdout.isTTY) {
64-
usage = usage
65-
.replace(/## Usage\n[\s]*/m, '')
66-
.replace(/\*\*/g, '')
67-
.replace(/`/g, '')
68-
}
47+
.replace(/## Usage\n[\s]*/m, 'Usage: ')
48+
.replace(/\*\*/g, '')
49+
.replace(/`/g, '')
6950

7051
process.stdout.write(usage)
7152
}
@@ -81,6 +62,18 @@ function replace (s, m) {
8162
return s
8263
}
8364

65+
const _startrefcmd = replace(refcmd, { ref: argv['start-ref'] || defaultRef })
66+
const _endrefcmd = argv['end-ref'] && replace(refcmd, { ref: argv['end-ref'] })
67+
const _sincecmd = replace(commitdatecmd, { refcmd: _startrefcmd })
68+
const _untilcmd = argv['end-ref'] ? replace(commitdatecmd, { refcmd: _endrefcmd }) : untilcmd
69+
const _gitcmd = replace(gitcmd, { sincecmd: _sincecmd, untilcmd: _untilcmd })
70+
71+
debug('%s', _startrefcmd)
72+
debug('%s', _endrefcmd)
73+
debug('%s', _sincecmd)
74+
debug('%s', _untilcmd)
75+
debug('%s', _gitcmd)
76+
8477
function organiseCommits (list) {
8578
if (argv['start-ref'] || argv.a || argv.all) {
8679
if (argv['filter-release']) {
@@ -105,86 +98,22 @@ function organiseCommits (list) {
10598
})
10699
}
107100

108-
async function printCommits (list) {
109-
for await (let commit of list) {
110-
if (!process.stdout.isTTY) {
111-
commit = stripAnsi(commit)
112-
}
113-
process.stdout.write(commit)
114-
}
115-
}
116-
117-
function onCommitList (err, list) {
118-
if (err) {
119-
return fatal(err)
120-
}
121-
122-
list = organiseCommits(list)
123-
124-
collectCommitLabels(list, (err) => {
125-
if (err) {
126-
return fatal(err)
127-
}
128-
129-
if (argv.group) {
130-
list = groupCommits(list)
131-
}
132-
133-
const format = getFormat()
134-
if (format === formatType.PLAINTEXT) {
135-
const formatted = []
136-
137-
let currentGroup
138-
for (const commit of list) {
139-
const commitGroup = toGroups(commit.summary)
140-
if (currentGroup !== commitGroup) {
141-
formatted.push(`${commitGroup}:`)
142-
currentGroup = commitGroup
143-
}
144-
formatted.push(commitToOutput(commit, formatType.PLAINTEXT, ghId, commitUrl))
101+
async function run () {
102+
let commitList = []
103+
await pipeline(
104+
gitexec.exec(process.cwd(), _gitcmd),
105+
split2(),
106+
commitStream(ghId.user, ghId.repo),
107+
async function * (source) {
108+
for await (const commit of source) {
109+
commitList.push(commit)
145110
}
146-
147-
list = formatted.map((line) => `${line}\n`)
148-
} else {
149-
list = list.map(async (commit) => {
150-
let output = commitToOutput(commit, format, ghId, commitUrl)
151-
if (format === formatType.MARKDOWN) {
152-
if (!process.stdout.isTTY) {
153-
output = stripAnsi(output)
154-
}
155-
if (process.versions.node.startsWith('10.')) {
156-
return `${output}\n`
157-
}
158-
return formatMarkdown.then((module) => module.default(output))
159-
}
160-
return `${output}\n`
161-
})
162-
}
163-
164-
if (!quiet) {
165-
printCommits(list)
166-
}
167-
})
111+
})
112+
commitList = organiseCommits(commitList)
113+
await processCommits(argv, ghId, commitList)
168114
}
169115

170-
function fatal (err) {
171-
console.error(`Fatal error: ${err.message}`)
116+
run().catch((err) => {
117+
console.error(err)
172118
process.exit(1)
173-
}
174-
175-
const _startrefcmd = replace(refcmd, { ref: argv['start-ref'] || defaultRef })
176-
const _endrefcmd = argv['end-ref'] && replace(refcmd, { ref: argv['end-ref'] })
177-
const _sincecmd = replace(commitdatecmd, { refcmd: _startrefcmd })
178-
const _untilcmd = argv['end-ref'] ? replace(commitdatecmd, { refcmd: _endrefcmd }) : untilcmd
179-
const _gitcmd = replace(gitcmd, { sincecmd: _sincecmd, untilcmd: _untilcmd })
180-
181-
debug('%s', _startrefcmd)
182-
debug('%s', _endrefcmd)
183-
debug('%s', _sincecmd)
184-
debug('%s', _untilcmd)
185-
debug('%s', _gitcmd)
186-
187-
gitexec.exec(process.cwd(), _gitcmd)
188-
.pipe(split2())
189-
.pipe(commitStream(ghId.user, ghId.repo))
190-
.pipe(list.obj(onCommitList))
119+
})

collect-commit-labels.js

Lines changed: 38 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,63 @@
11
'use strict'
22

3-
const ghauth = require('ghauth')
4-
const ghissues = require('ghissues')
5-
const async = require('async')
3+
import { promisify } from 'util'
4+
import ghauth from 'ghauth'
5+
import ghissues from 'ghissues'
6+
import async from 'async'
67

78
const authOptions = {
89
configName: 'changelog-maker',
910
scopes: ['repo'],
1011
noDeviceFlow: true
1112
}
1213

13-
function collectCommitLabels (list, callback) {
14+
export async function collectCommitLabels (list) {
1415
const sublist = list.filter((commit) => {
1516
return typeof commit.ghIssue === 'number' && commit.ghUser && commit.ghProject
1617
})
1718

1819
if (!sublist.length) {
19-
return setImmediate(callback)
20+
return
2021
}
2122

22-
ghauth(authOptions, (err, authData) => {
23-
if (err) {
24-
return callback(err)
25-
}
26-
27-
const cache = {}
28-
29-
const q = async.queue((commit, next) => {
30-
function onFetch (err, issue) {
31-
if (err) {
32-
console.error('Error fetching issue #%s: %s', commit.ghIssue, err.message)
33-
return next()
34-
}
23+
const authData = await promisify(ghauth)(authOptions)
3524

36-
if (issue.labels) {
37-
commit.labels = issue.labels.map((label) => label.name)
38-
}
25+
const cache = {}
3926

40-
next()
27+
const q = async.queue((commit, next) => {
28+
function onFetch (err, issue) {
29+
if (err) {
30+
console.error('Error fetching issue #%s: %s', commit.ghIssue, err.message)
31+
return next()
4132
}
4233

43-
if (commit.ghUser === 'iojs') {
44-
commit.ghUser = 'nodejs' // forcibly rewrite as the GH API doesn't do it for us
34+
if (issue.labels) {
35+
commit.labels = issue.labels.map((label) => label.name)
4536
}
4637

47-
// To prevent multiple simultaneous requests for the same issue
48-
// from hitting the network at the same time, immediately assign a Promise
49-
// to the cache that all commits with the same ghIssue value will use.
50-
const key = `${commit.ghUser}/${commit.ghProject}#${commit.ghIssue}`
51-
cache[key] = cache[key] || new Promise((resolve, reject) => {
52-
ghissues.get(authData, commit.ghUser, commit.ghProject, commit.ghIssue, (err, issue) => {
53-
if (err) {
54-
return reject(err)
55-
}
38+
next()
39+
}
5640

57-
resolve(issue)
58-
})
41+
if (commit.ghUser === 'iojs') {
42+
commit.ghUser = 'nodejs' // forcibly rewrite as the GH API doesn't do it for us
43+
}
44+
45+
// To prevent multiple simultaneous requests for the same issue
46+
// from hitting the network at the same time, immediately assign a Promise
47+
// to the cache that all commits with the same ghIssue value will use.
48+
const key = `${commit.ghUser}/${commit.ghProject}#${commit.ghIssue}`
49+
cache[key] = cache[key] || new Promise((resolve, reject) => {
50+
ghissues.get(authData, commit.ghUser, commit.ghProject, commit.ghIssue, (err, issue) => {
51+
if (err) {
52+
return reject(err)
53+
}
54+
55+
resolve(issue)
5956
})
60-
cache[key].then((val) => onFetch(null, val), (err) => onFetch(err))
61-
}, 15)
62-
q.drain(callback)
63-
q.push(sublist)
64-
})
65-
}
57+
})
58+
cache[key].then((val) => onFetch(null, val), (err) => onFetch(err))
59+
}, 15)
6660

67-
module.exports = collectCommitLabels
61+
q.push(sublist)
62+
await q.drain()
63+
}

commit-to-output.js

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
'use strict'
2-
3-
const chalk = require('chalk')
4-
const reverts = require('./reverts')
5-
const groups = require('./groups')
1+
import chalk from 'chalk'
2+
import { isRevert, cleanSummary as cleanRevertSummary } from './reverts.js'
3+
import { toGroups, cleanSummary as cleanGroupSummary } from './groups.js'
64

75
function cleanUnsupportedMarkdown (txt) {
86
// escape _~*\[]<>`
97
return txt.replace(/([_~*\\[\]<>`])/g, '\\$1')
108
}
9+
1110
function cleanMarkdown (txt) {
1211
// Escape backticks for edge case scenarii (no code span support).
1312
if (txt.includes('``') || txt.includes('\\`')) {
@@ -30,7 +29,8 @@ function cleanMarkdown (txt) {
3029
return cleanMdString
3130
}
3231

33-
const formatType = {
32+
export const formatType = {
33+
SHA: 'sha',
3434
PLAINTEXT: 'plaintext',
3535
MARKDOWN: 'markdown',
3636
SIMPLE: 'simple'
@@ -65,7 +65,7 @@ function toStringSimple (data) {
6565
s = s.trim()
6666

6767
return (data.semver && data.semver.length)
68-
? chalk.green(chalk.bold(s))
68+
? chalk.green.bold(s)
6969
: (data.group === 'doc'
7070
? chalk.grey(s)
7171
: s)
@@ -84,13 +84,13 @@ function toStringMarkdown (data) {
8484
s = s.trim()
8585

8686
return (data.semver && data.semver.length)
87-
? chalk.green(chalk.bold(s))
87+
? chalk.green.bold(s)
8888
: (data.group === 'doc'
8989
? chalk.grey(s)
9090
: s)
9191
}
9292

93-
function commitToOutput (commit, format, ghId, commitUrl) {
93+
export function commitToOutput (commit, format, ghId, commitUrl) {
9494
const data = {}
9595
const prUrlMatch = commit.prUrl && commit.prUrl.match(/^https?:\/\/.+\/([^/]+\/[^/]+)\/\w+\/\d+$/i)
9696
const urlHash = `#${commit.ghIssue}` || commit.prUrl
@@ -99,9 +99,9 @@ function commitToOutput (commit, format, ghId, commitUrl) {
9999
data.sha = commit.sha
100100
data.shaUrl = commitUrl.replace(/\{ghUser\}/g, ghId.user).replace(/\{ghRepo\}/g, ghId.repo).replace(/\{ref\}/g, ref)
101101
data.semver = commit.labels && commit.labels.filter((l) => l.includes('semver'))
102-
data.revert = reverts.isRevert(commit.summary)
103-
data.group = groups.toGroups(commit.summary)
104-
data.summary = groups.cleanSummary(reverts.cleanSummary(commit.summary))
102+
data.revert = isRevert(commit.summary)
103+
data.group = toGroups(commit.summary)
104+
data.summary = cleanGroupSummary(cleanRevertSummary(commit.summary))
105105
data.author = (commit.author && commit.author.name) || ''
106106
data.pr = prUrlMatch && ((prUrlMatch[1] !== `${ghId.user}/${ghId.repo}` ? prUrlMatch[1] : '') + urlHash)
107107
data.prUrl = prUrlMatch && commit.prUrl
@@ -114,8 +114,3 @@ function commitToOutput (commit, format, ghId, commitUrl) {
114114

115115
return toStringMarkdown(data)
116116
}
117-
118-
module.exports = {
119-
commitToOutput,
120-
formatType
121-
}

0 commit comments

Comments
 (0)