duckscript
duckscript copied to clipboard
Builtin function for downloading latest||specific release for your OS from GitHub
trafficstars
Feature Description
It seems like a very common use-case.
Describe The Solution You'd Like
Here are two examples for getting the latest version:
rye_version="$(curl -s https://api.github.com/repos/astral-sh/rye/tags | jq -r .[0].name)"
oxen_version="$(curl -s https://api.github.com/repos/Oxen-AI/Oxen/releases/latest | jq -r .tag_name)"
arch='my-arch'
Now let's continue with just rye:
# https://github.com/astral-sh/rye/releases/tag/0.38.0
filename='rye-'"$ARCH"'-'"$OS"'.gz'
curl -OL 'https://github.com/astral-sh/rye/releases/download/'"$rye_version"'/'"$filename"
# you could also download the checksum and check it, then:
tar xf "$rye_version"
# then you might `install` this somewhere
Code Sample
*EDIT rewrote with jq because of #435. Can you embed jaq into duckscript? - Or fix that bug?
#!/usr/bin/env duck
fn get_tag_name_from_github
if eq "${2}" "latest"
echo "GET https://api.github.com/repos/${1}/tags"
tags = http_client "https://api.github.com/repos/${1}/tags"
json = json_parse ${tags}
return "${json[0].name}"
end
return "${2}"
end
fn download_from_github
download_attempts = set 3
# First get `arch` and `os`
if is_windows
arch = get_env PROCESSOR_ARCHITECTURE
if eq "${arch}" "AMD64"
arch = set "x86_64"
end
os = set "windows"
# ext = set ".exe"
else
os = os_family
arch = exec --fail-on-error "uname" "-m"
arch = trim "${arch.stdout}"
# ext = set ".gz"
end
org = set "${1}"
repo = set "${2}"
version = set "${3}"
# output_file = set "${repo}-${arch}-${os}${ext}"
releases_file = set "./${org}_${repo}_${version}_releases.json"
:redownload
if not is_file "${releases_file}"
releases_url = set "https://api.github.com/repos/${org}/${repo}/releases/tags/${version}"
echo "GET ${releases_url}"
releases = http_client --output-file "${releases_file}" "${releases_url}"
if not "${releases}"
echo "unable to download ${releases_url} to ${releases_file}"
exit 3
end
end
releases = readfile "${releases_file}"
org = set "${1}"
repo = set "${2}"
version = set "${3}"
# releases = json_parse "${releases}"
#
# candidate_url = set ""
# candidate_name = set ""
# releases_assets_range = range 0 ${releases.assets.length}
#
# for i in ${releases_assets_range}
# release = get_by_name releases.assets[${i}]
# if eq ${os} "windows"
# # TODO: .msi, .cmd, .zip
# if eq content_type "application/x-msdownload"
# if contains ${release.name} ${arch}
# candidate_url = set ${release.url}
# candidate_name = set ${release.name}
# break
# else if eq ${candidate_url} ""
# candidate_url = set ${release.url}
# candidate_name = set ${release.name}
# end
# else eq ${candidate_url} "" and ( contains ${release.name} ${os} or contains ${release.name} "win" )
# candidate_url = set ${release.url}
# candidate_name = set ${release.name}
# end
# else if eq ${os} "linux"
# # TODO: .deb, .rpm
# if contains ${release.name} ${os} or contains ${release.name} "Linux"
# if contains ${release.name} ${arch}
# candidate_url = set ${release.url}
# candidate_name = set ${release.name}
# break
# else if eq ${candidate_url} ""
# candidate_url = set ${release.url}
# candidate_name = set ${release.name}
# end
# end
# else if eq ${os} "macOS"
# # TODO: .dmg, .pkg
# if contains ${release.name} "mac" or contains ${release.name} "Mac"
# if contains ${release.name} ${arch}
# candidate_url = set ${release.url}
# candidate_name = set ${release.name}
# break
# else if eq ${candidate_url} ""
# candidate_url = set ${release.url}
# candidate_name = set ${release.name}
# end
# end
# end
# end
jq_str = set ".assets[] | pick(.name, .size, .content_type, .browser_download_url)"
if eq "${os}" "windows"
candidate = exec jq -r --arg arch "${arch}" "${jq_str} | select(.content_type == \"application/x-msdownload\" and (.name | contains(\"${arch}\")))" "${releases_file}"
else if eq "${os}" "linux"
candidate = exec jq -r --arg arch "${arch}" "${jq_str} | select(.content_type == \"application/gzip\" and (.name | contains(\"${arch}\")) and (.name | contains(\"${arch}\")) and (.name | ascii_downcase | contains(\"linux\")))" "${releases_file}"
else if eq "${os}" "macos"
candidate = exec jq -r --arg arch "${arch}" "${jq_str} | select(.content_type == \"application/gzip\" and (.name | contains(\"${arch}\")) and (.name | contains(\"${arch}\")) and (.name | ascii_downcase | contains(\"mac\")))" "${releases_file}"
end
candidate = trim "${candidate.stdout}"
candidate = json_parse "${candidate}"
download_filename = set "${candidate.name}"
size = set "${candidate.size}"
content_type = set "${candidate.content_type}"
browser_download_url = set "${candidate.browser_download_url}"
if not is_defined browser_download_url
echo "Unable to determine download URL for your OS and architecture"
exit 2
end
cwd = pwd
target = join_path "${cwd}" / "${download_filename}"
downloaded = get_file_size "${target}"
if not eq "${size}" "${downloaded}"
echo "GET ${browser_download_url}"
downloaded = http_client --output-file "${download_filename}" "${browser_download_url}"
end
# could do proper checksum checking here after this check
if not eq "${downloaded}" "${size}"
echo "Expected ${size} downloaded got ${downloaded}"
rm "${download_filename}"
download_attempts = calc "${download_attempts}" - 1
if eq "${download_attempts}" 0
echo "All attempts to download failed."
exit 3
else
rm "${releases_file}"
goto :redownload
end
end
# rm
end
org = set "astral-sh"
repo = set "rye"
version = get_tag_name_from_github "${org}/${repo}" "latest"
# version = set "0.38.0"
download_from_github "${org}" "${repo}" "${version}"
as mentioned in #435 i don't see any bug. seems that access is just fine so double check your code against mine and the docs.
as for adding new commands, its possible to just PR this. there are 2 ways for this,
- create a duckscript as you did (just not OS binary dependencies please) and wrap it as a command (like array concat command is actually implemented as duckscript)
- create a rust command (probably better in this case)
and just put it under src/sdk/std/scm/github or something.
and make sure to keep it super simple and not sophisticated and push basic options