The build_npm_package.py script only accepts specific package choices: 'llmx', 'llmx-responses-api-proxy', 'llmx-sdk'. The scoped package name '@valknar/llmx' is not a valid choice. The package name 'llmx' is the correct identifier used internally by the build system, which then produces the scoped npm package.
543 lines
19 KiB
YAML
543 lines
19 KiB
YAML
# Release workflow for llmx-rs.
|
|
# To release, follow a workflow like:
|
|
# ```
|
|
# git tag -a rust-v0.1.0 -m "Release 0.1.0"
|
|
# git push origin rust-v0.1.0
|
|
# ```
|
|
|
|
name: rust-release
|
|
on:
|
|
push:
|
|
tags:
|
|
- "rust-v*.*.*"
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
tag-check:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v5
|
|
|
|
- name: Validate tag matches Cargo.toml version
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
echo "::group::Tag validation"
|
|
|
|
# 1. Must be a tag and match the regex
|
|
[[ "${GITHUB_REF_TYPE}" == "tag" ]] \
|
|
|| { echo "❌ Not a tag push"; exit 1; }
|
|
[[ "${GITHUB_REF_NAME}" =~ ^rust-v[0-9]+\.[0-9]+\.[0-9]+(-(alpha|beta)(\.[0-9]+)?)?$ ]] \
|
|
|| { echo "❌ Tag '${GITHUB_REF_NAME}' doesn't match expected format"; exit 1; }
|
|
|
|
# 2. Extract versions
|
|
tag_ver="${GITHUB_REF_NAME#rust-v}"
|
|
cargo_ver="$(grep -m1 '^version' llmx-rs/Cargo.toml \
|
|
| sed -E 's/version *= *"([^"]+)".*/\1/')"
|
|
|
|
# 3. Compare
|
|
[[ "${tag_ver}" == "${cargo_ver}" ]] \
|
|
|| { echo "❌ Tag ${tag_ver} ≠ Cargo.toml ${cargo_ver}"; exit 1; }
|
|
|
|
echo "✅ Tag and Cargo.toml agree (${tag_ver})"
|
|
echo "::endgroup::"
|
|
|
|
build:
|
|
needs: tag-check
|
|
name: Build - ${{ matrix.runner }} - ${{ matrix.target }}
|
|
runs-on: ${{ matrix.runner }}
|
|
timeout-minutes: 30
|
|
defaults:
|
|
run:
|
|
working-directory: llmx-rs
|
|
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- runner: macos-15-xlarge
|
|
target: aarch64-apple-darwin
|
|
- runner: macos-15-xlarge
|
|
target: x86_64-apple-darwin
|
|
- runner: ubuntu-24.04
|
|
target: x86_64-unknown-linux-musl
|
|
- runner: ubuntu-24.04
|
|
target: x86_64-unknown-linux-gnu
|
|
- runner: ubuntu-24.04-arm
|
|
target: aarch64-unknown-linux-musl
|
|
- runner: ubuntu-24.04-arm
|
|
target: aarch64-unknown-linux-gnu
|
|
- runner: windows-latest
|
|
target: x86_64-pc-windows-msvc
|
|
- runner: windows-11-arm
|
|
target: aarch64-pc-windows-msvc
|
|
|
|
steps:
|
|
- uses: actions/checkout@v5
|
|
- uses: dtolnay/rust-toolchain@1.90
|
|
with:
|
|
targets: ${{ matrix.target }}
|
|
|
|
- uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
~/.cargo/bin/
|
|
~/.cargo/registry/index/
|
|
~/.cargo/registry/cache/
|
|
~/.cargo/git/db/
|
|
${{ github.workspace }}/llmx-rs/target/
|
|
key: cargo-${{ matrix.runner }}-${{ matrix.target }}-release-${{ hashFiles('**/Cargo.lock') }}
|
|
|
|
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
|
|
name: Install musl build tools
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y musl-tools pkg-config
|
|
|
|
- name: Cargo build
|
|
run: cargo build --target ${{ matrix.target }} --release --bin llmx --bin llmx-responses-api-proxy
|
|
|
|
- if: ${{ matrix.runner == 'macos-15-xlarge' }}
|
|
name: Configure Apple code signing
|
|
shell: bash
|
|
env:
|
|
KEYCHAIN_PASSWORD: actions
|
|
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE_P12 }}
|
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
if [[ -z "${APPLE_CERTIFICATE:-}" ]]; then
|
|
echo "⚠️ APPLE_CERTIFICATE not set - skipping macOS code signing"
|
|
echo "SKIP_MACOS_SIGNING=true" >> "$GITHUB_ENV"
|
|
exit 0
|
|
fi
|
|
|
|
if [[ -z "${APPLE_CERTIFICATE_PASSWORD:-}" ]]; then
|
|
echo "⚠️ APPLE_CERTIFICATE_PASSWORD not set - skipping macOS code signing"
|
|
echo "SKIP_MACOS_SIGNING=true" >> "$GITHUB_ENV"
|
|
exit 0
|
|
fi
|
|
|
|
cert_path="${RUNNER_TEMP}/apple_signing_certificate.p12"
|
|
echo "$APPLE_CERTIFICATE" | base64 -d > "$cert_path"
|
|
|
|
keychain_path="${RUNNER_TEMP}/llmx-signing.keychain-db"
|
|
security create-keychain -p "$KEYCHAIN_PASSWORD" "$keychain_path"
|
|
security set-keychain-settings -lut 21600 "$keychain_path"
|
|
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$keychain_path"
|
|
|
|
keychain_args=()
|
|
cleanup_keychain() {
|
|
if ((${#keychain_args[@]} > 0)); then
|
|
security list-keychains -s "${keychain_args[@]}" || true
|
|
security default-keychain -s "${keychain_args[0]}" || true
|
|
else
|
|
security list-keychains -s || true
|
|
fi
|
|
if [[ -f "$keychain_path" ]]; then
|
|
security delete-keychain "$keychain_path" || true
|
|
fi
|
|
}
|
|
|
|
while IFS= read -r keychain; do
|
|
[[ -n "$keychain" ]] && keychain_args+=("$keychain")
|
|
done < <(security list-keychains | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/"//g')
|
|
|
|
if ((${#keychain_args[@]} > 0)); then
|
|
security list-keychains -s "$keychain_path" "${keychain_args[@]}"
|
|
else
|
|
security list-keychains -s "$keychain_path"
|
|
fi
|
|
|
|
security default-keychain -s "$keychain_path"
|
|
security import "$cert_path" -k "$keychain_path" -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security
|
|
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$keychain_path" > /dev/null
|
|
|
|
codesign_hashes=()
|
|
while IFS= read -r hash; do
|
|
[[ -n "$hash" ]] && codesign_hashes+=("$hash")
|
|
done < <(security find-identity -v -p codesigning "$keychain_path" \
|
|
| sed -n 's/.*\([0-9A-F]\{40\}\).*/\1/p' \
|
|
| sort -u)
|
|
|
|
if ((${#codesign_hashes[@]} == 0)); then
|
|
echo "No signing identities found in $keychain_path"
|
|
cleanup_keychain
|
|
rm -f "$cert_path"
|
|
exit 1
|
|
fi
|
|
|
|
if ((${#codesign_hashes[@]} > 1)); then
|
|
echo "Multiple signing identities found in $keychain_path:"
|
|
printf ' %s\n' "${codesign_hashes[@]}"
|
|
cleanup_keychain
|
|
rm -f "$cert_path"
|
|
exit 1
|
|
fi
|
|
|
|
APPLE_CODESIGN_IDENTITY="${codesign_hashes[0]}"
|
|
|
|
rm -f "$cert_path"
|
|
|
|
echo "APPLE_CODESIGN_IDENTITY=$APPLE_CODESIGN_IDENTITY" >> "$GITHUB_ENV"
|
|
echo "APPLE_CODESIGN_KEYCHAIN=$keychain_path" >> "$GITHUB_ENV"
|
|
echo "::add-mask::$APPLE_CODESIGN_IDENTITY"
|
|
|
|
- if: ${{ matrix.runner == 'macos-15-xlarge' && env.SKIP_MACOS_SIGNING != 'true' }}
|
|
name: Sign macOS binaries
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
if [[ -z "${APPLE_CODESIGN_IDENTITY:-}" ]]; then
|
|
echo "⚠️ APPLE_CODESIGN_IDENTITY not set - skipping macOS signing"
|
|
exit 0
|
|
fi
|
|
|
|
keychain_args=()
|
|
if [[ -n "${APPLE_CODESIGN_KEYCHAIN:-}" && -f "${APPLE_CODESIGN_KEYCHAIN}" ]]; then
|
|
keychain_args+=(--keychain "${APPLE_CODESIGN_KEYCHAIN}")
|
|
fi
|
|
|
|
for binary in llmx llmx-responses-api-proxy; do
|
|
path="target/${{ matrix.target }}/release/${binary}"
|
|
codesign --force --options runtime --timestamp --sign "$APPLE_CODESIGN_IDENTITY" "${keychain_args[@]}" "$path"
|
|
done
|
|
|
|
- if: ${{ matrix.runner == 'macos-15-xlarge' && env.SKIP_MACOS_SIGNING != 'true' }}
|
|
name: Notarize macOS binaries
|
|
shell: bash
|
|
env:
|
|
APPLE_NOTARIZATION_KEY_P8: ${{ secrets.APPLE_NOTARIZATION_KEY_P8 }}
|
|
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
|
|
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
for var in APPLE_NOTARIZATION_KEY_P8 APPLE_NOTARIZATION_KEY_ID APPLE_NOTARIZATION_ISSUER_ID; do
|
|
if [[ -z "${!var:-}" ]]; then
|
|
echo "⚠️ $var not set - skipping macOS notarization"
|
|
exit 0
|
|
fi
|
|
done
|
|
|
|
notary_key_path="${RUNNER_TEMP}/notarytool.key.p8"
|
|
echo "$APPLE_NOTARIZATION_KEY_P8" | base64 -d > "$notary_key_path"
|
|
cleanup_notary() {
|
|
rm -f "$notary_key_path"
|
|
}
|
|
trap cleanup_notary EXIT
|
|
|
|
notarize_binary() {
|
|
local binary="$1"
|
|
local source_path="target/${{ matrix.target }}/release/${binary}"
|
|
local archive_path="${RUNNER_TEMP}/${binary}.zip"
|
|
|
|
if [[ ! -f "$source_path" ]]; then
|
|
echo "Binary $source_path not found"
|
|
exit 1
|
|
fi
|
|
|
|
rm -f "$archive_path"
|
|
ditto -c -k --keepParent "$source_path" "$archive_path"
|
|
|
|
submission_json=$(xcrun notarytool submit "$archive_path" \
|
|
--key "$notary_key_path" \
|
|
--key-id "$APPLE_NOTARIZATION_KEY_ID" \
|
|
--issuer "$APPLE_NOTARIZATION_ISSUER_ID" \
|
|
--output-format json \
|
|
--wait)
|
|
|
|
status=$(printf '%s\n' "$submission_json" | jq -r '.status // "Unknown"')
|
|
submission_id=$(printf '%s\n' "$submission_json" | jq -r '.id // ""')
|
|
|
|
if [[ -z "$submission_id" ]]; then
|
|
echo "Failed to retrieve submission ID for $binary"
|
|
exit 1
|
|
fi
|
|
|
|
echo "::notice title=Notarization::$binary submission ${submission_id} completed with status ${status}"
|
|
|
|
if [[ "$status" != "Accepted" ]]; then
|
|
echo "Notarization failed for ${binary} (submission ${submission_id}, status ${status})"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
notarize_binary "llmx"
|
|
notarize_binary "llmx-responses-api-proxy"
|
|
|
|
- name: Stage artifacts
|
|
shell: bash
|
|
run: |
|
|
dest="dist/${{ matrix.target }}"
|
|
mkdir -p "$dest"
|
|
|
|
if [[ "${{ matrix.runner }}" == windows* ]]; then
|
|
cp target/${{ matrix.target }}/release/llmx.exe "$dest/llmx-${{ matrix.target }}.exe"
|
|
cp target/${{ matrix.target }}/release/llmx-responses-api-proxy.exe "$dest/llmx-responses-api-proxy-${{ matrix.target }}.exe"
|
|
else
|
|
cp target/${{ matrix.target }}/release/llmx "$dest/llmx-${{ matrix.target }}"
|
|
cp target/${{ matrix.target }}/release/llmx-responses-api-proxy "$dest/llmx-responses-api-proxy-${{ matrix.target }}"
|
|
fi
|
|
|
|
- if: ${{ matrix.runner == 'windows-11-arm' }}
|
|
name: Install zstd
|
|
shell: powershell
|
|
run: choco install -y zstandard
|
|
|
|
- name: Compress artifacts
|
|
shell: bash
|
|
run: |
|
|
# Path that contains the uncompressed binaries for the current
|
|
# ${{ matrix.target }}
|
|
dest="dist/${{ matrix.target }}"
|
|
|
|
# We want to ship the raw Windows executables in the GitHub Release
|
|
# in addition to the compressed archives. Keep the originals for
|
|
# Windows targets; remove them elsewhere to limit the number of
|
|
# artifacts that end up in the GitHub Release.
|
|
keep_originals=false
|
|
if [[ "${{ matrix.runner }}" == windows* ]]; then
|
|
keep_originals=true
|
|
fi
|
|
|
|
# For compatibility with environments that lack the `zstd` tool we
|
|
# additionally create a `.tar.gz` for all platforms and `.zip` for
|
|
# Windows alongside every single binary that we publish. The end result is:
|
|
# llmx-<target>.zst (existing)
|
|
# llmx-<target>.tar.gz (new)
|
|
# llmx-<target>.zip (only for Windows)
|
|
|
|
# 1. Produce a .tar.gz for every file in the directory *before* we
|
|
# run `zstd --rm`, because that flag deletes the original files.
|
|
for f in "$dest"/*; do
|
|
base="$(basename "$f")"
|
|
# Skip files that are already archives (shouldn't happen, but be
|
|
# safe).
|
|
if [[ "$base" == *.tar.gz || "$base" == *.zip ]]; then
|
|
continue
|
|
fi
|
|
|
|
# Create per-binary tar.gz
|
|
tar -C "$dest" -czf "$dest/${base}.tar.gz" "$base"
|
|
|
|
# Create zip archive for Windows binaries
|
|
# Must run from inside the dest dir so 7z won't
|
|
# embed the directory path inside the zip.
|
|
if [[ "${{ matrix.runner }}" == windows* ]]; then
|
|
(cd "$dest" && 7z a "${base}.zip" "$base")
|
|
fi
|
|
|
|
# Also create .zst (existing behaviour) *and* remove the original
|
|
# uncompressed binary to keep the directory small.
|
|
zstd_args=(-T0 -19)
|
|
if [[ "${keep_originals}" == false ]]; then
|
|
zstd_args+=(--rm)
|
|
fi
|
|
zstd "${zstd_args[@]}" "$dest/$base"
|
|
done
|
|
|
|
- name: Remove signing keychain
|
|
if: ${{ always() && matrix.runner == 'macos-15-xlarge' && env.SKIP_MACOS_SIGNING != 'true' }}
|
|
shell: bash
|
|
env:
|
|
APPLE_CODESIGN_KEYCHAIN: ${{ env.APPLE_CODESIGN_KEYCHAIN }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [[ -n "${APPLE_CODESIGN_KEYCHAIN:-}" ]]; then
|
|
keychain_args=()
|
|
while IFS= read -r keychain; do
|
|
[[ "$keychain" == "$APPLE_CODESIGN_KEYCHAIN" ]] && continue
|
|
[[ -n "$keychain" ]] && keychain_args+=("$keychain")
|
|
done < <(security list-keychains | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/"//g')
|
|
if ((${#keychain_args[@]} > 0)); then
|
|
security list-keychains -s "${keychain_args[@]}"
|
|
security default-keychain -s "${keychain_args[0]}"
|
|
fi
|
|
|
|
if [[ -f "$APPLE_CODESIGN_KEYCHAIN" ]]; then
|
|
security delete-keychain "$APPLE_CODESIGN_KEYCHAIN"
|
|
fi
|
|
fi
|
|
|
|
- uses: actions/upload-artifact@v5
|
|
with:
|
|
name: ${{ matrix.target }}
|
|
# Upload the per-binary .zst files as well as the new .tar.gz
|
|
# equivalents we generated in the previous step.
|
|
path: |
|
|
llmx-rs/dist/${{ matrix.target }}/*
|
|
|
|
release:
|
|
needs: build
|
|
name: release
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
actions: read
|
|
outputs:
|
|
version: ${{ steps.release_name.outputs.name }}
|
|
tag: ${{ github.ref_name }}
|
|
should_publish_npm: ${{ steps.npm_publish_settings.outputs.should_publish }}
|
|
npm_tag: ${{ steps.npm_publish_settings.outputs.npm_tag }}
|
|
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v5
|
|
|
|
- uses: actions/download-artifact@v4
|
|
with:
|
|
path: dist
|
|
|
|
- name: List
|
|
run: ls -R dist/
|
|
|
|
- name: Define release name
|
|
id: release_name
|
|
run: |
|
|
# Extract the version from the tag name, which is in the format
|
|
# "rust-v0.1.0".
|
|
version="${GITHUB_REF_NAME#rust-v}"
|
|
echo "name=${version}" >> $GITHUB_OUTPUT
|
|
|
|
- name: Determine npm publish settings
|
|
id: npm_publish_settings
|
|
env:
|
|
VERSION: ${{ steps.release_name.outputs.name }}
|
|
run: |
|
|
set -euo pipefail
|
|
version="${VERSION}"
|
|
|
|
if [[ "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
echo "should_publish=true" >> "$GITHUB_OUTPUT"
|
|
echo "npm_tag=" >> "$GITHUB_OUTPUT"
|
|
elif [[ "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+-alpha\.[0-9]+$ ]]; then
|
|
echo "should_publish=true" >> "$GITHUB_OUTPUT"
|
|
echo "npm_tag=alpha" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "should_publish=false" >> "$GITHUB_OUTPUT"
|
|
echo "npm_tag=" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Setup pnpm
|
|
uses: pnpm/action-setup@v4
|
|
with:
|
|
run_install: false
|
|
|
|
- name: Setup Node.js for npm packaging
|
|
uses: actions/setup-node@v5
|
|
with:
|
|
node-version: 22
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
# stage_npm_packages.py requires DotSlash when staging releases.
|
|
- uses: facebook/install-dotslash@v2
|
|
- name: Stage npm packages
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
./scripts/stage_npm_packages.py \
|
|
--release-version "${{ steps.release_name.outputs.name }}" \
|
|
--package llmx
|
|
|
|
- name: Create GitHub Release
|
|
uses: softprops/action-gh-release@v2
|
|
with:
|
|
name: ${{ steps.release_name.outputs.name }}
|
|
tag_name: ${{ github.ref_name }}
|
|
files: dist/**
|
|
# Mark as prerelease only when the version has a suffix after x.y.z
|
|
# (e.g. -alpha, -beta). Otherwise publish a normal release.
|
|
prerelease: ${{ contains(steps.release_name.outputs.name, '-') }}
|
|
|
|
- uses: facebook/dotslash-publish-release@v2
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
with:
|
|
tag: ${{ github.ref_name }}
|
|
config: .github/dotslash-config.json
|
|
|
|
# Publish to npm using OIDC authentication.
|
|
# July 31, 2025: https://github.blog/changelog/2025-07-31-npm-trusted-publishing-with-oidc-is-generally-available/
|
|
# npm docs: https://docs.npmjs.com/trusted-publishers
|
|
publish-npm:
|
|
# Publish to npm for stable releases and alpha pre-releases with numeric suffixes.
|
|
if: ${{ needs.release.outputs.should_publish_npm == 'true' }}
|
|
name: publish-npm
|
|
needs: release
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
id-token: write # Required for OIDC
|
|
contents: read
|
|
|
|
steps:
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v5
|
|
with:
|
|
node-version: 22
|
|
registry-url: "https://registry.npmjs.org"
|
|
scope: "@valknar"
|
|
|
|
# Trusted publishing requires npm CLI version 11.5.1 or later.
|
|
- name: Update npm
|
|
run: npm install -g npm@latest
|
|
|
|
- name: Download npm tarballs from release
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
set -euo pipefail
|
|
version="${{ needs.release.outputs.version }}"
|
|
tag="${{ needs.release.outputs.tag }}"
|
|
mkdir -p dist/npm
|
|
gh release download "$tag" \
|
|
--repo "${GITHUB_REPOSITORY}" \
|
|
--pattern "valknar-llmx-npm-${version}.tgz" \
|
|
--dir dist/npm
|
|
|
|
# No NODE_AUTH_TOKEN needed because we use OIDC.
|
|
- name: Publish to npm
|
|
env:
|
|
VERSION: ${{ needs.release.outputs.version }}
|
|
NPM_TAG: ${{ needs.release.outputs.npm_tag }}
|
|
run: |
|
|
set -euo pipefail
|
|
tag_args=()
|
|
if [[ -n "${NPM_TAG}" ]]; then
|
|
tag_args+=(--tag "${NPM_TAG}")
|
|
fi
|
|
|
|
tarballs=(
|
|
"valknar-llmx-npm-${VERSION}.tgz"
|
|
)
|
|
|
|
for tarball in "${tarballs[@]}"; do
|
|
npm publish "${GITHUB_WORKSPACE}/dist/npm/${tarball}" "${tag_args[@]}"
|
|
done
|
|
|
|
update-branch:
|
|
name: Update latest-alpha-cli branch
|
|
permissions:
|
|
contents: write
|
|
needs: release
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Update latest-alpha-cli branch
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
set -euo pipefail
|
|
gh api \
|
|
repos/${GITHUB_REPOSITORY}/git/refs/heads/latest-alpha-cli \
|
|
-X PATCH \
|
|
-f sha="${GITHUB_SHA}" \
|
|
-F force=true
|