diff --git a/docs/build.js b/docs/build.js index 09fd145dfd..cab592dab9 100644 --- a/docs/build.js +++ b/docs/build.js @@ -5,7 +5,7 @@ import routes from './src/Routes'; import Root from './src/Root'; import fsp from 'fs-promise'; import { copy } from '../tools/fs-utils'; -import { exec } from 'child-process-promise'; +import { exec } from '../tools/exec'; const repoRoot = path.resolve(__dirname, '../'); const docsBuilt = path.join(repoRoot, 'docs-built'); diff --git a/tools/amd/build.js b/tools/amd/build.js index 94919f78e6..e2e601e462 100644 --- a/tools/amd/build.js +++ b/tools/amd/build.js @@ -2,7 +2,7 @@ import _ from 'lodash'; import path from 'path'; import fsp from 'fs-promise'; import { copy } from '../fs-utils'; -import { exec } from 'child-process-promise'; +import { exec } from '../exec'; const repoRoot = path.resolve(__dirname, '../../'); const amd = path.join(repoRoot, 'amd'); diff --git a/tools/build.js b/tools/build.js index 8a66546ddc..aa0d5c85e6 100644 --- a/tools/build.js +++ b/tools/build.js @@ -7,6 +7,7 @@ import lib from './lib/build'; import docs from '../docs/build'; import dist from './dist/build'; import { copy } from './fs-utils'; +import { setExecOptions } from './exec'; import yargs from 'yargs'; @@ -19,8 +20,15 @@ const argv = yargs demand: false, default: false }) + .option('verbose', { + demand: false, + default: false, + describe: 'Increased debug output' + }) .argv; +setExecOptions(argv); + export default function Build(noExitOnFailure) { if (argv.docsOnly) { return docs(); diff --git a/tools/release-scripts/constants.js b/tools/constants.js similarity index 90% rename from tools/release-scripts/constants.js rename to tools/constants.js index 24f94934ef..4a5338874e 100644 --- a/tools/release-scripts/constants.js +++ b/tools/constants.js @@ -1,6 +1,6 @@ import path from 'path'; -const repoRoot = path.resolve(__dirname, '../../'); +const repoRoot = path.resolve(__dirname, '../'); const bowerRepo = 'git@github.com:react-bootstrap/react-bootstrap-bower.git'; const docsRepo = 'git@github.com:react-bootstrap/react-bootstrap.github.io.git'; diff --git a/tools/dist/build.js b/tools/dist/build.js index 9ad329f37f..6615f2e2dd 100644 --- a/tools/dist/build.js +++ b/tools/dist/build.js @@ -1,5 +1,5 @@ import path from 'path'; -import { exec } from 'child-process-promise'; +import { exec } from '../exec'; const repoRoot = path.resolve(__dirname, '../../'); const dist = path.join(repoRoot, 'dist'); diff --git a/tools/exec.js b/tools/exec.js new file mode 100644 index 0000000000..d27e7d8544 --- /dev/null +++ b/tools/exec.js @@ -0,0 +1,62 @@ +import 'colors'; +import _ from 'lodash'; +import { exec } from 'child-process-promise'; + +let executionOptions = { + dryRun: false, + verbose: false +}; + +function logWithPrefix(prefix, message) { + let formattedMessage = message.trim().split('\n') + .reduce((acc, line) => `${acc}${ acc !== '' ? '\n' : '' }${prefix} ${line}`, ''); + + console.log(formattedMessage); +} + +function execWrapper(command, options = {}) { + let proc = exec(command, options); + let title = options.title || command; + let log = message => logWithPrefix(`[${title}]`.grey, message); + + if (executionOptions.verbose) { + let output = (data, type) => { + logWithPrefix(`[${title}] ${type}:`.grey, data.toString()); + }; + proc = proc.progress(({stdout, stderr}) => { + stdout.on('data', data => output(data, 'stdout')); + stderr.on('data', data => output(data, 'stderr')); + }) + .then(result => { + log('Complete'.cyan); + return result; + }) + .catch(err => { + log(`ERROR: ${err.toString()}`.red); + throw err; + }); + } + + return proc; +} + +function safeExec(command, options = {}) { + let title = options.title || command; + + if (executionOptions.dryRun) { + logWithPrefix(`[${title}]`.grey, 'DRY RUN'.magenta); + return Promise.resolve(); + } + + return execWrapper(command, options); +} + +function setExecOptions(options) { + executionOptions = _.extend({}, executionOptions, options); +} + +export default { + exec: execWrapper, + safeExec, + setExecOptions +}; diff --git a/tools/lib/build.js b/tools/lib/build.js index 3451f8e105..a3037a69e1 100644 --- a/tools/lib/build.js +++ b/tools/lib/build.js @@ -1,6 +1,6 @@ import 'colors'; import path from 'path'; -import { exec } from 'child-process-promise'; +import { exec } from '../exec'; const repoRoot = path.resolve(__dirname, '../../'); const lib = path.join(repoRoot, 'lib'); diff --git a/tools/release-docs-scripts/release-docs.js b/tools/release-docs-scripts/release-docs.js index e5fe3cc71a..c9920bd471 100644 --- a/tools/release-docs-scripts/release-docs.js +++ b/tools/release-docs-scripts/release-docs.js @@ -1,7 +1,8 @@ /* eslint no-process-exit: 0 */ import 'colors'; -import { exec } from 'child-process-promise'; +import yargs from 'yargs'; +import { exec, safeExec, setExecOptions } from '../exec'; import preConditions from '../release-scripts/pre-conditions'; import versionBump from '../release-scripts/version-bump'; @@ -9,7 +10,24 @@ import repoRelease from '../release-scripts/repo-release'; import tag from '../release-scripts/tag'; import { lint } from '../release-scripts/test'; -import { repoRoot, docsRoot, docsRepo, tmpDocsRepo } from '../release-scripts/constants'; +import { repoRoot, docsRoot, docsRepo, tmpDocsRepo } from '../constants'; + +const yargsConf = yargs + .usage('Usage: $0 [-n|--dry-run] [--verbose]') + .option('dry-run', { + alias: 'n', + demand: false, + default: false, + describe: 'Execute command in dry run mode. Will not commit, tag, push, or publish anything. Userful for testing.' + }) + .option('verbose', { + demand: false, + default: false, + describe: 'Increased debug output' + }); + +const argv = yargsConf.argv; +setExecOptions(argv); let version; @@ -22,7 +40,7 @@ preConditions() .then(versionBump(repoRoot, versionBumpOptions)) .then(v => { version = v; }) .then(() => { - return exec('npm run docs-build') + return exec(`npm run docs-build${ argv.verbose ? ' -- --verbose' : '' }`) .catch(err => { console.log('Docs-build failed, reverting version bump'.red); return exec('git reset HEAD .') @@ -33,7 +51,7 @@ preConditions() }); }); }) - .then(() => exec(`git commit -m "Release v${version}"`)) + .then(() => safeExec(`git commit -m "Release v${version}"`)) .then(() => Promise.all([ tag(version), repoRelease(docsRepo, docsRoot, tmpDocsRepo, version) @@ -41,7 +59,11 @@ preConditions() .then(() => console.log('Version '.cyan + `v${version}`.green + ' released!'.cyan)) .catch(err => { if (!err.__handled) { - console.error(err.message.red); + if (argv.verbose) { + console.error(err.stack.red); + } else { + console.error(err.toString().red); + } } process.exit(1); diff --git a/tools/release-scripts/changelog.js b/tools/release-scripts/changelog.js index d1bac60d8b..e49c4c9c3d 100644 --- a/tools/release-scripts/changelog.js +++ b/tools/release-scripts/changelog.js @@ -1,9 +1,9 @@ import 'colors'; import path from 'path'; -import { exec } from 'child-process-promise'; +import { exec, safeExec } from '../exec'; export default (repoRoot, version) => { return exec(`node_modules/.bin/changelog -t v${version}`) - .then(() => exec(`git add ${path.join(repoRoot, 'CHANGELOG.md')}`)) + .then(() => safeExec(`git add ${path.join(repoRoot, 'CHANGELOG.md')}`)) .then(() => console.log('Generated Changelog'.cyan)); }; diff --git a/tools/release-scripts/pre-conditions.js b/tools/release-scripts/pre-conditions.js index 67a2410ac5..ef90f14054 100644 --- a/tools/release-scripts/pre-conditions.js +++ b/tools/release-scripts/pre-conditions.js @@ -1,5 +1,5 @@ import 'colors'; -import { exec } from 'child-process-promise'; +import { exec } from '../exec'; function ensureClean() { return exec('git diff-index --name-only HEAD --') diff --git a/tools/release-scripts/release.js b/tools/release-scripts/release.js index e0af96db59..2937eb7c97 100644 --- a/tools/release-scripts/release.js +++ b/tools/release-scripts/release.js @@ -1,6 +1,6 @@ /* eslint no-process-exit: 0 */ import yargs from 'yargs'; -import { exec } from 'child-process-promise'; +import { exec, safeExec, setExecOptions } from '../exec'; import preConditions from './pre-conditions'; import versionBump from './version-bump'; @@ -10,12 +10,13 @@ import tagAndPublish from './tag-and-publish'; import test from './test'; import build from '../build'; -import { repoRoot, bowerRepo, bowerRoot, tmpBowerRepo, docsRoot, docsRepo, tmpDocsRepo } from './constants'; +import { repoRoot, bowerRepo, bowerRoot, tmpBowerRepo, docsRoot, docsRepo, tmpDocsRepo } from '../constants'; const yargsConf = yargs .usage('Usage: $0 [--preid ]') .example('$0 minor --preid beta', 'Release with minor version bump with pre-release tag') .example('$0 major', 'Release with major version bump') + .example('$0 major --dry-run', 'Release dry run with patch version bump') .example('$0 --preid beta', 'Release same version with pre-release bump') .command('patch', 'Release patch') .command('minor', 'Release minor') @@ -25,9 +26,21 @@ const yargsConf = yargs demand: false, describe: 'pre-release identifier', type: 'string' + }) + .option('dry-run', { + alias: 'n', + demand: false, + default: false, + describe: 'Execute command in dry run mode. Will not commit, tag, push, or publish anything. Userful for testing.' + }) + .option('verbose', { + demand: false, + default: false, + describe: 'Increased debug output' }); const argv = yargsConf.argv; +setExecOptions(argv); let version; @@ -61,7 +74,7 @@ preConditions() }); }); }) - .then(() => exec(`git commit -m "Release v${version}"`)) + .then(() => safeExec(`git commit -m "Release v${version}"`)) .then(() => Promise.all([ tagAndPublish(version), repoRelease(bowerRepo, bowerRoot, tmpBowerRepo, version), @@ -70,7 +83,11 @@ preConditions() .then(() => console.log('Version '.cyan + `v${version}`.green + ' released!'.cyan)) .catch(err => { if (!err.__handled) { - console.error(err.message.red); + if (argv.verbose) { + console.error(err.stack.red); + } else { + console.error(err.toString().red); + } } process.exit(1); diff --git a/tools/release-scripts/repo-release.js b/tools/release-scripts/repo-release.js index d8347bd6ec..a5f8142aca 100644 --- a/tools/release-scripts/repo-release.js +++ b/tools/release-scripts/repo-release.js @@ -1,7 +1,7 @@ import 'colors'; import path from 'path'; import fsp from 'fs-promise'; -import { exec } from 'child-process-promise'; +import { exec, safeExec } from '../exec'; import { copy } from '../fs-utils'; const repoRoot = path.resolve(__dirname, '../../'); @@ -23,11 +23,11 @@ export default (repo, srcFolder, tmpFolder, version) => { }) .then(() => copy(srcFolder, tmpFolder)) .then(() => copy(license, tmpFolder)) - .then(() => exec(`cd ${tmpFolder} && git add -A .`)) - .then(() => exec(`cd ${tmpFolder} && git commmit -m "Release v${version}"`)) - .then(() => exec(`cd ${tmpFolder} && git tag -a --message=v${version} v${version}`)) - .then(() => exec(`cd ${tmpFolder} && git push`)) - .then(() => exec(`cd ${tmpFolder} && git push --tags`)) - .then(() => exec(`rimraf ${tmpFolder}`)) + .then(() => safeExec(`cd ${tmpFolder} && git add -A .`)) + .then(() => safeExec(`cd ${tmpFolder} && git commmit -m "Release v${version}"`)) + .then(() => safeExec(`cd ${tmpFolder} && git tag -a --message=v${version} v${version}`)) + .then(() => safeExec(`cd ${tmpFolder} && git push`)) + .then(() => safeExec(`cd ${tmpFolder} && git push --tags`)) + .then(() => safeExec(`rimraf ${tmpFolder}`)) .then(() => console.log('Released: '.cyan + repo.green)); }; diff --git a/tools/release-scripts/tag-and-publish.js b/tools/release-scripts/tag-and-publish.js index adbd5e14b3..e8eae61416 100644 --- a/tools/release-scripts/tag-and-publish.js +++ b/tools/release-scripts/tag-and-publish.js @@ -1,10 +1,10 @@ -import { exec } from 'child-process-promise'; +import { safeExec } from '../exec'; import tag from './tag'; export default (version) => { console.log('Releasing: '.cyan + 'npm module'.green); - return tag() - .then(() => exec('npm publish')) + return tag(version) + .then(() => safeExec('npm publish')) .then(() => console.log('Released: '.cyan + 'npm module'.green)); }; diff --git a/tools/release-scripts/tag.js b/tools/release-scripts/tag.js index 40c58cfc30..d372b4fa38 100644 --- a/tools/release-scripts/tag.js +++ b/tools/release-scripts/tag.js @@ -1,10 +1,10 @@ -import { exec } from 'child-process-promise'; +import { safeExec } from '../exec'; export default (version) => { console.log('Tagging: '.cyan + `v${version}`.green); - return exec(`git tag -a --message=v${version} v${version}`) - .then(() => exec(`git push`)) - .then(() => exec(`git push --tags`)) + return safeExec(`git tag -a --message=v${version} v${version}`) + .then(() => safeExec(`git push`)) + .then(() => safeExec(`git push --tags`)) .then(() => console.log('Tagged: '.cyan + `v${version}`.green)); }; diff --git a/tools/release-scripts/test.js b/tools/release-scripts/test.js index d681b4bfa8..99b031fb97 100644 --- a/tools/release-scripts/test.js +++ b/tools/release-scripts/test.js @@ -1,5 +1,5 @@ import 'colors'; -import { exec } from 'child-process-promise'; +import { exec } from '../exec'; function test() { console.log('Running: '.cyan + 'tests'.green); diff --git a/tools/release-scripts/version-bump.js b/tools/release-scripts/version-bump.js index c3dffd594e..495e3131ab 100644 --- a/tools/release-scripts/version-bump.js +++ b/tools/release-scripts/version-bump.js @@ -2,7 +2,7 @@ import 'colors'; import path from 'path'; import fsp from 'fs-promise'; import semver from 'semver'; -import { exec } from 'child-process-promise'; +import { safeExec } from '../exec'; export default function(repoRoot, { preid, type }) { const packagePath = path.join(repoRoot, 'package.json'); @@ -35,7 +35,7 @@ export default function(repoRoot, { preid, type }) { json.version = version; return fsp.writeFile(packagePath, JSON.stringify(json, null, 2)) - .then(() => exec(`git add ${packagePath}`)) + .then(() => safeExec(`git add ${packagePath}`)) .then(() => json.version); }); }