forked from heavy-duty/ceremony
123 lines
4.6 KiB
Bash
123 lines
4.6 KiB
Bash
|
|
#!/usr/bin/env bash
|
|||
|
|
# lib/version.sh — one version abstraction, two backends (issue #3).
|
|||
|
|
#
|
|||
|
|
# Sourced, never executed: no set -e/-u here — the sourcing script owns its
|
|||
|
|
# own shell options. No git in this lib either: "version at the base commit"
|
|||
|
|
# is the caller's job (git show / git worktree the base tree, then point
|
|||
|
|
# version_read at it) — keeping the lib pure keeps its tests trivial.
|
|||
|
|
#
|
|||
|
|
# Backends ($1 of version_read / version_write):
|
|||
|
|
# file — a VERSION file in the tree (box, rig, incubator)
|
|||
|
|
# package-json — package.json's version field (cast)
|
|||
|
|
|
|||
|
|
# version_read <backend> [dir] — print the version; fail loudly on a
|
|||
|
|
# missing/empty source. A wrong release is worse than a missing one, so
|
|||
|
|
# every unreadable state is exit 1 with a message, never an empty print.
|
|||
|
|
version_read() {
|
|||
|
|
local backend="${1:?version_read: backend required}" dir="${2:-.}"
|
|||
|
|
local path ver
|
|||
|
|
case "$backend" in
|
|||
|
|
file)
|
|||
|
|
path="$dir/VERSION"
|
|||
|
|
if [ ! -f "$path" ]; then
|
|||
|
|
echo "version_read: $path: no such file" >&2
|
|||
|
|
return 1
|
|||
|
|
fi
|
|||
|
|
# Whitespace-stripped, following box's drill-recorded.sh: a trailing
|
|||
|
|
# newline or a stray space in VERSION must never make 0.7.0 look
|
|||
|
|
# unlike 0.7.0 (whole-version matching everywhere).
|
|||
|
|
ver="$(tr -d '[:space:]' <"$path")"
|
|||
|
|
if [ -z "$ver" ]; then
|
|||
|
|
echo "version_read: $path is empty" >&2
|
|||
|
|
return 1
|
|||
|
|
fi
|
|||
|
|
printf '%s\n' "$ver"
|
|||
|
|
;;
|
|||
|
|
package-json)
|
|||
|
|
# A clear message beats a bare command-not-found from deep inside a
|
|||
|
|
# workflow log.
|
|||
|
|
if ! command -v node >/dev/null 2>&1; then
|
|||
|
|
echo "version_read: node is required for version-source: package-json" >&2
|
|||
|
|
return 1
|
|||
|
|
fi
|
|||
|
|
path="$dir/package.json"
|
|||
|
|
if [ ! -f "$path" ]; then
|
|||
|
|
echo "version_read: $path: no such file" >&2
|
|||
|
|
return 1
|
|||
|
|
fi
|
|||
|
|
# Read via node's own parser, never regex — cast's "pkg_version
|
|||
|
|
# discipline" (cast release.yml): grepping JSON for "version" finds
|
|||
|
|
# dependency versions, engine fields, anything. An absent or
|
|||
|
|
# non-string field fails here rather than printing "undefined".
|
|||
|
|
node -e '
|
|||
|
|
const p = require(require("path").resolve(process.argv[1]));
|
|||
|
|
if (typeof p.version !== "string" || p.version === "") {
|
|||
|
|
console.error("version_read: " + process.argv[1] + ": no version field");
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
console.log(p.version);
|
|||
|
|
' "$path"
|
|||
|
|
;;
|
|||
|
|
*)
|
|||
|
|
echo "version_read: unknown backend: $backend" >&2
|
|||
|
|
return 1
|
|||
|
|
;;
|
|||
|
|
esac
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# version_is_dev <ver> — exit 0 iff ver ends in the literal -dev suffix.
|
|||
|
|
# Only -dev: an rc (1.2.3-rc1) is a pre-release, not a dev tree, and
|
|||
|
|
# treating it as one would let the armed guard key on the wrong state.
|
|||
|
|
version_is_dev() {
|
|||
|
|
case "${1:?version_is_dev: version required}" in
|
|||
|
|
*-dev) return 0 ;;
|
|||
|
|
*) return 1 ;;
|
|||
|
|
esac
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# version_next_dev <ver> — bare X.Y.Z -> X.Y.(Z+1)-dev (print). Refuses
|
|||
|
|
# anything else, including -dev and -rc1: this is only ever called on a
|
|||
|
|
# just-released version to re-arm main, and an rc's "next" is a human
|
|||
|
|
# decision, not arithmetic (box's drills/ prefix-confusion lore is why
|
|||
|
|
# nothing here guesses around pre-release identifiers).
|
|||
|
|
version_next_dev() {
|
|||
|
|
local ver="${1:?version_next_dev: version required}"
|
|||
|
|
if [[ ! "$ver" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|||
|
|
echo "version_next_dev: refusing '$ver' — expected bare X.Y.Z" >&2
|
|||
|
|
return 1
|
|||
|
|
fi
|
|||
|
|
local major minor patch
|
|||
|
|
IFS=. read -r major minor patch <<<"$ver"
|
|||
|
|
# 10#: a zero-padded patch ("09") would otherwise be read as octal.
|
|||
|
|
printf '%s.%s.%s-dev\n' "$major" "$minor" "$((10#$patch + 1))"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# version_write <backend> <ver> [dir] — write the version into the tree.
|
|||
|
|
version_write() {
|
|||
|
|
local backend="${1:?version_write: backend required}"
|
|||
|
|
local ver="${2:?version_write: version required}"
|
|||
|
|
local dir="${3:-.}"
|
|||
|
|
case "$backend" in
|
|||
|
|
file)
|
|||
|
|
printf '%s\n' "$ver" >"$dir/VERSION"
|
|||
|
|
;;
|
|||
|
|
package-json)
|
|||
|
|
if ! command -v npm >/dev/null 2>&1; then
|
|||
|
|
echo "version_write: npm is required for version-source: package-json" >&2
|
|||
|
|
return 1
|
|||
|
|
fi
|
|||
|
|
# npm pkg set + a lockfile-only install, exactly cast's incantation
|
|||
|
|
# (cast release.yml L233–L239): package-lock.json embeds the version
|
|||
|
|
# twice, and a bump that skips the lockfile leaves every subsequent
|
|||
|
|
# `npm ci` failing on the mismatch. --ignore-scripts because a
|
|||
|
|
# version bump must never run anybody's install hooks.
|
|||
|
|
(cd "$dir" && npm pkg set version="$ver" \
|
|||
|
|
&& npm install --package-lock-only --ignore-scripts)
|
|||
|
|
;;
|
|||
|
|
*)
|
|||
|
|
echo "version_write: unknown backend: $backend" >&2
|
|||
|
|
return 1
|
|||
|
|
;;
|
|||
|
|
esac
|
|||
|
|
}
|