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.
313 lines
7.2 KiB
Markdown
313 lines
7.2 KiB
Markdown
# 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" >&2` for errors
|
|
|
|
---
|
|
|
|
## 1. check-version.sh
|
|
|
|
**Purpose**: Compare current OpenCode version against latest GitHub release.
|
|
|
|
### Interface
|
|
|
|
```bash
|
|
./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_available` values: `yes` or `no`
|
|
- 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
|
|
|
|
```bash
|
|
./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:**
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
./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:**
|
|
```diff
|
|
--- /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
|
|
|
|
```bash
|
|
./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)
|
|
- `timeout` command (for safety)
|
|
|
|
**Behavior:**
|
|
- Runs `opencode --version` to 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 stderr
|
|
- `NO_COLOR`: If set, disable color output in error messages
|
|
|
|
**Example:**
|
|
```bash
|
|
DOTFILES_PATH=/custom/path DEBUG=1 ./check-version.sh
|
|
```
|
|
|
|
---
|
|
|
|
## Error Handling Patterns
|
|
|
|
All scripts follow these patterns:
|
|
|
|
**Dependency checking:**
|
|
```bash
|
|
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:**
|
|
```bash
|
|
if [[ $# -lt 1 ]]; then
|
|
echo "Error: Version argument required" >&2
|
|
echo "Usage: $0 VERSION" >&2
|
|
exit 1
|
|
fi
|
|
```
|
|
|
|
**Network error handling:**
|
|
```bash
|
|
if ! response=$(curl -s --fail https://...); then
|
|
echo "Error: GitHub API request failed" >&2
|
|
exit 1
|
|
fi
|
|
```
|
|
|
|
**Pattern matching validation:**
|
|
```bash
|
|
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
|
|
bash -n script.sh # Check for syntax errors
|
|
```
|
|
|
|
**Unit test pattern:**
|
|
```bash
|
|
# 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:**
|
|
```bash
|
|
# 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
|
|
```
|