From 79147bc594300cd5c9c48b1549ab12ac5b878ce4 Mon Sep 17 00:00:00 2001 From: Aaron Schlesinger <70865+arschles@users.noreply.github.com> Date: Sat, 11 Jul 2026 11:26:47 -0700 Subject: [PATCH 1/8] progress --- .github/workflows/cli-release.yml | 92 +++++++++++++++++++++++++ cli/mise.toml | 6 +- cli/scripts/publish-cli-r2.sh | 73 -------------------- cli/scripts/upload-install-script-r2.sh | 0 4 files changed, 95 insertions(+), 76 deletions(-) create mode 100644 .github/workflows/cli-release.yml delete mode 100755 cli/scripts/publish-cli-r2.sh mode change 100755 => 100644 cli/scripts/upload-install-script-r2.sh diff --git a/.github/workflows/cli-release.yml b/.github/workflows/cli-release.yml new file mode 100644 index 0000000..5ee6af7 --- /dev/null +++ b/.github/workflows/cli-release.yml @@ -0,0 +1,92 @@ +name: cli-release + +on: + push: + tags: ["v*"] + workflow_dispatch: + +permissions: + contents: read + +env: + QT_R2_BUCKET: ${{ vars.QT_R2_BUCKET || 'quantiles-cli' }} + +jobs: + build: + name: ${{ matrix.target }} + strategy: + fail-fast: false + matrix: + include: + - runner: macos-13 + target: x86_64-apple-darwin + - runner: macos-14 + target: aarch64-apple-darwin + - runner: ubuntu-24.04 + target: x86_64-unknown-linux-gnu + - runner: ubuntu-24.04-arm + target: aarch64-unknown-linux-gnu + runs-on: ${{ matrix.runner }} + defaults: + run: + working-directory: cli + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + - uses: Swatinem/rust-cache@v2 + with: + workspaces: cli + - name: Build release binary + run: cargo build --locked --release --target ${{ matrix.target }} + - name: Package release artifact + env: + TARGET: ${{ matrix.target }} + run: | + version="$(cargo metadata --locked --no-deps --format-version 1 | jq -r '.packages[0].version')" + archive="qt-${version}-${TARGET}.tar.gz" + mkdir -p dist/package + cp "target/${TARGET}/release/qt" dist/package/qt + COPYFILE_DISABLE=1 tar -czf "dist/${archive}" -C dist/package qt + shasum -a 256 "dist/${archive}" > "dist/${archive}.sha256" + - uses: actions/upload-artifact@v4 + with: + name: cli-${{ matrix.target }} + path: cli/dist/qt-*.tar.gz* + if-no-files-found: error + + publish: + name: publish to R2 + needs: build + runs-on: ubuntu-24.04 + environment: release + env: + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + pattern: cli-* + path: dist + merge-multiple: true + - uses: dtolnay/rust-toolchain@stable + - name: Install Wrangler + run: npm install --global wrangler@4 + - name: Publish release artifacts and installer + working-directory: cli + run: | + version="$(cargo metadata --locked --no-deps --format-version 1 | jq -r '.packages[0].version')" + if [[ "$GITHUB_REF_TYPE" == "tag" && "$GITHUB_REF_NAME" != "v${version}" ]]; then + echo "tag $GITHUB_REF_NAME does not match CLI version v${version}" >&2 + exit 1 + fi + object_prefix="releases/v${version}" + + for file in ../dist/qt-*.tar.gz ../dist/qt-*.tar.gz.sha256; do + wrangler r2 object put "${QT_R2_BUCKET}/${object_prefix}/$(basename "$file")" --file "$file" --remote --cache-control "no-store" + done + + wrangler r2 object put "${QT_R2_BUCKET}/install.sh" --file scripts/install.sh --remote --cache-control "no-store" + wrangler r2 object put "${QT_R2_BUCKET}/${object_prefix}/install.sh" --file scripts/install.sh --remote --cache-control "no-store" diff --git a/cli/mise.toml b/cli/mise.toml index 5b9cdca..80a1f28 100644 --- a/cli/mise.toml +++ b/cli/mise.toml @@ -40,10 +40,10 @@ run = [ [tasks.run-http-client-example] run="cargo run --example http_client" -# build the CLI for ARM Mac, package the binaries, and publish the archives, -# checksums, and install script to the quantiles-cli R2 bucket +# dispatch the GitHub Actions release workflow, which builds the CLI for all +# supported macOS and Linux targets and publishes the release artifacts to R2 [tasks.publish-cli-r2] -run="bash scripts/publish-cli-r2.sh" +run="gh workflow run cli-release.yml" # upload only the install script to R2; use this when installer logic changes # but the already-published CLI release artifact does not need to be rebuilt diff --git a/cli/scripts/publish-cli-r2.sh b/cli/scripts/publish-cli-r2.sh deleted file mode 100755 index 72e2c80..0000000 --- a/cli/scripts/publish-cli-r2.sh +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# macOS defaults to 256 open files, which is too low for linking large Rust binaries. -ulimit -n 65536 2>/dev/null || true - -bucket="${QT_R2_BUCKET:-quantiles-cli}" -dist_dir="${QT_DIST_DIR:-dist}" - -if ! command -v wrangler >/dev/null 2>&1; then - echo "wrangler is required to publish to Cloudflare R2" >&2 - exit 1 -fi - -if ! command -v jq >/dev/null 2>&1; then - echo "jq is required to parse cargo metadata" >&2 - exit 1 -fi - -version="$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version')" - -if [[ -z "$version" || "$version" == "null" ]]; then - echo "failed to read crate version from cargo metadata" >&2 - exit 1 -fi - -mkdir -p "$dist_dir" - -object_prefix="releases/v${version}" - -echo "Building target: aarch64-apple-darwin" - -cargo build --target aarch64-apple-darwin - -mac_binary_path="target/aarch64-apple-darwin/debug/qt" -linux_binary_path="./qt-linux" - -if [[ ! -x "$mac_binary_path" ]]; then - echo "built binary not found at ${mac_binary_path}" >&2 - exit 1 -fi - -if [[ ! -x "$linux_binary_path" ]]; then - echo "linux binary not found at ${linux_binary_path}. please build it and place it there first." >&2 - exit 1 -fi - -for target in "aarch64-apple-darwin" "x86_64-unknown-linux-gnu"; do - if [[ "$target" == "aarch64-apple-darwin" ]]; then - binary_path="$mac_binary_path" - else - binary_path="$linux_binary_path" - fi - - archive="${dist_dir}/qt-${version}-${target}.tar.gz" - checksum="${archive}.sha256" - - tmpdir="$(mktemp -d)" - cp "$binary_path" "$tmpdir/qt" - COPYFILE_DISABLE=1 tar -czf "$archive" -C "$tmpdir" "qt" - rm -rf "$tmpdir" - ( - cd "$dist_dir" - shasum -a 256 "$(basename "$archive")" > "$(basename "$checksum")" - ) - - wrangler r2 object put "${bucket}/${object_prefix}/$(basename "$archive")" --file "$archive" --remote --cache-control "no-store" - wrangler r2 object put "${bucket}/${object_prefix}/$(basename "$checksum")" --file "$checksum" --remote --cache-control "no-store" - - echo "published ${archive} to r2://${bucket}/${object_prefix}/$(basename "$archive")" -done - -echo "All targets published successfully." diff --git a/cli/scripts/upload-install-script-r2.sh b/cli/scripts/upload-install-script-r2.sh old mode 100755 new mode 100644 From ec3732a4f5607080e8c2f53c103b8a4f59de1978 Mon Sep 17 00:00:00 2001 From: Aaron Schlesinger <70865+arschles@users.noreply.github.com> Date: Sun, 19 Jul 2026 16:21:17 -0700 Subject: [PATCH 2/8] allow for testing from PRs --- .github/workflows/cli-release.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/cli-release.yml b/.github/workflows/cli-release.yml index 5ee6af7..14f032d 100644 --- a/.github/workflows/cli-release.yml +++ b/.github/workflows/cli-release.yml @@ -1,6 +1,13 @@ name: cli-release on: + pull_request: + paths: + - "cli/**" + # This line allows us to test at least the build part of this + # workflow definition if/when we change it. Below, we make sure + # not to publish the CLI from pull requests, though. + - ".github/workflows/cli-release.yml" push: tags: ["v*"] workflow_dispatch: @@ -57,6 +64,9 @@ jobs: if-no-files-found: error publish: + # make sure we do not publish the CLI to R2 when we're just testing + # the workflow definition in a PR + if: github.event_name != 'pull_request' name: publish to R2 needs: build runs-on: ubuntu-24.04 From eae9c75f10b0f7c03abceb24c2a1caca4ebf7d93 Mon Sep 17 00:00:00 2001 From: Aaron Schlesinger <70865+arschles@users.noreply.github.com> Date: Sun, 19 Jul 2026 16:52:54 -0700 Subject: [PATCH 3/8] using the right macOS intel runner --- .github/workflows/cli-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cli-release.yml b/.github/workflows/cli-release.yml index 14f032d..8be6b23 100644 --- a/.github/workflows/cli-release.yml +++ b/.github/workflows/cli-release.yml @@ -25,7 +25,7 @@ jobs: fail-fast: false matrix: include: - - runner: macos-13 + - runner: macos-15-intel target: x86_64-apple-darwin - runner: macos-14 target: aarch64-apple-darwin From bb863daeb20b882fdf0a4e5b2b466e32168d8919 Mon Sep 17 00:00:00 2001 From: Aaron Schlesinger <70865+arschles@users.noreply.github.com> Date: Sun, 19 Jul 2026 16:57:05 -0700 Subject: [PATCH 4/8] adding sccache --- .github/workflows/cli-release.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/cli-release.yml b/.github/workflows/cli-release.yml index 8be6b23..5c91dca 100644 --- a/.github/workflows/cli-release.yml +++ b/.github/workflows/cli-release.yml @@ -21,6 +21,11 @@ env: jobs: build: name: ${{ matrix.target }} + env: + RUSTC_WRAPPER: sccache + SCCACHE_WEBDAV_ENDPOINT: https://cache.depot.dev + SCCACHE_WEBDAV_TOKEN: ${{ secrets.SCCACHE_WEBDAV_TOKEN }} + SCCACHE_GHA_ENABLED: "true" strategy: fail-fast: false matrix: @@ -42,6 +47,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} + - uses: mozilla-actions/sccache-action@v0.0.9 - uses: Swatinem/rust-cache@v2 with: workspaces: cli From 8f681d7f441cf4dc4648ef13892e4c21125836bf Mon Sep 17 00:00:00 2001 From: Aaron Schlesinger <70865+arschles@users.noreply.github.com> Date: Sun, 19 Jul 2026 16:59:31 -0700 Subject: [PATCH 5/8] adding TODO --- .github/workflows/cli-release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cli-release.yml b/.github/workflows/cli-release.yml index 5c91dca..8dc3021 100644 --- a/.github/workflows/cli-release.yml +++ b/.github/workflows/cli-release.yml @@ -63,6 +63,7 @@ jobs: cp "target/${TARGET}/release/qt" dist/package/qt COPYFILE_DISABLE=1 tar -czf "dist/${archive}" -C dist/package qt shasum -a 256 "dist/${archive}" > "dist/${archive}.sha256" + # TODO: upgrade upload-artifact to v7 and download-artifact to v8 together - uses: actions/upload-artifact@v4 with: name: cli-${{ matrix.target }} From addefefdf223eb6bb9eb0f40e778c11095566cb7 Mon Sep 17 00:00:00 2001 From: Aaron Schlesinger <70865+arschles@users.noreply.github.com> Date: Sun, 19 Jul 2026 17:12:40 -0700 Subject: [PATCH 6/8] progress --- .github/workflows/cli-release.yml | 24 ++++++++++++++---------- cli/scripts/install.sh | 7 +++---- cli/scripts/upload-install-script-r2.sh | 15 ++------------- 3 files changed, 19 insertions(+), 27 deletions(-) diff --git a/.github/workflows/cli-release.yml b/.github/workflows/cli-release.yml index 8dc3021..4169afc 100644 --- a/.github/workflows/cli-release.yml +++ b/.github/workflows/cli-release.yml @@ -9,6 +9,7 @@ on: # not to publish the CLI from pull requests, though. - ".github/workflows/cli-release.yml" push: + # TODO: upload CLI builds from pushes to main to the beta channel tags: ["v*"] workflow_dispatch: @@ -57,8 +58,7 @@ jobs: env: TARGET: ${{ matrix.target }} run: | - version="$(cargo metadata --locked --no-deps --format-version 1 | jq -r '.packages[0].version')" - archive="qt-${version}-${TARGET}.tar.gz" + archive="qt-${TARGET}.tar.gz" mkdir -p dist/package cp "target/${TARGET}/release/qt" dist/package/qt COPYFILE_DISABLE=1 tar -czf "dist/${archive}" -C dist/package qt @@ -94,16 +94,20 @@ jobs: - name: Publish release artifacts and installer working-directory: cli run: | - version="$(cargo metadata --locked --no-deps --format-version 1 | jq -r '.packages[0].version')" - if [[ "$GITHUB_REF_TYPE" == "tag" && "$GITHUB_REF_NAME" != "v${version}" ]]; then - echo "tag $GITHUB_REF_NAME does not match CLI version v${version}" >&2 - exit 1 + object_prefixes=("releases/latest") + if [[ "$GITHUB_REF_TYPE" == "tag" ]]; then + object_prefixes+=("releases/${GITHUB_REF_NAME}") + printf '%s\n' "$GITHUB_REF_NAME" > ../dist/version.txt fi - object_prefix="releases/v${version}" - for file in ../dist/qt-*.tar.gz ../dist/qt-*.tar.gz.sha256; do - wrangler r2 object put "${QT_R2_BUCKET}/${object_prefix}/$(basename "$file")" --file "$file" --remote --cache-control "no-store" + for object_prefix in "${object_prefixes[@]}"; do + for file in ../dist/qt-*.tar.gz ../dist/qt-*.tar.gz.sha256; do + wrangler r2 object put "${QT_R2_BUCKET}/${object_prefix}/$(basename "$file")" --file "$file" --remote --cache-control "no-store" + done + wrangler r2 object put "${QT_R2_BUCKET}/${object_prefix}/install.sh" --file scripts/install.sh --remote --cache-control "no-store" done wrangler r2 object put "${QT_R2_BUCKET}/install.sh" --file scripts/install.sh --remote --cache-control "no-store" - wrangler r2 object put "${QT_R2_BUCKET}/${object_prefix}/install.sh" --file scripts/install.sh --remote --cache-control "no-store" + if [[ "$GITHUB_REF_TYPE" == "tag" ]]; then + wrangler r2 object put "${QT_R2_BUCKET}/releases/latest/version.txt" --file ../dist/version.txt --remote --cache-control "no-store" + fi diff --git a/cli/scripts/install.sh b/cli/scripts/install.sh index 8712c46..e6e6acb 100755 --- a/cli/scripts/install.sh +++ b/cli/scripts/install.sh @@ -1,7 +1,6 @@ #!/usr/bin/env bash set -euo pipefail -version="${QT_VERSION:-0.1.0}" base_url="https://cli.quantiles.io" install_dir="${QT_INSTALL_DIR:-$HOME/.quantiles}" @@ -43,9 +42,9 @@ case "${os}:${arch}" in ;; esac -archive="qt-${version}-${target}.tar.gz" +archive="qt-${target}.tar.gz" checksum="${archive}.sha256" -url="${base_url%/}/releases/v${version}/${archive}" +url="${base_url%/}/releases/latest/${archive}" checksum_url="${url}.sha256" tmpdir="$(mktemp -d 2>/dev/null || mktemp -d -t qt-install)" @@ -54,7 +53,7 @@ cleanup() { } trap cleanup EXIT INT TERM -echo "Downloading qt ${version} for ${target}" +echo "Downloading latest qt for ${target}" curl -fsSL "$url" -o "$tmpdir/$archive" curl -fsSL "$checksum_url" -o "$tmpdir/$checksum" diff --git a/cli/scripts/upload-install-script-r2.sh b/cli/scripts/upload-install-script-r2.sh index dc8d2d1..70fc0de 100644 --- a/cli/scripts/upload-install-script-r2.sh +++ b/cli/scripts/upload-install-script-r2.sh @@ -9,20 +9,9 @@ if ! command -v wrangler >/dev/null 2>&1; then exit 1 fi -if ! command -v jq >/dev/null 2>&1; then - echo "jq is required to parse cargo metadata" >&2 - exit 1 -fi - -version="$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version')" -if [[ -z "$version" || "$version" == "null" ]]; then - echo "failed to read crate version from cargo metadata" >&2 - exit 1 -fi - mkdir -p "$dist_dir" install_script="${dist_dir}/install.sh" -object_prefix="releases/v${version}" +object_prefix="releases/latest" cp scripts/install.sh "$install_script" chmod +x "$install_script" @@ -31,4 +20,4 @@ wrangler r2 object put "${bucket}/install.sh" --file "$install_script" --remote wrangler r2 object put "${bucket}/${object_prefix}/install.sh" --file "$install_script" --remote --cache-control "no-store" echo "published installer to r2://${bucket}/install.sh" -echo "published versioned installer to r2://${bucket}/${object_prefix}/install.sh" +echo "published latest installer to r2://${bucket}/${object_prefix}/install.sh" From 8faa5b87f71e1fbeb0952b1e9862757eb4b96087 Mon Sep 17 00:00:00 2001 From: Aaron Schlesinger <70865+arschles@users.noreply.github.com> Date: Sun, 19 Jul 2026 17:18:00 -0700 Subject: [PATCH 7/8] progress --- .github/workflows/cli-release.yml | 30 +++++--------- cli/scripts/publish-release-artifacts-r2.sh | 45 +++++++++++++++++++++ 2 files changed, 54 insertions(+), 21 deletions(-) create mode 100644 cli/scripts/publish-release-artifacts-r2.sh diff --git a/.github/workflows/cli-release.yml b/.github/workflows/cli-release.yml index 4169afc..fca04ef 100644 --- a/.github/workflows/cli-release.yml +++ b/.github/workflows/cli-release.yml @@ -31,8 +31,13 @@ jobs: fail-fast: false matrix: include: - - runner: macos-15-intel - target: x86_64-apple-darwin + # TODO: get Intel Mac builds working. The are currently + # failing with this error: + # + # https://github.com/quantiles-evals/quantiles/actions/runs/29709160146/job/88250642027?pr=22 + # + # - runner: macos-15-intel + # target: x86_64-apple-darwin - runner: macos-14 target: aarch64-apple-darwin - runner: ubuntu-24.04 @@ -91,23 +96,6 @@ jobs: - uses: dtolnay/rust-toolchain@stable - name: Install Wrangler run: npm install --global wrangler@4 - - name: Publish release artifacts and installer + - name: Publish release artifacts working-directory: cli - run: | - object_prefixes=("releases/latest") - if [[ "$GITHUB_REF_TYPE" == "tag" ]]; then - object_prefixes+=("releases/${GITHUB_REF_NAME}") - printf '%s\n' "$GITHUB_REF_NAME" > ../dist/version.txt - fi - - for object_prefix in "${object_prefixes[@]}"; do - for file in ../dist/qt-*.tar.gz ../dist/qt-*.tar.gz.sha256; do - wrangler r2 object put "${QT_R2_BUCKET}/${object_prefix}/$(basename "$file")" --file "$file" --remote --cache-control "no-store" - done - wrangler r2 object put "${QT_R2_BUCKET}/${object_prefix}/install.sh" --file scripts/install.sh --remote --cache-control "no-store" - done - - wrangler r2 object put "${QT_R2_BUCKET}/install.sh" --file scripts/install.sh --remote --cache-control "no-store" - if [[ "$GITHUB_REF_TYPE" == "tag" ]]; then - wrangler r2 object put "${QT_R2_BUCKET}/releases/latest/version.txt" --file ../dist/version.txt --remote --cache-control "no-store" - fi + run: bash scripts/publish-release-artifacts-r2.sh diff --git a/cli/scripts/publish-release-artifacts-r2.sh b/cli/scripts/publish-release-artifacts-r2.sh new file mode 100644 index 0000000..b267078 --- /dev/null +++ b/cli/scripts/publish-release-artifacts-r2.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +repo_dir="$(cd -- "$script_dir/../.." && pwd)" + +bucket="${QT_R2_BUCKET:-quantiles-cli}" +dist_dir="${QT_DIST_DIR:-$repo_dir/dist}" + +if ! command -v wrangler >/dev/null 2>&1; then + echo "wrangler is required to publish to Cloudflare R2" >&2 + exit 1 +fi + +artifacts=() +for file in "$dist_dir"/qt-*.tar.gz "$dist_dir"/qt-*.tar.gz.sha256; do + if [[ -f "$file" ]]; then + artifacts+=("$file") + fi +done + +if [[ ${#artifacts[@]} -eq 0 ]]; then + echo "no CLI release artifacts found in $dist_dir" >&2 + exit 1 +fi + +object_prefixes=("releases/latest") +if [[ "${GITHUB_REF_TYPE:-}" == "tag" ]]; then + if [[ -z "${GITHUB_REF_NAME:-}" ]]; then + echo "GITHUB_REF_NAME is required for tag releases" >&2 + exit 1 + fi + object_prefixes+=("releases/${GITHUB_REF_NAME}") + printf '%s\n' "$GITHUB_REF_NAME" > "$dist_dir/version.txt" +fi + +for object_prefix in "${object_prefixes[@]}"; do + for file in "${artifacts[@]}"; do + wrangler r2 object put "${bucket}/${object_prefix}/$(basename "$file")" --file "$file" --remote --cache-control "no-store" + done +done + +if [[ "${GITHUB_REF_TYPE:-}" == "tag" ]]; then + wrangler r2 object put "${bucket}/releases/latest/version.txt" --file "$dist_dir/version.txt" --remote --cache-control "no-store" +fi From 8a702a752c537340aadab3065d8fcbfa3c307218 Mon Sep 17 00:00:00 2001 From: Aaron Schlesinger <70865+arschles@users.noreply.github.com> Date: Sun, 19 Jul 2026 17:28:15 -0700 Subject: [PATCH 8/8] adding push-to-main upload --- .github/workflows/cli-release.yml | 28 ++- cli/scripts/install-beta.sh | 241 +++++++++++++++++++++++ cli/scripts/publish-beta-artifacts-r2.sh | 40 ++++ cli/scripts/upload-install-script-r2.sh | 3 - 4 files changed, 305 insertions(+), 7 deletions(-) create mode 100644 cli/scripts/install-beta.sh create mode 100644 cli/scripts/publish-beta-artifacts-r2.sh diff --git a/.github/workflows/cli-release.yml b/.github/workflows/cli-release.yml index fca04ef..17084fa 100644 --- a/.github/workflows/cli-release.yml +++ b/.github/workflows/cli-release.yml @@ -9,7 +9,7 @@ on: # not to publish the CLI from pull requests, though. - ".github/workflows/cli-release.yml" push: - # TODO: upload CLI builds from pushes to main to the beta channel + branches: [main] tags: ["v*"] workflow_dispatch: @@ -76,9 +76,7 @@ jobs: if-no-files-found: error publish: - # make sure we do not publish the CLI to R2 when we're just testing - # the workflow definition in a PR - if: github.event_name != 'pull_request' + if: github.event_name == 'workflow_dispatch' || github.ref_type == 'tag' name: publish to R2 needs: build runs-on: ubuntu-24.04 @@ -99,3 +97,25 @@ jobs: - name: Publish release artifacts working-directory: cli run: bash scripts/publish-release-artifacts-r2.sh + + publish-beta: + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + name: publish beta to R2 + needs: build + runs-on: ubuntu-24.04 + environment: release + env: + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + pattern: cli-* + path: dist + merge-multiple: true + - name: Install Wrangler + run: npm install --global wrangler@4 + - name: Publish beta artifacts + working-directory: cli + run: bash scripts/publish-beta-artifacts-r2.sh diff --git a/cli/scripts/install-beta.sh b/cli/scripts/install-beta.sh new file mode 100644 index 0000000..738663f --- /dev/null +++ b/cli/scripts/install-beta.sh @@ -0,0 +1,241 @@ +#!/usr/bin/env bash +set -euo pipefail + +base_url="https://cli.quantiles.io" + +install_dir="${QT_INSTALL_DIR:-$HOME/.quantiles}" +bin_dir="$install_dir/bin" +exe="$bin_dir/qt" + +installed_help_text() { + echo "Quantiles is a full-featured local-native toolchain for running and analyzing AI evals at scale." + echo "" + echo "Run your first benchmark example without calling an external model API or incurring any usage charges:" + echo "" + echo " qt run pubmedqa" + echo "" + echo "Run qt --help for the complete CLI command reference, or visit quantiles.io/documentation " + echo "for full documentation." +} + +need_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "required command not found: $1" >&2 + exit 1 + fi +} + +need_cmd curl +need_cmd tar + +os="$(uname -s)" +arch="$(uname -m)" + +case "${os}:${arch}" in + Darwin:arm64) target="aarch64-apple-darwin" ;; + Darwin:x86_64) target="x86_64-apple-darwin" ;; + Linux:x86_64) target="x86_64-unknown-linux-gnu" ;; + Linux:aarch64 | Linux:arm64) target="aarch64-unknown-linux-gnu" ;; + *) + echo "unsupported platform: ${os}/${arch}" >&2 + exit 1 + ;; +esac + +archive="qt-${target}.tar.gz" +checksum="${archive}.sha256" +url="${base_url%/}/channel/beta/latest/${archive}" +checksum_url="${url}.sha256" + +tmpdir="$(mktemp -d 2>/dev/null || mktemp -d -t qt-install)" +cleanup() { + rm -rf "$tmpdir" +} +trap cleanup EXIT INT TERM + +echo "Downloading latest beta qt for ${target}" +curl -fsSL "$url" -o "$tmpdir/$archive" +curl -fsSL "$checksum_url" -o "$tmpdir/$checksum" + +( + cd "$tmpdir" + expected="$(awk '{print $1}' "$checksum")" + if command -v shasum >/dev/null 2>&1; then + actual="$(shasum -a 256 "$archive" | awk '{print $1}')" + elif command -v sha256sum >/dev/null 2>&1; then + actual="$(sha256sum "$archive" | awk '{print $1}')" + else + echo "required command not found: shasum or sha256sum" >&2 + exit 1 + fi + if [ "$actual" != "$expected" ]; then + echo "checksum mismatch for $archive" >&2 + echo "expected: $expected" >&2 + echo "actual: $actual" >&2 + exit 1 + fi +) + +mkdir -p "$bin_dir" +tar -xzf "$tmpdir/$archive" -C "$tmpdir" +install -m 0755 "$tmpdir/qt" "$exe" + +echo "Installed qt to $exe" +echo "" + +# If qt is already in PATH, nothing more to do. +if command -v qt >/dev/null 2>&1; then + installed_help_text + exit 0 +fi + +# Helper to abbreviate $HOME as ~ +tildify() { + if [[ "$1" == "$HOME"/* ]]; then +# shellcheck disable=SC2088 + echo "~/${1#"$HOME"/}" + else + echo "$1" + fi +} + +refresh_command="" +tilde_bin_dir="$(tildify "$bin_dir")" +quoted_install_dir="${install_dir//\"/\\\"}" + +if [[ "$quoted_install_dir" == "\"$HOME/"* ]]; then + quoted_install_dir="${quoted_install_dir/\"$HOME\//\"\$HOME/}" +fi + +# Check if the export block is already present in the given file. +config_has_export() { + local file="$1" + [[ -f "$file" ]] && grep -q "# quantiles" "$file" 2>/dev/null && grep -q "$bin_dir" "$file" 2>/dev/null +} + +case "$(basename "$SHELL")" in + fish) + commands=( + "set --export QT_INSTALL_DIR $quoted_install_dir" + "set --export PATH $bin_dir \\$PATH" + ) + fish_config="$HOME/.config/fish/config.fish" + tilde_fish_config="$(tildify "$fish_config")" + + if [[ -w "$fish_config" ]] && ! config_has_export "$fish_config"; then + { + echo "" + echo "# quantiles" + for command in "${commands[@]}"; do + echo "$command" + done + } >> "$fish_config" + echo "Added \"$tilde_bin_dir\" to \$PATH in \"$tilde_fish_config\"" + refresh_command="source $tilde_fish_config" + else + if config_has_export "$fish_config"; then + echo "qt PATH export already present in \"$tilde_fish_config\"" + refresh_command="source $tilde_fish_config" + else + echo "Manually add the directory to $tilde_fish_config (or similar):" + for command in "${commands[@]}"; do + echo " $command" + done + fi + fi + ;; + zsh) + commands=( + "export QT_INSTALL_DIR=$quoted_install_dir" + "export PATH=\"$bin_dir:\$PATH\"" + ) + zsh_config="$HOME/.zshrc" + tilde_zsh_config="$(tildify "$zsh_config")" + + if [[ -w "$zsh_config" ]] && ! config_has_export "$zsh_config"; then + { + echo "" + echo "# quantiles" + for command in "${commands[@]}"; do + echo "$command" + done + } >> "$zsh_config" + echo "Added \"$tilde_bin_dir\" to \$PATH in \"$tilde_zsh_config\"" + refresh_command="exec $SHELL" + else + if config_has_export "$zsh_config"; then + echo "qt PATH export already present in \"$tilde_zsh_config\"" + refresh_command="exec $SHELL" + else + echo "Manually add the directory to $tilde_zsh_config (or similar):" + for command in "${commands[@]}"; do + echo " $command" + done + fi + fi + ;; + bash) + commands=( + "export QT_INSTALL_DIR=$quoted_install_dir" + "export PATH=\"$bin_dir:\$PATH\"" + ) + bash_configs=( + "$HOME/.bash_profile" + "$HOME/.bashrc" + ) + if [[ "${XDG_CONFIG_HOME:-}" ]]; then + bash_configs+=( + "$XDG_CONFIG_HOME/.bash_profile" + "$XDG_CONFIG_HOME/.bashrc" + "$XDG_CONFIG_HOME/bash_profile" + "$XDG_CONFIG_HOME/bashrc" + ) + fi + + set_manually=true + bash_refresh_command="" + for bash_config in "${bash_configs[@]}"; do + tilde_bash_config="$(tildify "$bash_config")" + if [[ -w "$bash_config" ]] && ! config_has_export "$bash_config"; then + { + echo "" + echo "# quantiles" + for command in "${commands[@]}"; do + echo "$command" + done + } >> "$bash_config" + echo "Added \"$tilde_bin_dir\" to \$PATH in \"$tilde_bash_config\"" + refresh_command="source $tilde_bash_config" + set_manually=false + break + elif config_has_export "$bash_config"; then + bash_refresh_command="source $tilde_bash_config" + fi + done + + if [[ "$set_manually" == true ]]; then + if [[ -n "$bash_refresh_command" ]]; then + echo "qt PATH export already present in a bash config file." + refresh_command="$bash_refresh_command" + else + echo "Manually add the directory to ~/.bashrc (or similar):" + for command in "${commands[@]}"; do + echo " $command" + done + fi + fi + ;; + *) + echo "Manually add the directory to ~/.bashrc (or similar):" + echo " export QT_INSTALL_DIR=$quoted_install_dir" + echo " export PATH=\"$bin_dir:\$PATH\"" + ;; +esac + +if [[ "$refresh_command" ]]; then + echo "The 'qt' CLI has been added to your PATH. Run this command to apply the changes:" + echo " $refresh_command" + echo "" +fi + +installed_help_text diff --git a/cli/scripts/publish-beta-artifacts-r2.sh b/cli/scripts/publish-beta-artifacts-r2.sh new file mode 100644 index 0000000..0284b04 --- /dev/null +++ b/cli/scripts/publish-beta-artifacts-r2.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +repo_dir="$(cd -- "$script_dir/../.." && pwd)" + +bucket="${QT_R2_BUCKET:-quantiles-cli}" +dist_dir="${QT_DIST_DIR:-$repo_dir/dist}" +commit="${GITHUB_SHA:-}" + +if ! command -v wrangler >/dev/null 2>&1; then + echo "wrangler is required to publish to Cloudflare R2" >&2 + exit 1 +fi + +if [[ -z "$commit" ]]; then + echo "GITHUB_SHA is required to publish beta artifacts" >&2 + exit 1 +fi + +artifacts=() +for file in "$dist_dir"/qt-*.tar.gz "$dist_dir"/qt-*.tar.gz.sha256; do + if [[ -f "$file" ]]; then + artifacts+=("$file") + fi +done + +if [[ ${#artifacts[@]} -eq 0 ]]; then + echo "no CLI release artifacts found in $dist_dir" >&2 + exit 1 +fi + +for object_prefix in "channel/beta/${commit}" "channel/beta/latest"; do + for file in "${artifacts[@]}"; do + wrangler r2 object put "${bucket}/${object_prefix}/$(basename "$file")" --file "$file" --remote --cache-control "no-store" + done +done + +printf '%s\n' "$commit" > "$dist_dir/version.txt" +wrangler r2 object put "${bucket}/channel/beta/latest/version.txt" --file "$dist_dir/version.txt" --remote --cache-control "no-store" diff --git a/cli/scripts/upload-install-script-r2.sh b/cli/scripts/upload-install-script-r2.sh index 70fc0de..508dc84 100644 --- a/cli/scripts/upload-install-script-r2.sh +++ b/cli/scripts/upload-install-script-r2.sh @@ -11,13 +11,10 @@ fi mkdir -p "$dist_dir" install_script="${dist_dir}/install.sh" -object_prefix="releases/latest" cp scripts/install.sh "$install_script" chmod +x "$install_script" wrangler r2 object put "${bucket}/install.sh" --file "$install_script" --remote --cache-control "no-store" -wrangler r2 object put "${bucket}/${object_prefix}/install.sh" --file "$install_script" --remote --cache-control "no-store" echo "published installer to r2://${bucket}/install.sh" -echo "published latest installer to r2://${bucket}/${object_prefix}/install.sh"