duckscript icon indicating copy to clipboard operation
duckscript copied to clipboard

Builtin function for downloading latest||specific release for your OS from GitHub

Open SamuelMarks opened this issue 1 year ago • 1 comments
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}"

SamuelMarks avatar Aug 04 '24 02:08 SamuelMarks

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,

  1. 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)
  2. 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

sagiegurari avatar Aug 17 '24 08:08 sagiegurari