diff --git a/cli.js b/cli.js index fb0ca9b6f..ef44f64f8 100755 --- a/cli.js +++ b/cli.js @@ -2,6 +2,10 @@ 'use strict' +process.on('unhandledRejection', (err) => { + throw err +}) + const updateNotifier = require('update-notifier') const chalk = require('chalk') const pkg = require('./package.json') diff --git a/cmds/publish-rc.js b/cmds/publish-rc.js new file mode 100644 index 000000000..c454b6eaf --- /dev/null +++ b/cmds/publish-rc.js @@ -0,0 +1,33 @@ +'use strict' + +module.exports = { + command: 'publish-rc', + desc: 'Publish a release candidate', + builder: { + branch: { + describe: 'Where the latest good build is', + type: 'string', + default: 'build/last-successful' + }, + distTag: { + describe: 'The dist tag to publish the rc as', + type: 'string', + default: 'next' + }, + preId: { + describe: 'What to call the rc', + type: 'string', + default: 'rc' + }, + type: { + describe: 'What sort of update this will be', + type: 'string', + default: 'minor' + } + }, + handler (argv) { + const publishRc = require('../src/publish-rc') + const onError = require('../src/error-handler') + publishRc(argv).catch(onError) + } +} diff --git a/cmds/update-last-successful-build.js b/cmds/update-last-successful-build.js new file mode 100644 index 000000000..e83c8f7bf --- /dev/null +++ b/cmds/update-last-successful-build.js @@ -0,0 +1,23 @@ +'use strict' + +module.exports = { + command: 'update-last-successful-build', + desc: 'Update last known good build branch', + builder: { + branch: { + describe: 'Which branch to update', + type: 'string', + default: 'build/last-successful' + }, + message: { + describe: 'The message to use when adding the shrinkwrap and yarn.lock file', + type: 'string', + default: 'chore: add lockfiles' + } + }, + handler (argv) { + const updateLastPassingBuild = require('../src/update-last-successful-build') + const onError = require('../src/error-handler') + updateLastPassingBuild(argv).catch(onError) + } +} diff --git a/src/publish-rc/index.js b/src/publish-rc/index.js new file mode 100644 index 000000000..aefdd4d2b --- /dev/null +++ b/src/publish-rc/index.js @@ -0,0 +1,65 @@ +'use strict' + +const { + exec +} = require('../utils') +const path = require('path') +const release = require('../release') +const semver = require('semver') + +async function publishRc (opts) { + try { + console.info(`Removing local copy of ${opts.branch}`) // eslint-disable-line no-console + await exec('git', ['branch', '-D', opts.branch]) + } catch (err) { + if (!err.message.includes(`branch '${opts.branch}' not found`)) { + throw err + } + } + + console.info('Fetching repo history') // eslint-disable-line no-console + await exec('git', ['fetch']) + + console.info(`Checking out branch ${opts.branch}`) // eslint-disable-line no-console + await exec('git', ['checkout', opts.branch]) + + console.info('Reading version number') // eslint-disable-line no-console + const pkg = require(path.join(process.cwd(), 'package.json')) + + console.info('Found version number', pkg.version) // eslint-disable-line no-console + const version = pkg.version + const newVersion = semver.inc(version, opts.type) + const newVersionBranch = `v${newVersion.split('.').filter((sub, i) => { + return i < 2 + }).join('.')}.x` + console.info('Creating release branch', newVersionBranch) // eslint-disable-line no-console + + await exec('git', ['checkout', '-b', newVersionBranch]) + await exec('git', ['push', 'origin', `${newVersionBranch}:${newVersionBranch}`]) + + if (version.includes('-')) { + // already a pre${opts.type}, change from prepatch, preminor, etc to 'prerelease' + // e.g. 0.38.0-pre.1 -> 0.38.0-rc.0 + opts.type = 'release' + } + + console.info('Creating', opts.preId) // eslint-disable-line no-console + await release({ + type: `pre${opts.type}`, + preid: opts.preId, + 'dist-tag': opts.distTag, + build: false, + test: false, + lint: false, + docsFormats: ['html'], + contributors: true, + bump: true, + changelog: true, + publish: true, + ghrelease: true, + docs: true, + ghtoken: process.env.AEGIR_GHTOKEN + }) +} + +module.exports = publishRc diff --git a/src/update-last-successful-build/index.js b/src/update-last-successful-build/index.js new file mode 100644 index 000000000..dc6d53cca --- /dev/null +++ b/src/update-last-successful-build/index.js @@ -0,0 +1,74 @@ +'use strict' + +const { + exec +} = require('../utils') + +async function findCurrentBranch () { + const result = await exec('cat', ['.git/HEAD']) + + return result.stdout.replace('ref: ', '').trim() +} + +async function findMasterCommit () { + const result = await exec('git', ['show-ref', '-s', 'origin/master']) + + return result.stdout.trim() +} + +async function isHeadOfMaster () { + const master = await findMasterCommit() + const branch = await findCurrentBranch() + + // we either have master checked out or a single commit + return branch === 'refs/heads/master' || branch === master +} + +async function updateLastSuccessfulBuild (opts) { + if (!isHeadOfMaster()) { + console.info(`Will only run on the master branch`) // eslint-disable-line no-console + + return + } + + console.info('Fetching latest') // eslint-disable-line no-console + await exec('git', ['fetch']) + + const tempBranch = 'update-branch-' + Date.now() + + console.info('Creating temp branch') // eslint-disable-line no-console + await exec('git', ['checkout', '-b', tempBranch]) + + console.info('Creating yarn.lock') // eslint-disable-line no-console + await exec('yarn') + + console.info('Creating npm-shrinkwrap.json') // eslint-disable-line no-console + await exec('npm', ['shrinkwrap']) + + try { + console.info('Committing') // eslint-disable-line no-console + await exec('git', ['add', '-f', 'npm-shrinkwrap.json', 'yarn.lock']) + await exec('git', ['commit', '-m', opts.message]) + } catch (err) { + if (err.message.includes('nothing to commit, working tree clean')) { + console.info('No changes detected, nothing to do') // eslint-disable-line no-console + return + } + + throw err + } + + try { + console.info(`Deleting remote ${opts.branch} branch`) // eslint-disable-line no-console + await exec('git', ['push', 'origin', `:${opts.branch}`]) + } catch (err) { + if (!err.message.includes('remote ref does not exist')) { + throw err + } + } + + console.info(`Pushing ${opts.branch} branch`) // eslint-disable-line no-console + await exec('git', ['push', 'origin', `${tempBranch}:${opts.branch}`]) +} + +module.exports = updateLastSuccessfulBuild diff --git a/src/utils.js b/src/utils.js index 74b74161d..1b79a53fe 100644 --- a/src/utils.js +++ b/src/utils.js @@ -12,6 +12,7 @@ const fs = require('fs-extra') const arrify = require('arrify') const _ = require('lodash') const VerboseRenderer = require('listr-verbose-renderer') +const execa = require('execa') const { package: pkg, path: pkgPath } = readPkgUp.sync({ cwd: fs.realpathSync(process.cwd()) @@ -213,3 +214,12 @@ exports.hook = (env, key) => (ctx) => { return Promise.resolve() } + +exports.exec = (command, args) => { + const result = execa(command, args) + + result.stdout.pipe(process.stdout) + result.stderr.pipe(process.stderr) + + return result +}