diff --git a/README.md b/README.md index 0fd3fda..78deea8 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ # thoughtful-release [![NPM version](https://badge.fury.io/js/thoughtful-release.svg)](http://badge.fury.io/js/thoughtful-release) -[![Build Status](https://travis-ci.org/nknapp/thoughtful-release.svg?branch=master)](https://travis-ci.org/nknapp/thoughtful-release) -[![Coverage Status](https://img.shields.io/coveralls/nknapp/thoughtful-release.svg)](https://coveralls.io/r/nknapp/thoughtful-release) + [![Travis Build Status](https://travis-ci.org/nknapp/thoughtful-release.svg?branch=master)](https://travis-ci.org/nknapp/thoughtful-release) + [![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/github/nknapp/thoughtful-release?svg=true&branch=master)](https://ci.appveyor.com/project/nknapp/thoughtful-release) + [![Coverage Status](https://img.shields.io/coveralls/nknapp/thoughtful-release.svg)](https://coveralls.io/r/nknapp/thoughtful-release) + > Create high quality releases with less work diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..73f0803 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,27 @@ +# Test against this version of Node.js +environment: + matrix: + - nodejs_version: "4" + - nodejs_version: "5" + +# Install scripts. (runs after repo cloning) +install: + # Get the latest stable version of Node.js or io.js + - ps: Install-Product node $env:nodejs_version + # install modules + - npm install + +# Post-install test scripts. +test_script: + # Output useful info for debugging. + - node --version + - npm --version + # run tests + - npm install -g istanbul + - istanbul cover node_modules/mocha/bin/_mocha --report lcovonly + - npm install -g coveralls + - cat ./coverage/lcov.info | coveralls + +# Don't actually build. +build: off + \ No newline at end of file diff --git a/bin/thoughtful.js b/bin/thoughtful.js index 15131ec..3a6c879 100755 --- a/bin/thoughtful.js +++ b/bin/thoughtful.js @@ -47,7 +47,7 @@ program .command('cleanup-history [target-branch]') .description('Rebase the current branch onto another branch, condensing the whole branch into a single commit.') .action((targetBranch) => { - thoughtful.cleanupHistory({targetBranch: targetBranch}).done(() => console.log(`Cleanup complete`)) + thoughtful.cleanupHistory({targetBranch: targetBranch}).done(() => console.log('Cleanup complete')) }) program.parse(process.argv) diff --git a/lib/changelog.js b/lib/changelog.js index 87b4623..145aaea 100755 --- a/lib/changelog.js +++ b/lib/changelog.js @@ -48,7 +48,11 @@ function Changelog (cwd) { */ this.formatRelease = function (version, date, changes) { var dateStr = date.toUTCString() - return `# Version ${version} (${dateStr})\n\n${changes}\n\n` + return `# Version ${version} (${dateStr}) + +${changes} + +` } /** diff --git a/lib/git.js b/lib/git.js index adeac69..5ce2ccc 100644 --- a/lib/git.js +++ b/lib/git.js @@ -25,6 +25,12 @@ var _ = { */ module.exports = (dir) => new Git(dir) +/** + * Path to a node-script to be used instead of the real 'git' for test-cases + * @type {undefined} + */ +module.exports.mockCmd = undefined + /** * The Git class for accessing git repositories * @param {string} dir the working directory @@ -35,7 +41,7 @@ function Git (dir) { 'LANG': 'en_US' }, process.env) - var gitcmd = process.env['THOUGHTFUL_GIT_CMD'] || 'git' + var gitcmd = module.exports.mockCmd ? [ 'node', module.exports.mockCmd ] : 'git' /** * Execute git with a list of cmd-line arguments in the working directory @@ -55,7 +61,7 @@ function Git (dir) { * that need a tty to work * @returns {*} */ - this.spawn = function runGit () { + this.spawn = function spawnGit () { debug(`Running in ${dir}`, gitcmd, _.toArray(arguments)) return cpp.spawn(gitcmd, _.toArray(arguments), {env: env, cwd: dir, stdio: 'inherit'}) } @@ -181,8 +187,9 @@ function Git (dir) { this.cleanupHistory = function cleanupHistory (options) { var targetBranch = (options && options.targetBranch) || 'master' var thoughtful = process.argv[1] - /* istanbul ignore else The default value for "thoughtful" is the current process. It - is hardly possible to use this default value in a coverage test */ + /* istanbul ignore else + The default value for "thoughtful" is the current process. It + is hardly possible to use this default value in a unit-test */ if (options && options.thoughtful) { thoughtful = options.thoughtful } @@ -192,7 +199,7 @@ function Git (dir) { .then((tagOutput) => { return this.run('merge-base', 'HEAD', targetBranch) // Get merge-base // Squash - .then((mergeBase) => this.spawn('-c', `sequence.editor=${thoughtful} sequence-editor`, 'rebase', '--interactive', mergeBase.stdout.trim())) + .then((mergeBase) => this.spawn('-c', `sequence.editor='${thoughtful}' sequence-editor`, 'rebase', '--interactive', mergeBase.stdout.trim())) // Rebase on target branch .then(() => this.run('rebase', targetBranch).invoke('appendTo', tagOutput.appendTo(''))) }) diff --git a/lib/q-child-process.js b/lib/q-child-process.js index 8920b87..11c0ae9 100644 --- a/lib/q-child-process.js +++ b/lib/q-child-process.js @@ -1,6 +1,9 @@ var cp = require('child_process') var Q = require('q') var debug = require('debug')('thoughtful:q-child-process') +var _ = { + isArray: require('lodash.isarray') +} /** * Promise-Based functions from child_process @@ -20,6 +23,12 @@ module.exports = { * @returns {{ stdout: string, stderr: string}} the output of the process on stdout and stderr */ function execFile (cmd, args, options) { + // if the cmd is an array, use the first entry as cmd and insert the rest as argument + if (_.isArray(cmd)) { + args = cmd.concat(args) + args.shift() + cmd = cmd[0] + } var defer = Q.defer() cp.execFile(cmd, args, options, (err, stdout, stderr) => { if (err) { @@ -33,8 +42,10 @@ function execFile (cmd, args, options) { * @param {string} output the previous output */ appendTo: function (output) { - var myOutput = `${stdout.trim()}\n${stderr.trim()}` - return `${output}\n${myOutput.trim()}`.trim() + var myOutput = `${stdout.trim()} +${stderr.trim()}` + return `${output} +${myOutput.trim()}`.trim() } }) }) @@ -44,12 +55,19 @@ function execFile (cmd, args, options) { /** * Spawn a process and return a promise that is resolved when the process exits with exit-code zero, or rejected * on a non-zero exit-code. - * @param {string} cmd the command to execute + * @param {string|array} cmd the command to execute * @param {string[]} args command line arguments for the command * @param {object} options options for the {@link child_process#spawn}-method. * @returns {*} a promise that is resolved or rejected based on the exit-code of the child-process */ function spawn (cmd, args, options) { + // if the cmd is an array, use the first entry as cmd and insert the rest as argument + if (_.isArray(cmd)) { + args = cmd.concat(args) + args.shift() + cmd = cmd[0] + } + debug('spawn', cmd, args, options) var defer = Q.defer() var child = cp.spawn(cmd, args, options) @@ -58,7 +76,7 @@ function spawn (cmd, args, options) { defer.resolve() debug('spawn resolved', cmd, args, options) } else { - defer.reject(new Error(`Command "${cmd} ${args && args.join(' ')}" exited with ${code}`)) + defer.reject(new Error(`Command "${cmd} ${args && JSON.stringify(args)}" exited with ${code}`)) } }) return defer.promise @@ -75,29 +93,14 @@ function spawn (cmd, args, options) { * @param {object=} options options for the {@link child_process#spawn}-method. */ function spawnWithShell (command, options) { - var os = require('os') - switch (os.type()) { - case 'Linux': - case 'Dawin': - return spawn('/bin/sh', ['-c', command], options) - case 'Windows_NT': - return spawn('cmd.exe', ['/s', '/c', command], options) - default: - throw new Error(`Unexpected OS-type ${os.type()}`) - } + return spawn('sh', ['-c', command], options) } +/** + * Escape parameters for use with the `spawnWithShell` + **/ function escapeParam (param) { - var os = require('os') - switch (os.type()) { - case 'Linux': - case 'Dawin': - // Escape " -> \" and \ -> \\ - return `"${param.replace(/[\\"]/g, '\\$&')}"` - case 'Windows_NT': - // No escaping in windows until somebody files an issue to do otherwise - return param - default: - throw new Error(`Unexpected OS-type ${os.type()}`) - } + // Same on windows and linux, since we are spawning the git-shell + // Wrap the parameter in quotes and escape '"' and '\' with a backslash to allow shell parameters to be recognized as a unit + return `"${param.replace(/[\\"]/g, '\\$&')}"` } diff --git a/package.json b/package.json index d0b5de5..f9e216a 100644 --- a/package.json +++ b/package.json @@ -35,9 +35,11 @@ "dependencies": { "commander": "^2.9.0", "debug": "^2.2.0", + "head": "^1.0.0", "lodash.contains": "^2.4.3", "lodash.defaults": "^3.1.2", "lodash.defaultsdeep": "^3.10.0", + "lodash.isarray": "^4.0.0", "lodash.isplainobject": "^3", "lodash.isstring": "^3.0.1", "lodash.toarray": "^3.0.2", @@ -50,8 +52,9 @@ "chai": "^3.2.0", "chai-as-promised": "^5.2.0", "ghooks": "^1.0.1", + "lodash.escaperegexp": "^3.0.1", "mocha": "^2.2.5", - "lodash.escaperegexp": "^3.0.1" + "trace": "^2.3.0" }, "files": [ "bin", @@ -65,7 +68,7 @@ }, "config": { "ghooks": { - "pre-commit": "thoughtful precommit && standard --format && thought run -a" + "pre-commit": "thoughtful precommit && standard && thought run -a" } }, "keywords": [] diff --git a/test/changelog-spec.js b/test/changelog-spec.js index e089350..3449bdc 100644 --- a/test/changelog-spec.js +++ b/test/changelog-spec.js @@ -21,6 +21,7 @@ var expect = chai.expect var changelog = require('../lib/changelog.js') var qfs = require('q-io/fs') var path = require('path') +require('trace') function fixture (filename) { return require('fs').readFileSync(path.join('test', 'fixtures', filename), {encoding: 'utf-8'}) @@ -85,7 +86,7 @@ describe('changelog-library:', () => { it('should be able to load, store a CHANGELOG.md file without modifying it.', () => { var changelogContents = qfs.copy('test/fixtures/changelog-with-a-release.md', workDir('CHANGELOG.md')) .then(() => changelog(workDir()) - .save()) + .save()) .then(() => qfs.read(workDir('CHANGELOG.md'))) return expect(changelogContents).to.eventually.equal(fixture('changelog-with-a-release.md')) }) @@ -93,8 +94,8 @@ describe('changelog-library:', () => { it('should be able to load a CHANGELOG.md and store a modified version', () => { var changelogContents = qfs.copy('test/fixtures/changelog-with-a-release.md', workDir('CHANGELOG.md')) .then(() => changelog(workDir()) - .newRelease('2.0.0', new Date('2015-11-25T13:06:00Z'), '* Change 1\n* Change 2') - .save()) + .newRelease('2.0.0', new Date('2015-11-25T13:06:00Z'), '* Change 1\n* Change 2') + .save()) .then(() => qfs.read(workDir('CHANGELOG.md'))) return expect(changelogContents).to.eventually.equal(fixture('changelog-with-two-releases.md')) }) @@ -124,12 +125,28 @@ describe('changelog-library:', () => { it('should call THOUGHTFUL_CHANGELOG_EDITOR as editor, if set', () => { const dummyEditor = path.resolve(__dirname, 'dummy-editor', 'dummy-editor.js') - process.env['THOUGHTFUL_CHANGELOG_EDITOR'] = `${dummyEditor} changelog-editor` - process.env['EDITOR'] = `${dummyEditor} default-editor` + // Dummy editor prepends contents with first argument ("changelog editor") + process.env['THOUGHTFUL_CHANGELOG_EDITOR'] = `node "${dummyEditor}" changelog-editor` + process.env['EDITOR'] = `node "${dummyEditor}" default-editor` var changelogContents = changelog(workDir()).openEditor() .then(() => qfs.read(workDir('CHANGELOG.md'))) return expect(changelogContents).to.eventually.equal(`changelog-editor +# Release-Notes + +`) + }) + + it('should call escape the changelog-path propery if it contains spaces', () => { + const dummyEditor = path.resolve(__dirname, 'dummy-editor', 'dummy-editor.js') + // Dummy editor prepends contents with first argument ("changelog editor") + process.env['THOUGHTFUL_CHANGELOG_EDITOR'] = `node "${dummyEditor}" changelog-editor` + + var changelogContents = qfs.makeTree(workDir('with spaces')) + .then(() => changelog(workDir('with spaces')).openEditor()) + .then(() => qfs.read(workDir('with spaces/CHANGELOG.md'))) + return expect(changelogContents).to.eventually.equal(`changelog-editor + # Release-Notes `) @@ -138,7 +155,7 @@ describe('changelog-library:', () => { it('should call EDITOR as editor, if THOUGHTFUL_CHANGELOG_EDITOR is not set', () => { const dummyEditor = path.resolve(__dirname, 'dummy-editor', 'dummy-editor.js') process.env['THOUGHTFUL_CHANGELOG_EDITOR'] = '' - process.env['EDITOR'] = `${dummyEditor} default-editor` + process.env['EDITOR'] = `node "${dummyEditor}" default-editor` var changelogContents = changelog(workDir()).openEditor() .then(() => qfs.read(workDir('CHANGELOG.md'))) return expect(changelogContents).to.eventually.equal(`default-editor diff --git a/test/dummy-editor/dummy-editor.js b/test/dummy-editor/dummy-editor.js index cf69d2f..61c713c 100755 --- a/test/dummy-editor/dummy-editor.js +++ b/test/dummy-editor/dummy-editor.js @@ -10,4 +10,7 @@ var fs = require('fs') var contents = fs.readFileSync(process.argv[3], {encoding: 'utf-8'}) contents = contents.replace(/\n+/g, '\n') -fs.writeFileSync(process.argv[3], `${process.argv[2]}\n\n${contents.trim()}\n`) +fs.writeFileSync(process.argv[3], `${process.argv[2]} + +${contents.trim()} +`) diff --git a/test/dummy-git/commit-editor.js b/test/dummy-git/commit-editor.js index fd2f182..d2bed6c 100755 --- a/test/dummy-git/commit-editor.js +++ b/test/dummy-git/commit-editor.js @@ -8,4 +8,7 @@ var fs = require('fs') var contents = fs.readFileSync(process.argv[3], {encoding: 'utf-8'}) contents = contents.replace(/\n+/g, '\n') -fs.writeFileSync(process.argv[3], `${process.argv[2]}\n\n${contents.trim()}\n`) +fs.writeFileSync(process.argv[3], `${process.argv[2]} + +${contents.trim()} +`) diff --git a/test/dummy-git/commit-editor.sh b/test/dummy-git/commit-editor.sh new file mode 100755 index 0000000..03f779f --- /dev/null +++ b/test/dummy-git/commit-editor.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +node "${0%.sh}.js" "$@" \ No newline at end of file diff --git a/test/dummy-git/git-lastRelease-v0.8.3.js b/test/dummy-git/git-lastRelease-v0.8.3.js index 049cd65..9891532 100755 --- a/test/dummy-git/git-lastRelease-v0.8.3.js +++ b/test/dummy-git/git-lastRelease-v0.8.3.js @@ -14,10 +14,10 @@ require('./mock-kit')({ 'log|--no-merges|--pretty=tformat:* %h %s - %an|v0.8.3..v0.8.5': { stdout: 'log|--no-merges|--pretty=tformat:* %h %s - %an|v0.8.3..v0.8.5' }, - '-c|sequence.editor=thoughtful sequence-editor|rebase|--interactive|master': { + "-c|sequence.editor='thoughtful' sequence-editor|rebase|--interactive|master": { stdout: 'rebase onto master' }, - '-c|sequence.editor=thoughtful sequence-editor|rebase|--interactive||stable': { + "-c|sequence.editor='thoughtful' sequence-editor|rebase|--interactive|stable": { stdout: 'rebase onto stable' }, 'merge-base|HEAD|master': { @@ -26,10 +26,10 @@ require('./mock-kit')({ 'merge-base|HEAD|stable': { stdout: 'fork-point-stable' }, - '-c|sequence.editor=thoughtful sequence-editor|rebase|--interactive|fork-point-stable': { + "-c|sequence.editor='thoughtful' sequence-editor|rebase|--interactive|fork-point-stable": { stdout: 'Squash to fork-point-stable' }, - '-c|sequence.editor=thoughtful sequence-editor|rebase|--interactive|fork-point-master': { + "-c|sequence.editor='thoughtful' sequence-editor|rebase|--interactive|fork-point-master": { stdout: 'Squash to fork-point-master' }, 'rebase|stable': { diff --git a/test/git-spec.js b/test/git-spec.js index b083059..496bf25 100644 --- a/test/git-spec.js +++ b/test/git-spec.js @@ -17,15 +17,14 @@ var chai = require('chai') var chaiAsPromised = require('chai-as-promised') chai.use(chaiAsPromised) var expect = chai.expect -var path = require('path') var git = require('../lib/git.js') var qfs = require('q-io/fs') var Q = require('q') +var path = require('path') describe('git-library:', () => { afterEach(() => { - process.env['THOUGHTFUL_GIT_CMD'] = '' - delete process.env['THOUGHTFUL_GIT_CMD'] + delete git.mockCmd }) describe('the "isRepo"-method', () => { it('should return false in a directory where .git does not exist', () => { @@ -38,14 +37,14 @@ describe('git-library:', () => { describe('the "lastRelease"-method', () => { it('should parse a simple version correctly', () => { - process.env['THOUGHTFUL_GIT_CMD'] = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-v0.8.3.js') + git.mockCmd = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-v0.8.3.js') return expect(git('test').lastRelease()).to.eventually.deep.equal({ tag: 'v0.8.3', newCommits: 0 }) }) it('should parse a version with qualifier correctly', () => { - process.env['THOUGHTFUL_GIT_CMD'] = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-v0.8.3-beta.js') + git.mockCmd = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-v0.8.3-beta.js') return expect(git('test').lastRelease()).to.eventually.deep.equal({ tag: 'v0.8.3-beta', newCommits: 0 @@ -53,22 +52,22 @@ describe('git-library:', () => { }) it("should relay git's stderr as error thrown", () => { - process.env['THOUGHTFUL_GIT_CMD'] = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-error.js') + git.mockCmd = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-error.js') return expect(git('test').lastRelease()).to.be.rejected }) it("should relay git's stderr as error thrown, even if the exit code is 0", () => { - process.env['THOUGHTFUL_GIT_CMD'] = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-error-exit0.js') + git.mockCmd = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-error-exit0.js') return expect(git('test').lastRelease()).to.be.rejected }) it('should throw an error the found tag does not match the schema (this can actually never happen)', () => { - process.env['THOUGHTFUL_GIT_CMD'] = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-non-matching-tag.js') + git.mockCmd = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-non-matching-tag.js') return expect(git('test').lastRelease()).to.be.rejected }) it('should return a special object if no version tags exist', () => { - process.env['THOUGHTFUL_GIT_CMD'] = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-no-version-tag.js') + git.mockCmd = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-no-version-tag.js') return expect(git('test').lastRelease()).to.eventually.deep.equal({ tag: null, newCommits: null @@ -76,41 +75,41 @@ describe('git-library:', () => { }) it('should throw an error if the git process throws an error', () => { - process.env['THOUGHTFUL_GIT_CMD'] = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-error.js') + git.mockCmd = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-error.js') return expect(git('test').lastRelease()).to.be.rejected }) }) describe('the "changes"-method', () => { it('should call "git log" with the correct parameters and return the plain output', () => { - process.env['THOUGHTFUL_GIT_CMD'] = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-v0.8.3.js') + git.mockCmd = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-v0.8.3.js') return expect(git('test').changes('v0.8.3')) .to.eventually.equal('log|--no-merges|--pretty=tformat:* %h %s - %an|v0.8.3..\n') }) it('should include the repository in the tformat if specified (converting github repository urls into weblinks)', () => { - process.env['THOUGHTFUL_GIT_CMD'] = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-v0.8.3.js') + git.mockCmd = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-v0.8.3.js') return expect(git('test').changes('v0.8.3', { url: 'git+ssh://github.com/nknapp/bootprint.git' })).to.eventually.equal('log|--no-merges|--pretty=tformat:* [%h](https://github.com/nknapp/bootprint/commit/%h) %s - %an|v0.8.3..\n') }) it('should include the target commit in the git-call if present', () => { - process.env['THOUGHTFUL_GIT_CMD'] = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-v0.8.3.js') + git.mockCmd = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-v0.8.3.js') return expect(git('test').changes('v0.8.3', { to: 'v0.8.5' })).to.eventually.equal('log|--no-merges|--pretty=tformat:* %h %s - %an|v0.8.3..v0.8.5\n') }) it('should treat output of git on stderr as error', () => { - process.env['THOUGHTFUL_GIT_CMD'] = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-error-exit0.js') + git.mockCmd = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-error-exit0.js') return expect(git('test').changes('v0.8.3', { to: 'v0.8.5' })).to.be.rejected }) it('should not add markdown links to non-github repos', () => { - process.env['THOUGHTFUL_GIT_CMD'] = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-v0.8.3.js') + git.mockCmd = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-v0.8.3.js') return expect(git('test').changes('v0.8.3', { url: 'http://blog.knappi.org' })) @@ -118,7 +117,7 @@ describe('git-library:', () => { }) it('should add markdown links to github repos', () => { - process.env['THOUGHTFUL_GIT_CMD'] = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-v0.8.3.js') + git.mockCmd = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-v0.8.3.js') return expect(git('test').changes('v0.8.3', { url: 'https://github.com/nknapp/bootprint' })) @@ -128,7 +127,7 @@ describe('git-library:', () => { describe('the currentBranch-method', () => { it('should call "git symbolic-ref --short HEAD" to determine the branch', () => { - process.env['THOUGHTFUL_GIT_CMD'] = path.resolve(__dirname, 'dummy-git', 'git-branch-feature.js') + git.mockCmd = path.resolve(__dirname, 'dummy-git', 'git-branch-feature.js') return expect(git('test').currentBranch()).to.eventually.equal('feature') }) }) @@ -157,13 +156,13 @@ describe('git-library:', () => { describe('the cleanupHistory-method', () => { it('should rebase and squash the branch onto master by default', () => { - process.env['THOUGHTFUL_GIT_CMD'] = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-v0.8.3.js') + git.mockCmd = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-v0.8.3.js') return expect(git('test').cleanupHistory({thoughtful: 'thoughtful'})) .to.eventually.equal('Tagging thoughtful-backup\nRebase on master') }) it('should rebase and squash the branch onto stable if specified as targetBranch', () => { - process.env['THOUGHTFUL_GIT_CMD'] = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-v0.8.3.js') + git.mockCmd = path.resolve(__dirname, 'dummy-git', 'git-lastRelease-v0.8.3.js') return expect(git('test').cleanupHistory({targetBranch: 'stable', thoughtful: 'thoughtful'})) .to.eventually.equal('Tagging thoughtful-backup\nRebase on stable') }) @@ -171,19 +170,19 @@ describe('git-library:', () => { describe('ths add-method', () => { it('should add a single file in case of a string', () => { - process.env['THOUGHTFUL_GIT_CMD'] = path.resolve(__dirname, 'dummy-git', 'git-add.js') + git.mockCmd = path.resolve(__dirname, 'dummy-git', 'git-add.js') return expect(git('test').add('test/file1.js')) .not.to.be.rejected }) it('should add multiple files in case of a array', () => { - process.env['THOUGHTFUL_GIT_CMD'] = path.resolve(__dirname, 'dummy-git', 'git-add.js') + git.mockCmd = path.resolve(__dirname, 'dummy-git', 'git-add.js') return expect(git('test').add(['test/file2.js', 'test/file3.js'])) .not.to.be.rejected }) it('should return a rejected promise for non-existing files', () => { - process.env['THOUGHTFUL_GIT_CMD'] = path.resolve(__dirname, 'dummy-git', 'git-add.js') + git.mockCmd = path.resolve(__dirname, 'dummy-git', 'git-add.js') return expect(git('test').add('test/file4.js')) .to.be.rejected }) diff --git a/test/index-spec.js b/test/index-spec.js index d7f769b..3858ea8 100644 --- a/test/index-spec.js +++ b/test/index-spec.js @@ -11,9 +11,13 @@ // /* global xit */ /* global beforeEach */ /* global afterEach */ +/* global before */ +/* global after */ 'use strict' +require('trace') + var chai = require('chai') var chaiAsPromised = require('chai-as-promised') chai.use(chaiAsPromised) @@ -70,7 +74,6 @@ describe('main-module:', () => { function gitCommit (file, contents, message) { return qfs.write(workDir(file), contents) - .then(() => qcp.execFile('ls', ['-l'], { cwd: workDir() })) .then(() => git('add', file)) .then(() => git('commit', '-a', '-m', message)) } @@ -116,10 +119,13 @@ describe('main-module:', () => { return expect(changelogContents.then(normalize)).to.eventually.equal(fixture('index-spec/CHANGELOG-current.md')) }) - it('should update CHANGELOG.md file for a second release', () => { + it('should update CHANGELOG.md file for a second release', function () { + this.timeout(5000) // Update changelog and bump version var changelogContents = thoughtful.updateChangelog({release: 'minor'}) .then(() => qcp.execFile('npm', ['version', 'minor'], {cwd: workDir()})) + // On Windows, we need to call the '.cmd' file + .catch(() => qcp.execFile('npm.cmd', ['version', 'minor'], {cwd: workDir()})) // Add another file .then(() => qfs.write(workDir('index.js'), "'use strict'")) .then(() => qcp.execFile('git', ['add', 'index.js'], {cwd: workDir()})) @@ -161,7 +167,7 @@ describe('main-module:', () => { it('should open an editor for the CHANGELOG.md file if openEditor is true', () => { const dummyEditor = path.resolve(__dirname, 'dummy-editor', 'dummy-editor.js') - process.env['THOUGHTFUL_CHANGELOG_EDITOR'] = `${dummyEditor} changelog-editor` + process.env['THOUGHTFUL_CHANGELOG_EDITOR'] = `node '${dummyEditor}' changelog-editor` var contents = thoughtful.updateChangelog({ openEditor: true }) .then(() => qfs.read(workDir('CHANGELOG.md'))) return expect(contents.then(normalize)).to.eventually.equal(fixture('index-spec/CHANGELOG-current-edited.md')) @@ -191,10 +197,17 @@ describe('main-module:', () => { }) }) - describe('the cleanupHistory-method', () => { + describe('the cleanupHistory-method', function () { + before(() => { + // Git editor command that does keeps the commit-message complete (to some extent) and works on all platforms + process.env['GIT_EDITOR'] = `'${require.resolve('./dummy-git/commit-editor.sh').replace(/[\\]/g, '/$&')}' "Rebased commit"` + }) + after(() => { + process.env['GIT_EDITOR'] = '' + }) + + this.timeout(30000) it('should rebase a branch onto the master ', () => { - // Git editor command that does not modify the commit message and works on all platforms - process.env['GIT_EDITOR'] = `${require.resolve('./dummy-git/commit-editor.js')} "Rebased commit"` thoughtful.reset() // Test setup: two feature branches forked from master~1 return git('branch', 'feature1') @@ -209,12 +222,10 @@ describe('main-module:', () => { .then(() => thoughtful.cleanupHistory({targetBranch: 'master', thoughtful: require.resolve('../bin/thoughtful.js')})) // Test conditions .then(() => expect(git('log', '--pretty===%s%n%b').then((output) => output.stdout.trim().replace(/\n+/g, ' '))) - .to.eventually.equal('==Rebased commit Added file2 Modified file2 Added file3 ==Added file1.txt ==Added package.json')) + .to.eventually.equal('==Rebased commit Added file2 Modified file2 Added file3 ==Added file1.txt ==Added package.json')) }) it('should also work when no rebase is necessary in the end', () => { - // Git editor command that does not modify the commit message and works on all platforms - process.env['GIT_EDITOR'] = `${require.resolve('./dummy-git/commit-editor.js')} "Rebased commit"` thoughtful.reset() // Test setup: two feature branches forked from master~1 return git('branch', 'feature1') @@ -225,12 +236,10 @@ describe('main-module:', () => { .then(() => thoughtful.cleanupHistory({targetBranch: 'master', thoughtful: require.resolve('../bin/thoughtful.js')})) // Test conditions .then(() => expect(git('log', '--pretty===%s%n%b').then((output) => output.stdout.trim().replace(/\n+/g, ' '))) - .to.eventually.equal('==Rebased commit Added file2 Modified file2 Added file3 ==Added package.json')) + .to.eventually.equal('==Rebased commit Added file2 Modified file2 Added file3 ==Added package.json')) }) it('should rebase a branch, that consists of a single commit, onto the master and offer to change the commit message', () => { - // Git editor command that does not modify the commit message and works on all platforms - process.env['GIT_EDITOR'] = `${require.resolve('./dummy-git/commit-editor.js')} "Rebased commit"` thoughtful.reset() // Test setup: two feature branches forked from master~1 return git('branch', 'feature1') @@ -240,7 +249,7 @@ describe('main-module:', () => { .then(() => thoughtful.cleanupHistory({targetBranch: 'master', thoughtful: require.resolve('../bin/thoughtful.js')})) // Test conditions .then(() => expect(git('log', '--pretty===%s%n%b').then((output) => output.stdout.trim().replace(/\n+/g, ' '))) - .to.eventually.equal('==Rebased commit Added file2 ==Added file1.txt ==Added package.json')) + .to.eventually.equal('==Rebased commit Added file2 ==Added file1.txt ==Added package.json')) }) }) diff --git a/test/q-child-process-spec.js b/test/q-child-process-spec.js index d9c8d87..cd3ef14 100644 --- a/test/q-child-process-spec.js +++ b/test/q-child-process-spec.js @@ -23,43 +23,28 @@ var qcp = require('../lib/q-child-process') describe('q-child-process-library:', () => { describe('the spawn-method', () => { it('should reject the promise if the child-process exits with exit-code!=0', () => { - return expect(qcp.spawn(path.resolve(__dirname, path.resolve(__dirname, 'dummy-git', 'git-lastRelease-error.js')))) + return expect(qcp.spawn(['node', path.resolve(__dirname, path.resolve(__dirname, 'dummy-git', 'git-lastRelease-error.js'))])) .to.be.rejected }) }) - describe('the spawn-shell', () => { + describe('the spawnShell-method', () => { it('should reject the promise if the child-process exits with exit-code!=0', () => { const cmd = path.resolve(__dirname, 'dummy-git', 'git-add.js') - return expect(qcp.spawnWithShell(cmd + ' add file4.js')) + return expect(qcp.spawnWithShell("node '" + cmd + "' add file4.js")) .to.be.rejected }) - }) - describe('the spawn-shell', () => { - it('should fulfull the promise if the child-process exits with exit-code===0', () => { + it('should fulfill the promise if the child-process exits with exit-code===0', () => { const cmd = path.resolve(__dirname, 'dummy-git', 'git-add.js') - return expect(qcp.spawnWithShell(cmd + ' add file1.js')) + return expect(qcp.spawnWithShell("node '" + cmd + "' add file1.js", { stdio: 'inherit' })) .not.to.be.rejected }) }) describe('The escapeParam-method', () => { - var os = require('os') - switch (os.type()) { - case 'Linux': - case 'Dawin': - it('should escape \\ and " on Linux', () => { - expect() - }) - break - case 'Windows_NT': - it('should have test-cases for Windows (but does not yet)', () => { - throw new Error('should have test-cases for Windows (but does not yet)') - }) - break - default: - throw new Error(`Unexpected OS-type ${os.type()}`) - } + it('should wrap the string in quotes, escaping \\ and " on any platform (since bash is always used as shell, even on windows)', () => { + expect(qcp.escapeParam('abc"bcd"\\test')).to.equal('"abc\\"bcd\\"\\\\test"') + }) }) })