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
51 changes: 51 additions & 0 deletions .github/workflows/release-github.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,25 @@ jobs:
echo "Archive: ${ARCHIVE_NAME}"
echo "SHA256: $(cat "${ARCHIVE_NAME}.sha256")"

# Scoop and winget consume the raw Windows binaries rather than a
# tarball, and both need a SHA256 sidecar. Windows runners use bash from
# Git for Windows, which has sha256sum; macOS only has shasum.
- name: Checksum release assets
shell: bash
run: |
cd release
Comment thread
setoelkahfi marked this conversation as resolved.
for asset in *; do
case "${asset}" in *.sha256) continue ;; esac

if command -v sha256sum >/dev/null 2>&1; then
sha256sum "${asset}" | awk '{print $1}' > "${asset}.sha256"
else
shasum -a 256 "${asset}" | awk '{print $1}' > "${asset}.sha256"
fi

echo "${asset}: $(cat "${asset}.sha256")"
done

- name: Upload binary artifact
uses: actions/upload-artifact@v4
with:
Expand Down Expand Up @@ -203,3 +222,35 @@ jobs:
}
})
console.log('Dispatched release-pub.yml for tag ${{ steps.tag.outputs.tag }}')

# These channels publish outside this repo (a Scoop bucket,
# microsoft/winget-pkgs) and read checksums off the release created
# above, so they hang off this job rather than firing on the tag
# directly.
- name: Trigger OS package manager releases
uses: actions/github-script@v7
with:
script: |
const tag = '${{ steps.tag.outputs.tag }}'
const workflows = [
'release-scoop.yml',
'release-winget.yml',
]

for (const workflow_id of workflows) {
try {
await github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id,
ref: tag,
inputs: { tag },
})
console.log(`Dispatched ${workflow_id} for tag ${tag}`)
} catch (error) {
// One packaging channel being unconfigured (a missing secret,
// a bucket repo that does not exist yet) should not take the
// rest of the fan-out down with it.
core.warning(`Failed to dispatch ${workflow_id}: ${error.message}`)
}
}
148 changes: 148 additions & 0 deletions .github/workflows/release-scoop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
name: Scoop Release

# Dispatched by release-github.yml once the release and its checksums exist.
on:
workflow_dispatch:
inputs:
tag:
description: "Release tag (e.g. v0.4.0)"
required: true

concurrency:
# A re-pushed or re-dispatched tag fires this a second time. Queue the
# duplicate behind the original instead of cancelling it: cancelling a run
# mid-publish can leave a channel half-uploaded, whereas a queued duplicate
# just hits the "already published" check and exits clean.
group: ${{ github.workflow }}-${{ github.event.inputs.tag || github.ref_name }}
cancel-in-progress: false

permissions:
contents: read

env:
REPO: ondeinference/onde-cli

jobs:
update-scoop-bucket:
name: Update Scoop bucket
runs-on: ubuntu-latest

steps:
- name: Resolve tag and version
id: release
shell: bash
run: |
TAG="${{ github.event.inputs.tag }}"
VERSION="${TAG#v}"

echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"

- name: Read SHA256 checksums from release
id: sha
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
mkdir -p artifacts

gh release download "${{ steps.release.outputs.tag }}" \
--repo "${{ env.REPO }}" \
--pattern "onde-win-*.exe.sha256" \
--dir artifacts/

AMD64_SHA=$(cat artifacts/onde-win-amd64.exe.sha256)
ARM64_SHA=$(cat artifacts/onde-win-arm64.exe.sha256)
Comment thread
setoelkahfi marked this conversation as resolved.

echo "amd64=${AMD64_SHA}" >> "$GITHUB_OUTPUT"
echo "arm64=${ARM64_SHA}" >> "$GITHUB_OUTPUT"

echo "AMD64 SHA256: ${AMD64_SHA}"
echo "ARM64 SHA256: ${ARM64_SHA}"

- name: Checkout Scoop bucket
uses: actions/checkout@v6
with:
repository: ondeinference/scoop-bucket
token: ${{ secrets.SCOOP_BUCKET_TOKEN }}
path: scoop-bucket

- name: Generate manifest
shell: bash
run: |
VERSION="${{ steps.release.outputs.version }}"
TAG="${{ steps.release.outputs.tag }}"
AMD64_SHA="${{ steps.sha.outputs.amd64 }}"
ARM64_SHA="${{ steps.sha.outputs.arm64 }}"

mkdir -p scoop-bucket/bucket

