diff --git a/bower.json b/bower.json index 7c9491a..7a12d05 100644 --- a/bower.json +++ b/bower.json @@ -1,9 +1,8 @@ { - "name": "KaTeX", - "version": "0.8.0-pre", + "name": "katex", "main": [ - "dist/katex.min.js", - "dist/katex.min.css" + "dist/katex.js", + "dist/katex.css" ], "homepage": "http://khan.github.io/KaTeX/", "description": "Fast math typesetting for the web.", diff --git a/package.json b/package.json index 1cc1b74..4f00ff3 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "nomnom": "^1.8.1", "pako": "1.0.4", "selenium-webdriver": "^2.48.2", + "sri-toolbox": "^0.2.0", "uglify-js": "~2.7.5" }, "bin": "cli.js", diff --git a/release.sh b/release.sh index 2a8e126..809a6a8 100755 --- a/release.sh +++ b/release.sh @@ -1,51 +1,125 @@ #!/usr/bin/env bash set -e -o pipefail +shopt -s extglob -if [ $# -lt 1 ]; then +VERSION= +NEXT_VERSION= +BRANCH=$(git rev-parse --abbrev-ref HEAD) +NARGS=0 +DRY_RUN= +INSANE=0 + +# usage [ERROR-MESSAGES...] EXIT-STATUS +usage() { + while [[ $# -gt 1 ]]; do + echo "$1" >&2 + shift + done echo "Usage:" - echo "./release.sh [NEXT_VERSION]" + echo "./release.sh [OPTIONS] [NEXT_VERSION]" + echo "" + echo "Options:" + echo " --dry-run|-n: only print commands, do not execute them." echo "" echo "Examples:" echo " When releasing a new point release:" echo " ./release.sh 0.6.3" echo " When releasing a new major version:" echo " ./release.sh 0.7.0 0.8.0" - exit + exit $1 +} + +while [ $# -gt 0 ]; do + case "$1" in + --dry-run|-n|--just-print) + DRY_RUN=true + git() { echo "git $*"; } + npm() { echo "npm $*"; } + ;; + -h|-\?|--help) + usage 0 + ;; + -*) + usage "Unknown option: $1" "" 1 + ;; + *) + case "$NARGS" in + 0) + VERSION="$1" + NARGS=1 + ;; + 1) + NEXT_VERSION="$1" + NARGS=2 + ;; + *) + usage "Too many arguments: $1" "" 1 + ;; + esac + ;; + esac + shift +done + +if [[ $NARGS = 0 ]]; then + usage "Missing argument: version number" "" 1 fi -VERSION=$1 -NEXT_VERSION=$2 +# Some sanity checks up front +if ! command git diff --stat --exit-code HEAD; then + echo "Please make sure you have no uncommitted changes" >&2 + : $((++INSANE)) +fi +if ! command npm owner ls katex | grep -q "^$(command npm whoami) <"; then + echo "You don't seem do be logged into npm, use \`npm login\`" >&2 + : $((++INSANE)) +fi +if [[ $BRANCH != @(v*|master) ]]; then + echo "'$BRANCH' does not like a release branch to me" >&2 + : $((++INSANE)) +fi -if [ -z "$NEXT_VERSION" ]; then - PROMPT="About to release $VERSION. Look good? [y/n] " +if [[ -z "$NEXT_VERSION" ]]; then + echo "About to release $VERSION from $BRANCH. " else - PROMPT="About to release $VERSION and bump master to $NEXT_VERSION-pre. Look good? [y/n] " + echo "About to release $VERSION from $BRANCH and bump to $NEXT_VERSION-pre." fi - -read -r -p "$PROMPT" CONFIRM -if [ "$CONFIRM" != "y" ]; then - exit +if [[ $INSANE != 0 ]]; then + read -r -p "$INSANE sanity check(s) failed, really proceed? [y/n] " CONFIRM +else + read -r -p "Look good? [y/n] " CONFIRM +fi +if [[ "$CONFIRM" != "y" ]]; then + exit 1 fi # Make a new detached HEAD -git checkout master +git checkout "$BRANCH" git pull git checkout --detach + +# Build generated files and add them to the repository (for bower) +git clean -fdx build dist make setup dist -git add dist/ +sed -i.bak -E '/^\/dist\/$/d' .gitignore +rm -f .gitignore.bak +git add .gitignore dist/ # Edit package.json and bower.json to the right version (see # http://stackoverflow.com/a/22084103 for why we need the .bak file to make # this mac & linux compatible) sed -i.bak -E 's|"version": "[^"]+",|"version": "'$VERSION'",|' package.json -sed -i.bak -E 's|"version": "[^"]+",|"version": "'$VERSION'",|' bower.json -rm -f package.json.bak bower.json.bak +rm -f package.json.bak + +# Update the version number in CDN URLs included in the README files, +# and regenerate the Subresource Integrity hash for these files. +node update-sri.js "${VERSION}" README.md contrib/*/README.md # Make the commit and tag, and push them. git add package.json bower.json git commit -n -m "v$VERSION" -git tag "v$VERSION" +git tag -a "v$VERSION" -m "v$VERSION" git push origin "v$VERSION" # Update npm (bower and cdnjs update automatically) @@ -53,22 +127,35 @@ npm publish if [ ! -z "$NEXT_VERSION" ]; then # Go back to master to bump - git checkout master + git checkout "$BRANCH" # Edit package.json and bower.json to the right version sed -i.bak -E 's|"version": "[^"]+",|"version": "'$NEXT_VERSION'-pre",|' package.json - sed -i.bak -E 's|"version": "[^"]+",|"version": "'$NEXT_VERSION'-pre",|' bower.json - rm -f package.json.bak bower.json.bak + rm -f package.json.bak + + # Refer to the just-released version in the documentation of the + # development branch, too. Most people will read docs on master. + node update-sri.js "${VERSION}" README.md contrib/*/README.md git add package.json bower.json git commit -n -m "Bump master to v$NEXT_VERSION-pre" - git push origin master + git push origin "$BRANCH" # Go back to the tag which has build/katex.tar.gz and build/katex.zip git checkout "v$VERSION" fi +echo "" echo "The automatic parts are done!" echo "Now all that's left is to create the release on github." echo "Visit https://github.com/Khan/KaTeX/releases/new?tag=v$VERSION to edit the release notes" echo "Don't forget to upload build/katex.tar.gz and build/katex.zip to the release!" + +if [[ ${DRY_RUN} ]]; then + echo "" + echo "This was a dry run." + echo "Operations using git or npm were printed not executed." + echo "Some files got modified, though, so you might want to undo " + echo "these changes now, e.g. using \`git checkout -- .\` or similar." + echo "" +fi diff --git a/update-sri.js b/update-sri.js new file mode 100644 index 0000000..3a2c8d6 --- /dev/null +++ b/update-sri.js @@ -0,0 +1,49 @@ +const fs = require("fs"); +const path = require("path"); +const sriToolbox = require("sri-toolbox"); + +const version = process.argv[2]; + +function read(file, encoding) { + return new Promise((resolve, reject) => + fs.readFile(file, encoding, (err, body) => + err ? reject(err) : resolve(body))); +} + +function write(file, data) { + return new Promise((resolve, reject) => + fs.writeFile(file, data, (err) => + err ? reject(err) : resolve())); +} + +Promise.all(process.argv.slice(3).map(file => + read(file, "utf8") + .then(body => { + // 1 - url prefix: "http…/KaTeX/ + // 2 - opening quote: " + // 3 - preserved suffix: /katex.min.js" integrity="…" + // 4 - file name: katex.min.js + // 5 - integrity opening quote: " + // 6 - old hash: sha384-… + // 7 - integrity hash algorithm: sha384 + const re = /((["'])https?:\/\/cdnjs.cloudflare.com\/ajax\/libs\/KaTeX\/)[^\/"']+(\/([^"']+)\2(?:\s+integrity=(["'])(([^-]+)-[^"']+)\5)?)/g; + const hashes = {}; + body = body.replace(re, (m, pre, oq1, post, file, oq2, old, algo) => { + if (old) { + hashes[old] = { file, algo }; + } + return pre + version + post; + }); + return Promise.all(Object.keys(hashes).map(hash => + read(path.join("dist", hashes[hash].file), null) + .then(data => { + body = body.replace(hash, sriToolbox.generate({ + algorithms: [hashes[hash].algo], + }, data)); + }) + )).then(() => write(file, body)); + }) +)).then(() => process.exit(0), err => { + console.error(err.stack); + process.exit(1); +});