├── .gitignore ├── .jshintrc ├── HISTORY ├── README.md ├── bin ├── cli.js ├── install.js └── usage.md ├── changelog ├── chunk.js ├── header.js ├── line.js ├── parse.js ├── read.js └── record.js ├── docs.mli ├── index.js ├── lib ├── exec.js └── transact-json-file.js ├── package.json ├── tasks ├── commit-changes.js ├── compute-version.js ├── update-changelog.js └── update-version.js └── test ├── does-patch.js ├── fresh-repo.js ├── index.js ├── lib ├── git-start.js ├── init-repo.js └── with-fixtures.js └── second-call.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.a 8 | *.o 9 | *.so 10 | *.node 11 | 12 | # Node Waf Byproducts # 13 | ####################### 14 | .lock-wscript 15 | build/ 16 | autom4te.cache/ 17 | 18 | # Node Modules # 19 | ################ 20 | # Better to let npm install these from the package.json defintion 21 | # rather than maintain this manually 22 | node_modules/ 23 | 24 | # Packages # 25 | ############ 26 | # it's better to unpack these files and commit the raw source 27 | # git has its own built in compression methods 28 | *.7z 29 | *.dmg 30 | *.gz 31 | *.iso 32 | *.jar 33 | *.rar 34 | *.tar 35 | *.zip 36 | 37 | # Logs and databases # 38 | ###################### 39 | *.log 40 | dump.rdb 41 | *.js.tap 42 | *.coffee.tap 43 | 44 | # OS generated files # 45 | ###################### 46 | .DS_Store? 47 | .DS_Store 48 | ehthumbs.db 49 | Icon? 50 | Thumbs.db 51 | coverage 52 | 53 | # Text Editor Byproducts # 54 | ########################## 55 | *.swp 56 | *.swo 57 | .idea/ 58 | 59 | *.pyc 60 | 61 | # All translation files # 62 | ######################### 63 | static/translations-s3/ 64 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": false, 3 | "camelcase": true, 4 | "curly": false, 5 | "eqeqeq": true, 6 | "forin": true, 7 | "immed": true, 8 | "indent": 4, 9 | "latedef": true, 10 | "newcap": true, 11 | "noarg": true, 12 | "nonew": true, 13 | "plusplus": false, 14 | "quotmark": false, 15 | "regexp": false, 16 | "undef": true, 17 | "unused": true, 18 | "strict": false, 19 | "trailing": true, 20 | "noempty": true, 21 | "maxdepth": 4, 22 | "maxparams": 4, 23 | "globals": { 24 | "console": true, 25 | "Buffer": true, 26 | "setTimeout": true, 27 | "clearTimeout": true, 28 | "setInterval": true, 29 | "clearInterval": true, 30 | "require": false, 31 | "module": false, 32 | "exports": true, 33 | "global": false, 34 | "process": true, 35 | "__dirname": false, 36 | "__filename": false 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /HISTORY: -------------------------------------------------------------------------------- 1 | 2014-03-12 - 1.4.2 (0879098) 2 | 0879098 (HEAD, master) make open source 3 | 2806a14 (tag: v1.4.0, origin/master) 1.4.0 4 | 5 | 6 | 2014-03-12 - 1.4.0 (b2d9310) 7 | b2d9310 (HEAD, master) Merge remote-tracking branch 'origin/master' 8 | e43f529 (tag: v1.3.0) 1.3.0 9 | 0350b8f (origin/master) Merge pull request #6 from uber/fix-install-path 10 | 93c31a9 (origin/fix-install-path, fix-install-path) add bin/cli.js 11 | 5675e57 fix install path 12 | 13 | 14 | 2014-03-12 - 1.3.0 (13b6935) 15 | 13b6935 (HEAD, origin/master, master) Merge pull request #1 from uber/initial-version 16 | 488cf14 (origin/initial-version) make it executable 17 | bd271af (tag: v1.2.0) 1.2.0 18 | 19 | 20 | 2014-03-05 - 1.2.0 (76b1e3d) 21 | 76b1e3d (HEAD, initial-version) add read command to CLI 22 | 2272fa0 fixes changelog parsing bug 23 | 52cb577 (tag: v1.1.0, origin/initial-version) 1.1.0 24 | 25 | 26 | 2014-03-05 - 1.1.0 (cb06a7e) 27 | cb06a7e (HEAD, initial-version) gaurd against non existant shrinkwrap file 28 | 9b9da8b allow customizing the filename 29 | c362770 support non shrinkwrap use case 30 | ad71f00 (origin/initial-version) updated tap-spec dep 31 | 267feb3 fix require bug in install 32 | c7e07c2 make logFlags overwrite all flags 33 | a80c40c re-order variables 34 | 8a9dcd3 rename bump minor to update version 35 | 92843fa do not call lambda on content 36 | 4fd1999 JSON.parse safely in transact json file 37 | a7f8608 moved computeVersion logic into bump-minor 38 | bd5d205 rename tasks file 39 | 2c1dbe9 pass all exec options forward 40 | 6b1bf86 rebuild index of README 41 | d23b937 rename index.js to record.js 42 | 11c6f97 use default values 43 | 0aaac8a updated docs to be upto date 44 | 99cf94a default scripts to an empty hash 45 | bc36013 add install command 46 | 4dff37f add version command 47 | dbe02ba moved transact file to lib 48 | 05599a9 do not return a value if err 49 | 15bab77 move formatting into the header itself 50 | c06453f move a ton of files & code around 51 | 19ff38a add initial cli with help usage 52 | 9deb571 refactored entry to allow for nextVersion argument 53 | 5ddbf2f fix docs typo 54 | 678f3c4 updated toc 55 | bba35f0 more docs 56 | 1b42580 better documentation 57 | 2da1aa1 allow a string opts 58 | c41c120 make npm run cover work 59 | 15b1c03 test with two calls 60 | c32609d refactor test 61 | aaac29d added a naive cli 62 | 3ced70f added tests 63 | c1c651c initial code 64 | 48896cf the individual tasks 65 | 4f0053b docs 66 | 4aaab98 (origin/master, master) copy initial file 67 | 8b5d269 initial 68 | 69 | 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # build-changelog 2 | 3 | A CLI to auto-generate a deploy ready changelog 4 | 5 | **Table of Contents** *generated with [DocToc](http://doctoc.herokuapp.com/)* 6 | 7 | - [build-changelog](#build-changelog) 8 | - [Usage](#usage) 9 | - [Steps of changelog procedure](#steps-of-changelog-procedure) 10 | - [Example](#example) 11 | - [Example changelog file](#example-changelog-file) 12 | - [Involved example](#involved-example) 13 | - [Parsing a changelog file](#parsing-a-changelog-file) 14 | - [Docs](#docs) 15 | - [Type definitions](#type-definitions) 16 | - [`buildChangelog(options, cb)`](#buildchangelogoptions-cb) 17 | - [`options.folder`](#optionsfolder) 18 | - [`options.nextVersion`](#optionsnextversion) 19 | - [`options.major`](#optionsmajor) 20 | - [`options.patch`](#optionspatch) 21 | - [`options.logFlags`](#optionslogflags) 22 | - [`var changelog = parseChangelog(text)`](#var-changelog-=-parsechangelogtext) 23 | - [`text`](#text) 24 | - [Installation](#installation) 25 | - [Tests](#tests) 26 | - [Contributors](#contributors) 27 | 28 | ## Usage 29 | 30 | `$ build-changelog [flags]` 31 | 32 | See [usage.md][usage] for more documentation 33 | 34 | ### Steps of changelog procedure 35 | 36 | Running `build-changelog` will do the following steps. 37 | 38 | - Bump the minor version number and update the package.json 39 | and npm-shrinkwrap.json. 40 | - Generate the changelog additions. Generate a header line 41 | that conatins the date & version number and then find all 42 | the commits since the last `build-changelog` call. Add the 43 | header and commits to the CHANGELOG file 44 | - Commit the package.json, shrinkwrap and CHANGELOG changes as 45 | a new version number and tags the commit as `vVERSION_NUMBER` 46 | 47 | ## Example 48 | 49 | ```js 50 | var buildChangelog = require('build-changelog'); 51 | 52 | // takes any commits not written to the changelog and adds them 53 | // reads package.json in process.cwd() to find the new version 54 | // to set the project to. 55 | buildChangelog(process.cwd(), function (err, nextVersion) { 56 | if (err) throw err; 57 | 58 | console.log('the new version', nextVersion); 59 | }); 60 | ``` 61 | 62 | ## Example changelog file 63 | 64 | ``` 65 | 2014-03-04 - 1.1.0 (f6ec3cc) 66 | f6ec3cc (HEAD, initial-version) better documentation 67 | e207a6e allow a string opts 68 | 0a77206 (origin/initial-version) make npm run cover work 69 | df03ac1 test with two calls 70 | b8b0f0f refactor test 71 | fa1b602 added a naive cli 72 | bd78f26 added tests 73 | d3df8cc initial code 74 | ca5bda1 the individual tasks 75 | e3b8fd4 docs 76 | 8b5d269 (origin/master, master) initial 77 | 78 | 79 | ``` 80 | 81 | ### Involved example 82 | 83 | ```js 84 | var updateVersion = require('build-changelog/tasks/update-version') 85 | var updateChangelog = require('build-changelog/tasks/update-changelog') 86 | var commitChanges = require('build-changelog/tasks/commit-changes') 87 | 88 | var opts = { 89 | nextVersion: '1.1.0', 90 | logFlags: '--oneline --decorate --first-parent', 91 | folder: process.cwd() 92 | }; 93 | 94 | // skip this step if you dont want to change the package.json 95 | // version 96 | updateVersion(opts, function (err) { 97 | if (err) throw err; 98 | 99 | updateChangelog(opts, function (err) { 100 | if (err) throw err; 101 | 102 | // skip this step if you dont want to commit or tag your 103 | // git repo 104 | commitChanges(opts, function (err) { 105 | if (err) throw err; 106 | 107 | console.log('done'); 108 | }); 109 | }); 110 | }); 111 | ``` 112 | 113 | ### Parsing a changelog file 114 | 115 | ```js 116 | var path = require('path') 117 | var readChangelog = require('build-changelog/changelog/read') 118 | 119 | var loc = path.join(process.cwd(), 'CHANGELOG') 120 | 121 | readChangelog(loc, function (err, changelog) { 122 | if (err) throw err; 123 | 124 | changelog.chunks.forEach(function (chunk) { 125 | console.log('header', chunk.header) 126 | console.log('commit lines', chunk.lines) 127 | }) 128 | }) 129 | ``` 130 | 131 | ## Docs 132 | 133 | ### Type definitions 134 | 135 | See [docs.mli][docs] for type definitions 136 | 137 | ### `buildChangelog(options, cb)` 138 | 139 | ```ocaml 140 | build-changelog := (folder: String | { 141 | folder: String, 142 | nextVersion?: String, 143 | major?: Boolean, 144 | minor?: Boolean, 145 | patch?: Boolean, 146 | filename?: String, 147 | logFlags?: String 148 | }, cb: Callback) 149 | ``` 150 | 151 | Your `cb` will get called with an `Error` if any operation fails 152 | and will be called with a `nextVersion` string if the steps 153 | have completed successfully, the `nextVersion` string will 154 | be whatever version is written to the `package.json` file in 155 | the given `folder` 156 | 157 | if `options` is a `"string"` then it's a shorthand for 158 | `{ folder: string }`. 159 | 160 | #### `options.folder` 161 | 162 | This is the `folder` in which the `CHANGELOG` will be written to. 163 | It is assumed that the folder is also a git repository. The 164 | `package.json` and `npm-shrinkwrap.json` files there will also 165 | be edited. 166 | 167 | #### `options.nextVersion` 168 | 169 | When set to a valid semver string this version will be used to 170 | set the package.json version to. When not set a version number 171 | will be computed based on bumping either the minor, major or 172 | patch version. 173 | 174 | #### `options.major` 175 | 176 | This flag defaults to `false`. When set to `true` the major 177 | version number will be increased instead of the patch version 178 | number. 179 | 180 | #### `options.minor` 181 | 182 | This flag defaults to `false`. When set to `true` the minor 183 | version number will be increased instead of the patch version 184 | number. 185 | 186 | #### `options.patch` 187 | 188 | This flag defaults to `true`. When set to `true` the patch 189 | version number will be increased. 190 | 191 | #### `options.logFlags` 192 | 193 | To customize the generation of the actual `CHANGELOG` content 194 | you can pass extra flags to be passed to `git log`. 195 | 196 | By default `build-changelog` runs `git log --decorate --oneline`. 197 | You may want to pass different flags, for example if you have 198 | a merge only git strategy you may want to pass 199 | `--oneline --first-parent` or if you have a squash 200 | only git strategy you may want to pass `--oneline --no-merges`. 201 | 202 | #### `options.filename` 203 | 204 | This defaults to `CHANGELOG`. You can specify a different 205 | filename to read and write the CHANGELOG to. 206 | 207 | ### `var changelog = parseChangelog(text)` 208 | 209 | ```ocaml 210 | type ChangeLogRecord := { 211 | chunks: Array<{ 212 | header: { 213 | date: String, 214 | version: String, 215 | commit?: String 216 | }, 217 | lines: Array<{ 218 | sha?: String, 219 | decorations?: Array, 220 | message: String 221 | }> 222 | }>, 223 | content: String 224 | } 225 | 226 | build-changelog/changelog/record := 227 | (content: String) => ChangeLogRecord 228 | ``` 229 | 230 | `parseChangelog(text)` will return a `ChangeLog` data record 231 | that is the structured form of the textual `CHANGELOG` file 232 | content. 233 | 234 | the `changelog` returned contains an array of chunks, each 235 | chunk correspond to a versioned section of the changelog. A 236 | chunk contains a header section, for the versioned header line 237 | and an array of lines for each commit message. 238 | 239 | #### `text` 240 | 241 | A `"string"` of text, this will most likely be taken by reading 242 | your `CHANGELOG` file 243 | 244 | ## Installation 245 | 246 | `npm install build-changelog` 247 | 248 | ## Tests 249 | 250 | `npm test` 251 | 252 | ## Contributors 253 | 254 | - Raynos 255 | 256 | [docs]: https://github.com/uber/build-changelog/tree/master/docs.mli 257 | [usage]: https://github.com/uber/build-changelog/tree/master/bin/usage.md 258 | [thunk]: https://github.com/Raynos/continuable/blob/master/spec.md 259 | -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require('path'); 4 | var parseArgs = require('minimist'); 5 | var msee = require('msee'); 6 | var fs = require('fs'); 7 | var template = require('string-template'); 8 | 9 | var installModule = require('./install.js'); 10 | var buildChangelog = require('../index.js'); 11 | var readChangelog = require('../changelog/read.js'); 12 | 13 | function printHelp(opts) { 14 | opts = opts || {}; 15 | 16 | var loc = path.join(__dirname, 'usage.md'); 17 | var content = fs.readFileSync(loc, 'utf8'); 18 | 19 | content = template(content, { 20 | cmd: opts.cmd || 'build-changelog' 21 | }); 22 | 23 | return console.log(msee.parse(content, { 24 | paragraphStart: '\n' 25 | })); 26 | } 27 | 28 | function main(opts) { 29 | var command = opts._[0]; 30 | 31 | if (opts.h || opts.help || command === 'help') { 32 | return printHelp(opts); 33 | } 34 | 35 | if (!opts.folder) { 36 | opts.folder = process.cwd(); 37 | } 38 | 39 | if (opts['log-flags']) { 40 | opts.logFlags = opts['log-flags']; 41 | } 42 | 43 | if (command === 'read') { 44 | return readChangelog(opts._[1], function (err, changelog) { 45 | if (err) { 46 | throw err; 47 | } 48 | 49 | console.log(JSON.stringify(changelog, null, ' ')); 50 | }); 51 | } 52 | 53 | if (command === 'install') { 54 | return installModule(opts, function (err) { 55 | if (err) { 56 | throw err; 57 | } 58 | 59 | console.log('added %s to package.json', opts.cmd); 60 | }); 61 | } 62 | 63 | if (command === 'version') { 64 | var nextVersion = opts._[1]; 65 | 66 | if (nextVersion === 'major') { 67 | opts.major = true; 68 | } else if (nextVersion === 'minor') { 69 | opts.minor = true; 70 | } else if (nextVersion === 'patch') { 71 | opts.patch = true; 72 | } else { 73 | opts.nextVersion = nextVersion; 74 | } 75 | } 76 | 77 | buildChangelog(opts, function (err, nextVersion) { 78 | if (err) { 79 | throw err; 80 | } 81 | 82 | console.log('now at version %s', nextVersion); 83 | }); 84 | } 85 | 86 | module.exports = main; 87 | 88 | if (require.main === module) { 89 | main(parseArgs(process.argv.slice(2))); 90 | } 91 | -------------------------------------------------------------------------------- /bin/install.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var template = require('string-template'); 3 | 4 | var version = require('../package.json').version; 5 | var transactJsonFile = require('../lib/transact-json-file.js'); 6 | 7 | var majorCommand = '{cmd} --major'; 8 | var minorCommand = '{cmd} --minor'; 9 | var patchCommand = '{cmd} --patch'; 10 | var deprecatedCommand = 'echo DEPRECATED: please run either ' + 11 | 'of the changelog-patch or changelog-minor scripts; exit 1'; 12 | 13 | function installModule(opts, cb) { 14 | var file = path.join(opts.folder, 'package.json'); 15 | 16 | opts.cmd = opts.cmd || 'build-changelog'; 17 | opts.packageVersion = opts.packageVersion || '^' + version; 18 | opts.moduleName = opts.moduleName || 'build-changelog'; 19 | 20 | transactJsonFile(file, function (package) { 21 | package.scripts = package.scripts || {}; 22 | 23 | package.scripts['changelog-major'] = 24 | template(majorCommand, opts); 25 | package.scripts['changelog-minor'] = 26 | template(minorCommand, opts); 27 | package.scripts['changelog-patch'] = 28 | template(patchCommand, opts); 29 | 30 | if (package.scripts.changelog) { 31 | package.scripts.changelog = deprecatedCommand; 32 | } 33 | 34 | if (!opts.onlyScripts) { 35 | package.devDependencies = package.devDependencies || {}; 36 | package.devDependencies[opts.moduleName] = 37 | opts.packageVersion; 38 | } 39 | 40 | return package; 41 | }, cb); 42 | } 43 | 44 | module.exports = installModule; 45 | -------------------------------------------------------------------------------- /bin/usage.md: -------------------------------------------------------------------------------- 1 | # {cmd} [options] 2 | 3 | Builds the CHANGELOG file and commits it to git. Either creates 4 | a new CHANGELOG file or appends to the top of an existing one. 5 | 6 | Options: 7 | --major bump the major version number 8 | --minor bump the minor version number 9 | --log-flags=[str] extra flags to pass to `git log` 10 | --folder=[str] sets the git repo & CHANGELOG location 11 | --filename=[str] the filename we write the CHANGELOG too 12 | 13 | - `--major` defaults to `false` 14 | - `--minor` defaults to `false` 15 | - `--log-flags` defaults to `--decorate --oneline` 16 | - `--folder` defaults to `process.cwd()` 17 | - `--filename` defaults to `CHANGELOG` 18 | 19 | ## `{cmd} --help` 20 | 21 | Prints this message 22 | 23 | ## `{cmd} install` 24 | 25 | Will write a `changelog` script to your `package.json` file. 26 | 27 | ```json 28 | { 29 | "scripts": { 30 | "changelog": "{cmd}" 31 | } 32 | } 33 | ``` 34 | 35 | Options: 36 | --folder sets the folder location of the package.json file 37 | 38 | ## `{cmd} version [ | major | minor | patch]` 39 | 40 | This is the same as `{cmd}` except allows you to set 41 | a specific version. `{cmd}` will by default bump 42 | the minor version number. 43 | 44 | This is an alternative to `npm version`, the main addition is 45 | changing the CHANGELOG file. 46 | 47 | ## `{cmd} read ` 48 | 49 | Reads and parses a changelog file and writes JSON to stdout 50 | -------------------------------------------------------------------------------- /changelog/chunk.js: -------------------------------------------------------------------------------- 1 | function ChangeLogChunk(header, lines) { 2 | if (!(this instanceof ChangeLogChunk)) { 3 | return new ChangeLogChunk(header, lines); 4 | } 5 | 6 | this.header = header; 7 | this.lines = lines; 8 | } 9 | 10 | module.exports = ChangeLogChunk; 11 | -------------------------------------------------------------------------------- /changelog/header.js: -------------------------------------------------------------------------------- 1 | var format = require('util').format; 2 | 3 | function ChangeLogHeader(date, version, commit) { 4 | if (!(this instanceof ChangeLogHeader)) { 5 | return new ChangeLogHeader(date, version, commit); 6 | } 7 | 8 | this.date = date; 9 | this.version = version; 10 | this.commit = commit || null; 11 | } 12 | 13 | ChangeLogHeader.prototype.toString = function () { 14 | return format('%s - %s (%s)\n', 15 | this.date, this.version, this.commit); 16 | }; 17 | 18 | module.exports = ChangeLogHeader; 19 | -------------------------------------------------------------------------------- /changelog/line.js: -------------------------------------------------------------------------------- 1 | function ChangeLogLine(sha, decorations, message) { 2 | if (!(this instanceof ChangeLogLine)) { 3 | return new ChangeLogLine(sha, decorations, message); 4 | } 5 | 6 | this.sha = sha || null; 7 | this.decorations = decorations || null; 8 | this.message = message; 9 | } 10 | 11 | module.exports = ChangeLogLine; 12 | -------------------------------------------------------------------------------- /changelog/parse.js: -------------------------------------------------------------------------------- 1 | var isDoubleNewLine = /\n{2,}/g; 2 | var matchHeaderLines = /^(\d{4}-\d{2}-\d{2}) +- +(\d+\.\d+\.\d+) +\(([0-9a-fA-F]{6,})\)/; 3 | var matchLogLine = /^([a-fA-F0-9]{7,})(?:\s+\((.+?)\))?\s+(.*)$/; 4 | 5 | var ChangeLogHeader = require('./header.js'); 6 | var ChangeLogRecord = require('./record.js'); 7 | var ChangeLogLine = require('./line.js'); 8 | var ChangeLogChunk = require('./chunk.js'); 9 | 10 | function parseLogLine(logLine) { 11 | var match = matchLogLine.exec(logLine); 12 | 13 | if (!match) { 14 | return null; 15 | } 16 | 17 | var decorations = match[2] ? match[2].split(',').map(function (s) { 18 | return s.trim(); 19 | }) : null; 20 | 21 | return new ChangeLogLine(match[1], decorations, match[3]); 22 | } 23 | 24 | function parseHeaderLine(headerLine) { 25 | var match = matchHeaderLines.exec(headerLine); 26 | 27 | if (!match) { 28 | return null; 29 | } 30 | 31 | return new ChangeLogHeader(match[1], match[2], match[3]); 32 | } 33 | 34 | 35 | function parseChangelog(content) { 36 | if (!content) { 37 | return null; 38 | } 39 | 40 | var parts = content.split(isDoubleNewLine); 41 | 42 | var chunks = parts.filter(function (chunk) { 43 | return Boolean(chunk.trim()); 44 | }).map(function (chunk) { 45 | var lines = chunk.split('\n'); 46 | var headerLine = lines[0]; 47 | 48 | var logLines = lines.slice(1); 49 | 50 | return new ChangeLogChunk( 51 | parseHeaderLine(headerLine), 52 | logLines.map(parseLogLine) 53 | ); 54 | }); 55 | 56 | return new ChangeLogRecord(chunks, content); 57 | } 58 | 59 | module.exports = parseChangelog; 60 | -------------------------------------------------------------------------------- /changelog/read.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | var parseChangeLog = require('./parse.js'); 4 | 5 | function readChangelog(file, cb) { 6 | fs.readFile(file, 'utf8', function (err, content) { 7 | if (err && err.code === 'ENOENT') { 8 | return cb(null, null); 9 | } 10 | 11 | if (err) { 12 | return cb(err); 13 | } 14 | 15 | cb(null, parseChangeLog(content)); 16 | }); 17 | } 18 | 19 | module.exports = readChangelog; 20 | -------------------------------------------------------------------------------- /changelog/record.js: -------------------------------------------------------------------------------- 1 | function ChangeLogRecord(chunks, content) { 2 | if (!(this instanceof ChangeLogRecord)) { 3 | return new ChangeLogRecord(chunks, content); 4 | } 5 | 6 | this.chunks = chunks; 7 | this.content = content; 8 | } 9 | 10 | module.exports = ChangeLogRecord; 11 | -------------------------------------------------------------------------------- /docs.mli: -------------------------------------------------------------------------------- 1 | (* ChangeLog types define what the changelog parser returns 2 | 3 | It's the structured datatype for the CHANGELOG file. 4 | *) 5 | type ChangeLogHeader := { 6 | date: String, 7 | version: String, 8 | commit?: String 9 | } 10 | 11 | type ChangeLogLine := { 12 | sha?: String, 13 | decorations?: Array, 14 | message: String 15 | } 16 | 17 | type ChangeLogChunk := { 18 | header: ChangeLogHeader 19 | lines: Array 20 | } 21 | 22 | type ChangeLogRecord := { 23 | chunks: Array, 24 | content: String 25 | } 26 | 27 | build-changelog/changelog/chunk := 28 | (ChangeLogHeader, Array) => ChangeLogChunk 29 | 30 | build-changelog/changelog/header := ( 31 | date: String, 32 | version: String, 33 | commit?: String 34 | ) => ChangeLogHeader 35 | 36 | build-changelog/changelog/record := 37 | (Array, content: String) => ChangeLogRecord 38 | 39 | build-changelog/changelog/line := ( 40 | sha: String | null, 41 | decorations: Array | null, 42 | message: String 43 | ) => ChangeLogLine 44 | 45 | build-changelog/changelog/parse := (String) => ChangeLogRecord 46 | 47 | build-changelog/changelog/read := 48 | (fileName: String, Callback) 49 | 50 | (* ChangeLog options are passed into each tasks function. *) 51 | type ChangelogOptions := { 52 | folder: String, 53 | nextVersion: String, 54 | logFlags: String, 55 | major: Boolean, 56 | minor: Boolean, 57 | patch: Boolean 58 | } 59 | 60 | build-changelog/tasks/update-version := 61 | (ChangelogOptions, Callback) 62 | 63 | build-changelog/tasks/commit-changes := 64 | (ChangelogOptions, Callback) 65 | 66 | build-changelog/tasks/update-changelog := 67 | (ChangelogOptions, Callback) 68 | 69 | build-changelog/tasks/compute-next-version := 70 | (ChangelogOptions, Callback) 71 | 72 | build-changelog := (folder: String | { 73 | folder: String, 74 | nextVersion?: String, 75 | major?: Boolean, 76 | minor?: Boolean, 77 | patch?: Boolean, 78 | filename?: String, 79 | logFlags?: String 80 | }, cb: Callback) 81 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var extend = require('xtend'); 2 | 3 | var updateVersion = require('./tasks/update-version.js'); 4 | var updateChangelog = require('./tasks/update-changelog.js'); 5 | var commitChanges = require('./tasks/commit-changes.js'); 6 | 7 | var defaults = { 8 | major: false, 9 | minor: false, 10 | patch: true, 11 | logFlags: '--decorate --first-parent --oneline', 12 | filename: 'CHANGELOG' 13 | }; 14 | 15 | function main(opts, cb) { 16 | if (typeof opts === 'string') { 17 | opts = { folder: opts }; 18 | } 19 | 20 | opts = extend(defaults, opts); 21 | 22 | updateVersion(opts, function (err, nextVersion) { 23 | if (err) { 24 | return cb(err); 25 | } 26 | 27 | opts.nextVersion = opts.nextVersion || nextVersion; 28 | 29 | updateChangelog(opts, function (err) { 30 | if (err) { 31 | return cb(err); 32 | } 33 | 34 | commitChanges(opts, function (err) { 35 | cb(err, err ? null : nextVersion); 36 | }); 37 | }); 38 | }); 39 | } 40 | 41 | module.exports = main; 42 | -------------------------------------------------------------------------------- /lib/exec.js: -------------------------------------------------------------------------------- 1 | var childProcess = require('child_process'); 2 | 3 | function exec(cmd, opts, cb) { 4 | if (typeof opts === 'function') { 5 | cb = opts; 6 | opts = {}; 7 | } 8 | 9 | opts = opts || {}; 10 | 11 | opts.maxBuffer = opts.maxBuffer || 2000 * 1024; 12 | 13 | childProcess.exec(cmd, opts, function (err, stdout, stderr) { 14 | if (err) { 15 | return cb(err); 16 | } 17 | 18 | if (stderr && !opts.silent) { 19 | return cb(new Error(stderr)); 20 | } 21 | 22 | cb(null, stdout); 23 | }); 24 | } 25 | 26 | module.exports = exec; 27 | -------------------------------------------------------------------------------- /lib/transact-json-file.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var jsonParse = require('safe-json-parse'); 3 | 4 | function transactJsonFile(file, lambda, cb) { 5 | fs.readFile(file, 'utf8', function (err, buf) { 6 | if (err) { 7 | return cb(err); 8 | } 9 | 10 | jsonParse(buf, function (err, payload) { 11 | if (err) { 12 | return cb(err); 13 | } 14 | 15 | var content = JSON.stringify(lambda(payload), null, ' '); 16 | 17 | fs.writeFile(file, content, cb); 18 | }); 19 | }); 20 | } 21 | 22 | module.exports = transactJsonFile; 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "build-changelog", 3 | "version": "2.1.2", 4 | "description": "A CLI to auto-generate a deploy ready changelog", 5 | "keywords": [], 6 | "author": "Raynos ", 7 | "repository": "git://github.com/uber/build-changelog.git", 8 | "main": "index", 9 | "homepage": "https://github.com/uber/build-changelog", 10 | "bugs": { 11 | "url": "https://github.com/uber/build-changelog/issues", 12 | "email": "raynos2@gmail.com" 13 | }, 14 | "dependencies": { 15 | "continuable-series": "~1.2.0", 16 | "xtend": "~2.1.2", 17 | "continuable-para": "^1.2.0", 18 | "date-format": "0.0.2", 19 | "semver": "^2.2.1", 20 | "safe-json-parse": "^1.0.1", 21 | "minimist": "0.0.8", 22 | "msee": "^0.1.1", 23 | "string-template": "~0.1.3" 24 | }, 25 | "devDependencies": { 26 | "jshint": "2.4.2", 27 | "istanbul": "~0.1.46", 28 | "tap-spec": "^0.1.4", 29 | "tape": "^2.6.0", 30 | "uuid": "^1.4.1", 31 | "rimraf": "^2.2.6", 32 | "pre-commit": "0.0.4" 33 | }, 34 | "scripts": { 35 | "test": "npm run jshint && NODE_ENV=test node test/index.js | tspec", 36 | "jshint": "jshint --verbose --exclude-path .gitignore .", 37 | "cover": "istanbul cover --report none --print detail test/index.js", 38 | "shrinkwrap": "rm npm-shrinkwrap.json; rm -rf ./node_modules && npm cache clear && npm --registry=http://archive.uber.com/npm install && npm shrinkwrap --depth 100 --dev", 39 | "view-cover": "istanbul report html && open ./coverage/index.html" 40 | }, 41 | "bin": { 42 | "build-changelog": "./bin/cli.js" 43 | }, 44 | "engine": { 45 | "node": ">= 0.8.x" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tasks/commit-changes.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var format = require('util').format; 4 | var series = require('continuable-series'); 5 | 6 | var exec = require('../lib/exec.js'); 7 | 8 | function commitChanges(opts, cb) { 9 | var nextVersion = opts.nextVersion; 10 | var folder = opts.folder; 11 | 12 | function onStat(err, stat) { 13 | var shrinkwrap = stat ? 'npm-shrinkwrap.json' : ''; 14 | 15 | var addCmd = format( 16 | 'git add %s package.json %s', 17 | opts.filename, 18 | shrinkwrap 19 | ); 20 | var commitCmd = format('git commit --no-verify -m \'%s\'', 21 | nextVersion); 22 | var tagCmd = format('git tag v%s -am \'%s\'', nextVersion, 23 | nextVersion); 24 | 25 | series([ 26 | exec.bind(null, addCmd, { cwd: folder }), 27 | exec.bind(null, commitCmd, { silent: true, cwd: folder }), 28 | exec.bind(null, tagCmd, { cwd: folder }) 29 | ], cb); 30 | } 31 | 32 | fs.stat(path.join(folder, 'npm-shrinkwrap.json'), onStat); 33 | } 34 | 35 | module.exports = commitChanges; 36 | -------------------------------------------------------------------------------- /tasks/compute-version.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | var jsonParse = require('safe-json-parse'); 4 | var semver = require('semver'); 5 | 6 | function createNextVersion(currentVersion, opts) { 7 | opts = opts || {}; 8 | 9 | var version = semver.parse(currentVersion); 10 | 11 | if (opts.major) { 12 | version.major++; 13 | version.minor = 0; 14 | version.patch = 0; 15 | } else if (opts.minor) { 16 | version.minor++; 17 | version.patch = 0; 18 | } else if (opts.patch) { 19 | version.patch++; 20 | } 21 | 22 | return version.format(); 23 | } 24 | function computeVersion(opts, cb) { 25 | var package = path.join(opts.folder, 'package.json'); 26 | 27 | fs.readFile(package, function (err, buf) { 28 | if (err) { 29 | return cb(err); 30 | } 31 | 32 | jsonParse(String(buf), function (err, json) { 33 | if (err) { 34 | return cb(err); 35 | } 36 | 37 | var currentVersion = json.version; 38 | var nextVersion = createNextVersion( 39 | currentVersion, opts); 40 | 41 | cb(null, nextVersion); 42 | }); 43 | }); 44 | } 45 | 46 | module.exports = computeVersion; 47 | -------------------------------------------------------------------------------- /tasks/update-changelog.js: -------------------------------------------------------------------------------- 1 | var parallel = require('continuable-para'); 2 | var formatDate = require('date-format'); 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | 6 | var exec = require('../lib/exec.js'); 7 | var readChangelog = require('../changelog/read.js'); 8 | var ChangeLogHeader = require('../changelog/header.js'); 9 | 10 | function createCommands(changelog, opts) { 11 | var headCmd = 'git rev-parse --short HEAD'; 12 | var logFlags = opts.logFlags || ''; 13 | var logCmd = 'git log ' + logFlags; 14 | 15 | if (changelog && changelog.chunks.length) { 16 | var commit = changelog.chunks[0].header.commit; 17 | logCmd += ' ' + commit + '..'; 18 | } 19 | 20 | return { 21 | head: exec.bind(null, headCmd, { cwd: opts.folder }), 22 | log: exec.bind(null, logCmd, { cwd: opts.folder }) 23 | }; 24 | } 25 | 26 | function updateChangelog(opts, cb) { 27 | var nextVersion = opts.nextVersion; 28 | var changelogFile = path.join(opts.folder, opts.filename); 29 | 30 | readChangelog(changelogFile, function (err, changelog) { 31 | if (err) { 32 | return cb(err); 33 | } 34 | 35 | var commands = createCommands(changelog, opts); 36 | 37 | parallel(commands, function (err, result) { 38 | if (err) { 39 | return cb(err); 40 | } 41 | 42 | var header = new ChangeLogHeader( 43 | formatDate('yyyy-MM-dd'), 44 | nextVersion, 45 | result.head.split('\n')[0] 46 | ); 47 | var content = changelog ? changelog.content : ''; 48 | var newContent = header.toString() + result.log + 49 | '\n\n' + content; 50 | 51 | fs.writeFile(changelogFile, newContent, cb); 52 | }); 53 | }); 54 | 55 | } 56 | 57 | module.exports = updateChangelog; 58 | -------------------------------------------------------------------------------- /tasks/update-version.js: -------------------------------------------------------------------------------- 1 | var parallel = require('continuable-para'); 2 | var path = require('path'); 3 | var fs = require('fs'); 4 | var semver = require('semver'); 5 | 6 | var computeVersion = require('./compute-version.js'); 7 | var transactJsonFile = require('../lib/transact-json-file.js'); 8 | 9 | function updateVersion(opts, cb) { 10 | function next(err, nextVersion) { 11 | if (err) { 12 | return cb(err); 13 | } 14 | 15 | function setVersion(package) { 16 | package.version = nextVersion; 17 | return package; 18 | } 19 | 20 | var packageFile = path.join(opts.folder, 'package.json'); 21 | var shrinkwrapFile = path.join(opts.folder, 'npm-shrinkwrap.json'); 22 | 23 | fs.stat(shrinkwrapFile, function (err, stat) { 24 | var tasks = stat ? [ 25 | transactJsonFile.bind(null, packageFile, setVersion), 26 | transactJsonFile.bind(null, shrinkwrapFile, setVersion) 27 | ] : [ 28 | transactJsonFile.bind(null, packageFile, setVersion) 29 | ]; 30 | 31 | parallel(tasks, function (err) { 32 | cb(err, err ? null : nextVersion); 33 | }); 34 | }); 35 | } 36 | 37 | if (!opts.nextVersion) { 38 | computeVersion(opts, next); 39 | } else { 40 | var versionStr = semver.parse(opts.nextVersion); 41 | if (!versionStr) { 42 | throw new Error('invalid version: ' + opts.nextVersion); 43 | } 44 | 45 | opts.nextVersion = versionStr.toString(); 46 | 47 | next(null, opts.nextVersion); 48 | } 49 | } 50 | 51 | module.exports = updateVersion; 52 | -------------------------------------------------------------------------------- /test/does-patch.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | 5 | var initRepo = require('./lib/init-repo.js'); 6 | var buildChangelog = require('../index.js'); 7 | 8 | var folder = path.join(__dirname, 'repo'); 9 | 10 | test('run build-changelog & verify patch', initRepo(__dirname, { 11 | 'repo': { 12 | 'package.json': '{ "version": "0.1.0" }', 13 | 'index.js': 'module.exports = 42;' 14 | } 15 | }, function (assert) { 16 | buildChangelog({ folder: folder }, function (err, nextVersion) { 17 | assert.ifError(err); 18 | assert.equal(nextVersion, '0.1.1'); 19 | 20 | var loc = path.join(folder, 'package.json'); 21 | fs.readFile(loc, 'utf8', function (err, str) { 22 | assert.ifError(err); 23 | 24 | var json = JSON.parse(str); 25 | 26 | assert.equal(json.version, '0.1.1'); 27 | 28 | assert.end(); 29 | }); 30 | }); 31 | })); 32 | -------------------------------------------------------------------------------- /test/fresh-repo.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var test = require('tape'); 3 | var formatDate = require('date-format'); 4 | var parallel = require('continuable-para'); 5 | var fs = require('fs'); 6 | 7 | var buildChangelog = require('../index.js'); 8 | var initRepo = require('./lib/init-repo.js'); 9 | var exec = require('../lib/exec.js'); 10 | var readChangelog = require('../changelog/read.js'); 11 | 12 | var folder = path.join(__dirname, 'repo'); 13 | 14 | test('run build-changelog on fresh repo', initRepo(__dirname, { 15 | 'repo': { 16 | 'package.json': '{ "version": "0.1.0" }', 17 | 'npm-shrinkwrap.json': '{ "version": "0.1.0" }', 18 | 'index.js': 'module.exports = 42;' 19 | } 20 | }, function (assert) { 21 | buildChangelog({ folder: folder }, function (err, nextVersion) { 22 | assert.ifError(err); 23 | assert.equal(nextVersion, '0.1.1'); 24 | 25 | parallel({ 26 | log: exec.bind(null, 'git log --oneline', { 27 | cwd: folder 28 | }), 29 | diff: exec.bind(null, 'git diff HEAD~1 -- CHANGELOG', { 30 | cwd: folder 31 | }), 32 | diff2: exec.bind(null, 'git diff HEAD~1 -- package.json', { 33 | cwd: folder 34 | }), 35 | package: fs.readFile.bind(null, path.join(folder, 'package.json')), 36 | changelog: readChangelog.bind(null, path.join(folder, 'CHANGELOG')) 37 | }, function (err, data) { 38 | assert.ifError(err); 39 | 40 | var changelog = data.changelog; 41 | var diff = data.diff; 42 | var log = data.log; 43 | 44 | var logLines = log.trim().split('\n'); 45 | assert.equal(logLines.length, 2); 46 | assert.notEqual(logLines[0].indexOf('0.1.1'), -1); 47 | assert.notEqual(logLines[1].indexOf('initial commit'), -1); 48 | 49 | assert.notEqual(diff.indexOf('new file mode'), -1); 50 | 51 | assert.equal( 52 | JSON.parse(String(data.package)).version, 53 | '0.1.1'); 54 | 55 | assert.notEqual(data.diff2.indexOf('0.1.1'), -1); 56 | 57 | assert.ok(changelog); 58 | 59 | var chunks = changelog.chunks; 60 | 61 | assert.equal(chunks.length, 1); 62 | 63 | var chunk = chunks[0]; 64 | var header = chunk.header; 65 | 66 | assert.equal(header.date, formatDate('yyyy-MM-dd')); 67 | assert.equal(header.version, '0.1.1'); 68 | assert.ok(header.commit); 69 | 70 | var lines = chunk.lines; 71 | var line = lines[0]; 72 | 73 | assert.equal(lines.length, 1); 74 | assert.ok(line.sha); 75 | assert.deepEqual(line.decorations, ['HEAD', 'master']); 76 | assert.equal(line.message, 'initial commit'); 77 | 78 | assert.end(); 79 | }); 80 | }); 81 | })); 82 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | 3 | var buildChangelog = require('../index.js'); 4 | 5 | test('buildChangelog is a function', function (assert) { 6 | assert.strictEqual(typeof buildChangelog, 'function'); 7 | 8 | assert.end(); 9 | }); 10 | 11 | require('./fresh-repo.js'); 12 | require('./second-call.js'); 13 | require('./does-patch.js'); 14 | -------------------------------------------------------------------------------- /test/lib/git-start.js: -------------------------------------------------------------------------------- 1 | var series = require('continuable-series'); 2 | 3 | var exec = require('../../lib/exec.js'); 4 | 5 | var DEBUG = false; 6 | 7 | function execCmd(cmd, opts) { 8 | if (DEBUG) { 9 | return function (cb) { 10 | exec(cmd, opts, function (err, stdout) { 11 | console.log('run command', cmd); 12 | console.log('got error', err); 13 | console.log('got stdout', stdout); 14 | 15 | cb(err, stdout); 16 | }); 17 | }; 18 | } 19 | 20 | return exec.bind(null, cmd, opts); 21 | } 22 | 23 | function gitStart(dirname, opts, cb) { 24 | if (typeof opts === 'function') { 25 | cb = opts; 26 | opts = {}; 27 | } 28 | 29 | var message = opts.message || 'initial commit'; 30 | var commitCmd = 'git commit -m \'' + message + '\''; 31 | 32 | series([ 33 | !opts.exists ? execCmd('git init', { cwd: dirname }) : null, 34 | execCmd('git add . --all', { cwd: dirname }), 35 | execCmd(commitCmd, { cwd: dirname }) 36 | ].filter(Boolean), cb); 37 | } 38 | 39 | module.exports = gitStart; 40 | -------------------------------------------------------------------------------- /test/lib/init-repo.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var withFixtures = require('./with-fixtures.js'); 4 | var gitStart = require('./git-start.js'); 5 | 6 | function initRepo(dirname, fixtures, task) { 7 | return function (assert) { 8 | var _end = assert.end; 9 | 10 | withFixtures(dirname, fixtures, function (cb) { 11 | assert.end = cb; 12 | 13 | gitStart(path.join(dirname, 'repo'), function (err) { 14 | if (err) { 15 | return cb(err); 16 | } 17 | 18 | task(assert); 19 | }); 20 | }, function (err) { 21 | assert.ifError(err); 22 | _end.call(assert); 23 | }); 24 | }; 25 | } 26 | 27 | module.exports = initRepo; 28 | -------------------------------------------------------------------------------- /test/lib/with-fixtures.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var parallel = require('continuable-para'); 4 | var series = require('continuable-series'); 5 | var rimraf = require('rimraf'); 6 | 7 | function createFixtures(dirname, fixtures, callback) { 8 | var tasks = Object.keys(fixtures).map(function (key) { 9 | var value = fixtures[key]; 10 | var loc = path.join(dirname, key); 11 | 12 | if (typeof value === 'string') { 13 | return fs.writeFile.bind(null, loc, value); 14 | } else if (typeof value === 'object') { 15 | return series([ 16 | fs.mkdir.bind(null, loc), 17 | createFixtures.bind(null, loc, value) 18 | ]); 19 | } 20 | }); 21 | 22 | parallel(tasks, callback); 23 | } 24 | 25 | function teardownFixtures(dirname, fixtures, callback) { 26 | var tasks = Object.keys(fixtures).map(function (key) { 27 | var value = fixtures[key]; 28 | var loc = path.join(dirname, key); 29 | 30 | if (typeof value === 'string') { 31 | return fs.unlink.bind(null, loc); 32 | } else if (typeof value === 'object') { 33 | return series([ 34 | teardownFixtures.bind(null, loc, value), 35 | rimraf.bind(null, loc) 36 | ]); 37 | } 38 | }); 39 | 40 | parallel(tasks, callback); 41 | } 42 | 43 | /* withFixtures takes a hash of file fixtures and a task to 44 | execute. 45 | 46 | It then ensures the fixtures exists in the file system, 47 | runs the task and then removes the fixtures. 48 | 49 | When it's done with the task it will call the callback. 50 | 51 | ```js 52 | var test = require('mocha').test; 53 | var assert = require('assert'); 54 | var configChain = require('config-chain'); 55 | 56 | test('run some test', withFixtures(__dirname, { 57 | json: { 58 | 'config.json': '{ "port": 3000, "awesome": true }', 59 | 'test.json': '{ "port": 4000 }' 60 | } 61 | }, function (end) { 62 | var config = configChain( 63 | './json/' + process.env.NODE_ENV + '.json', 64 | './json/config.json' 65 | ); 66 | 67 | assert.equal(config.port, 4000); 68 | assert.equal(config.awesome, true); 69 | 70 | end(); 71 | })); 72 | ``` 73 | 74 | `withFixtures` is very useful to use with writing integration 75 | tests. It allows you to declare a file system as a simple 76 | object and then run a test case against it knowing that 77 | it will be cleaned up after the test case finishes. 78 | 79 | Notice the usage of the `__dirname` to tell `withFixtures` 80 | where the folders are local to. In this case the dirname 81 | of the test file, but it can be set to `process.cwd()` or 82 | `os.tmpDir()` or whatever location you want. 83 | 84 | */ 85 | 86 | function withFixtures(dirname, fixtures, task, callback) { 87 | if (!callback) { 88 | return withFixtures.bind(null, dirname, fixtures, task); 89 | } 90 | 91 | createFixtures(dirname, fixtures, function onFixtures(err) { 92 | if (err) { 93 | return callback(err); 94 | } 95 | 96 | task(function onTask(err) { 97 | function onTeardown(newErr) { 98 | callback(err || newErr); 99 | } 100 | 101 | teardownFixtures(dirname, fixtures, onTeardown); 102 | }); 103 | }); 104 | } 105 | 106 | withFixtures.createFixtures = createFixtures; 107 | withFixtures.teardownFixtures = teardownFixtures; 108 | 109 | module.exports = withFixtures; 110 | -------------------------------------------------------------------------------- /test/second-call.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var path = require('path'); 3 | var series = require('continuable-series'); 4 | var parallel = require('continuable-para'); 5 | var fs = require('fs'); 6 | 7 | var exec = require('../lib/exec.js'); 8 | var readChangelog = require('../changelog/read.js'); 9 | var buildChangelog = require('../index.js'); 10 | var initRepo = require('./lib/init-repo.js'); 11 | var gitStart = require('./lib/git-start.js'); 12 | 13 | var folder = path.join(__dirname, 'repo'); 14 | 15 | function setupRepo(dirname, fixtures, task) { 16 | return initRepo(dirname, fixtures, function (assert) { 17 | series([ 18 | buildChangelog.bind(null, { folder: folder }), 19 | fs.writeFile.bind(null, path.join(folder, 'index.js'), 20 | 'module.exports = 43;'), 21 | gitStart.bind(null, folder, { 22 | exists: true, 23 | message: 'an extra commit' 24 | }) 25 | ], function (err) { 26 | if (err) { 27 | // non-standard usage of assert.end() 28 | return assert.end(err); 29 | } 30 | 31 | task(assert); 32 | }); 33 | }); 34 | } 35 | 36 | test('run build-changelog twice', setupRepo(__dirname, { 37 | 'repo': { 38 | 'package.json': '{ "version": "0.1.0" }', 39 | 'npm-shrinkwrap.json': '{ "version": "0.1.0" }', 40 | 'index.js': 'module.exports = 42;' 41 | } 42 | }, function (assert) { 43 | buildChangelog({ folder: folder }, function (err, nextVersion) { 44 | assert.ifError(err); 45 | assert.equal(nextVersion, '0.1.2'); 46 | 47 | parallel({ 48 | log: exec.bind(null, 'git log --oneline', { 49 | cwd: folder 50 | }), 51 | diff: exec.bind(null, 'git diff HEAD~1 -- CHANGELOG', { 52 | cwd: folder 53 | }), 54 | changelog: readChangelog.bind(null, path.join(folder, 'CHANGELOG')) 55 | }, function (err, data) { 56 | assert.ifError(err); 57 | 58 | var logLines = data.log.trim().split('\n'); 59 | assert.equal(logLines.length, 4); 60 | assert.notEqual(logLines[0].indexOf('0.1.2'), -1); 61 | assert.notEqual(logLines[1].indexOf('an extra commit'), -1); 62 | assert.notEqual(logLines[2].indexOf('0.1.1'), -1); 63 | assert.notEqual(logLines[3].indexOf('initial commit'), -1); 64 | 65 | var diff = data.diff; 66 | 67 | assert.equal(diff.indexOf('new file mode'), -1); 68 | assert.notEqual(diff.indexOf('@@ -1,3 +1,8 @@'), -1); 69 | 70 | var changelog = data.changelog; 71 | 72 | assert.equal(changelog.chunks.length, 2); 73 | 74 | var chunk1 = changelog.chunks[0]; 75 | var chunk2 = changelog.chunks[1]; 76 | 77 | assert.equal(chunk2.header.version, '0.1.1'); 78 | assert.equal(chunk1.header.version, '0.1.2'); 79 | 80 | var lines1 = chunk1.lines; 81 | var lines2 = chunk2.lines; 82 | 83 | assert.equal(lines2.length, 1); 84 | assert.equal(lines2[0].message, 'initial commit'); 85 | assert.deepEqual(lines2[0].decorations, 86 | ['HEAD', 'master']); 87 | 88 | assert.equal(lines1.length, 2); 89 | assert.equal(lines1[0].message, 'an extra commit'); 90 | assert.deepEqual(lines1[0].decorations, 91 | ['HEAD', 'master']); 92 | assert.equal(lines1[1].message, '0.1.1'); 93 | assert.deepEqual(lines1[1].decorations, 94 | ['tag: v0.1.1']); 95 | 96 | assert.end(); 97 | }); 98 | }); 99 | })); 100 | --------------------------------------------------------------------------------