Skip to content

Publish SDKs to OperatorStack Registry #10

Publish SDKs to OperatorStack Registry

Publish SDKs to OperatorStack Registry #10

name: Publish SDKs to OperatorStack Registry
on:
pull_request:
paths: [".github/workflows/private-registry.yml"]
push:
tags: ["v*"]
workflow_run:
workflows: ["Release Yield"]
types: [completed]
workflow_dispatch:
inputs:
version:
description: "Existing release version without the leading v"
required: true
default: "0.1.8"
type: string
permissions:
contents: read
id-token: write
concurrency:
group: publish-yield-sdks
cancel-in-progress: false
jobs:
validate:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: stable
- name: Verify the current release source
run: go test ./...
- name: Check the one-package release contract
run: |
set -euo pipefail
grep -F 'CGO_ENABLED=0' .github/workflows/private-registry.yml
grep -F 'packaging/assemble.mjs' .github/workflows/private-registry.yml
grep -F 'Install and test all language packages' .github/workflows/private-registry.yml
publish:
if: github.event_name != 'pull_request' && (github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success')
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event_name == 'workflow_dispatch' && format('v{0}', inputs.version) || github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.ref }}
- uses: actions/setup-go@v5
with:
go-version: stable
- uses: actions/setup-node@v4
with:
node-version: "24"
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- uses: dtolnay/rust-toolchain@stable
- id: version
name: Resolve published version
env:
REQUESTED_VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
if [[ "$GITHUB_EVENT_NAME" == "workflow_run" ]]; then
tag="$(git tag --points-at HEAD --list 'v[0-9]*' --sort=-v:refname | head -n 1)"
test -n "$tag"
version="${tag#v}"
elif [[ "$GITHUB_REF" == refs/tags/v* ]]; then
version="${GITHUB_REF_NAME#v}"
else
version="$REQUESTED_VERSION"
fi
[[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]
git rev-parse --verify "refs/tags/v${version}"
test "$(git rev-list -n 1 "v${version}")" = "$(git rev-parse HEAD)"
echo "version=$version" >> "$GITHUB_OUTPUT"
- id: auth
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ vars.WIF_PROVIDER }}
service_account: ${{ vars.DEPLOYER_SA_EMAIL }}
- uses: google-github-actions/setup-gcloud@v2
with:
install_components: package-go-module
- name: Verify Go SDK and runtime
run: go test ./...
- name: Build immutable runtimes and language packages
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
set -euo pipefail
mkdir -p dist/bin
while read -r goos goarch; do
suffix=""
if [[ "$goos" == "windows" ]]; then suffix=".exe"; fi
CGO_ENABLED=0 GOOS="$goos" GOARCH="$goarch" go build \
-trimpath -ldflags "-s -w -X main.version=${VERSION}" \
-o "dist/bin/yskill-${goos}-${goarch}${suffix}" ./cmd/yskill
done <<'TARGETS'
darwin amd64
darwin arm64
linux amd64
linux arm64
windows amd64
windows arm64
TARGETS
node packaging/assemble.mjs --version "$VERSION" --binaries dist/bin --output dist/packages
- name: Keep runtime checksums with the release run
uses: actions/upload-artifact@v4
with:
name: yskill-${{ steps.version.outputs.version }}-runtimes
path: |
dist/bin/
dist/packages/SHA256SUMS.json
if-no-files-found: error
- name: Publish Go module
run: |
set -euo pipefail
version="${{ steps.version.outputs.version }}"
if gcloud artifacts versions describe "v${version}" \
--project="${{ vars.AR_PROJECT }}" --location="${{ vars.AR_LOCATION }}" \
--repository="${{ vars.AR_GO_REPO }}" --package=github.com/operatorstack/yield \
>/dev/null 2>&1; then
echo "Go module already published."
else
source_dir="$(mktemp -d)"
git archive "v${version}" | tar -x -C "$source_dir"
gcloud artifacts go upload \
--project="${{ vars.AR_PROJECT }}" \
--location="${{ vars.AR_LOCATION }}" \
--repository="${{ vars.AR_GO_REPO }}" \
--module-path=github.com/operatorstack/yield \
--version="v${version}" \
--source="$source_dir"
fi
- name: Configure npm publishing
run: |
gcloud artifacts print-settings npm \
--project="${{ vars.AR_PROJECT }}" --location="${{ vars.AR_LOCATION }}" \
--repository="${{ vars.AR_NPM_REPO }}" --scope=@operatorstack > "$RUNNER_TEMP/yield.npmrc"
npx -y google-artifactregistry-auth "$RUNNER_TEMP/yield.npmrc"
- name: Publish TypeScript package
env:
VERSION: ${{ steps.version.outputs.version }}
NPM_CONFIG_USERCONFIG: ${{ runner.temp }}/yield.npmrc
run: |
set -euo pipefail
for directory in dist/packages/npm/darwin-amd64 dist/packages/npm/darwin-arm64 dist/packages/npm/linux-amd64 dist/packages/npm/linux-arm64 dist/packages/npm/windows-amd64 dist/packages/npm/windows-arm64; do
package="$(node -p "require('./${directory}/package.json').name")"
if ! gcloud artifacts versions describe "$VERSION" \
--project="${{ vars.AR_PROJECT }}" --location="${{ vars.AR_LOCATION }}" \
--repository="${{ vars.AR_NPM_REPO }}" --package="$package" >/dev/null 2>&1; then
npm publish "$directory"
fi
done
if ! gcloud artifacts versions describe "$VERSION" \
--project="${{ vars.AR_PROJECT }}" --location="${{ vars.AR_LOCATION }}" \
--repository="${{ vars.AR_NPM_REPO }}" --package=@operatorstack/yield >/dev/null 2>&1; then
npm publish dist/packages/npm/yield
fi
- name: Build and publish Python wheels
run: |
set -euo pipefail
python -m pip install --quiet build twine keyrings.google-artifactregistry-auth
mkdir -p dist/python-wheels
for directory in dist/packages/python/*; do
python -m build --wheel --outdir "$GITHUB_WORKSPACE/dist/python-wheels" "$directory"
done
twine upload --skip-existing \
--repository-url "https://${{ vars.AR_LOCATION }}-python.pkg.dev/${{ vars.AR_PROJECT }}/${{ vars.AR_PYTHON_REPO }}/" \
dist/python-wheels/*
- name: Publish Rust runtime crates
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
set -euo pipefail
mkdir -p dist/rust-runtime-publish
for directory in dist/packages/rust/runtime/*; do
name="$(sed -n 's/^name = "\(.*\)"/\1/p' "$directory/Cargo.toml")"
cargo package --manifest-path "$directory/Cargo.toml" --allow-dirty --no-verify
crate="$directory/target/package/${name}-${VERSION}.crate"
checksum="$(sha256sum "$crate" | cut -d' ' -f1)"
curl -fsSL "https://get.operatorstack.systems/cargo/index/$(printf '%s' "$name" | cut -c1-2)/$(printf '%s' "$name" | cut -c3-4)/${name}" \
-o "dist/rust-runtime-publish/${name}-index.json" || true
node packaging/cargo-index.mjs --name "$name" --version "$VERSION" --checksum "$checksum" \
--output "dist/rust-runtime-publish/${name}-index.json"
cp "$crate" dist/rust-runtime-publish/
done
gcloud artifacts generic upload \
--project="${{ vars.AR_PROJECT }}" --location="${{ vars.AR_LOCATION }}" \
--repository="${{ vars.AR_GENERIC_REPO }}" --package=yield-rust --version="$VERSION" \
--source-directory=dist/rust-runtime-publish --skip-existing
- name: Publish Rust public crate
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
set -euo pipefail
mkdir -p "$HOME/.cargo" dist/rust-public-publish
printf '[registries.operatorstack]\nindex = "sparse+https://get.operatorstack.systems/cargo/index/"\n' > "$HOME/.cargo/config.toml"
directory=dist/packages/rust/yieldskill
rm -f "$directory/Cargo.lock"
cargo generate-lockfile --manifest-path "$directory/Cargo.toml"
cargo package --manifest-path "$directory/Cargo.toml" --registry operatorstack --allow-dirty
crate="$directory/target/package/yieldskill-${VERSION}.crate"
checksum="$(sha256sum "$crate" | cut -d' ' -f1)"
curl -fsSL https://get.operatorstack.systems/cargo/index/yi/el/yieldskill \
-o dist/rust-public-publish/yieldskill-index.json || true
node packaging/cargo-index.mjs --name yieldskill --version "$VERSION" --checksum "$checksum" \
--output dist/rust-public-publish/yieldskill-index.json
cp "$crate" dist/rust-public-publish/
gcloud artifacts generic upload \
--project="${{ vars.AR_PROJECT }}" --location="${{ vars.AR_LOCATION }}" \
--repository="${{ vars.AR_GENERIC_REPO }}" --package=yield-rust --version="$VERSION" \
--source-directory=dist/rust-public-publish --skip-existing
install-journeys:
name: Install journeys (${{ matrix.target }})
needs: publish
strategy:
fail-fast: false
matrix:
include:
- target: linux-amd64
runner: ubuntu-24.04
- target: linux-arm64
runner: ubuntu-24.04-arm
- target: darwin-amd64
runner: macos-15-intel
- target: darwin-arm64
runner: macos-15
- target: windows-amd64
runner: windows-2025
- target: windows-arm64
runner: windows-11-arm
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/setup-go@v5
with:
go-version: stable
- uses: actions/setup-node@v4
with:
node-version: "24"
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- uses: dtolnay/rust-toolchain@stable
- name: Install and test all language packages
shell: bash
env:
VERSION: ${{ needs.publish.outputs.version }}
GOPROXY: https://get.operatorstack.systems/go,direct
run: |
set -euo pipefail
workspace="$RUNNER_TEMP/yield-install"
mkdir -p "$workspace/typescript" "$workspace/python" "$workspace/go" "$workspace/rust"
cd "$workspace/typescript"
npm init -y >/dev/null
npm install "@operatorstack/yield@${VERSION}" --registry=https://get.operatorstack.systems/npm/
npm exec -- yskill --version | grep -F "yskill ${VERSION}"
npm exec -- yskill init skill --language typescript
npm exec -- yskill test skill
cd "$workspace/python"
python -m venv .venv
if [[ "$RUNNER_OS" == "Windows" ]]; then python_bin=.venv/Scripts/python; else python_bin=.venv/bin/python; fi
"$python_bin" -m pip install "yieldskill==${VERSION}" --index-url https://get.operatorstack.systems/pip/simple/
"$python_bin" -m yieldskill --version | grep -F "yskill ${VERSION}"
"$python_bin" -m yieldskill init skill --language python
"$python_bin" -m yieldskill test skill
cd "$workspace/go"
go install "github.com/operatorstack/yield/cmd/yskill@v${VERSION}"
go_cli="$(go env GOPATH)/bin/yskill"
if [[ "$RUNNER_OS" == "Windows" ]]; then go_cli="${go_cli}.exe"; fi
"$go_cli" --version | grep -F "yskill ${VERSION}"
"$go_cli" init skill --language go
"$go_cli" test skill
cd "$workspace/rust"
cargo install "yieldskill@${VERSION}" --index sparse+https://get.operatorstack.systems/cargo/index/ --locked --root .cargo-root
if [[ "$RUNNER_OS" == "Windows" ]]; then rust_cli=.cargo-root/bin/yskill.exe; else rust_cli=.cargo-root/bin/yskill; fi
"$rust_cli" --version | grep -F "yskill ${VERSION}"
"$rust_cli" init skill --language rust
"$rust_cli" test skill