# The `#/onde.exe` URL fragment is Scoop's rename-on-download
# syntax: the release asset is onde-win-amd64.exe, but the shim has
# to end up as onde.exe for `onde` to work on PATH.
cat > scoop-bucket/bucket/onde.json <<MANIFEST
{
"version": "${VERSION}",
"description": "Onde Inference CLI — fine-tune models and manage your Onde Inference account from the terminal.",
"homepage": "https://ondeinference.com/cli",
"license": "MIT OR Apache-2.0",
"architecture": {
"64bit": {
"url": "https://github.com/${REPO}/releases/download/${TAG}/onde-win-amd64.exe#/onde.exe",
"hash": "${AMD64_SHA}"
},
"arm64": {
"url": "https://github.com/${REPO}/releases/download/${TAG}/onde-win-arm64.exe#/onde.exe",
"hash": "${ARM64_SHA}"
}
},
"bin": "onde.exe",
"checkver": {
"github": "https://github.com/${REPO}"
},
"autoupdate": {
"architecture": {
"64bit": {
"url": "https://github.com/${REPO}/releases/download/v\$version/onde-win-amd64.exe#/onde.exe"
},
"arm64": {
"url": "https://github.com/${REPO}/releases/download/v\$version/onde-win-arm64.exe#/onde.exe"
}
},
"hash": {
"url": "\$url.sha256"
}
}
}
MANIFEST

echo "Generated manifest:"
cat scoop-bucket/bucket/onde.json

- name: Validate manifest JSON
shell: bash
run: |
if ! jq empty scoop-bucket/bucket/onde.json; then
echo "::error::Generated Scoop manifest is not valid JSON" >&2
exit 1
fi

