From d087fc4a6e8a6e40f3bfa0cb65aa95a063d2bd6e Mon Sep 17 00:00:00 2001 From: bitruskespo-collab Date: Sat, 29 Aug 2026 14:19:02 +0000 Subject: [PATCH] feat: implement wave 8 operational tooling improvements Closes #482: Add shellcheck CI coverage for all scripts - Create .github/workflows/shellcheck.yml for automated shell script linting Closes #483: Add --output json flag to scripts/verify.sh - Support machine-readable JSON output for post-deployment verification - Maintain backward compatibility with human-readable text output Closes #484: Fix check_abi_snapshot_hygiene.sh silent pass on missing abis/ - Add explicit check for abis/ directory existence - Fail loudly if directory is missing or misconfigured Closes #485: Add scripts/generate_release_notes.sh for changelog automation - Generate CHANGELOG entries from merged PR titles - Group by conventional-commit prefix (feat, fix, perf, etc.) --- .github/workflows/shellcheck.yml | 23 +++++++ scripts/check_abi_snapshot_hygiene.sh | 8 +++ scripts/generate_release_notes.sh | 90 +++++++++++++++++++++++++++ scripts/verify.sh | 69 ++++++++++++++++---- 4 files changed, 179 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/shellcheck.yml create mode 100755 scripts/generate_release_notes.sh diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml new file mode 100644 index 0000000..32d34e4 --- /dev/null +++ b/.github/workflows/shellcheck.yml @@ -0,0 +1,23 @@ +name: ShellCheck + +on: + push: + branches: [main] + paths: + - 'scripts/*.sh' + - '.github/workflows/shellcheck.yml' + pull_request: + branches: [main] + paths: + - 'scripts/*.sh' + - '.github/workflows/shellcheck.yml' + +jobs: + shellcheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ludeeus/action-shellcheck@master + with: + scandir: './scripts' + format: gcc diff --git a/scripts/check_abi_snapshot_hygiene.sh b/scripts/check_abi_snapshot_hygiene.sh index b0f3382..4e1ef72 100755 --- a/scripts/check_abi_snapshot_hygiene.sh +++ b/scripts/check_abi_snapshot_hygiene.sh @@ -13,6 +13,14 @@ REQUIRED_ABIS=( abis/compliance.json ) +# Check if abis/ directory exists +if [ ! -d "$REPO_ROOT/abis" ]; then + echo "ERROR: abis/ directory is missing or misconfigured" >&2 + echo "This may indicate a bad checkout or missing repository structure." >&2 + echo "Restore the directory or clone the repository properly." >&2 + exit 1 +fi + missing=0 for abi_file in "${REQUIRED_ABIS[@]}"; do if [ ! -f "$REPO_ROOT/$abi_file" ]; then diff --git a/scripts/generate_release_notes.sh b/scripts/generate_release_notes.sh new file mode 100755 index 0000000..64fc19d --- /dev/null +++ b/scripts/generate_release_notes.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +# generate_release_notes.sh — draft CHANGELOG.md entries from merged PR titles. +# +# Queries merged PRs since the last tag and groups them by conventional-commit prefix. +# Outputs a markdown section formatted for Keep a Changelog. +# +# Usage: +# ./scripts/generate_release_notes.sh +# +# Environment variables (optional): +# GITHUB_TOKEN — GitHub personal access token for higher rate limits +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +# Get the last tag or default to a reasonable starting point +LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "HEAD~50") + +# Query merged PRs since last tag using gh CLI +echo "Querying merged PRs since $LAST_TAG..." +MERGED_PRS=$(gh pr list \ + --search "merged:>$(git log -1 --format=%ci "$LAST_TAG" | cut -d' ' -f1)" \ + --state merged \ + --limit 100 \ + --json title,number,mergedAt \ + --jq '.[] | "\(.number): \(.title)"' 2>/dev/null || echo "") + +if [ -z "$MERGED_PRS" ]; then + echo "No merged PRs found since $LAST_TAG" + exit 0 +fi + +# Group by conventional-commit prefix +echo "" +echo "## Unreleased" +echo "" + +declare -A sections +declare -a order=("feat" "fix" "perf" "docs" "style" "refactor" "test" "chore") + +while IFS=: read -r number title; do + number=$(echo "$number" | xargs) + title=$(echo "$title" | xargs) + + # Extract conventional-commit prefix + prefix=$(echo "$title" | sed -n 's/^\([a-z]*\).*/\1/p' || echo "chore") + + if [ -z "$prefix" ]; then + prefix="chore" + fi + + sections[$prefix]="${sections[$prefix]:-}- $title (#$number) +" +done <<< "$MERGED_PRS" + +# Output grouped sections +for prefix in "${order[@]}"; do + if [ -n "${sections[$prefix]:-}" ]; then + case "$prefix" in + feat) + echo "### Added" + ;; + fix) + echo "### Fixed" + ;; + perf) + echo "### Performance" + ;; + docs) + echo "### Documentation" + ;; + style) + echo "### Changed" + ;; + refactor) + echo "### Changed" + ;; + test) + echo "### Testing" + ;; + chore) + echo "### Maintenance" + ;; + esac + echo "" + echo "${sections[$prefix]}" + fi +done + +echo "Generated release notes. Edit CHANGELOG.md and adjust as needed." diff --git a/scripts/verify.sh b/scripts/verify.sh index cd68949..635cd4d 100755 --- a/scripts/verify.sh +++ b/scripts/verify.sh @@ -5,6 +5,9 @@ # the locally built artifacts. Exits non-zero and prints a clear diff if any # mismatch is detected. # +# Usage: +# ./verify.sh [--output {text|json}] +# # Required environment variables: # SOROBAN_RPC_URL — Soroban RPC endpoint # INVOICE_CONTRACT_ID — deployed invoice contract ID (C…) @@ -18,6 +21,27 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +# Parse command-line arguments +OUTPUT_FORMAT="text" +while [[ $# -gt 0 ]]; do + case "$1" in + --output) + OUTPUT_FORMAT="$2" + shift 2 + ;; + *) + echo "Unknown option: $1" >&2 + exit 1 + ;; + esac +done + +# Validate output format +if [[ ! "$OUTPUT_FORMAT" =~ ^(text|json)$ ]]; then + echo "ERROR: --output must be 'text' or 'json'" >&2 + exit 1 +fi + # ── ABI metadata sanity check ───────────────────────────────────────────────── echo "Checking ABI metadata…" test -f "$ROOT_DIR/abis/invoice.json" || { echo "ERROR: abis/invoice.json missing"; exit 1; } @@ -47,6 +71,7 @@ CONTRACTS_DIR="${CONTRACTS_DIR:-$ROOT_DIR/../COMEBACKHERE-contracts/target/wasm3 FAIL=0 MISMATCHES=() +CHECKS=() # For JSON output: array of check results verify_contract() { local name="$1" @@ -93,13 +118,19 @@ print(hashlib.sha256(wasm_bytes).hexdigest()) fi if [[ "$local_hash" == "$deployed_hash" ]]; then - echo " ✓ $name: hash match ($local_hash)" + if [[ "$OUTPUT_FORMAT" == "text" ]]; then + echo " ✓ $name: hash match ($local_hash)" + fi + CHECKS+=("{\"contract\":\"$name\",\"status\":\"pass\",\"local_hash\":\"$local_hash\",\"deployed_hash\":\"$deployed_hash\"}") else - echo " ✗ $name: HASH MISMATCH" >&2 - echo " local: $local_hash" >&2 - echo " deployed: $deployed_hash" >&2 + if [[ "$OUTPUT_FORMAT" == "text" ]]; then + echo " ✗ $name: HASH MISMATCH" >&2 + echo " local: $local_hash" >&2 + echo " deployed: $deployed_hash" >&2 + fi FAIL=1 MISMATCHES+=("$name") + CHECKS+=("{\"contract\":\"$name\",\"status\":\"fail\",\"local_hash\":\"$local_hash\",\"deployed_hash\":\"$deployed_hash\"}") fi } @@ -118,13 +149,29 @@ verify_contract "compliance" \ "$COMPLIANCE_CONTRACT_ID" \ "$CONTRACTS_DIR/comebackhere_compliance.wasm" +if [[ "$OUTPUT_FORMAT" == "json" ]]; then + # Output JSON result + echo "{" + echo " \"status\": \"$([ $FAIL -eq 0 ] && echo 'pass' || echo 'fail')\"," + echo " \"checks\": [" + for i in "${!CHECKS[@]}"; do + echo " ${CHECKS[$i]}$([ $i -lt $((${#CHECKS[@]} - 1)) ] && echo ',' || echo '')" + done + echo " ]" + echo "}" +else + # Text output + if (( FAIL )); then + echo "" + echo "VERIFICATION FAILED — the following contracts have WASM hash mismatches:" >&2 + printf ' - %s\n' "${MISMATCHES[@]}" >&2 + echo "Ensure you are comparing the correct build artifacts to the correct deployment." >&2 + else + echo "" + echo "All WASM hashes verified successfully." + fi +fi + if (( FAIL )); then - echo "" - echo "VERIFICATION FAILED — the following contracts have WASM hash mismatches:" >&2 - printf ' - %s\n' "${MISMATCHES[@]}" >&2 - echo "Ensure you are comparing the correct build artifacts to the correct deployment." >&2 exit 1 fi - -echo "" -echo "All WASM hashes verified successfully."