UNPKG

6.81 kBJavaScriptView Raw
1#!/usr/bin/env node
2import { resolve, extname } from 'path'
3import { pathToFileURL } from 'url'
4import {
5 createReadStream,
6 createWriteStream
7} from 'fs'
8import { readFile } from 'fs/promises'
9import addStream from 'add-stream'
10import tempfile from 'tempfile'
11import meow from 'meow'
12import conventionalChangelog from 'conventional-changelog'
13
14function relativeResolve (filePath) {
15 return pathToFileURL(resolve(process.cwd(), filePath))
16}
17
18async function loadDataFile (filePath) {
19 const resolvedFilePath = relativeResolve(filePath)
20 const ext = extname(resolvedFilePath.toString())
21
22 if (ext === '.json') {
23 return JSON.parse(await readFile(resolvedFilePath, 'utf8'))
24 }
25
26 return (await import(resolvedFilePath)).default
27}
28
29const cli = meow(`
30 Usage
31 conventional-changelog
32
33 Example
34 conventional-changelog -i CHANGELOG.md --same-file
35
36 Options
37 -i, --infile Read the CHANGELOG from this file
38
39 -o, --outfile Write the CHANGELOG to this file
40 If unspecified, it prints to stdout
41
42 -s, --same-file Outputting to the infile so you don't need to specify the same file as outfile
43
44 -p, --preset Name of the preset you want to use. Must be one of the following:
45 angular, atom, codemirror, conventionalcommits, ember, eslint, express, jquery or jshint
46
47 -k, --pkg A filepath of where your package.json is located
48 Default is the closest package.json from cwd
49
50 -a, --append Should the newer release be appended to the older release
51 Default: false
52
53 -r, --release-count How many releases to be generated from the latest
54 If 0, the whole changelog will be regenerated and the outfile will be overwritten
55 Default: 1
56
57 --skip-unstable If given, unstable tags will be skipped, e.g., x.x.x-alpha.1, x.x.x-rc.2
58
59 -u, --output-unreleased Output unreleased changelog
60
61 -v, --verbose Verbose output. Use this for debugging
62 Default: false
63
64 -n, --config A filepath of your config script
65 Example of a config script: https://github.com/conventional-changelog/conventional-changelog/blob/master/packages/conventional-changelog-cli/test/fixtures/config.cjs
66
67 -c, --context A filepath of a json that is used to define template variables
68 -l, --lerna-package Generate a changelog for a specific lerna package (:pkg-name@1.0.0)
69 -t, --tag-prefix Tag prefix to consider when reading the tags
70 --commit-path Generate a changelog scoped to a specific directory
71`, {
72 importMeta: import.meta,
73 booleanDefault: undefined,
74 flags: {
75 infile: {
76 shortFlag: 'i',
77 type: 'string'
78 },
79 outfile: {
80 shortFlag: 'o',
81 type: 'string'
82 },
83 sameFile: {
84 shortFlag: 's',
85 type: 'boolean'
86 },
87 preset: {
88 shortFlag: 'p',
89 type: 'string'
90 },
91 pkg: {
92 shortFlag: 'k',
93 type: 'string'
94 },
95 append: {
96 shortFlag: 'a',
97 type: 'boolean'
98 },
99 releaseCount: {
100 shortFlag: 'r',
101 type: 'number'
102 },
103 skipUnstable: {
104 type: 'boolean'
105 },
106 outputUnreleased: {
107 shortFlag: 'u',
108 type: 'boolean'
109 },
110 verbose: {
111 shortFlag: 'v',
112 type: 'boolean'
113 },
114 config: {
115 shortFlag: 'n',
116 type: 'string'
117 },
118 context: {
119 shortFlag: 'c',
120 type: 'string'
121 },
122 lernaPackage: {
123 shortFlag: 'l',
124 type: 'string'
125 },
126 tagPrefix: {
127 shortFlag: 't',
128 type: 'string'
129 }
130 }
131})
132
133let config
134const flags = cli.flags
135const infile = flags.infile
136let outfile = flags.outfile
137let sameFile = flags.sameFile
138const append = flags.append
139const releaseCount = flags.releaseCount
140const skipUnstable = flags.skipUnstable
141
142if (infile && infile === outfile) {
143 sameFile = true
144} else if (sameFile) {
145 if (infile) {
146 outfile = infile
147 } else {
148 console.error('infile must be provided if same-file flag presents.')
149 process.exit(1)
150 }
151}
152
153let options = {
154 preset: flags.preset,
155 pkg: {
156 path: flags.pkg
157 },
158 append,
159 releaseCount,
160 skipUnstable,
161 outputUnreleased: flags.outputUnreleased,
162 lernaPackage: flags.lernaPackage,
163 tagPrefix: flags.tagPrefix
164}
165
166if (flags.verbose) {
167 options.debug = console.info.bind(console)
168 options.warn = console.warn.bind(console)
169}
170
171let templateContext
172
173let outStream
174
175try {
176 if (flags.context) {
177 templateContext = await loadDataFile(flags.context)
178 }
179
180 if (flags.config) {
181 config = await loadDataFile(flags.config)
182 options.config = config
183
184 if (config.options) {
185 options = {
186 ...options,
187 ...config.options,
188 pkg: {
189 ...options.pkg,
190 ...config.options.pkg
191 }
192 }
193 }
194 } else {
195 config = {}
196 }
197} catch (err) {
198 console.error('Failed to get file. ' + err)
199 process.exit(1)
200}
201
202const gitRawCommitsOpts = {
203 ...config.gitRawCommitsOpts
204}
205if (flags.commitPath) gitRawCommitsOpts.path = flags.commitPath
206
207const changelogStream = conventionalChangelog(options, templateContext, gitRawCommitsOpts, config.parserOpts, config.writerOpts)
208 .on('error', (err) => {
209 if (flags.verbose) {
210 console.error(err.stack)
211 } else {
212 console.error(err.toString())
213 }
214 process.exit(1)
215 })
216
217function noInputFile () {
218 if (outfile) {
219 outStream = createWriteStream(outfile)
220 } else {
221 outStream = process.stdout
222 }
223
224 changelogStream
225 .pipe(outStream)
226}
227
228if (infile && releaseCount !== 0) {
229 const readStream = createReadStream(infile)
230 .on('error', () => {
231 if (flags.verbose) {
232 console.warn('infile does not exist.')
233 }
234
235 if (sameFile) {
236 noInputFile()
237 }
238 })
239
240 if (sameFile) {
241 if (options.append) {
242 changelogStream
243 .pipe(createWriteStream(outfile, {
244 flags: 'a'
245 }))
246 } else {
247 const tmp = tempfile()
248
249 changelogStream
250 .pipe(addStream(readStream))
251 .pipe(createWriteStream(tmp))
252 .on('finish', () => {
253 createReadStream(tmp)
254 .pipe(createWriteStream(outfile))
255 })
256 }
257 } else {
258 if (outfile) {
259 outStream = createWriteStream(outfile)
260 } else {
261 outStream = process.stdout
262 }
263
264 let stream
265
266 if (options.append) {
267 stream = readStream
268 .pipe(addStream(changelogStream))
269 } else {
270 stream = changelogStream
271 .pipe(addStream(readStream))
272 }
273
274 stream
275 .pipe(outStream)
276 }
277} else {
278 noInputFile()
279}