Skip to content

refactor: derive lifecycle labels with strum #1421

refactor: derive lifecycle labels with strum

refactor: derive lifecycle labels with strum #1421

Workflow file for this run

# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
name: Build pull request
on:
push:
branches:
- 'pull-request/**'
- 'main'
- 'release/**'
tags:
- '*'
# This allows a subsequently queued workflow run to interrupt previous runs
concurrency:
group: '${{ github.workflow }} @ ${{ github.event_name }} @ ${{ github.head_ref || github.ref }}'
cancel-in-progress: true
jobs:
pr-builder:
needs:
- prepare
- ci_required
if: >-
${{
always()
&& !cancelled()
&& needs.prepare.result == 'success'
&& needs.ci_required.result == 'success'
}}
permissions:
contents: read
uses: rapidsai/shared-workflows/.github/workflows/pr-builder.yaml@4866bb5437e913caf5bf775f57c91abd144ed391 # main
with:
needs: ${{ toJSON(needs) }}
prepare:
# Executes the get-pr-info action to determine if the PR has the skip-ci label, if the action fails we assume the
# PR does not have the label
name: Prepare
runs-on: ubuntu-latest
permissions:
pull-requests: read
steps:
- name: Get PR Info
id: get-pr-info
uses: nv-gha-runners/get-pr-info@090577647b8ddc4e06e809e264f7881650ecdccf # main
if: ${{ startsWith(github.ref_name, 'pull-request/') }}
- name: Validate release tag format
if: ${{ github.ref_type == 'tag' }}
run: |
set -e
tag="${{ github.ref_name }}"
if [[ "$tag" == v* ]]; then
echo "Error: release tags must not start with 'v'; use raw SemVer such as 0.1.0 or 0.1.0-rc.1" >&2
exit 1
fi
if [[ ! "$tag" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-((alpha|beta|rc)\.[0-9]+))?$ ]]; then
echo "Error: unsupported release tag format '$tag'; use 0.1.0 or prereleases like 0.1.0-rc.1" >&2
exit 1
fi
- name: Derive workflow policy
id: policy
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
EVENT_NAME: ${{ github.event_name }}
REF_NAME: ${{ github.ref_name }}
REF_TYPE: ${{ github.ref_type }}
run: |
set -euo pipefail
full_ci=false
publish_packages=false
if [[ "$REF_TYPE" == "tag" || "$REF_NAME" == "$DEFAULT_BRANCH" ]]; then
full_ci=true
fi
if [[ "$REF_TYPE" == "tag" && ! "$REF_NAME" =~ -alpha\.[0-9]+$ ]]; then
publish_packages=true
fi
{
printf 'full_ci=%s\n' "$full_ci"
printf 'publish_packages=%s\n' "$publish_packages"
} >> "$GITHUB_OUTPUT"
outputs:
full_ci: ${{ steps.policy.outputs.full_ci }}
is_pr: ${{ startsWith(github.ref_name, 'pull-request/') }}
is_main_branch: ${{ github.ref_name == 'main' }}
has_skip_ci_label: ${{ steps.get-pr-info.outcome == 'success' && contains(fromJSON(steps.get-pr-info.outputs.pr-info).labels.*.name, 'skip-ci') || false }}
publish_packages: ${{ steps.policy.outputs.publish_packages }}
pr_info: ${{ steps.get-pr-info.outcome == 'success' && steps.get-pr-info.outputs.pr-info || '' }}
ci_changes:
name: Changes
needs: [prepare]
uses: ./.github/workflows/ci_changes.yml
if: ${{ ! fromJSON(needs.prepare.outputs.has_skip_ci_label) }}
permissions:
contents: read
with:
# Info about the PR. Empty for non PR branches. Useful for extracting PR number, title, etc.
pr_info: ${{ needs.prepare.outputs.pr_info }}
full_ci: ${{ needs.prepare.outputs.full_ci == 'true' }}
ref_name: ${{ github.ref_name }}
default_branch: ${{ github.event.repository.default_branch }}
ci_check:
name: Check
needs: [prepare, ci_changes]
uses: ./.github/workflows/ci_check.yml
if: ${{ needs.ci_changes.result == 'success' }}
permissions:
contents: read
with:
full_ci: ${{ needs.prepare.outputs.full_ci == 'true' }}
base: ${{ needs.ci_changes.outputs.base }}
run_python_integration_langchain: ${{ needs.ci_changes.outputs.run_python_integration_langchain == 'true' }}
ci_license_diff:
name: License Diff
needs: [prepare, ci_changes, ci_check]
uses: ./.github/workflows/ci_license_diff.yml
if: >-
${{
needs.prepare.outputs.is_pr == 'true'
&& needs.ci_check.result == 'success'
&& needs.ci_changes.outputs.run_dependencies == 'true'
}}
permissions:
contents: read
pull-requests: write
with:
base: ${{ needs.ci_changes.outputs.base }}
default_branch: ${{ github.event.repository.default_branch }}
ci_rust:
name: Rust
needs: [prepare, ci_changes, ci_check]
uses: ./.github/workflows/ci_rust.yml
if: ${{ needs.ci_check.result == 'success' && needs.ci_changes.outputs.run_rust == 'true' }}
permissions:
contents: read
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
run_package: ${{ needs.ci_changes.outputs.run_rust_package == 'true' }}
ci_go:
name: Go
needs: [prepare, ci_changes, ci_check]
uses: ./.github/workflows/ci_go.yml
if: ${{ needs.ci_check.result == 'success' && needs.ci_changes.outputs.run_go == 'true' }}
permissions:
contents: read
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
ci_node:
name: Node.js
needs: [prepare, ci_changes, ci_check]
uses: ./.github/workflows/ci_node.yml
if: ${{ needs.ci_check.result == 'success' && needs.ci_changes.outputs.run_node == 'true' }}
permissions:
contents: read
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
ref_type: ${{ github.ref_type }}
ref_name: ${{ github.ref_name }}
run_package: ${{ needs.ci_changes.outputs.run_node_package == 'true' }}
run_openclaw: ${{ needs.ci_changes.outputs.run_openclaw == 'true' }}
ci_python:
name: Python
needs: [prepare, ci_changes, ci_check]
uses: ./.github/workflows/ci_python.yml
if: ${{ needs.ci_check.result == 'success' && ( needs.ci_changes.outputs.run_python == 'true' || needs.ci_changes.outputs.run_python_integration_langchain == 'true' ) }}
permissions:
contents: read
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
ref_type: ${{ github.ref_type }}
ref_name: ${{ github.ref_name }}
run_package: ${{ ( needs.ci_changes.outputs.run_python == 'true' || needs.ci_changes.outputs.run_python_integration_langchain == 'true' ) }}
run_integration_langchain: ${{ needs.ci_changes.outputs.run_python_integration_langchain == 'true' }}
ci_wasm:
name: WebAssembly
needs: [prepare, ci_changes, ci_check]
uses: ./.github/workflows/ci_wasm.yml
if: ${{ needs.ci_check.result == 'success' && needs.ci_changes.outputs.run_wasm == 'true' }}
permissions:
contents: read
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
ref_type: ${{ github.ref_type }}
ref_name: ${{ github.ref_name }}
run_package: ${{ needs.ci_changes.outputs.run_wasm_package == 'true' }}
ci_required:
name: CI Pipeline
needs:
- prepare
- ci_changes
- ci_check
- ci_rust
- ci_go
- ci_node
- ci_python
- ci_wasm
if: ${{ always() && !cancelled() && needs.prepare.result == 'success' && ! fromJSON(needs.prepare.outputs.has_skip_ci_label) }}
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Verify required CI jobs
env:
CHANGES_RESULT: ${{ needs.ci_changes.result }}
CHECK_RESULT: ${{ needs.ci_check.result }}
RUST_RESULT: ${{ needs.ci_rust.result }}
GO_RESULT: ${{ needs.ci_go.result }}
NODE_RESULT: ${{ needs.ci_node.result }}
PYTHON_RESULT: ${{ needs.ci_python.result }}
WEBASSEMBLY_RESULT: ${{ needs.ci_wasm.result }}
publish_packages: ${{ needs.prepare.outputs.publish_packages }}
run: |
set -euo pipefail
failed=false
require_success() {
local name="$1"
local result="$2"
if [[ "$result" != "success" ]]; then
echo "Error: ${name} finished with result '${result}', expected success" >&2
failed=true
fi
}
allow_success_or_skipped() {
local name="$1"
local result="$2"
if [[ "$result" != "success" && "$result" != "skipped" ]]; then
echo "Error: ${name} finished with result '${result}', expected success or skipped" >&2
failed=true
fi
}
require_success "Changes" "$CHANGES_RESULT"
require_success "Check" "$CHECK_RESULT"
if [[ "$publish_packages" == "true" ]]; then
require_success "Rust" "$RUST_RESULT"
require_success "Node.js" "$NODE_RESULT"
require_success "Python" "$PYTHON_RESULT"
require_success "WebAssembly" "$WEBASSEMBLY_RESULT"
else
allow_success_or_skipped "Rust" "$RUST_RESULT"
allow_success_or_skipped "Node.js" "$NODE_RESULT"
allow_success_or_skipped "Python" "$PYTHON_RESULT"
allow_success_or_skipped "WebAssembly" "$WEBASSEMBLY_RESULT"
fi
allow_success_or_skipped "Go" "$GO_RESULT"
if [[ "$failed" == "true" ]]; then
exit 1
fi
release-cli-artifacts:
name: Release CLI Artifacts
needs: [prepare, ci_required]
if: ${{ needs.prepare.outputs.publish_packages == 'true' && needs.ci_required.result == 'success' }}
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Download CLI binary artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: cli-*
merge-multiple: true
path: release-assets/
- name: Generate CLI release checksums
run: |
set -euo pipefail
(
cd release-assets
cli_assets=(nemo-relay-cli-*)
sha256sum "${cli_assets[@]}" > SHA256SUMS
sha256sum --check SHA256SUMS
while read -r digest filename; do
printf '%s %s\n' "$digest" "$filename" > "${filename}.sha256"
done < SHA256SUMS
printf 'CLI release assets:\n'
printf ' release-assets/%s\n' *
)
- name: Upload CLI assets and checksums to draft GitHub Release
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
with:
draft: true
prerelease: ${{ contains(github.ref_name, '-beta.') || contains(github.ref_name, '-rc.') }}
overwrite_files: true
fail_on_unmatched_files: true
files: release-assets/*
publish-rust:
name: Publish (crates.io)
needs: [prepare, ci_required]
if: ${{ needs.prepare.outputs.publish_packages == 'true' && needs.ci_required.result == 'success' }}
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
id-token: write
environment: crates.io
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Load CI tool versions
id: ci-config
uses: ./.github/actions/load-ci-tool-versions
- uses: actions-rust-lang/setup-rust-toolchain@150fca883cd4034361b621bd4e6a9d34e5143606 # v1.15.4
with:
cache: false
toolchain: ${{ steps.ci-config.outputs.rust_version }}
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8
with:
version: ${{ steps.ci-config.outputs.uv_version }}
enable-cache: true
cache-dependency-glob: ${{ github.workspace }}/uv.lock
- name: Install managed Python
run: |
set -e
UV_PYTHON_DOWNLOADS=manual uv python install --managed-python ${{ steps.ci-config.outputs.default_python_version }}
- uses: taiki-e/install-action@c070f87102a1c75b3183910f391c1cb887fe13c8 # v2.77.6
with:
tool: just@${{ steps.ci-config.outputs.just_version }}
- name: Set project release version
working-directory: ${{ github.workspace }}
run: just set-version "${{ github.ref_name }}"
- name: Authenticate to crates.io with trusted publishing
id: crates-io-auth
uses: rust-lang/crates-io-auth-action@bbd81622f20ce9e2dd9622e3218b975523e45bbe # v1.0.4
- name: Publish to crates.io with trusted publishing
working-directory: ${{ github.workspace }}
env:
CARGO_REGISTRY_TOKEN: ${{ steps.crates-io-auth.outputs.token }}
run: |
set -euo pipefail
version="${{ github.ref_name }}"
packages=(
nemo-relay
nemo-relay-adaptive
nemo-relay-pii-redaction
nemo-relay-ffi
nemo-relay-cli
)
for package in "${packages[@]}"; do
if cargo info "${package}@${version}" --registry crates-io >/dev/null 2>&1; then
echo "${package} ${version} already exists on crates.io; skipping"
continue
fi
cargo publish --package "$package" --no-verify --allow-dirty
done
publish-python:
name: Publish (PyPI)
needs: [prepare, ci_required]
if: ${{ needs.prepare.outputs.publish_packages == 'true' && needs.ci_required.result == 'success' }}
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
id-token: write
environment: pypi
steps:
- name: Download wheel artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: wheel-*
merge-multiple: true
path: dist/
- name: Publish to PyPI with trusted publishing
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
with:
skip-existing: true
publish-npm:
name: Publish (npm)
needs: [prepare, ci_required]
if: ${{ needs.prepare.outputs.publish_packages == 'true' && needs.ci_required.result == 'success' }}
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
id-token: write
environment: npm
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Load CI tool versions
id: ci-config
uses: ./.github/actions/load-ci-tool-versions
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version: ${{ steps.ci-config.outputs.node_version }}
registry-url: "https://registry.npmjs.org"
- name: Download consolidated Node package artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: npm-consolidated
path: .
- name: Download WebAssembly artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: wasm-bundler
path: wasm-package/
- name: Download OpenClaw plugin artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: openclaw-npm
path: openclaw-package/
- name: Select npm dist-tag
run: |
set -e
npm_tag="latest"
if [[ "${{ github.ref_name }}" =~ -(alpha|beta|rc)\.[0-9]+$ ]]; then
npm_tag="next"
fi
printf 'NEMO_RELAY_NPM_DIST_TAG=%s\n' "$npm_tag" >> "$GITHUB_ENV"
- name: Publish Node.js package to npm
run: |
set -euo pipefail
version="${{ github.ref_name }}"
if npm view "nemo-relay-node@${version}" version --registry https://registry.npmjs.org >/dev/null 2>&1; then
echo "nemo-relay-node ${version} already exists on npm; skipping"
exit 0
fi
unzip -q ./consolidated.zip -d combined
echo "Platform binaries included:"
ls -la combined/package/*.node
npm publish ./combined/package --access public --tag "${NEMO_RELAY_NPM_DIST_TAG}"
- name: Publish OpenClaw plugin package to npm
run: |
set -euo pipefail
version="${{ github.ref_name }}"
if npm view "nemo-relay-openclaw@${version}" version --registry https://registry.npmjs.org >/dev/null 2>&1; then
echo "nemo-relay-openclaw ${version} already exists on npm; skipping"
exit 0
fi
for pkg in ./openclaw-package/*.tgz; do
echo "Publishing ${pkg}..."
npm publish "${pkg}" --access public --tag "${NEMO_RELAY_NPM_DIST_TAG}"
done
- name: Publish WebAssembly package to npm
run: |
set -euo pipefail
version="${{ github.ref_name }}"
if npm view "nemo-relay-wasm@${version}" version --registry https://registry.npmjs.org >/dev/null 2>&1; then
echo "nemo-relay-wasm ${version} already exists on npm; skipping"
exit 0
fi
for pkg in ./wasm-package/*.tgz; do
echo "Publishing ${pkg}..."
npm publish "${pkg}" --access public --tag "${NEMO_RELAY_NPM_DIST_TAG}"
done