├── .gitattributes
├── .gitignore
├── .jscsrc
├── .jshintrc
├── .travis.yml
├── CHANGELOG.md
├── appveyor.yml
├── hosts
├── bitbucket.json
├── github.json
└── gitlab.json
├── index.js
├── lib
└── merge-config.js
├── package.json
├── readme.md
└── test
├── fixtures
├── _host-only.json
├── _malformation.json
├── _package.json
├── _short.json
├── _unknown-host.json
└── _version-only.json
└── test.js
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | tmp
3 |
--------------------------------------------------------------------------------
/.jscsrc:
--------------------------------------------------------------------------------
1 | {
2 | "preset": "google",
3 | "maximumLineLength": null,
4 | "excludeFiles": ["node_modules/**"]
5 | }
6 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "boss": true,
3 | "curly": true,
4 | "eqeqeq": true,
5 | "eqnull": true,
6 | "immed": true,
7 | "latedef": true,
8 | "mocha" : true,
9 | "newcap": true,
10 | "noarg": true,
11 | "node": true,
12 | "sub": true,
13 | "undef": true,
14 | "unused": true
15 | }
16 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '6'
4 | - '5'
5 | - '4'
6 | - '0.12'
7 | before_script:
8 | - git config --global user.name 'Travis-CI'
9 | - git config --global user.email 'dummy@example.org'
10 | after_script: NODE_ENV=test istanbul cover ./node_modules/mocha/bin/_mocha -- -R spec --timeout 30000 && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage
11 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | # [1.5.0](https://github.com/conventional-changelog/conventional-changelog-core/compare/v1.4.0...v1.5.0) (2016-05-10)
3 |
4 |
5 | ### Features
6 |
7 | * **context:** fallback to repoUrl([da0b096](https://github.com/conventional-changelog/conventional-changelog-core/commit/da0b096)), closes [#7](https://github.com/conventional-changelog/conventional-changelog-core/issues/7)
8 |
9 |
10 |
11 |
12 | # [1.4.0](https://github.com/conventional-changelog/conventional-changelog-core/compare/v1.3.4...v1.4.0) (2016-05-08)
13 |
14 |
15 | ### Features
16 |
17 | * **debug:** make options.debug as default writeOpts.debug([eeb7e8f](https://github.com/conventional-changelog/conventional-changelog-core/commit/eeb7e8f))
18 |
19 |
20 |
21 |
22 | ## [1.3.4](https://github.com/conventional-changelog/conventional-changelog-core/compare/v1.3.3...v1.3.4) (2016-05-07)
23 |
24 |
25 | ### Bug Fixes
26 |
27 | * **mergeConfig:** respect issuePrefixes option ([4be052b](https://github.com/conventional-changelog/conventional-changelog-core/commit/4be052b)), closes [#6](https://github.com/conventional-changelog/conventional-changelog-core/issues/6) [#8](https://github.com/conventional-changelog/conventional-changelog-core/issues/8)
28 |
29 |
30 |
31 |
32 | ## [1.3.3](https://github.com/conventional-changelog/conventional-changelog-core/compare/v1.3.2...v1.3.3) (2016-04-19)
33 |
34 |
35 | ### Bug Fixes
36 |
37 | * **unknownHost:** default context.repository ([eaa3b6f](https://github.com/conventional-changelog/conventional-changelog-core/commit/eaa3b6f))
38 |
39 |
40 |
41 |
42 | ## [1.3.2](https://github.com/conventional-changelog/conventional-changelog-core/compare/v1.3.1...v1.3.2) (2016-04-17)
43 |
44 |
45 |
46 |
47 |
48 | ## [1.3.1](https://github.com/conventional-changelog/conventional-changelog-core/compare/v1.3.0...v1.3.1) (2016-04-09)
49 |
50 |
51 | ### Bug Fixes
52 |
53 | * **defaults:** context tags ([2571038](https://github.com/conventional-changelog/conventional-changelog-core/commit/2571038))
54 |
55 |
56 |
57 |
58 | # [1.3.0](https://github.com/stevemao/conventional-changelog-core/compare/v1.2.0...v1.3.0) (2016-02-13)
59 |
60 |
61 | ### Features
62 |
63 | * **debug:** add options.debug function ([aa56ae6](https://github.com/stevemao/conventional-changelog-core/commit/aa56ae6))
64 |
65 |
66 |
67 |
68 | # [1.2.0](https://github.com/stevemao/conventional-changelog-core/compare/v1.1.0...v1.2.0) (2016-02-11)
69 |
70 |
71 | ### Features
72 |
73 | * **merge:** ignore merge commits ([8f788dc](https://github.com/stevemao/conventional-changelog-core/commit/8f788dc))
74 |
75 |
76 |
77 |
78 | # [1.1.0](https://github.com/stevemao/conventional-changelog-core/compare/v1.0.2...v1.1.0) (2016-02-08)
79 |
80 |
81 | ### Bug Fixes
82 |
83 | * **default:** firstCommit and lastCommit should based on original unfiltered commits ([7fc49c9](https://github.com/stevemao/conventional-changelog-core/commit/7fc49c9)), closes [#2](https://github.com/stevemao/conventional-changelog-core/issues/2)
84 |
85 |
86 |
87 |
88 | ## [1.0.2](https://github.com/stevemao/conventional-changelog-core/compare/v1.0.1...v1.0.2) (2016-02-06)
89 |
90 |
91 | ### Bug Fixes
92 |
93 | * **currentTag:** if unreleased, currentTag should be last commit hash ([e3d25ae](https://github.com/stevemao/conventional-changelog-core/commit/e3d25ae))
94 |
95 |
96 |
97 |
98 | ## [1.0.1](https://github.com/stevemao/conventional-changelog-core/compare/v1.0.0...v1.0.1) (2016-02-06)
99 |
100 |
101 | ### Bug Fixes
102 |
103 | * **unreleased:** now it can output unreleased commits ([87b7340](https://github.com/stevemao/conventional-changelog-core/commit/87b7340))
104 |
105 |
106 |
107 |
108 | # [1.0.0](https://github.com/stevemao/conventional-changelog-core/compare/v0.0.2...v1.0.0) (2016-02-05)
109 |
110 |
111 | ### Bug Fixes
112 |
113 | * **oldNode:** git remote origin url feature is only available under node>=4 ([c69db53](https://github.com/stevemao/conventional-changelog-core/commit/c69db53))
114 |
115 | ### Features
116 |
117 | * **pkg:** fallback to git remote origin url ([5b56952](https://github.com/stevemao/conventional-changelog-core/commit/5b56952))
118 | * **unreleased:** option to output or not unreleased changelog ([9dfe8d8](https://github.com/stevemao/conventional-changelog-core/commit/9dfe8d8)), closes [ajoslin/conventional-changelog#120](https://github.com/ajoslin/conventional-changelog/issues/120)
119 |
120 |
121 | ### BREAKING CHANGES
122 |
123 | * unreleased: If `context.version` is the same as the version of the last release, by default the unreleased chagnelog will not output.
124 |
125 |
126 |
127 |
128 | ## [0.0.2](https://github.com/stevemao/conventional-changelog-core/compare/v0.0.1...v0.0.2) (2016-01-30)
129 |
130 |
131 | ### Bug Fixes
132 |
133 | * **error:** better error handling ([614ce1a](https://github.com/stevemao/conventional-changelog-core/commit/614ce1a)), closes [ajoslin/conventional-changelog#130](https://github.com/ajoslin/conventional-changelog/issues/130)
134 |
135 |
136 |
137 |
138 | ## 0.0.1 (2015-12-26)
139 |
140 |
141 | ### Features
142 |
143 | * **config:** change preset to config ([85fd9d9](https://github.com/stevemao/conventional-changelog-core/commit/85fd9d9))
144 | * **init:** extract core from conventional-changelog ([4a4bca3](https://github.com/stevemao/conventional-changelog-core/commit/4a4bca3))
145 |
146 |
147 | ### BREAKING CHANGES
148 |
149 | * config: `options.preset` is removed in favour of `options.config`
150 |
151 |
152 |
153 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | environment:
2 | matrix:
3 | - nodejs_version: '6'
4 | - nodejs_version: '5'
5 | - nodejs_version: '4'
6 | - nodejs_version: '0.12'
7 | install:
8 | - ps: Install-Product node $env:nodejs_version
9 | - set CI=true
10 | - npm -g install npm@latest
11 | - set PATH=%APPDATA%\npm;%PATH%
12 | - npm install
13 | matrix:
14 | fast_finish: true
15 | build: off
16 | version: '{build}'
17 | test_script:
18 | - git config --global user.name "Appveyor-CI"
19 | - git config --global user.email "dummy@example.org"
20 | - node --version
21 | - npm --version
22 | - npm test
23 |
--------------------------------------------------------------------------------
/hosts/bitbucket.json:
--------------------------------------------------------------------------------
1 | {
2 | "issue": "issue",
3 | "commit": "commits",
4 | "referenceActions": [
5 | "close",
6 | "closes",
7 | "closed",
8 | "closing",
9 | "fix",
10 | "fixes",
11 | "fixed",
12 | "fixing",
13 | "resolve",
14 | "resolves",
15 | "resolved",
16 | "resolving"
17 | ],
18 | "issuePrefixes": [
19 | "#"
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/hosts/github.json:
--------------------------------------------------------------------------------
1 | {
2 | "issue": "issues",
3 | "commit": "commit",
4 | "referenceActions": [
5 | "close",
6 | "closes",
7 | "closed",
8 | "fix",
9 | "fixes",
10 | "fixed",
11 | "resolve",
12 | "resolves",
13 | "resolved"
14 | ],
15 | "issuePrefixes": [
16 | "#",
17 | "gh-"
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/hosts/gitlab.json:
--------------------------------------------------------------------------------
1 | {
2 | "issue": "issues",
3 | "commit": "commit",
4 | "referenceActions": [
5 | "close",
6 | "closes",
7 | "closed",
8 | "closing",
9 | "fix",
10 | "fixes",
11 | "fixed",
12 | "fixing"
13 | ],
14 | "issuePrefixes": [
15 | "#"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var gitRawCommits = require('git-raw-commits');
3 | var conventionalCommitsParser = require('conventional-commits-parser');
4 | var conventionalChangelogWriter = require('conventional-changelog-writer');
5 | var stream = require('stream');
6 | var through = require('through2');
7 | var mergeConfig = require('./lib/merge-config');
8 |
9 | function conventionalChangelog(options, context, gitRawCommitsOpts, parserOpts, writerOpts) {
10 | writerOpts = writerOpts || {};
11 |
12 | var readable = new stream.Readable({
13 | objectMode: writerOpts.includeDetails
14 | });
15 | readable._read = function() {};
16 |
17 | mergeConfig(options, context, gitRawCommitsOpts, parserOpts, writerOpts)
18 | .then(function(data) {
19 | options = data.options;
20 | context = data.context;
21 | gitRawCommitsOpts = data.gitRawCommitsOpts;
22 | parserOpts = data.parserOpts;
23 | writerOpts = data.writerOpts;
24 |
25 | gitRawCommits(gitRawCommitsOpts)
26 | .on('error', function(err) {
27 | err.message = 'Error in git-raw-commits: ' + err.message;
28 | setImmediate(readable.emit.bind(readable), 'error', err);
29 | })
30 | .pipe(conventionalCommitsParser(parserOpts))
31 | .on('error', function(err) {
32 | err.message = 'Error in conventional-commits-parser: ' + err.message;
33 | setImmediate(readable.emit.bind(readable), 'error', err);
34 | })
35 | // it would be better if `gitRawCommits` could spit out better formatted data
36 | // so we don't need to transform here
37 | .pipe(through.obj(function(chunk, enc, cb) {
38 | try {
39 | options.transform.call(this, chunk, cb);
40 | } catch (err) {
41 | cb(err);
42 | }
43 | }))
44 | .on('error', function(err) {
45 | err.message = 'Error in options.transform: ' + err.message;
46 | setImmediate(readable.emit.bind(readable), 'error', err);
47 | })
48 | .pipe(conventionalChangelogWriter(context, writerOpts))
49 | .on('error', function(err) {
50 | err.message = 'Error in conventional-changelog-writer: ' + err.message;
51 | setImmediate(readable.emit.bind(readable), 'error', err);
52 | })
53 | .pipe(through({
54 | objectMode: writerOpts.includeDetails
55 | }, function(chunk, enc, cb) {
56 | try {
57 | readable.push(chunk);
58 | } catch (err) {
59 | setImmediate(function() {
60 | throw err;
61 | });
62 | }
63 |
64 | cb();
65 | }, function(cb) {
66 | readable.push(null);
67 |
68 | cb();
69 | }));
70 | })
71 | .catch(function(err) {
72 | setImmediate(readable.emit.bind(readable), 'error', err);
73 | });
74 |
75 | return readable;
76 | }
77 |
78 | module.exports = conventionalChangelog;
79 |
--------------------------------------------------------------------------------
/lib/merge-config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var dateFormat = require('dateformat');
3 | var getPkgRepo = require('get-pkg-repo');
4 | var gitSemverTags = require('git-semver-tags');
5 | var normalizePackageData = require('normalize-package-data');
6 | var Q = require('q');
7 | var gitRemoteOriginUrl;
8 | try {
9 | gitRemoteOriginUrl = require('git-remote-origin-url');
10 | } catch (err) {
11 | gitRemoteOriginUrl = function() {
12 | return Q.reject(err);
13 | };
14 | }
15 | var readPkg = require('read-pkg');
16 | var readPkgUp = require('read-pkg-up');
17 | var url = require('url');
18 | var _ = require('lodash');
19 |
20 | var rhosts = /github|bitbucket|gitlab/i;
21 | var rtag = /tag:\s*[v=]?(.+?)[,\)]/gi;
22 |
23 | function mergeConfig(options, context, gitRawCommitsOpts, parserOpts, writerOpts) {
24 | var configPromise;
25 | var pkgPromise;
26 | var semverTagsPromise;
27 | var gitRemoteOriginUrlPromise;
28 |
29 | context = context || {};
30 | gitRawCommitsOpts = gitRawCommitsOpts || {};
31 |
32 | options = _.merge({
33 | pkg: {
34 | transform: function(pkg) {
35 | return pkg;
36 | }
37 | },
38 | append: false,
39 | releaseCount: 1,
40 | debug: function() {},
41 | transform: function(commit, cb) {
42 | if (_.isString(commit.gitTags)) {
43 | var match = rtag.exec(commit.gitTags);
44 | rtag.lastIndex = 0;
45 |
46 | if (match) {
47 | commit.version = match[1];
48 | }
49 | }
50 |
51 | if (commit.committerDate) {
52 | commit.committerDate = dateFormat(commit.committerDate, 'yyyy-mm-dd', true);
53 | }
54 |
55 | cb(null, commit);
56 | }
57 | }, options);
58 |
59 | options.warn = options.warn || options.debug;
60 |
61 | if (options.config) {
62 | if (_.isFunction(options.config)) {
63 | configPromise = Q.nfcall(options.config);
64 | } else {
65 | configPromise = Q(options.config); // jshint ignore:line
66 | }
67 | }
68 |
69 | if (options.pkg) {
70 | if (options.pkg.path) {
71 | pkgPromise = Q(readPkg(options.pkg.path)); // jshint ignore:line
72 | } else {
73 | pkgPromise = Q(readPkgUp()); // jshint ignore:line
74 | }
75 | }
76 |
77 | semverTagsPromise = Q.nfcall(gitSemverTags);
78 |
79 | gitRemoteOriginUrlPromise = Q(gitRemoteOriginUrl()); // jshint ignore:line
80 |
81 | return Q.allSettled([configPromise, pkgPromise, semverTagsPromise, gitRemoteOriginUrlPromise])
82 | .spread(function(configObj, pkgObj, tagsObj, gitRemoteOriginUrlObj) {
83 | var config;
84 | var pkg;
85 | var fromTag;
86 | var repo;
87 |
88 | var hostOpts;
89 |
90 | var gitSemverTags = [];
91 |
92 | if (configPromise) {
93 | if (configObj.state === 'fulfilled') {
94 | config = configObj.value;
95 | } else {
96 | options.warn('Error in config' + configObj.reason.toString());
97 | config = {};
98 | }
99 | } else {
100 | config = {};
101 | }
102 |
103 | context = _.assign(context, config.context);
104 |
105 | if (options.pkg) {
106 | if (pkgObj.state === 'fulfilled') {
107 | if (options.pkg.path) {
108 | pkg = pkgObj.value;
109 | } else {
110 | pkg = pkgObj.value.pkg || {};
111 | }
112 |
113 | pkg = options.pkg.transform(pkg);
114 |
115 | } else if (options.pkg.path) {
116 | options.warn(pkgObj.reason.toString());
117 | }
118 | }
119 |
120 | if ((!pkg || !pkg.repository || !pkg.repository.url) && gitRemoteOriginUrlObj.state === 'fulfilled') {
121 | pkg = pkg || {};
122 | pkg.repository = pkg.repository || {};
123 | pkg.repository.url = gitRemoteOriginUrlObj.value;
124 | normalizePackageData(pkg);
125 | }
126 |
127 | if (pkg) {
128 | context.version = context.version || pkg.version;
129 |
130 | try {
131 | repo = getPkgRepo(pkg);
132 | } catch (err) {
133 | repo = {};
134 | }
135 |
136 | if (repo.browse) {
137 | var browse = repo.browse();
138 | var parsedBrowse = url.parse(browse);
139 | context.host = context.host || (repo.domain ? (parsedBrowse.protocol + (parsedBrowse.slashes ? '//' : '') + repo.domain) : null);
140 | context.owner = context.owner || repo.user || '';
141 | context.repository = context.repository || repo.project;
142 | context.repoUrl = browse;
143 | }
144 |
145 | context.packageData = pkg;
146 | }
147 |
148 | if (tagsObj.state === 'fulfilled') {
149 | gitSemverTags = context.gitSemverTags = tagsObj.value;
150 | fromTag = gitSemverTags[options.releaseCount - 1];
151 |
152 | var lastTag = gitSemverTags[0];
153 |
154 | if (lastTag === context.version || lastTag === 'v' + context.version) {
155 | if (options.outputUnreleased) {
156 | context.version = 'Unreleased';
157 | } else {
158 | options.outputUnreleased = false;
159 | }
160 | }
161 | }
162 |
163 | if (!_.isBoolean(options.outputUnreleased)) {
164 | options.outputUnreleased = true;
165 | }
166 |
167 | if (context.host && (!context.issue || !context.commit || !parserOpts || !parserOpts.referenceActions)) {
168 | var type;
169 |
170 | if (context.host) {
171 | var match = context.host.match(rhosts);
172 | if (match) {
173 | type = match[0];
174 | }
175 | } else if (repo && repo.type) {
176 | type = repo.type;
177 | }
178 |
179 | if (type) {
180 | hostOpts = require('../hosts/' + type);
181 |
182 | context = _.assign({
183 | issue: hostOpts.issue,
184 | commit: hostOpts.commit
185 | }, context);
186 | } else {
187 | options.warn('Host: "' + context.host + '" does not exist');
188 | hostOpts = {};
189 | }
190 | } else {
191 | hostOpts = {};
192 | }
193 |
194 | gitRawCommitsOpts = _.assign({
195 | format: '%B%n-hash-%n%H%n-gitTags-%n%d%n-committerDate-%n%ci',
196 | from: fromTag,
197 | merges: false,
198 | debug: options.debug
199 | },
200 | config.gitRawCommitsOpts,
201 | gitRawCommitsOpts
202 | );
203 |
204 | if (options.append) {
205 | gitRawCommitsOpts.reverse = gitRawCommitsOpts.reverse || true;
206 | }
207 |
208 | parserOpts = _.assign(
209 | {}, config.parserOpts, {
210 | warn: options.warn
211 | },
212 | parserOpts);
213 |
214 | if (hostOpts.referenceActions && parserOpts) {
215 | parserOpts.referenceActions = hostOpts.referenceActions;
216 | }
217 |
218 | if (_.isEmpty(parserOpts.issuePrefixes) && hostOpts.issuePrefixes) {
219 | parserOpts.issuePrefixes = hostOpts.issuePrefixes;
220 | }
221 |
222 | writerOpts = _.assign({
223 | finalizeContext: function(context, writerOpts, filteredCommits, keyCommit, originalCommits) {
224 | var firstCommit = originalCommits[0];
225 | var lastCommit = originalCommits[originalCommits.length - 1];
226 | var firstCommitHash = firstCommit ? firstCommit.hash : null;
227 | var lastCommitHash = lastCommit ? lastCommit.hash : null;
228 |
229 | if ((!context.currentTag || !context.previousTag) && keyCommit) {
230 | var match = /tag:\s*(.+?)[,\)]/gi.exec(keyCommit.gitTags);
231 | var currentTag = context.currentTag;
232 | context.currentTag = currentTag || match ? match[1] : null;
233 | var index = gitSemverTags.indexOf(context.currentTag);
234 |
235 | // if `keyCommit.gitTags` is not a semver
236 | if (index === -1) {
237 | context.currentTag = currentTag || null;
238 | } else {
239 | var previousTag = context.previousTag = gitSemverTags[index + 1];
240 |
241 | if (!previousTag) {
242 | if (options.append) {
243 | context.previousTag = context.previousTag || firstCommitHash;
244 | } else {
245 | context.previousTag = context.previousTag || lastCommitHash;
246 | }
247 | }
248 | }
249 | } else {
250 | context.previousTag = context.previousTag || gitSemverTags[0];
251 |
252 | if (context.version === 'Unreleased') {
253 | if (options.append) {
254 | context.currentTag = context.currentTag || lastCommitHash;
255 | } else {
256 | context.currentTag = context.currentTag || firstCommitHash;
257 | }
258 | } else {
259 | context.currentTag = context.currentTag || 'v' + context.version;
260 | }
261 | }
262 |
263 | if (!_.isBoolean(context.linkCompare) && context.previousTag && context.currentTag) {
264 | context.linkCompare = true;
265 | }
266 |
267 | return context;
268 | },
269 | debug: options.debug
270 | },
271 | config.writerOpts, {
272 | reverse: options.append,
273 | doFlush: options.outputUnreleased
274 | },
275 | writerOpts
276 | );
277 |
278 | return {
279 | options: options,
280 | context: context,
281 | gitRawCommitsOpts: gitRawCommitsOpts,
282 | parserOpts: parserOpts,
283 | writerOpts: writerOpts
284 | };
285 | });
286 | }
287 |
288 | module.exports = mergeConfig;
289 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "conventional-changelog-core",
3 | "version": "1.5.0",
4 | "description": "conventional-changelog core",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/conventional-changelog/conventional-changelog-core.git"
8 | },
9 | "keywords": [
10 | "conventional-changelog",
11 | "conventional",
12 | "changelog",
13 | "log"
14 | ],
15 | "license": "MIT",
16 | "dependencies": {
17 | "conventional-changelog-writer": "^1.1.0",
18 | "conventional-commits-parser": "^1.0.0",
19 | "dateformat": "^1.0.12",
20 | "get-pkg-repo": "^1.0.0",
21 | "git-raw-commits": "^1.1.0",
22 | "git-remote-origin-url": "^2.0.0",
23 | "git-semver-tags": "^1.1.0",
24 | "lodash": "^4.0.0",
25 | "normalize-package-data": "^2.3.5",
26 | "q": "^1.4.1",
27 | "read-pkg": "^1.1.0",
28 | "read-pkg-up": "^1.0.1",
29 | "through2": "^2.0.0"
30 | },
31 | "devDependencies": {
32 | "better-than-before": "^1.0.0",
33 | "chai": "^3.4.1",
34 | "coveralls": "^2.11.6",
35 | "git-dummy-commit": "^1.1.0",
36 | "git-tails": "^1.0.0",
37 | "istanbul": "^0.4.1",
38 | "jscs": "^3.0.3",
39 | "jshint": "^2.9.1",
40 | "mocha": "*",
41 | "pinkie-promise": "^2.0.0",
42 | "semver": "^5.1.0",
43 | "shelljs": "^0.7.0"
44 | },
45 | "scripts": {
46 | "coverage": "istanbul cover _mocha -- -R spec --timeout 30000 && rm -rf ./coverage",
47 | "lint": "jshint . --exclude node_modules && jscs .",
48 | "test": "mocha --timeout 30000 && npm run-script lint"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # [![NPM version][npm-image]][npm-url] [![Build Status: Linux][travis-image]][travis-url] [![Build Status: Windows][appveyor-image]][appveyor-url] [![Dependency Status][daviddm-image]][daviddm-url] [![Coverage Status][coveralls-image]][coveralls-url]
2 |
3 | > [conventional-changelog](https://github.com/conventional-changelog/conventional-changelog) core
4 |
5 | You are probably looking for the [cli](https://github.com/conventional-changelog/conventional-changelog-cli) module. Or use one of the plugins if you are already using the tool: [grunt](https://github.com/btford/grunt-conventional-changelog)/[gulp](https://github.com/conventional-changelog/gulp-conventional-changelog)/[atom](https://github.com/conventional-changelog/atom-conventional-changelog).
6 |
7 |
8 | ## Usage
9 |
10 | ```sh
11 | $ npm install --save conventional-changelog-core
12 | ```
13 |
14 | ```js
15 | var conventionalChangelogCore = require('conventional-changelog-core');
16 |
17 | conventionalChangelogCore()
18 | .pipe(process.stdout); // or any writable stream
19 | ```
20 |
21 |
22 | ## API
23 |
24 | ### conventionalChangelogCore([options, [context, [gitRawCommitsOpts, [parserOpts, [writerOpts]]]]])
25 |
26 | Returns a readable stream.
27 |
28 | *Note:* [`options.transform`](#transform-1), [`options.pkg.transform`](#transform) and [`writerOpts.transform`](https://github.com/conventional-changelog/conventional-changelog-writer#transform) are different. If you have a better naming suggestion, please send a PR.
29 |
30 | #### options
31 |
32 | ##### config
33 |
34 | Type: `promise`, `function` or `object`
35 |
36 | This should serve as default values for other arguments of `conventionalChangelogCore` so you don't need to rewrite the same or similar config across your projects. Any value in this could be overwritten.
37 | If this is a promise (recommended if async), it should resolve with the config.
38 | If this is a function, it expects a node style callback with the config object.
39 | If this is an object, it is the config object. The config object should include `context`, `gitRawCommitsOpts`, `parserOpts` and `writerOpts`.
40 |
41 | ##### pkg
42 |
43 | Type: `object`
44 |
45 | ###### path
46 |
47 | Type: `string` Default: [closest package.json](https://github.com/sindresorhus/read-pkg-up).
48 |
49 | The location of your "package.json".
50 |
51 | ###### transform
52 |
53 | Type: `function` Default: pass through.
54 |
55 | A function that takes `package.json` data as the argument and returns the modified data. Note this is performed before normalizing package.json data. Useful when you need to add a leading 'v' to your version or modify your repository url, etc.
56 |
57 | ##### append
58 |
59 | Type: `boolean` Default: `false`
60 |
61 | Should the log be appended to existing data.
62 |
63 | ##### releaseCount
64 |
65 | Type: `number` Default: `1`
66 |
67 | How many releases of changelog you want to generate. It counts from the upcoming release. Useful when you forgot to generate any previous changelog. Set to `0` to regenerate all.
68 |
69 | ##### debug
70 |
71 | Type: `function` Default: `function() {}`
72 |
73 | A debug function. EG: `console.debug.bind(console)`
74 |
75 | ##### warn
76 |
77 | Type: `function` Default: `options.debug`
78 |
79 | A warn function. EG: `grunt.verbose.writeln`
80 |
81 | ##### transform
82 |
83 | Type: `function` Default: get the version (without leading 'v') from tag and format date.
84 |
85 | ###### function(commit, cb)
86 |
87 | A transform function that applies after the parser and before the writer.
88 |
89 | This is the place to modify the parsed commits.
90 |
91 | ####### commit
92 |
93 | The commit from conventional-commits-parser.
94 |
95 | ####### cb
96 |
97 | Callback when you are done.
98 |
99 | ####### this
100 |
101 | `this` arg of through2.
102 |
103 | ##### outputUnreleased
104 |
105 | Type: `boolean` Default: `true` if a different version than last release is given. Otherwise `false`.
106 |
107 | If this value is `true` and `context.version` equals last release then `context.version` will be changed to `'Unreleased'`.
108 |
109 | #### context
110 |
111 | See the [conventional-changelog-writer](https://github.com/conventional-changelog/conventional-changelog-writer) docs. There are some defaults or changes:
112 |
113 | ##### host
114 |
115 | Default: normalized host found in `package.json`.
116 |
117 | ##### version
118 |
119 | Default: version found in `package.json`.
120 |
121 | ##### owner
122 |
123 | Default: extracted from normalized `package.json` `repository.url` field.
124 |
125 | ##### repository
126 |
127 | Default: extracted from normalized `package.json` `repository.url` field.
128 |
129 | ##### repoUrl
130 |
131 | Default: The whole normalized repository url in `package.json`.
132 |
133 | ##### gitSemverTags
134 |
135 | Type: `array`
136 |
137 | All git semver tags found in the repository. You can't overwrite this value.
138 |
139 | ##### previousTag
140 |
141 | Type: `string` Default: previous semver tag or the first commit hash if no previous tag.
142 |
143 | ##### currentTag
144 |
145 | Type: `string` Default: current semver tag or `'v'` + version if no current tag.
146 |
147 | ##### packageData
148 |
149 | Type: `object`
150 |
151 | Your `package.json` data. You can't overwrite this value.
152 |
153 | ##### linkCompare
154 |
155 | Type: `boolean` Default: `true` if `previousTag` and `currentTag` are truthy.
156 |
157 | Should link to the page that compares current tag with previous tag?
158 |
159 | #### gitRawCommitsOpts
160 |
161 | See the [git-raw-commits](https://github.com/conventional-changelog/git-raw-commits) docs. There are some defaults:
162 |
163 | ##### format
164 |
165 | Default: `'%B%n-hash-%n%H%n-gitTags-%n%d%n-committerDate-%n%ci'`
166 |
167 | ##### from
168 |
169 | Default: based on `options.releaseCount`.
170 |
171 | ##### reverse
172 |
173 | Default: `true` if `options.append` is truthy.
174 |
175 | ##### debug
176 |
177 | Type: `function` Default: `options.debug`
178 |
179 | #### parserOpts
180 |
181 | See the [conventional-commits-parser](https://github.com/conventional-changelog/conventional-commits-parser) docs.
182 |
183 | ##### warn
184 |
185 | Default: `options.warn`
186 |
187 | #### writerOpts
188 |
189 | See the [conventional-changelog-writer](https://github.com/conventional-changelog/conventional-changelog-writer) docs. There are some defaults:
190 |
191 | ##### finalizeContext
192 |
193 | Finalize context is used for generating above context.
194 |
195 | **NOTE:** If you overwrite this value the above context defaults will be gone.
196 |
197 | ##### debug
198 |
199 | Type: `function` Default: `options.debug`
200 |
201 | ##### reverse
202 |
203 | Default: `options.append`
204 |
205 | ##### doFlush
206 |
207 | Default: `options.outputUnreleased`
208 |
209 |
210 | ## Notes for parent modules
211 |
212 | This module has options `append` and `releaseCount`. However, it doesn't read your previous changelog. Reasons being:
213 |
214 | 1. The old logs is just to be appended or prepended to the newly generated logs, which is a very simple thing that could be done in the parent module.
215 | 2. We want it to be very flexible for the parent module. You could create a readable stream from the file or you could just read the file.
216 | 3. We want the duty of this module to be very minimum.
217 |
218 | So, when you build a parent module, you need to read the old logs and append or prepend to them based on `options.append`. However, if `options.releaseCount` is `0` you need to ignore any previous logs. Please see [conventional-github-releaser](https://github.com/conventional-changelog/conventional-github-releaser) as an example.
219 |
220 | Arguments passed to `conventionalChangelogCore` will be mutated.
221 |
222 |
223 | ## License
224 |
225 | MIT
226 |
227 |
228 | [npm-image]: https://badge.fury.io/js/conventional-changelog-core.svg
229 | [npm-url]: https://npmjs.org/package/conventional-changelog-core
230 | [travis-image]: https://travis-ci.org/conventional-changelog/conventional-changelog-core.svg?branch=master
231 | [travis-url]: https://travis-ci.org/conventional-changelog/conventional-changelog-core
232 | [appveyor-image]: https://ci.appveyor.com/api/projects/status/baoumm34w8c5o0hv/branch/master?svg=true
233 | [appveyor-url]: https://ci.appveyor.com/project/stevemao/conventional-changelog-core/branch/master
234 | [daviddm-image]: https://david-dm.org/conventional-changelog/conventional-changelog-core.svg?theme=shields.io
235 | [daviddm-url]: https://david-dm.org/conventional-changelog/conventional-changelog-core
236 | [coveralls-image]: https://coveralls.io/repos/conventional-changelog/conventional-changelog-core/badge.svg
237 | [coveralls-url]: https://coveralls.io/r/conventional-changelog/conventional-changelog-core
238 |
--------------------------------------------------------------------------------
/test/fixtures/_host-only.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.0.17",
3 | "repository": {
4 | "type": "git",
5 | "url": "https://unknown-host/.git"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/test/fixtures/_malformation.json:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/conventional-changelog-archived-repos/conventional-changelog-core/873a2fb3d08ec52a5178b7965700ed6acd17e32d/test/fixtures/_malformation.json
--------------------------------------------------------------------------------
/test/fixtures/_package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.0.17",
3 | "repository": {
4 | "type": "git",
5 | "url": "https://github.com/ajoslin/conventional-changelog.git"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/test/fixtures/_short.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.0.17",
3 | "repository": "ajoslin/conventional-changelog"
4 | }
5 |
--------------------------------------------------------------------------------
/test/fixtures/_unknown-host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.0.17",
3 | "repository": {
4 | "type": "git",
5 | "url": "https://stash.local/scm/conventional-changelog/conventional-changelog.git"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/test/fixtures/_version-only.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.0.17"
3 | }
4 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var conventionalChangelogCore = require('../');
3 | var expect = require('chai').expect;
4 | var gitTails = require('git-tails').sync;
5 | var shell = require('shelljs');
6 | var gitDummyCommit = require('git-dummy-commit');
7 | var through = require('through2');
8 | var Promise = require('pinkie-promise');
9 | var semver = require('semver');
10 | var betterThanBefore = require('better-than-before')();
11 | var preparing = betterThanBefore.preparing;
12 |
13 | betterThanBefore.setups([
14 | function() { // 1
15 | shell.config.silent = true;
16 | shell.rm('-rf', 'tmp');
17 | shell.mkdir('tmp');
18 | shell.cd('tmp');
19 | shell.mkdir('git-templates');
20 | shell.exec('git init --template=./git-templates');
21 | gitDummyCommit('First commit');
22 | },
23 | function() { // 2
24 | shell.exec('git tag v0.1.0');
25 | gitDummyCommit('Second commit');
26 | gitDummyCommit('Third commit closes #1');
27 | },
28 | function() { // 3
29 | shell.exec('git checkout -b feature');
30 | gitDummyCommit('This commit is from feature branch');
31 | shell.exec('git checkout master');
32 | gitDummyCommit('This commit is from master branch');
33 | shell.exec('git merge feature -m"Merge branch \'feature\'"');
34 | },
35 | function() { // 4
36 | gitDummyCommit('Custom prefix closes @42');
37 | },
38 | function() { // 5
39 | gitDummyCommit('Custom prefix closes @42');
40 | gitDummyCommit('Old prefix closes #71');
41 | },
42 | function() { // 6
43 | gitDummyCommit('some more features');
44 | shell.exec('git tag v2.0.0');
45 | },
46 | function() { // 7
47 | gitDummyCommit('test8');
48 | },
49 | function() { // 8
50 | gitDummyCommit('test8');
51 | },
52 | function() { // 9
53 | gitDummyCommit(['test9', 'Release note: super release!']);
54 | },
55 | function() { // 10
56 | shell.exec('git remote add origin https://github.com/user/repo.git');
57 | },
58 | function(context) { // 11
59 | shell.exec('git tag -d v0.1.0');
60 | var tails = gitTails();
61 | context.tail = tails[tails.length - 1].substring(0, 7);
62 | },
63 | function(context) { // 12
64 | shell.exec('git tag not-semver');
65 | gitDummyCommit();
66 |
67 | var head = shell.exec('git rev-parse HEAD').stdout.trim();
68 | gitDummyCommit('Revert \\"test9\\" This reverts commit ' + head + '.');
69 | context.head = shell.exec('git rev-parse HEAD').stdout.substring(0, 7);
70 | },
71 | function(context) { // 13
72 | var tail = context.tail;
73 | shell.exec('git tag v0.0.1 ' + tail);
74 | },
75 | function() { // 14
76 | gitDummyCommit();
77 | shell.exec('git tag v1.0.0');
78 | },
79 | function() { // 15
80 | gitDummyCommit();
81 | gitDummyCommit('something unreleased yet :)');
82 | }
83 | ]);
84 |
85 | betterThanBefore.tearsWithJoy(function() {
86 | shell.cd('../');
87 | shell.rm('-rf', 'tmp');
88 | });
89 |
90 | describe('conventionalChangelogCore', function() {
91 | it('should work if there is no tag', function(done) {
92 | preparing(1);
93 |
94 | conventionalChangelogCore()
95 | .pipe(through(function(chunk) {
96 | expect(chunk.toString()).to.include('First commit');
97 |
98 | done();
99 | }));
100 | });
101 |
102 | it('should generate the changelog for the upcoming release', function(done) {
103 | preparing(2);
104 |
105 | conventionalChangelogCore()
106 | .pipe(through(function(chunk) {
107 | chunk = chunk.toString();
108 |
109 | expect(chunk).to.include('Second commit');
110 | expect(chunk).to.include('Third commit');
111 |
112 | expect(chunk).to.not.include('First commit');
113 |
114 | done();
115 | }));
116 | });
117 |
118 | it('should generate the changelog of the last two releases', function(done) {
119 | preparing(2);
120 | var i = 0;
121 |
122 | conventionalChangelogCore({
123 | releaseCount: 2
124 | })
125 | .pipe(through(function(chunk, enc, cb) {
126 | chunk = chunk.toString();
127 |
128 | if (i === 0) {
129 | expect(chunk).to.include('Second commit');
130 | expect(chunk).to.include('Third commit');
131 | } else if (i === 1) {
132 | expect(chunk).to.include('First commit');
133 | }
134 |
135 | i++;
136 | cb();
137 | }, function() {
138 | expect(i).to.equal(2);
139 | done();
140 | }));
141 | });
142 |
143 | it('should generate the changelog of the last two releases even if release count exceeds the limit', function(done) {
144 | preparing(2);
145 | var i = 0;
146 |
147 | conventionalChangelogCore({
148 | releaseCount: 100
149 | })
150 | .pipe(through(function(chunk, enc, cb) {
151 | chunk = chunk.toString();
152 |
153 | if (i === 0) {
154 | expect(chunk).to.include('Second commit');
155 | expect(chunk).to.include('Third commit');
156 | } else if (i === 1) {
157 | expect(chunk).to.include('First commit');
158 | }
159 |
160 | i++;
161 | cb();
162 | }, function() {
163 | expect(i).to.equal(2);
164 | done();
165 | }));
166 | });
167 |
168 | it('should honour `gitRawCommitsOpts.from`', function(done) {
169 | preparing(2);
170 |
171 | conventionalChangelogCore({}, {}, {
172 | from: 'HEAD~2'
173 | }, {}, {
174 | commitsSort: null
175 | })
176 | .pipe(through(function(chunk) {
177 | chunk = chunk.toString();
178 |
179 | expect(chunk).to.include('Second commit');
180 | expect(chunk).to.include('Third commit');
181 | expect(chunk).to.match(/Third commit closes #1[\w\W]*?\* Second commit/);
182 |
183 | expect(chunk).to.not.include('First commit');
184 |
185 | done();
186 | }));
187 | });
188 |
189 | it('should ignore merge commits by default', function(done) {
190 | preparing(3);
191 |
192 | conventionalChangelogCore()
193 | .pipe(through(function(chunk) {
194 | chunk = chunk.toString();
195 |
196 | expect(chunk).to.include('This commit is from feature branch');
197 |
198 | expect(chunk).to.not.include('Merge');
199 |
200 | done();
201 | }));
202 | });
203 |
204 | it('should spit out some debug info', function(done) {
205 | preparing(3);
206 |
207 | var first = true;
208 |
209 | conventionalChangelogCore({
210 | debug: function(cmd) {
211 | if (first) {
212 | first = false;
213 | expect(cmd).to.include('Your git-log command is:');
214 | done();
215 | }
216 | }
217 | });
218 | });
219 |
220 | it('should load package.json for data', function(done) {
221 | preparing(3);
222 |
223 | conventionalChangelogCore({
224 | pkg: {
225 | path: __dirname + '/fixtures/_package.json'
226 | }
227 | })
228 | .pipe(through(function(chunk) {
229 | chunk = chunk.toString();
230 |
231 | expect(chunk).to.include('## 0.0.17');
232 | expect(chunk).to.include('Second commit');
233 | expect(chunk).to.include('closes [#1](https://github.com/ajoslin/conventional-changelog/issues/1)');
234 |
235 | done();
236 | }));
237 | });
238 |
239 | it('should load package.json for data even if repository field is missing', function(done) {
240 | preparing(3);
241 |
242 | conventionalChangelogCore({
243 | pkg: {
244 | path: __dirname + '/fixtures/_version-only.json'
245 | }
246 | })
247 | .pipe(through(function(chunk) {
248 | chunk = chunk.toString();
249 |
250 | expect(chunk).to.include('## 0.0.17');
251 | expect(chunk).to.include('Second commit');
252 |
253 | done();
254 | }));
255 | });
256 |
257 | it('should fallback to use repo url if repo is repository is null', function(done) {
258 | preparing(3);
259 |
260 | conventionalChangelogCore({
261 | pkg: {
262 | path: __dirname + '/fixtures/_host-only.json'
263 | }
264 | }, {
265 | linkReferences: true
266 | }).pipe(through(function(chunk) {
267 | chunk = chunk.toString();
268 |
269 | expect(chunk).to.include('](https://unknown-host/commits/');
270 | expect(chunk).to.include('closes [#1](https://unknown-host/issues/1)');
271 |
272 | done();
273 | }));
274 | });
275 |
276 | it('should fallback to use repo url if repo is repository is null', function(done) {
277 | preparing(3);
278 |
279 | conventionalChangelogCore({
280 | pkg: {
281 | path: __dirname + '/fixtures/_unknown-host.json'
282 | }
283 | }, {
284 | linkReferences: true
285 | }).pipe(through(function(chunk) {
286 | chunk = chunk.toString();
287 |
288 | expect(chunk).to.include('](https://stash.local/scm/conventional-changelog/conventional-changelog/commits/');
289 | expect(chunk).to.include('closes [#1](https://stash.local/scm/conventional-changelog/conventional-changelog/issues/1)');
290 |
291 | done();
292 | }));
293 | });
294 |
295 | it('should transform package.json data', function(done) {
296 | preparing(3);
297 |
298 | conventionalChangelogCore({
299 | pkg: {
300 | path: __dirname + '/fixtures/_short.json',
301 | transform: function(pkg) {
302 | pkg.version = 'v' + pkg.version;
303 | pkg.repository = 'a/b';
304 | return pkg;
305 | }
306 | }
307 | })
308 | .pipe(through(function(chunk) {
309 | chunk = chunk.toString();
310 |
311 | expect(chunk).to.include('## v0.0.17');
312 | expect(chunk).to.include('Second commit');
313 | expect(chunk).to.include('closes [#1](https://github.com/a/b/issues/1)');
314 |
315 | done();
316 | }));
317 | });
318 |
319 | it('should work in append mode', function(done) {
320 | preparing(3);
321 |
322 | conventionalChangelogCore({
323 | append: true,
324 | })
325 | .pipe(through(function(chunk) {
326 | chunk = chunk.toString();
327 |
328 | expect(chunk).to.match(/Second commit[\w\W]*?\* Third commit/);
329 |
330 | done();
331 | }));
332 | });
333 |
334 | it('should read package.json if only `context.version` is missing', function(done) {
335 | preparing(3);
336 |
337 | conventionalChangelogCore({
338 | pkg: {
339 | path: __dirname + '/fixtures/_package.json'
340 | }
341 | }, {
342 | host: 'github',
343 | owner: 'a',
344 | repository: 'b'
345 | }).pipe(through(function(chunk) {
346 | chunk = chunk.toString();
347 |
348 | expect(chunk).to.include('## 0.0.17');
349 | expect(chunk).to.include('closes [#1](github/a/b/issues/1)');
350 |
351 | done();
352 | }));
353 | });
354 |
355 | it('should read the closest package.json by default', function(done) {
356 | preparing(3);
357 |
358 | conventionalChangelogCore()
359 | .pipe(through(function(chunk) {
360 | expect(chunk.toString()).to.include('closes [#1](https://github.com/conventional-changelog/conventional-changelog-core/issues/1)');
361 |
362 | done();
363 | }));
364 | });
365 |
366 | it('should ignore other prefixes if an `issuePrefixes` option is not provided', function(done) {
367 | preparing(4);
368 |
369 | conventionalChangelogCore({}, {
370 | host: 'github',
371 | owner: 'b',
372 | repository: 'a'
373 | }, {}, {}).pipe(through(function(chunk) {
374 | chunk = chunk.toString();
375 |
376 | expect(chunk).to.include('](github/b/a/commit/');
377 | expect(chunk).to.not.include('closes [#42](github/b/a/issues/42)');
378 |
379 | done();
380 | }));
381 | });
382 |
383 | it('should use custom prefixes if an `issuePrefixes` option is provided', function(done) {
384 | preparing(5);
385 |
386 | conventionalChangelogCore({}, {
387 | host: 'github',
388 | owner: 'b',
389 | repository: 'a'
390 | }, {}, {
391 | issuePrefixes: ['@']
392 | }).pipe(through(function(chunk) {
393 | chunk = chunk.toString();
394 |
395 | expect(chunk).to.include('](github/b/a/commit/');
396 | expect(chunk).to.include('closes [#42](github/b/a/issues/42)');
397 | expect(chunk).to.not.include('closes [#71](github/b/a/issues/71)');
398 |
399 | done();
400 | }));
401 | });
402 |
403 | it('should read host configs if only `parserOpts.referenceActions` is missing', function(done) {
404 | preparing(5);
405 |
406 | conventionalChangelogCore({}, {
407 | host: 'github',
408 | owner: 'b',
409 | repository: 'a',
410 | issue: 'issue',
411 | commit: 'commits'
412 | }, {}, {}).pipe(through(function(chunk) {
413 | chunk = chunk.toString();
414 |
415 | expect(chunk).to.include('](github/b/a/commits/');
416 | expect(chunk).to.include('closes [#1](github/b/a/issue/1)');
417 |
418 | done();
419 | }));
420 | });
421 |
422 | it('should read github\'s host configs', function(done) {
423 | preparing(5);
424 |
425 | conventionalChangelogCore({}, {
426 | host: 'github',
427 | owner: 'b',
428 | repository: 'a'
429 | }, {}, {}).pipe(through(function(chunk) {
430 | chunk = chunk.toString();
431 |
432 | expect(chunk).to.include('](github/b/a/commit/');
433 | expect(chunk).to.include('closes [#1](github/b/a/issues/1)');
434 |
435 | done();
436 | }));
437 | });
438 |
439 | it('should read bitbucket\'s host configs', function(done) {
440 | preparing(5);
441 |
442 | conventionalChangelogCore({}, {
443 | host: 'bitbucket',
444 | owner: 'b',
445 | repository: 'a'
446 | }, {}, {}).pipe(through(function(chunk) {
447 | chunk = chunk.toString();
448 |
449 | expect(chunk).to.include('](bitbucket/b/a/commits/');
450 | expect(chunk).to.include('closes [#1](bitbucket/b/a/issue/1)');
451 |
452 | done();
453 | }));
454 | });
455 |
456 | it('should read gitlab\'s host configs', function(done) {
457 | preparing(5);
458 |
459 | conventionalChangelogCore({}, {
460 | host: 'gitlab',
461 | owner: 'b',
462 | repository: 'a'
463 | }, {}, {}).pipe(through(function(chunk) {
464 | chunk = chunk.toString();
465 |
466 | expect(chunk).to.include('](gitlab/b/a/commit/');
467 | expect(chunk).to.include('closes [#1](gitlab/b/a/issues/1)');
468 |
469 | done();
470 | }));
471 | });
472 |
473 | it('should transform the commit', function(done) {
474 | preparing(5);
475 |
476 | conventionalChangelogCore({
477 | transform: function(chunk, cb) {
478 | chunk.header = 'A tiny header';
479 | cb(null, chunk);
480 | }
481 | })
482 | .pipe(through(function(chunk) {
483 | chunk = chunk.toString();
484 |
485 | expect(chunk).to.include('A tiny header');
486 | expect(chunk).to.not.include('Third');
487 |
488 | done();
489 | }));
490 | });
491 |
492 | it('should generate all log blocks', function(done) {
493 | preparing(5);
494 | var i = 0;
495 |
496 | conventionalChangelogCore({
497 | releaseCount: 0
498 | })
499 | .pipe(through(function(chunk, enc, cb) {
500 | chunk = chunk.toString();
501 |
502 | if (i === 0) {
503 | expect(chunk).to.include('Second commit');
504 | expect(chunk).to.include('Third commit closes #1');
505 | } else {
506 | expect(chunk).to.include('First commit');
507 | }
508 |
509 | i++;
510 | cb();
511 | }, function() {
512 | expect(i).to.equal(2);
513 | done();
514 | }));
515 | });
516 |
517 | it('should work if there are two semver tags', function(done) {
518 | preparing(6);
519 | var i = 0;
520 |
521 | conventionalChangelogCore({
522 | releaseCount: 0
523 | })
524 | .pipe(through(function(chunk, enc, cb) {
525 | chunk = chunk.toString();
526 |
527 | if (i === 1) {
528 | expect(chunk).to.include('# 2.0.0');
529 | } else if (i === 2) {
530 | expect(chunk).to.include('# 0.1.0');
531 | }
532 |
533 | i++;
534 | cb();
535 | }, function() {
536 | expect(i).to.equal(3);
537 | done();
538 | }));
539 | });
540 |
541 | it('semverTags should be attached to the `context` object', function(done) {
542 | preparing(6);
543 | var i = 0;
544 |
545 | conventionalChangelogCore({
546 | releaseCount: 0
547 | }, {}, {}, {}, {
548 | mainTemplate: '{{gitSemverTags}} or {{gitSemverTags.[0]}}'
549 | })
550 | .pipe(through(function(chunk, enc, cb) {
551 | chunk = chunk.toString();
552 |
553 | expect(chunk).to.equal('v2.0.0,v0.1.0 or v2.0.0');
554 |
555 | i++;
556 | cb();
557 | }, function() {
558 | expect(i).to.equal(3);
559 | done();
560 | }));
561 | });
562 |
563 | it('should not link compare', function(done) {
564 | preparing(6);
565 | var i = 0;
566 |
567 | conventionalChangelogCore({
568 | releaseCount: 0,
569 | append: true
570 | }, {
571 | version: '3.0.0',
572 | linkCompare: false
573 | }, {}, {}, {
574 | mainTemplate: '{{#if linkCompare}}{{previousTag}}...{{currentTag}}{{else}}Not linked{{/if}}',
575 | transform: function() {
576 | return null;
577 | }
578 | })
579 | .pipe(through(function(chunk, enc, cb) {
580 | chunk = chunk.toString();
581 |
582 | expect(chunk).to.equal('Not linked');
583 |
584 | i++;
585 | cb();
586 | }, function() {
587 | expect(i).to.equal(3);
588 | done();
589 | }));
590 | });
591 |
592 | it('should warn if host is not found', function(done) {
593 | preparing(6);
594 |
595 | conventionalChangelogCore({
596 | pkg: null,
597 | warn: function(warning) {
598 | expect(warning).to.equal('Host: "no" does not exist');
599 |
600 | done();
601 | }
602 | }, {
603 | host: 'no'
604 | });
605 | });
606 |
607 | it('should warn if package.json is not found', function(done) {
608 | preparing(6);
609 |
610 | conventionalChangelogCore({
611 | pkg: {
612 | path: 'no'
613 | },
614 | warn: function(warning) {
615 | expect(warning).to.include('Error');
616 |
617 | done();
618 | }
619 | });
620 | });
621 |
622 | it('should warn if package.json cannot be parsed', function(done) {
623 | preparing(6);
624 |
625 | conventionalChangelogCore({
626 | pkg: {
627 | path: __dirname + '/fixtures/_malformation.json'
628 | },
629 | warn: function(warning) {
630 | expect(warning).to.include('Error');
631 |
632 | done();
633 | }
634 | });
635 | });
636 |
637 | it('should error if anything throws', function(done) {
638 | preparing(6);
639 |
640 | conventionalChangelogCore({
641 | pkg: {
642 | path: __dirname + '/fixtures/_malformation.json'
643 | },
644 | warn: function() {
645 | undefined.a = 10;
646 | }
647 | }).on('error', function(err) {
648 | expect(err).to.be.ok; // jshint ignore:line
649 | done();
650 | });
651 | });
652 |
653 | it('should error if there is an error in `options.pkg.transform`', function(done) {
654 | preparing(6);
655 |
656 | conventionalChangelogCore({
657 | pkg: {
658 | path: __dirname + '/fixtures/_short.json',
659 | transform: function() {
660 | undefined.a = 10;
661 | }
662 | }
663 | })
664 | .on('error', function(err) {
665 | expect(err.message).to.include('undefined');
666 |
667 | done();
668 | });
669 | });
670 |
671 | it('should error if it errors in git-raw-commits', function(done) {
672 | preparing(6);
673 |
674 | conventionalChangelogCore({}, {}, {
675 | unknowOptions: false
676 | })
677 | .on('error', function(err) {
678 | expect(err.message).to.include('Error in git-raw-commits:');
679 |
680 | done();
681 | });
682 | });
683 |
684 | it('should error if it emits an error in `options.transform`', function(done) {
685 | preparing(7);
686 |
687 | conventionalChangelogCore({
688 | transform: function(commit, cb) {
689 | cb(new Error('error'));
690 | }
691 | })
692 | .on('error', function(err) {
693 | expect(err.message).to.include('Error in options.transform:');
694 |
695 | done();
696 | });
697 | });
698 |
699 | it('should error if there is an error in `options.transform`', function(done) {
700 | preparing(8);
701 |
702 | conventionalChangelogCore({
703 | transform: function() {
704 | undefined.a = 10;
705 | }
706 | })
707 | .on('error', function(err) {
708 | expect(err.message).to.include('Error in options.transform:');
709 |
710 | done();
711 | });
712 | });
713 |
714 | it('should error if it errors in conventional-changelog-writer', function(done) {
715 | preparing(8);
716 |
717 | conventionalChangelogCore({}, {}, {}, {}, {
718 | finalizeContext: function() {
719 | return undefined.a;
720 | }
721 | })
722 | .on('error', function(err) {
723 | expect(err.message).to.include('Error in conventional-changelog-writer:');
724 |
725 | done();
726 | });
727 | });
728 |
729 | it('should be object mode if `writerOpts.includeDetails` is `true`', function(done) {
730 | preparing(8);
731 |
732 | conventionalChangelogCore({}, {}, {}, {}, {
733 | includeDetails: true
734 | })
735 | .pipe(through.obj(function(chunk) {
736 | expect(chunk).to.be.an('object');
737 | done();
738 | }));
739 | });
740 |
741 | it('should pass `parserOpts` to conventional-commits-parser', function(done) {
742 | preparing(9);
743 |
744 | conventionalChangelogCore({}, {}, {}, {
745 | noteKeywords: [
746 | 'Release note'
747 | ]
748 | })
749 | .pipe(through(function(chunk, enc, cb) {
750 | chunk = chunk.toString();
751 |
752 | expect(chunk).to.include('* test9');
753 | expect(chunk).to.include('### Release note\n\n* super release!');
754 |
755 | cb();
756 | }, function() {
757 | done();
758 | }));
759 | });
760 |
761 | it('should pass fallback to git remote origin url', function(done) {
762 | if (semver.major(process.version) < 4) {
763 | console.log('This feature is only available under node>=4');
764 | done();
765 | return;
766 | }
767 |
768 | preparing(10);
769 |
770 | conventionalChangelogCore({
771 | pkg: {
772 | path: __dirname + '/fixtures/_version-only.json'
773 | },
774 | })
775 | .pipe(through(function(chunk, enc, cb) {
776 | chunk = chunk.toString();
777 |
778 | expect(chunk).to.include('https://github.com/user/repo');
779 | expect(chunk).to.not.include('.git');
780 |
781 | cb();
782 | }, function() {
783 | done();
784 | }));
785 | });
786 |
787 | describe('finalizeContext', function() {
788 | it('should make `context.previousTag` default to a previous semver version of generated log (prepend)', function(done) {
789 | var tail = preparing(11).tail;
790 | var i = 0;
791 |
792 | conventionalChangelogCore({
793 | releaseCount: 0
794 | }, {
795 | version: '3.0.0'
796 | }, {}, {}, {
797 | mainTemplate: '{{previousTag}}...{{currentTag}}'
798 | })
799 | .pipe(through(function(chunk, enc, cb) {
800 | chunk = chunk.toString();
801 |
802 | if (i === 0) {
803 | expect(chunk).to.equal('v2.0.0...v3.0.0');
804 | } else if (i === 1) {
805 | expect(chunk).to.equal(tail + '...v2.0.0');
806 | }
807 |
808 | i++;
809 | cb();
810 | }, function() {
811 | expect(i).to.equal(2);
812 | done();
813 | }));
814 | });
815 |
816 | it('should make `context.previousTag` default to a previous semver version of generated log (append)', function(done) {
817 | var tail = preparing(11).tail;
818 | var i = 0;
819 |
820 | conventionalChangelogCore({
821 | releaseCount: 0,
822 | append: true
823 | }, {
824 | version: '3.0.0'
825 | }, {}, {}, {
826 | mainTemplate: '{{previousTag}}...{{currentTag}}'
827 | })
828 | .pipe(through(function(chunk, enc, cb) {
829 | chunk = chunk.toString();
830 |
831 | if (i === 0) {
832 | expect(chunk).to.equal(tail + '...v2.0.0');
833 | } else if (i === 1) {
834 | expect(chunk).to.equal('v2.0.0...v3.0.0');
835 | }
836 |
837 | i++;
838 | cb();
839 | }, function() {
840 | expect(i).to.equal(2);
841 | done();
842 | }));
843 | });
844 |
845 | it('`context.previousTag` and `context.currentTag` should be `null` if `keyCommit.gitTags` is not a semver', function(done) {
846 | var tail = preparing(12).tail;
847 | var i = 0;
848 |
849 | conventionalChangelogCore({
850 | releaseCount: 0,
851 | append: true
852 | }, {
853 | version: '3.0.0'
854 | }, {}, {}, {
855 | mainTemplate: '{{previousTag}}...{{currentTag}}',
856 | generateOn: 'version'
857 | })
858 | .pipe(through(function(chunk, enc, cb) {
859 | chunk = chunk.toString();
860 |
861 | if (i === 0) {
862 | expect(chunk).to.equal(tail + '...v2.0.0');
863 | } else if (i === 1) {
864 | expect(chunk).to.equal('...');
865 | } else {
866 | expect(chunk).to.equal('v2.0.0...v3.0.0');
867 | }
868 |
869 | i++;
870 | cb();
871 | }, function() {
872 | expect(i).to.equal(3);
873 | done();
874 | }));
875 | });
876 |
877 | it('should still work if first release has no commits (prepend)', function(done) {
878 | preparing(13);
879 | var i = 0;
880 |
881 | conventionalChangelogCore({
882 | releaseCount: 0
883 | }, {
884 | version: '3.0.0'
885 | }, {}, {}, {
886 | mainTemplate: '{{previousTag}}...{{currentTag}}',
887 | transform: function() {
888 | return null;
889 | }
890 | })
891 | .pipe(through(function(chunk, enc, cb) {
892 | chunk = chunk.toString();
893 |
894 | if (i === 0) {
895 | expect(chunk).to.equal('v2.0.0...v3.0.0');
896 | } else if (i === 1) {
897 | expect(chunk).to.equal('v0.0.1...v2.0.0');
898 | } else if (i === 2) {
899 | expect(chunk).to.equal('...v0.0.1');
900 | }
901 |
902 | i++;
903 | cb();
904 | }, function() {
905 | expect(i).to.equal(3);
906 | done();
907 | }));
908 | });
909 |
910 | it('should still work if first release has no commits (append)', function(done) {
911 | preparing(13);
912 | var i = 0;
913 |
914 | conventionalChangelogCore({
915 | releaseCount: 0,
916 | append: true
917 | }, {
918 | version: '3.0.0'
919 | }, {}, {}, {
920 | mainTemplate: '{{previousTag}}...{{currentTag}}',
921 | transform: function() {
922 | return null;
923 | }
924 | })
925 | .pipe(through(function(chunk, enc, cb) {
926 | chunk = chunk.toString();
927 |
928 | if (i === 0) {
929 | expect(chunk).to.equal('...v0.0.1');
930 | } else if (i === 1) {
931 | expect(chunk).to.equal('v0.0.1...v2.0.0');
932 | } else if (i === 2) {
933 | expect(chunk).to.equal('v2.0.0...v3.0.0');
934 | }
935 |
936 | i++;
937 | cb();
938 | }, function() {
939 | expect(i).to.equal(3);
940 | done();
941 | }));
942 | });
943 |
944 | it('should change `context.currentTag` to last commit hash if it is unreleased', function(done) {
945 | var head = preparing(13).head;
946 | var i = 0;
947 |
948 | conventionalChangelogCore({
949 | outputUnreleased: true
950 | }, {
951 | version: '2.0.0'
952 | }, {}, {}, {
953 | mainTemplate: '{{previousTag}}...{{currentTag}}'
954 | })
955 | .pipe(through(function(chunk, enc, cb) {
956 | chunk = chunk.toString();
957 |
958 | expect(chunk).to.equal('v2.0.0...' + head);
959 |
960 | i++;
961 | cb();
962 | }, function() {
963 | expect(i).to.equal(1);
964 | done();
965 | }));
966 | });
967 |
968 | it('should not link compare if previousTag is not truthy', function(done) {
969 | preparing(13);
970 | var i = 0;
971 |
972 | conventionalChangelogCore({
973 | releaseCount: 0,
974 | append: true
975 | }, {
976 | version: '3.0.0'
977 | }, {}, {}, {
978 | mainTemplate: '{{#if linkCompare}}{{previousTag}}...{{currentTag}}{{else}}Not linked{{/if}}',
979 | transform: function() {
980 | return null;
981 | }
982 | })
983 | .pipe(through(function(chunk, enc, cb) {
984 | chunk = chunk.toString();
985 |
986 | if (i === 0) {
987 | expect(chunk).to.equal('Not linked');
988 | } else if (i === 1) {
989 | expect(chunk).to.equal('v0.0.1...v2.0.0');
990 | } else if (i === 2) {
991 | expect(chunk).to.equal('v2.0.0...v3.0.0');
992 | }
993 |
994 | i++;
995 | cb();
996 | }, function() {
997 | expect(i).to.equal(3);
998 | done();
999 | }));
1000 | });
1001 | });
1002 |
1003 | describe('config', function() {
1004 | var config = {
1005 | context: {
1006 | version: 'v100.0.0'
1007 | }
1008 | };
1009 |
1010 | var promise = new Promise(function(resolve) {
1011 | resolve(config);
1012 | });
1013 |
1014 | var fn = function(cb) {
1015 | cb(null, config);
1016 | };
1017 |
1018 | it('should load object config', function(done) {
1019 | conventionalChangelogCore({
1020 | config: config,
1021 | pkg: {
1022 | path: __dirname + '/fixtures/_package.json'
1023 | }
1024 | })
1025 | .pipe(through(function(chunk, enc, cb) {
1026 | chunk = chunk.toString();
1027 |
1028 | expect(chunk).to.include('v100.0.0');
1029 |
1030 | cb();
1031 | }, function() {
1032 | done();
1033 | }));
1034 | });
1035 |
1036 | it('should load promise config', function(done) {
1037 | conventionalChangelogCore({
1038 | config: promise
1039 | })
1040 | .pipe(through(function(chunk, enc, cb) {
1041 | chunk = chunk.toString();
1042 |
1043 | expect(chunk).to.include('v100.0.0');
1044 |
1045 | cb();
1046 | }, function() {
1047 | done();
1048 | }));
1049 | });
1050 |
1051 | it('should load function config', function(done) {
1052 | conventionalChangelogCore({
1053 | config: fn
1054 | })
1055 | .pipe(through(function(chunk, enc, cb) {
1056 | chunk = chunk.toString();
1057 |
1058 | expect(chunk).to.include('v100.0.0');
1059 |
1060 | cb();
1061 | }, function() {
1062 | done();
1063 | }));
1064 | });
1065 |
1066 | it('should warn if config errors', function(done) {
1067 | conventionalChangelogCore({
1068 | config: new Promise(function(solve, reject) {
1069 | reject('config error');
1070 | }),
1071 | warn: function(warning) {
1072 | expect(warning).to.include('config error');
1073 |
1074 | done();
1075 | }
1076 | });
1077 | });
1078 | });
1079 |
1080 | describe('unreleased', function() {
1081 | it('should not output unreleased', function(done) {
1082 | preparing(14);
1083 |
1084 | conventionalChangelogCore({}, {
1085 | version: '1.0.0'
1086 | })
1087 | .pipe(through(function() {
1088 | done(new Error('should not output unreleased'));
1089 | }, function() {
1090 | done();
1091 | }));
1092 | });
1093 |
1094 | it('should output unreleased', function(done) {
1095 | preparing(15);
1096 |
1097 | conventionalChangelogCore({
1098 | outputUnreleased: true
1099 | }, {
1100 | version: 'v1.0.0'
1101 | })
1102 | .pipe(through(function(chunk, enc, cb) {
1103 | chunk = chunk.toString();
1104 |
1105 | expect(chunk).to.include('something unreleased yet :)');
1106 | expect(chunk).to.include('Unreleased');
1107 |
1108 | cb();
1109 | }, function() {
1110 | done();
1111 | }));
1112 | });
1113 | });
1114 | });
1115 |
--------------------------------------------------------------------------------