diff --git a/.version-bump.json b/.version-bump.json index 7fc6cc9f..8b9c6d99 100644 --- a/.version-bump.json +++ b/.version-bump.json @@ -1,6 +1,7 @@ { "files": [ { "path": "package.json", "field": "version" }, + { "path": ".hermes-plugin/plugin.yaml", "field": "version" }, { "path": ".claude-plugin/plugin.json", "field": "version" }, { "path": ".cursor-plugin/plugin.json", "field": "version" }, { "path": ".codex-plugin/plugin.json", "field": "version" }, diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh index 01adec99..14ead002 100755 --- a/scripts/bump-version.sh +++ b/scripts/bump-version.sh @@ -40,12 +40,72 @@ write_json_field() { jq "$jq_path = \"$value\"" "$file" > "$tmp" && mv "$tmp" "$file" } +require_tool() { + command -v "$1" >/dev/null 2>&1 || { + echo "error: required tool '$1' is not on PATH" >&2 + return 1 + } +} + +read_yaml_field() { + local file="$1" field="$2" + require_tool yq || return 1 + FIELD="$field" yq -er '.[strenv(FIELD)] | select(tag == "!!str")' "$file" +} + +write_yaml_field() { + local file="$1" field="$2" value="$3" + FIELD="$field" VALUE="$value" \ + yq -i '.[strenv(FIELD)] = strenv(VALUE)' "$file" +} + +read_manifest_field() { + local file="$1" + + case "$file" in + *.json) read_json_field "$@" ;; + *.yaml) read_yaml_field "$@" ;; + *) + echo "error: unsupported manifest format: $file" >&2 + return 1 + ;; + esac +} + +write_manifest_field() { + local file="$1" + + case "$file" in + *.json) write_json_field "$@" ;; + *.yaml) write_yaml_field "$@" ;; + *) + echo "error: unsupported manifest format: $file" >&2 + return 1 + ;; + esac +} + # Read the list of declared files from config. # Outputs lines of "pathfield" declared_files() { jq -r '.files[] | "\(.path)\t\(.field)"' "$CONFIG" } +preflight_manifests() { + local path field fullpath + + require_tool jq || return 1 + while IFS=$'\t' read -r path field; do + fullpath="$REPO_ROOT/$path" + [[ -f "$fullpath" ]] || continue + + if ! read_manifest_field "$fullpath" "$field" >/dev/null; then + echo "error: cannot read declared manifest: $path ($field)" >&2 + return 1 + fi + done < <(declared_files) +} + # Read the audit exclude patterns from config. audit_excludes() { jq -r '.audit.exclude[]' "$CONFIG" 2>/dev/null @@ -68,7 +128,7 @@ cmd_check() { continue fi local ver - ver=$(read_json_field "$fullpath" "$field") + ver=$(read_manifest_field "$fullpath" "$field") printf " %-45s %s\n" "$path ($field)" "$ver" versions+=("$ver") done < <(declared_files) @@ -101,7 +161,7 @@ cmd_audit() { current_version=$( while IFS=$'\t' read -r path field; do local fullpath="$REPO_ROOT/$path" - [[ -f "$fullpath" ]] && read_json_field "$fullpath" "$field" + [[ -f "$fullpath" ]] && read_manifest_field "$fullpath" "$field" done < <(declared_files) | sort | uniq -c | sort -rn | head -1 | awk '{print $2}' ) @@ -172,6 +232,8 @@ cmd_bump() { exit 1 fi + preflight_manifests + echo "Bumping all declared files to $new_version..." echo "" @@ -182,8 +244,8 @@ cmd_bump() { continue fi local old_ver - old_ver=$(read_json_field "$fullpath" "$field") - write_json_field "$fullpath" "$field" "$new_version" + old_ver=$(read_manifest_field "$fullpath" "$field") + write_manifest_field "$fullpath" "$field" "$new_version" printf " %-45s %s -> %s\n" "$path ($field)" "$old_ver" "$new_version" done < <(declared_files) diff --git a/tests/version-bump/test-bump-version.sh b/tests/version-bump/test-bump-version.sh new file mode 100644 index 00000000..195d88a1 --- /dev/null +++ b/tests/version-bump/test-bump-version.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +SCRIPT_SOURCE="$REPO_ROOT/scripts/bump-version.sh" +TEST_ROOT="$(mktemp -d)" + +cleanup() { + rm -rf "$TEST_ROOT" +} +trap cleanup EXIT + +fail() { + echo "FAIL: $*" >&2 + exit 1 +} + +make_fixture() { + local repo="$1" + local yaml_body="$2" + + mkdir -p "$repo/scripts" "$repo/.hermes-plugin" + cp "$SCRIPT_SOURCE" "$repo/scripts/bump-version.sh" + cat >"$repo/.version-bump.json" <<'JSON' +{ + "files": [ + { "path": "package.json", "field": "version" }, + { "path": ".hermes-plugin/plugin.yaml", "field": "version" } + ], + "audit": { "exclude": [] } +} +JSON + cat >"$repo/package.json" <<'JSON' +{ + "name": "fixture", + "version": "1.2.3" +} +JSON + printf '%s\n' "$yaml_body" >"$repo/.hermes-plugin/plugin.yaml" +} + +happy_repo="$TEST_ROOT/happy" +make_fixture "$happy_repo" $'name: superpowers\nversion: 1.2.3' + +/bin/bash "$happy_repo/scripts/bump-version.sh" --check >"$TEST_ROOT/check.out" +/bin/bash "$happy_repo/scripts/bump-version.sh" --audit >"$TEST_ROOT/audit.out" +/bin/bash "$happy_repo/scripts/bump-version.sh" 2.3.4 >"$TEST_ROOT/bump.out" + +[[ "$(jq -r '.version' "$happy_repo/package.json")" == "2.3.4" ]] \ + || fail "JSON manifest was not bumped" +[[ "$(yq -r '.version' "$happy_repo/.hermes-plugin/plugin.yaml")" == "2.3.4" ]] \ + || fail "YAML manifest was not bumped" + +jq -e ' + any(.files[]; + .path == ".hermes-plugin/plugin.yaml" and .field == "version") +' "$REPO_ROOT/.version-bump.json" >/dev/null \ + || fail "Hermes manifest is not registered" + +invalid_repo="$TEST_ROOT/invalid" +make_fixture "$invalid_repo" $'name: superpowers\nversion: 123' +cp "$invalid_repo/package.json" "$TEST_ROOT/package.before" +cp "$invalid_repo/.hermes-plugin/plugin.yaml" "$TEST_ROOT/plugin.before" + +if /bin/bash "$invalid_repo/scripts/bump-version.sh" 2.3.4 \ + >"$TEST_ROOT/invalid.out" 2>&1; then + fail "bump accepted a non-string YAML version" +fi + +cmp -s "$TEST_ROOT/package.before" "$invalid_repo/package.json" \ + || fail "JSON manifest changed before YAML validation failed" +cmp -s "$TEST_ROOT/plugin.before" "$invalid_repo/.hermes-plugin/plugin.yaml" \ + || fail "invalid YAML manifest changed" + +echo "Version-bump tests passed"