Implement complete workflow for checking and applying OpenCode updates: - check-version.sh: Compare current vs latest GitHub release - fetch-sha256.sh: Fetch SHA256 hash using nix-prefetch-url - update-nix-file.sh: Update Nix package definition with dry-run support - verify-update.sh: Verify installed version matches expectation Includes comprehensive documentation (SKILL.md for agents, README.md for users) and full spec in specs/002-update-opencode/ with user stories and acceptance criteria. Eliminates manual Nix file editing and hash lookups for OpenCode updates.
7.2 KiB
Script Interface Contracts
Feature: 002-update-opencode
Date: 2025-11-11
Purpose: Define input/output contracts for bash helper scripts
Overview
All scripts follow bash best practices:
- Shebang:
#!/usr/bin/env bash - Error handling:
set -euo pipefail - Exit codes: 0 = success, 1 = error
- Output: Results to stdout, errors to stderr
- Logging: Use
echo "message" >&2for errors
1. check-version.sh
Purpose: Compare current OpenCode version against latest GitHub release.
Interface
./check-version.sh [--dotfiles PATH]
Arguments:
--dotfiles PATH: Optional. Path to dotfiles repo (default: ~/proj/dotfiles)
Exit Codes:
0: Success (version check completed)1: Error (file not found, API failure, parsing error)
Output (stdout):
current=1.0.44
latest=1.0.58
update_available=yes
Or if up-to-date:
current=1.0.58
latest=1.0.58
update_available=no
Output Format:
- Key-value pairs, one per line
update_availablevalues:yesorno- Can be sourced in bash:
eval "$(./check-version.sh)"
Error Output (stderr):
Error: Nix package file not found: /path/to/default.nix
Error: GitHub API request failed (network error)
Error: Failed to parse version from Nix file
Dependencies:
curl(GitHub API)jq(JSON parsing)grep(version extraction)
2. fetch-sha256.sh
Purpose: Fetch SHA256 hash for a specific OpenCode binary release using nix-prefetch-url.
Interface
./fetch-sha256.sh VERSION
Arguments:
VERSION: Required. Version to fetch (e.g., "1.0.58", no 'v' prefix)
Exit Codes:
0: Success (hash fetched and converted to SRI)1: Error (invalid version, network failure, nix-prefetch-url failed, conversion failed)
Output (stdout):
sha256-q3K5w1lhzd7GhBesKbaD3jDo2B/twUDLmi8wrkWzWh4=
Output Format:
- Single line containing SRI-formatted hash (sha256-...)
- Base64-encoded with possible trailing
=padding - No trailing newline issues (use
$(...)safely)
Error Output (stderr):
Error: Version argument required
Error: Invalid version format: X.Y.Z
Error: nix-prefetch-url failed for version 1.0.58
Error: GitHub binary release not found (404)
Error: Failed to convert hash to SRI format
Dependencies:
nix-prefetch-url(fetching binary zip)nix hash to-sri(converting to SRI format)
Performance:
- Typical: 2-5 seconds (download binary zip ~50MB + conversion)
- Cached: <0.5 seconds (Nix store hit + conversion)
Implementation Detail:
# Downloads: https://github.com/sst/opencode/releases/download/v${VERSION}/opencode-linux-x64.zip
# Converts: Nix base-32 → SRI format (sha256-...)
3. update-nix-file.sh
Purpose: Update version and sha256 fields in Nix package definition file.
Interface
./update-nix-file.sh VERSION SHA256 [--dotfiles PATH] [--dry-run]
Arguments:
VERSION: Required. New version string (e.g., "1.0.58")SHA256: Required. New sha256 hash in SRI format (sha256-...)--dotfiles PATH: Optional. Path to dotfiles repo (default: ~/proj/dotfiles)--dry-run: Optional. Show changes without writing
Exit Codes:
0: Success (file updated or dry-run completed)1: Error (file not found, not writable, pattern match failed)
Output (stdout) in dry-run mode:
--- /home/user/proj/dotfiles/pkgs/opencode/default.nix
+++ /home/user/proj/dotfiles/pkgs/opencode/default.nix
@@ -2,7 +2,7 @@
- version = "1.0.44";
+ version = "1.0.58";
- sha256 = "sha256-q3K5w1lhzd7GhBesKbaD3jDo2B/twUDLmi8wrkWzWh4=";
+ sha256 = "sha256-ABC123NewHashHere==";
Output (stdout) in normal mode:
Updated pkgs/opencode/default.nix:
version: 1.0.44 → 1.0.58
sha256: sha256-q3K5...Wh4= → sha256-ABC1...e==
Error Output (stderr):
Error: Nix package file not found: /path/to/default.nix
Error: File not writable: /path/to/default.nix
Error: Version pattern not found in file
Error: SHA256 pattern not found in file
Error: Invalid SHA256 format (must start with sha256-)
Dependencies:
sed(file modification)grep(pattern validation)
Safety:
- Validates patterns exist before modification
- Uses atomic sed -i (backup created automatically)
- Fails if either version or sha256 pattern not found exactly once
4. verify-update.sh
Purpose: Verify OpenCode was updated to expected version after rebuild.
Interface
./verify-update.sh EXPECTED_VERSION [--timeout SECONDS]
Arguments:
EXPECTED_VERSION: Required. Version that should be installed (e.g., "1.0.58")--timeout SECONDS: Optional. Max wait time for opencode command (default: 5)
Exit Codes:
0: Success (version matches)1: Error (command not found, version mismatch, timeout)
Output (stdout):
Verified: OpenCode version 1.0.58 installed successfully
Or on mismatch:
Warning: Expected 1.0.58 but found 1.0.44
Note: You may need to restart your shell or logout/login
Error Output (stderr):
Error: opencode command not found in PATH
Error: opencode --version timed out after 5 seconds
Error: Could not parse version from opencode output
Dependencies:
opencode(the CLI being updated)timeoutcommand (for safety)
Behavior:
- Runs
opencode --versionto check installed version - Compares output against expected version
- Exit code 1 on mismatch but still outputs helpful message
Environment Variables
All scripts may use:
DOTFILES_PATH: Override default dotfiles location (default: ~/proj/dotfiles)DEBUG: If set to "1", enable verbose logging to stderrNO_COLOR: If set, disable color output in error messages
Example:
DOTFILES_PATH=/custom/path DEBUG=1 ./check-version.sh
Error Handling Patterns
All scripts follow these patterns:
Dependency checking:
for cmd in jq curl grep; do
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "Error: Required command not found: $cmd" >&2
exit 1
fi
done
Input validation:
if [[ $# -lt 1 ]]; then
echo "Error: Version argument required" >&2
echo "Usage: $0 VERSION" >&2
exit 1
fi
Network error handling:
if ! response=$(curl -s --fail https://...); then
echo "Error: GitHub API request failed" >&2
exit 1
fi
Pattern matching validation:
if ! grep -q 'version = "[^"]*"' "$file"; then
echo "Error: Version pattern not found in $file" >&2
exit 1
fi
Testing Contracts
Each script should be testable independently:
Syntax validation:
bash -n script.sh # Check for syntax errors
Unit test pattern:
# Test successful execution
result=$(./script.sh args) || { echo "FAIL: Expected success"; exit 1; }
echo "PASS: Script executed successfully"
# Test error handling
./script.sh invalid-args 2>/dev/null && { echo "FAIL: Should have errored"; exit 1; }
echo "PASS: Error handled correctly"
Integration test:
# Full workflow test
version_info=$(./check-version.sh) || exit 1
eval "$version_info"
if [[ "$update_available" == "yes" ]]; then
hash=$(./fetch-sha256.sh "$latest") || exit 1
./update-nix-file.sh "$latest" "$hash" --dry-run || exit 1
fi