Skip to content

Instantly share code, notes, and snippets.

@dsoike
Created March 6, 2019 18:15
Show Gist options
  • Save dsoike/fab528b669619cf8eda748f9e22852cf to your computer and use it in GitHub Desktop.
Save dsoike/fab528b669619cf8eda748f9e22852cf to your computer and use it in GitHub Desktop.
Release Script
const chalk = require('chalk')
const exec = require('child_process').exec
const fs = require('fs')
const readline = require('readline')
const spawn = require('child_process').spawn
const repoName = 'MyRepo'
const repoUrl = `https://github.com/MyOrg/${repoName}`
const checkMark = chalk.green('\u2713')
async function createRelease() {
printTitle()
await confirmContinue()
const releaseVersion = await askForReleaseVersion()
const releaseBranchName = `release-v${releaseVersion}`
const builtReleaseBranchName = `release-v${releaseVersion}-built`
const releaseTagName = `v${releaseVersion}`
const nodeVersion = await getNodeVersion()
await checkNodeVersion(nodeVersion)
await createGitBranch(releaseBranchName)
await updatePackageJson(releaseVersion)
await commitVersionChanges(releaseVersion)
await pushGitBranch(releaseBranchName)
await createGitBranch(builtReleaseBranchName)
await deleteBuildFiles()
await generateBuildFiles()
await commitBuildFiles()
await pushGitBranch(builtReleaseBranchName)
await createReleaseTag(releaseTagName)
await pushReleaseTag(releaseTagName)
printDone(releaseTagName, releaseBranchName)
}
function printTitle() {
console.info(chalk.bold(chalk.underline(`Create ${repoName} Release`)))
console.info('You are about to create a release. This process will:')
console.info(' - Create a release branch (release-v1.2.3)')
console.info(' - Update package.json with your specified release version')
console.info(' - Push your release branch to the remote')
console.info(' - Create a built release branch (release-v1.2.3-built)')
console.info(' - Compile build assets, git force add, and commit to your built branch')
console.info(' - Tag this new commit with the release version (v1.2.3)')
console.info(' - Push the built release branch and release tag to the remote')
console.info('')
}
function confirmContinue() {
return new Promise((resolve, reject) => {
const rl = readline.createInterface({input: process.stdin, output: process.stdout})
rl.question('Do you want to continue? [y/n] ', (answer) => {
rl.close()
console.info('')
if (answer === 'y') {
resolve()
} else {
reject()
}
});
})
}
function askForReleaseVersion() {
return new Promise((resolve, reject) => {
const rl = readline.createInterface({input: process.stdin, output: process.stdout})
rl.question('What release version would you like to create? (ex: 1.2.3) ', (releaseVersion) => {
rl.close()
if (releaseVersion.match(/^\d+\.\d+\.\d+$/)) {
console.info(` -> Proceeding with release version: ${chalk.bold(releaseVersion)}\n`)
resolve(releaseVersion)
} else {
console.error(chalk.red(`${chalk.bold('Error!')} - ${releaseVersion} is not a valid release version\n`))
reject()
}
})
})
}
function getNodeVersion() {
return new Promise((resolve, reject) => {
let packageJsonPath = `${process.env.PWD}/package.json`
let packageJson = require(packageJsonPath)
if (!packageJson.hasOwnProperty('engines') || !packageJson.engines.hasOwnProperty('node')) {
const errMsg = `unable to retrieve node version from package.json b/c 'engines.node' field not found`
console.error(chalk.red(`${chalk.bold('Error!')} - ${errMsg}\n`))
reject()
} else {
resolve(packageJson.engines.node)
}
})
}
function checkNodeVersion(nodeVersion) {
return new Promise((resolve, reject) => {
if (process.version.indexOf(nodeVersion) > 0) {
resolve()
} else {
const errMsg = `your current node version is ${process.version} - not v${nodeVersion}`
console.error(chalk.red(`${chalk.bold('Error!')} - ${errMsg}`))
console.info(`You can check your current node version with: ${chalk.blue('node --version')}`)
console.info(`If you have Node Version Manager, you can: ${chalk.blue(`nvm install ${nodeVersion}`)}\n`)
reject()
}
})
}
function createGitBranch(branchName) {
return new Promise((resolve, reject) => {
console.info(chalk.underline(`Creating Git Branch`))
const childProcess = exec(`git checkout -b ${branchName}`, function(err) {
if (err) { console.error(err) }
})
childProcess.on('exit', function(exitCode) {
if (exitCode === 0) {
console.info(`${checkMark} git branch '${branchName}' created\n`)
resolve()
} else {
console.error(chalk.red(`${chalk.bold('Error!')} - unable to create git branch ${branchName}\n`))
reject()
}
})
})
}
function updatePackageJson(releaseVersion) {
return new Promise((resolve, reject) => {
console.info(chalk.underline(`Updating package.json`))
let packageJsonPath = `${process.env.PWD}/package.json`
let packageJson = require(packageJsonPath)
if (!packageJson.hasOwnProperty('version')) {
const errMsg = `unable to update package.json b/c 'version' field not found`
console.error(chalk.red(`${chalk.bold('Error!')} - ${errMsg}\n`))
reject()
} else {
packageJson.version = releaseVersion
fs.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, function(err) {
if (err) {
console.error(chalk.red(`${chalk.bold('Error!')} - unable to update package.json\n`))
console.error(err)
reject()
} else {
console.info(`${checkMark} package.json updated with version=${releaseVersion}\n`)
resolve()
}
})
}
})
}
function commitVersionChanges(releaseVersion) {
return new Promise((resolve, reject) => {
console.info(chalk.underline(`Committing Version Changes`))
const commitMessage = `Release v${releaseVersion}`
const childProcess = exec(`git add -A && git commit -m "${commitMessage}"`, function(err) {
if (err) { console.error(err) }
})
childProcess.on('exit', function(exitCode) {
if (exitCode === 0) {
console.info(`${checkMark} version changes committed\n`)
resolve()
} else {
console.error(chalk.red(`${chalk.bold('Error!')} - unable to commit version changes\n`))
reject()
}
})
})
}
function pushGitBranch(branchName) {
return new Promise((resolve, reject) => {
console.info(chalk.underline(`Pushing Git Branch`))
const childProcess = exec(`git push --set-upstream origin ${branchName}`, function(err) {
if (err) { console.error(err) }
})
childProcess.on('exit', function(exitCode) {
if (exitCode === 0) {
console.info(`${checkMark} git branch ${branchName} pushed\n`)
resolve()
} else {
console.error(chalk.red(`${chalk.bold('Error!')} - unable to push git branch ${branchName}\n`))
reject()
}
})
})
}
function deleteBuildFiles() {
return new Promise((resolve, reject) => {
console.info(chalk.underline(`Deleting Build Files`))
const childProcess = spawn('rm -rf $PWD/build/*', [], {shell: true})
childProcess.stdout.on('data', (data) => { console.info(data.toString()) })
childProcess.stderr.on('data', (data) => { console.error(data.toString()) })
childProcess.on('close', (exitCode) => {
if (exitCode === 0) {
console.info(`${checkMark} build files deleted\n`)
resolve()
} else {
console.error(chalk.red(`${chalk.bold('Error!')} - unable to delete build files\n`))
reject()
}
})
})
}
function generateBuildFiles() {
return new Promise((resolve, reject) => {
console.info(chalk.underline(`Generating Build Files`))
const childProcess = spawn('npm run build --silent', [], {shell: true})
childProcess.stdout.on('data', (data) => { console.info(data.toString().trim()) })
childProcess.stderr.on('data', (data) => { console.error(data.toString()) })
childProcess.on('close', (exitCode) => {
if (exitCode === 0) {
console.info(`${checkMark} build files generated\n`)
resolve()
} else {
console.error(chalk.red(`${chalk.bold('Error!')} - unable to generate build files\n`))
reject()
}
})
})
}
function commitBuildFiles() {
return new Promise((resolve, reject) => {
console.info(chalk.underline(`Committing Build Files`))
const commitMessage = `Force commit build files`
const childProcess = exec(`git add -f build && git commit -m "${commitMessage}"`, function(err) {
if (err) { console.error(err) }
})
childProcess.on('exit', function(exitCode) {
if (exitCode === 0) {
console.info(`${checkMark} build files committed\n`)
resolve()
} else {
console.error(chalk.red(`${chalk.bold('Error!')} - unable to commit build files\n`))
reject()
}
})
})
}
function createReleaseTag(tagName) {
return new Promise((resolve, reject) => {
console.info(chalk.underline(`Creating Release Tag`))
const childProcess = exec(`git tag ${tagName}`, function(err) {
if (err) { console.error(err) }
})
childProcess.on('exit', function(exitCode) {
if (exitCode === 0) {
console.info(`${checkMark} release tag ${tagName} created\n`)
resolve()
} else {
console.error(chalk.red(`${chalk.bold('Error!')} - unable to create release tag ${tagName}\n`))
reject()
}
})
})
}
function pushReleaseTag(tagName) {
return new Promise((resolve, reject) => {
console.info(chalk.underline(`Pushing Release Tag`))
const childProcess = exec(`git push origin ${tagName}`, function(err) {
if (err) { console.error(err) }
})
childProcess.on('exit', function(exitCode) {
if (exitCode === 0) {
console.info(`${checkMark} release tag ${tagName} pushed\n`)
resolve()
} else {
console.error(chalk.red(`${chalk.bold('Error!')} - unable to push release tag ${tagName}\n`))
reject()
}
})
})
}
function printDone(releaseTag, releaseBranch) {
console.info(chalk.bold(chalk.underline(`${repoName} Release Created!`)))
console.info('')
console.info('Please give your release a title and provide release notes.')
console.info('You can edit and publish your release here:')
console.info(`${repoUrl}/releases/tag/${releaseTag}`)
console.info('')
console.info('You should also create a pull request for your release branch.')
console.info('You can open the pull request here:')
console.info(`${repoUrl}/compare/${releaseBranch}?expand=1`)
console.info('')
}
if (require.main === module) {
createRelease()
.then(() => { process.exit(0) })
.catch((err) => { if (err) { console.error(err) } process.exit(1) })
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment