diff --git a/package.json b/package.json index ab5b2a2df1..291d9947b3 100644 --- a/package.json +++ b/package.json @@ -27,9 +27,13 @@ "babel-preset-react": "^6.24.1", "commit-status": "^4.1.0", "css-loader": "^0.28.4", + "execa": "^0.8.0", + "fs-extra": "^4.0.2", "gh-pages": "^1.0.0", "glob": "^7.1.2", + "globby": "^6.1.0", "html-to-react": "^1.2.11", + "isomorphic-fetch": "^2.2.1", "lerna": "^2.4.0", "node-sass": "^4.5.3", "npm-run-all": "^4.0.2", @@ -41,6 +45,7 @@ "react": "^15.6.1", "react-dom": "^15.6.1", "react-svg-inline": "^2.0.0", + "registry-url": "^3.1.0", "remark": "^8.0.0", "sass-loader": "^6.0.6", "semver": "^5.3.0", diff --git a/script/compare-published b/script/compare-published index 2f637c674e..df2cb2762c 100755 --- a/script/compare-published +++ b/script/compare-published @@ -5,9 +5,7 @@ set -e # script/compare-published [tag] tag=${1:-${NPM_TAG:-latest}} -# this is way faster than `lerna exec npm info . .name` -# (but will skip modules that don't have "primer-" in them) -modules=`ls -1 modules | egrep primer-` +packages=$($(dirname $0)/get-packages) # tabular output separator for column(1) s=, @@ -15,9 +13,10 @@ s=, echo "📦 Comparing Primer modules published @${tag}..." ( echo "module${s}tag${s}published${s}local" - for module in $modules; do - v_published=`npm info ${module}@${tag} .version` - v_local=`jq -Mr .version modules/${module}/package.json` + for package in $packages; do + module=$(jq -r .name "$package/package.json") + v_published=$(npm info "$module@$tag" .version) + v_local=$(jq -Mr .version "$package/package.json") echo "${module}${s}${tag}${s}${v_published:-x}${s}${v_local}" done ) | column -t -s=${s} diff --git a/script/get-packages b/script/get-packages new file mode 100755 index 0000000000..671d7b3784 --- /dev/null +++ b/script/get-packages @@ -0,0 +1,31 @@ +#!/usr/bin/env node +const fs = require('fs') +const globby = require('globby') +const path = require('path') + +const getPackages = (debug) => { + const lernaConfig = require('../lerna.json') + const packageGlobs = lernaConfig.packages + return globby(packageGlobs) + .then(packagePaths => { + return packagePaths.filter(pkg => { + try { + require.resolve(`../${pkg}/package.json`) + return true + } catch (error) { + if (debug) { + console.warn('No package.json in %s', pkg) + } + } + }) + }) +} + +if (module.parent) { + module.exports = getPackages +} else { + getPackages(true).then(packages => { + console.warn('%d packages:', packages.length) + packages.forEach(pkg => console.log(pkg)) + }) +} diff --git a/script/get-release-version b/script/get-release-version new file mode 100755 index 0000000000..a23154a22b --- /dev/null +++ b/script/get-release-version @@ -0,0 +1,28 @@ +#!/usr/bin/env node + +const RELEASE_PREFIX = 'release-' + +const getReleaseVersion = (pkg, branch) => { + const version = require(`../modules/${pkg}/package.json`).version + + if (branch && branch.indexOf(RELEASE_PREFIX) === 0) { + const release = branch.replace(RELEASE_PREFIX, '') + if (release !== version) { + console.warn('⚠️ Release branch version mismatch:') + console.warn(' "%s" should be "%s" in %s/package.json', version, release, pkg) + } + return release + } else { + return version + } +} + +if (module.parent) { + module.exports = getReleaseVersion +} else { + const args = process.argv.slice(2) + const pkg = args.shift() || 'primer-css' + const branch = args.shift() || process.env.TRAVIS_BRANCH + const version = getReleaseVersion(pkg, branch) + console.log(version) +} diff --git a/script/release-candidate b/script/release-candidate index ce7f80b659..8aa0ca0ee1 100755 --- a/script/release-candidate +++ b/script/release-candidate @@ -1,23 +1,144 @@ -#!/bin/bash -set -e -echo "👌 Publishing release candidate..." +#!/usr/bin/env node +const execa = require('execa') +const fetch = require('isomorphic-fetch') +const fse = require('fs-extra') +const registryUrl = require('registry-url') +const semver = require('semver') -npm_tag=rc +const bin = 'node_modules/.bin/' +const lernaBin = `${bin}lerna` +const getPackages = require('./get-packages') +const getReleaseVersion = require('./get-release-version') +const revertPackages = require('./revert-packages') -# if this is the same version, we need to bump the prerelease -# for all of the modules using the same prerelease identifier -echo "Updating all module versions in place..." -echo -module_dirs=modules/*primer* -for module_dir in $module_dirs; do - module=$(basename $module_dir) - $(dirname $0)/bump-rc $module -done +const PRERELEASE = 'prerelease' +const DIST_TAG = 'rc' +const PRIMER_CSS = 'primer-css' +const RELEASE_VERSION = getReleaseVersion( + PRIMER_CSS, + process.env.TRAVIS_BRANCH +) -# publish all the things! -$(dirname $0)/notify pending +const depFields = [ + 'dependencies', + 'devDependencies', + 'optionalDependencies', + 'peerDependencies', +] -# note: this should NOT fail, so --bail=true applies -$(npm bin)/lerna exec -- npm publish --tag=$npm_tag +const getUpdated = (args) => { + return execa(lernaBin, ['updated', '--json']) + .then(res => JSON.parse(res.stdout)) + .then(updated => updated.map(pkg => pkg.name)) +} -$(dirname $0)/notify success +const notify = status => { + return execa('script/notify', ['error'], {env: process.env}) + .catch(error => { + console.error('notify error:', error) + }) +} + +const writePackage = (pkg) => { + const {dir} = pkg + delete pkg.dir + const json = JSON.stringify(pkg, null, ' ') + '\n' + pkg.dir = dir + return fse.writeFile(`${pkg.dir}/package.json`, json, 'utf8') + .then(() => pkg) +} + +const bump = (pkg, by, preid) => { + if (pkg.name === PRIMER_CSS) { + pkg.version = RELEASE_VERSION + } + + const original = pkg.version + let version = increment(pkg.version, by, preid) + return getPackageInfo(pkg.name) + .then(info => { + while (version in info.versions) { + version = increment(version, by, preid) + } + console.warn('%s %s -> %s', pkg.name, original, version) + pkg.version = version + return writePackage(pkg) + }) +} + +const getPackageInfo = (name) => { + const url = registryUrl() + name + return fetch(url).then(res => res.json()) +} + +const increment = (version, by, preid) => { + const {major, minor, patch} = semver(version) + const prev = [major, minor, patch].join('.') + const next = semver.inc(version, by, preid) + // if this is a prerelease, "revert" major.minor.patch + // so that only the prerelease id is incremented + return by === PRERELEASE + ? next.replace(/^\d+\.\d+\.\d+/, prev) + : next +} + +const updateDependants = (pkg, pkgs) => { + return pkgs.filter(other => { + return depFields.some(field => { + if (other[field] && (pkg.name in other[field])) { + other[field][pkg.name] = pkg.version + return true + } + }) + }) +} + +revertPackages() + .then(() => getPackages()) + .then(dirs => { + return dirs.map(dir => { + const pkg = require(`../${dir}/package.json`) + pkg.dir = dir + return pkg + }) + }) + .then(pkgs => { + const by = PRERELEASE + const preid = DIST_TAG + return getUpdated() + .then(updated => { + console.warn('%d packages updated...', updated.length) + return pkgs.filter(pkg => updated.includes(pkg.name)) + }) + .then(updated => { + const changed = new Set(updated) + return Promise.all(updated.map(pkg => { + return bump(pkg, by, preid) + .then(pkg => updateDependants(pkg, pkgs)) + .then(dependants => { + dependants.forEach(dep => changed.add(dep)) + }) + })) + .then(() => { + const tasks = Array.from(changed) + .map(writePackage) + return Promise.all(tasks) + }) + .then(updated => { + const tasks = updated.map(pkg => { + return execa('npm', ['publish', '--tag', DIST_TAG], { + cwd: pkg.dir, + stdio: 'inherit', + }) + }) + return Promise.all(tasks) + }) + .then(() => notify('success')) + }) + }) + .catch(error => { + console.error('Error:', error) + process.exitCode = 1 + return notify('error') + }) + .then(() => process.exit()) diff --git a/script/revert-packages b/script/revert-packages new file mode 100755 index 0000000000..0c55add5d8 --- /dev/null +++ b/script/revert-packages @@ -0,0 +1,30 @@ +#!/usr/bin/env node +const execa = require('execa') +const fse = require('fs-extra') +const globby = require('globby') + +const revertPackages = () => { + const lernaConfig = require('../lerna.json') + const globs = lernaConfig.packages + const jsons = globs.map(glob => glob + '/package.json') + const tarballs = globs.map(glob => glob + '/*.tgz') + const opts = {stdio: 'inherit'} + return Promise.all([ + execa('git', ['checkout', '--'].concat(globs), opts), + globby(tarballs) + .then(paths => { + if (paths.length) { + console.warn('deleting %d tarball(s)', paths.length) + return Promise.all( + paths.map(tgz => fse.remove(tgz)) + ) + } + }), + ]) +} + +if (module.parent) { + module.exports = revertPackages +} else { + revertPackages().then(() => process.exit(0)) +}