From 51cc536d723ab15c020ef6bd7fce25b8e4735e76 Mon Sep 17 00:00:00 2001 From: Drew Ritter Date: Thu, 6 Aug 2026 14:47:14 -0700 Subject: [PATCH] fix(release): wire Hermes into version bumps Register the Hermes YAML manifest alongside the existing JSON manifests. Route manifest reads and writes by extension through jq or Mike Farah yq v4, with field names and values passed as data. Preflight every present manifest before the mutating bump loop so a deterministic YAML read failure cannot leave earlier JSON manifests partially updated. Cover check, audit, bump, registry wiring, and byte-for-byte no-partial-write behavior with one focused fixture test. --- .version-bump.json | 1 + scripts/bump-version.sh | 70 +++++++++++++++++++++-- tests/version-bump/test-bump-version.sh | 76 +++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 tests/version-bump/test-bump-version.sh 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"