for field in version bin architecture; do
if [ "$(jq -r "has(\"${field}\")" scoop-bucket/bucket/onde.json)" != "true" ]; then
echo "::error::Scoop manifest is missing the '${field}' field" >&2
exit 1
fi
done

- name: Commit and push
shell: bash
run: |
VERSION="${{ steps.release.outputs.version }}"

cd scoop-bucket
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add bucket/onde.json
Comment thread
setoelkahfi marked this conversation as resolved.
git diff --cached --quiet && echo "No changes to commit" && exit 0
git commit -m "Update onde to ${VERSION}"
git push
178 changes: 178 additions & 0 deletions .github/workflows/release-winget.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
name: winget Release

# Dispatched by release-github.yml once the release and its checksums exist.
#
# winget manifests live in microsoft/winget-pkgs, and `wingetcreate update`
# only works on a package that is already there. So this workflow branches:
#
# - package already in winget-pkgs -> `wingetcreate update`, which rewrites
# the URLs and re-hashes the installers for us.
# - package not there yet -> render the manifests in packaging/winget/ and
# `wingetcreate submit` them as a new package.
#
# The second path only runs once, but keeping it here means a first release
# does not need someone to sit at a Windows box running `wingetcreate new`
# interactively.
on:
workflow_dispatch:
inputs:
tag:
description: "Release tag (e.g. v0.4.0)"
required: true

concurrency:
# A re-pushed or re-dispatched tag fires this a second time. Queue the
# duplicate behind the original instead of cancelling it: cancelling a run
# mid-publish can leave a channel half-updated, whereas a queued duplicate
# just hits the "already published" check and exits clean.
group: ${{ github.workflow }}-${{ github.event.inputs.tag || github.ref_name }}
cancel-in-progress: false

permissions:
contents: read

env:
REPO: ondeinference/onde-cli
PACKAGE_IDENTIFIER: OndeInference.onde-cli

jobs:
submit-winget-manifest:
name: Submit winget manifest
# wingetcreate is a Windows-only tool.
runs-on: windows-latest

steps:
- name: Check out repository
uses: actions/checkout@v6

- name: Resolve tag, version and release date
id: release
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
TAG="${{ github.event.inputs.tag }}"
VERSION="${TAG#v}"
# wingetcreate wants ReleaseDate as YYYY-MM-DD.
DATE=$(gh release view "$TAG" --repo "$REPO" --json publishedAt --jq '.publishedAt[0:10]')
Comment thread
setoelkahfi marked this conversation as resolved.

echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "date=${DATE}" >> "$GITHUB_OUTPUT"

- name: Check whether the package is already in winget-pkgs
id: exists
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
# manifests are filed under the lowercased first letter of the
# publisher, e.g. manifests/o/OndeInference/onde-cli.
PUBLISHER="${PACKAGE_IDENTIFIER%%.*}"
NAME="${PACKAGE_IDENTIFIER#*.}"
FIRST=$(echo "${PUBLISHER:0:1}" | tr '[:upper:]' '[:lower:]')
PATH_IN_REPO="manifests/${FIRST}/${PUBLISHER}/${NAME}"

if gh api "repos/microsoft/winget-pkgs/contents/${PATH_IN_REPO}" >/dev/null 2>&1; then
echo "Found ${PACKAGE_IDENTIFIER} at ${PATH_IN_REPO}; submitting an update."
echo "found=true" >> "$GITHUB_OUTPUT"
else
echo "No ${PACKAGE_IDENTIFIER} at ${PATH_IN_REPO}; submitting it as a new package."
echo "found=false" >> "$GITHUB_OUTPUT"
fi

- name: Render manifests for a first submission
if: steps.exists.outputs.found == 'false'
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
TAG="${{ steps.release.outputs.tag }}"
VERSION="${{ steps.release.outputs.version }}"

mkdir -p artifacts manifests

gh release download "$TAG" \
--repo "$REPO" \
--pattern "onde-win-*.exe.sha256" \
--dir artifacts/

# The schema wants 64 hex characters; the community validation
# pipeline writes them uppercase, so match that.
SHA_AMD64=$(tr -d '[:space:]' < artifacts/onde-win-amd64.exe.sha256 | tr '[:lower:]' '[:upper:]')
Comment thread
setoelkahfi marked this conversation as resolved.
SHA_ARM64=$(tr -d '[:space:]' < artifacts/onde-win-arm64.exe.sha256 | tr '[:lower:]' '[:upper:]')

RELEASE_DATE="${{ steps.release.outputs.date }}"

for template in packaging/winget/*.yaml.in; do
out="manifests/$(basename "$template" .in)"
sed \
-e "s|@VERSION@|${VERSION}|g" \
-e "s|@TAG@|${TAG}|g" \
-e "s|@REPO@|${REPO}|g" \
-e "s|@SHA_AMD64@|${SHA_AMD64}|g" \
-e "s|@SHA_ARM64@|${SHA_ARM64}|g" \
-e "s|@RELEASE_DATE@|${RELEASE_DATE}|g" \
"$template" > "$out"

echo "--- ${out} ---"
cat "$out"
done

- name: Download wingetcreate
shell: pwsh
run: Invoke-WebRequest -Uri "https://aka.ms/wingetcreate/latest" -OutFile wingetcreate.exe

- name: Submit new package
if: steps.exists.outputs.found == 'false'
shell: pwsh
env:
# wingetcreate reads the token from this variable. Passing it as
# --token instead makes the tool warn that the token can end up in
# logs, which on a shared runner is not a warning worth ignoring.
WINGET_CREATE_GITHUB_TOKEN: ${{ secrets.WINGET_TOKEN }}
run: |
if (-not $env:WINGET_CREATE_GITHUB_TOKEN) {
Write-Error "WINGET_TOKEN is not set. It needs a PAT with public_repo scope so wingetcreate can fork microsoft/winget-pkgs and open the manifest PR."
}

$version = "${{ steps.release.outputs.version }}"

# --no-open because the runner has no browser to open the PR in.
.\wingetcreate.exe submit manifests `
--prtitle "New package: $env:PACKAGE_IDENTIFIER version $version" `
--no-open

if ($LASTEXITCODE -ne 0) {
Write-Error "wingetcreate submit failed with exit code $LASTEXITCODE"
}

- name: Submit manifest update
if: steps.exists.outputs.found == 'true'
shell: pwsh
env:
WINGET_CREATE_GITHUB_TOKEN: ${{ secrets.WINGET_TOKEN }}
run: |
if (-not $env:WINGET_CREATE_GITHUB_TOKEN) {
Write-Error "WINGET_TOKEN is not set. It needs a PAT with public_repo scope so wingetcreate can fork microsoft/winget-pkgs and open the manifest PR."
}

$tag = "${{ steps.release.outputs.tag }}"
$version = "${{ steps.release.outputs.version }}"
$base = "https://github.com/${env:REPO}/releases/download/$tag"

# The trailing |x64 and |arm64 tell wingetcreate which installer
# entry each URL replaces. Without them it guesses from the file
# name, and "onde-win-amd64.exe" is not a spelling it recognises.
.\wingetcreate.exe update $env:PACKAGE_IDENTIFIER `
Comment thread
setoelkahfi marked this conversation as resolved.
--version $version `
--urls "$base/onde-win-amd64.exe|x64" "$base/onde-win-arm64.exe|arm64" `
--release-notes-url "https://github.com/${env:REPO}/releases/tag/$tag" `
--release-date "${{ steps.release.outputs.date }}" `
--prtitle "Update $env:PACKAGE_IDENTIFIER to version $version" `
--submit `
--no-open

if ($LASTEXITCODE -ne 0) {
Write-Error "wingetcreate failed with exit code $LASTEXITCODE"
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@
/pub/onde_cli/pubspec.lock

.env

# Playwright MCP session artifacts
.playwright-mcp/
Loading
Loading