Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: asdf_plugin_test
uses: asdf-vm/actions/plugin-test@6a442392015fbbdd8b48696d41e0051b2698b2e4 # v2
uses: asdf-vm/actions/plugin-test@b7bcd026f18772e44fe1026d729e1611cc435d47 # v4.0.1
with:
asdf_branch: v0.16.7
command: opencode --version
version: ${{ matrix.version }}
135 changes: 126 additions & 9 deletions bin/install
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ TMPDIR=${TMPDIR:-/tmp}
[ -n "${ASDF_INSTALL_VERSION:-}" ] || { echo 'Missing ASDF_INSTALL_VERSION' >&2; exit 1; }
[ -n "${ASDF_INSTALL_PATH:-}" ] || { echo 'Missing ASDF_INSTALL_PATH' >&2; exit 1; }

curl_args=(--retry 10 --retry-delay 2 -sfS)
if [ -n "${GITHUB_API_TOKEN:-}" ]; then
curl_args+=(-H "Authorization: token $GITHUB_API_TOKEN")
fi

# Compare two semantic versions using portable sort
# Returns 0 if version1 >= version2, 1 otherwise
version_gte() {
Expand Down Expand Up @@ -41,7 +46,7 @@ get_arch() {
esac
}

get_download_url() {
get_asset_name() {
local version="$1"
local platform
local arch
Expand All @@ -57,17 +62,129 @@ get_download_url() {
fi
fi

echo "https://github.com/anomalyco/opencode/releases/download/v${version}/opencode-${platform}-${arch}.${ext}"
echo "opencode-${platform}-${arch}.${ext}"
}

fetch_release_json() {
local version="$1"

curl "${curl_args[@]}" "https://api.github.com/repos/anomalyco/opencode/releases/tags/v${version}"
}

get_release_asset_metadata() {
local release_json="$1"
local asset_name="$2"

printf '%s' "$release_json" | perl -MJSON::PP -e '
use strict;
use warnings;
local $/;

my $asset_name = shift @ARGV;
my $release = decode_json(<>);

for my $asset (@{$release->{assets} || []}) {
next unless ($asset->{name} // q{}) eq $asset_name;

my $url = $asset->{browser_download_url} // q{};
my $digest = $asset->{digest} // q{};

if ($url eq q{} || $digest eq q{}) {
exit 2;
}

print "$url\n$digest\n";
exit 0;
}

exit 1;
' "$asset_name"
}

sha256_file() {
local file_path="$1"

if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$file_path" | awk '{print $1}'
return 0
fi

if command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$file_path" | awk '{print $1}'
return 0
fi

echo "No SHA-256 tool found (expected sha256sum or shasum)" >&2
exit 1
}

download_and_verify_asset() {
local download_url="$1"
local expected_digest="$2"
local archive_path="$3"
local actual_digest
local expected_sha256

case "$expected_digest" in
sha256:*)
expected_sha256=${expected_digest#sha256:}
;;
'')
echo "Release asset digest is missing" >&2
exit 1
;;
*)
echo "Unsupported release asset digest: ${expected_digest}" >&2
exit 1
;;
esac

curl -fSL "$download_url" -o "$archive_path"

actual_digest="$(sha256_file "$archive_path")"
if [ "$actual_digest" != "$expected_sha256" ]; then
echo "Release asset digest mismatch for ${download_url}" >&2
echo "Expected: ${expected_sha256}" >&2
echo "Actual: ${actual_digest}" >&2
exit 1
fi
}

install_opencode() {
local install_type="$1"
local version="$2"
local install_path="$3"
local bin_install_path="${install_path}/bin"
local asset_name
local release_json
local asset_metadata
local download_url
local asset_digest
local archive_path

download_url="$(get_download_url "$version")"
asset_name="$(get_asset_name "$version")"
release_json="$(fetch_release_json "$version")"

if asset_metadata="$(get_release_asset_metadata "$release_json" "$asset_name")"; then
:
else
status=$?
case $status in
1)
echo "Release asset ${asset_name} not found for v${version}" >&2
;;
2)
echo "Release asset ${asset_name} is missing required digest metadata" >&2
;;
*)
echo "Failed to parse release metadata for v${version}" >&2
;;
esac
exit 1
fi

download_url=$(printf '%s\n' "$asset_metadata" | sed -n '1p')
asset_digest=$(printf '%s\n' "$asset_metadata" | sed -n '2p')

# Create a secure temporary directory for download
# Use global variable so trap can access it
Expand All @@ -81,14 +198,14 @@ install_opencode() {

echo "Downloading opencode from ${download_url}"

# Note: Upstream does not publish checksums, so verification is not possible.
# Using --fail to error on HTTP failures instead of downloading error pages.
archive_path="${TMP_DOWNLOAD_DIR}/${asset_name}"

if [[ "$download_url" == *.tar.gz ]]; then
curl -fSL "$download_url" -o "${TMP_DOWNLOAD_DIR}/opencode.tar.gz"
tar -xzf "${TMP_DOWNLOAD_DIR}/opencode.tar.gz" -C "$TMP_DOWNLOAD_DIR"
download_and_verify_asset "$download_url" "$asset_digest" "$archive_path"
tar -xzf "$archive_path" -C "$TMP_DOWNLOAD_DIR"
else
curl -fSL "$download_url" -o "${TMP_DOWNLOAD_DIR}/opencode.zip"
if ! unzip -p "${TMP_DOWNLOAD_DIR}/opencode.zip" "opencode" > "${TMP_DOWNLOAD_DIR}/opencode"; then
download_and_verify_asset "$download_url" "$asset_digest" "$archive_path"
if ! unzip -p "$archive_path" "opencode" > "${TMP_DOWNLOAD_DIR}/opencode"; then
echo "Failed to extract opencode from zip" >&2
exit 1
fi
Expand Down
38 changes: 4 additions & 34 deletions bin/list-all
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,11 @@

set -euo pipefail

releases_path="https://api.github.com/repos/anomalyco/opencode/releases"
repo_url="https://github.com/anomalyco/opencode.git"

# Build curl arguments as an array to avoid eval
curl_args=(--retry 10 --retry-delay 2 -sfS)
if [ -n "${GITHUB_API_TOKEN:-}" ]; then
curl_args+=(-H "Authorization: token $GITHUB_API_TOKEN")
fi

next_link="${releases_path}?per_page=100&page=1"
all_versions=""

while [ -n "${next_link}" ]; do
# Download releases page, capture headers separately
header_file=$(mktemp)
trap "rm -f '$header_file'" EXIT

response=$(curl "${curl_args[@]}" -D "$header_file" "$next_link") || {
rm -f "$header_file"
exit 1
}

# Get versions from response
versions=$(echo "$response" | grep '"tag_name"' | cut -d'"' -f4 | sed 's/^v//')
all_versions="${versions}"$'\n'"${all_versions}"

# Get next link from headers (safely parse Link header)
next_link=""
if grep -qi '^link:' "$header_file"; then
# Extract URL marked as rel="next"
next_link=$(grep -i '^link:' "$header_file" | sed -n 's/.*<\([^>]*\)>; rel="next".*/\1/p')
fi

rm -f "$header_file"
trap - EXIT
done
all_versions=$(git ls-remote --tags "$repo_url" | \
awk '{print $2}' | \
sed -n 's#refs/tags/v\([0-9][0-9.]*\)$#\1#p')

# stolen from https://github.com/rbenv/ruby-build/pull/631/files#diff-fdcfb8a18714b33b07529b7d02b54f1dR942
sort_versions() {
Expand Down