Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
692 lines
28 KiB
YAML
692 lines
28 KiB
YAML
name: OpenClaw Cross-OS Release Checks (Reusable)
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
ref:
|
|
description: Public OpenClaw ref to validate (tag, branch, or full commit SHA)
|
|
required: true
|
|
default: main
|
|
type: string
|
|
workflow_ref:
|
|
description: Optional openclaw/openclaw ref that provides the reusable workflow harness
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
provider:
|
|
description: Provider lane to use for onboarding and the end-to-end turn
|
|
required: true
|
|
default: openai
|
|
type: choice
|
|
options:
|
|
- openai
|
|
- anthropic
|
|
- minimax
|
|
mode:
|
|
description: Which release-check lanes to run
|
|
required: true
|
|
default: both
|
|
type: choice
|
|
options:
|
|
- fresh
|
|
- upgrade
|
|
- both
|
|
suite_filter:
|
|
description: Optional focused cross-OS suite filter, e.g. windows/packaged-upgrade or packaged-fresh
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
previous_version:
|
|
description: Optional baseline version for installer/dev-update and packaged upgrade
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
ubuntu_runner:
|
|
description: Optional Linux runner label override
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
windows_runner:
|
|
description: Optional Windows runner label override
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
macos_runner:
|
|
description: Optional macOS runner label override
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
candidate_artifact_name:
|
|
description: Optional current-run artifact name containing the candidate OpenClaw tarball
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
candidate_artifact_run_id:
|
|
description: Optional workflow run id for candidate_artifact_name
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
candidate_file_name:
|
|
description: Optional candidate tarball file name inside candidate_artifact_name
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
candidate_version:
|
|
description: Optional candidate OpenClaw package version
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
candidate_source_sha:
|
|
description: Optional source SHA used to build the candidate tarball
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
openai_model:
|
|
description: OpenAI model for release cross-OS agent-turn smoke
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
advisory:
|
|
description: Treat failures as advisory for the caller
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
workflow_call:
|
|
inputs:
|
|
advisory:
|
|
description: Treat failures as advisory for the caller
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
ref:
|
|
description: Public OpenClaw ref to validate (tag, branch, or full commit SHA)
|
|
required: true
|
|
type: string
|
|
workflow_ref:
|
|
description: Optional openclaw/openclaw ref that provides the reusable workflow harness
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
provider:
|
|
description: Provider lane to use for onboarding and the end-to-end turn
|
|
required: true
|
|
type: string
|
|
mode:
|
|
description: Which release-check lanes to run
|
|
required: true
|
|
type: string
|
|
suite_filter:
|
|
description: Optional focused cross-OS suite filter, e.g. windows/packaged-upgrade or packaged-fresh
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
previous_version:
|
|
description: Optional baseline version for the upgrade lane (defaults to npm latest)
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
ubuntu_runner:
|
|
description: Optional Linux runner label override
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
windows_runner:
|
|
description: Optional Windows runner label override
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
macos_runner:
|
|
description: Optional macOS runner label override
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
candidate_artifact_name:
|
|
description: Optional current-run artifact name containing the candidate OpenClaw tarball
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
candidate_artifact_run_id:
|
|
description: Optional workflow run id for candidate_artifact_name
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
candidate_file_name:
|
|
description: Optional candidate tarball file name inside candidate_artifact_name
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
candidate_version:
|
|
description: Optional candidate OpenClaw package version
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
candidate_source_sha:
|
|
description: Optional source SHA used to build the candidate tarball
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
openai_model:
|
|
description: OpenAI model for release cross-OS agent-turn smoke
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
secrets:
|
|
OPENAI_API_KEY:
|
|
required: false
|
|
ANTHROPIC_API_KEY:
|
|
required: false
|
|
MINIMAX_API_KEY:
|
|
required: false
|
|
OPENCLAW_DISCORD_SMOKE_BOT_TOKEN:
|
|
required: false
|
|
OPENCLAW_DISCORD_SMOKE_GUILD_ID:
|
|
required: false
|
|
OPENCLAW_DISCORD_SMOKE_CHANNEL_ID:
|
|
required: false
|
|
|
|
permissions: read-all
|
|
|
|
concurrency:
|
|
group: openclaw-cross-os-release-checks-${{ inputs.ref }}-${{ inputs.provider }}-${{ inputs.mode }}
|
|
cancel-in-progress: ${{ inputs.ref == 'main' }}
|
|
|
|
env:
|
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
|
NODE_VERSION: "24.15.0"
|
|
OPENCLAW_REPOSITORY: openclaw/openclaw
|
|
TSX_VERSION: "4.21.0"
|
|
OPENCLAW_CROSS_OS_OPENAI_MODEL: ${{ inputs.openai_model || vars.OPENCLAW_CROSS_OS_OPENAI_MODEL || 'openai/gpt-5.5' }}
|
|
|
|
jobs:
|
|
prepare:
|
|
runs-on: ubuntu-24.04
|
|
continue-on-error: ${{ inputs.advisory }}
|
|
outputs:
|
|
baseline_file_name: ${{ steps.baseline_metadata.outputs.file_name }}
|
|
baseline_spec: ${{ steps.baseline.outputs.value }}
|
|
candidate_file_name: ${{ steps.candidate_metadata.outputs.file_name }}
|
|
candidate_version: ${{ steps.candidate_metadata.outputs.version }}
|
|
matrix: ${{ steps.matrix.outputs.value }}
|
|
source_sha: ${{ steps.candidate_metadata.outputs.source_sha }}
|
|
workflow_ref: ${{ steps.workflow_ref.outputs.value }}
|
|
steps:
|
|
- name: Validate provider secret availability
|
|
env:
|
|
PROVIDER: ${{ inputs.provider }}
|
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
|
|
run: |
|
|
set -euo pipefail
|
|
case "${PROVIDER}" in
|
|
openai)
|
|
[[ -n "${OPENAI_API_KEY}" ]] || { echo "Missing OPENAI_API_KEY secret." >&2; exit 1; }
|
|
;;
|
|
anthropic)
|
|
[[ -n "${ANTHROPIC_API_KEY}" ]] || { echo "Missing ANTHROPIC_API_KEY secret." >&2; exit 1; }
|
|
;;
|
|
minimax)
|
|
[[ -n "${MINIMAX_API_KEY}" ]] || { echo "Missing MINIMAX_API_KEY secret." >&2; exit 1; }
|
|
;;
|
|
*)
|
|
echo "Unsupported provider: ${PROVIDER}" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
- name: Resolve workflow ref
|
|
id: workflow_ref
|
|
env:
|
|
INPUT_WORKFLOW_REF: ${{ inputs.workflow_ref }}
|
|
CALLER_REPOSITORY: ${{ github.repository }}
|
|
CURRENT_SHA: ${{ github.sha }}
|
|
WORKFLOW_CONTEXT_REF: ${{ github.workflow_ref }}
|
|
WORKFLOW_REPOSITORY: ${{ env.OPENCLAW_REPOSITORY }}
|
|
run: |
|
|
set -euo pipefail
|
|
resolve_unique_remote_ref() {
|
|
local remote_url="$1"
|
|
shift
|
|
local -a refs=("$@")
|
|
local -a matches=()
|
|
local ref=""
|
|
|
|
for ref in "${refs[@]}"; do
|
|
[[ -n "${ref}" ]] || continue
|
|
mapfile -t matches < <(
|
|
git ls-remote "${remote_url}" "${ref}" | awk '{print $1}' | awk '!seen[$0]++'
|
|
)
|
|
if [[ "${#matches[@]}" -eq 0 ]]; then
|
|
continue
|
|
fi
|
|
if [[ "${#matches[@]}" -ne 1 ]]; then
|
|
return 2
|
|
fi
|
|
|
|
printf '%s\n' "${matches[0]}"
|
|
return 0
|
|
done
|
|
return 1
|
|
}
|
|
|
|
if [[ -n "${INPUT_WORKFLOW_REF}" ]]; then
|
|
TARGET_REF="${INPUT_WORKFLOW_REF}"
|
|
elif [[ "${CALLER_REPOSITORY}" == "${WORKFLOW_REPOSITORY}" ]]; then
|
|
TARGET_REF="${CURRENT_SHA}"
|
|
elif [[ "${WORKFLOW_CONTEXT_REF}" == "${WORKFLOW_REPOSITORY}/"* ]] && [[ "${WORKFLOW_CONTEXT_REF}" == *"@"* ]]; then
|
|
TARGET_REF="${WORKFLOW_CONTEXT_REF##*@}"
|
|
else
|
|
echo "Failed to infer workflow ref from github.workflow_ref=${WORKFLOW_CONTEXT_REF}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "${TARGET_REF}" =~ ^[0-9a-fA-F]{40}$ ]]; then
|
|
echo "value=${TARGET_REF}" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
|
|
REMOTE_URL="https://github.com/${WORKFLOW_REPOSITORY}.git"
|
|
if [[ "${TARGET_REF}" == refs/* ]]; then
|
|
if [[ "${TARGET_REF}" == refs/tags/* ]]; then
|
|
mapfile -t MATCHES < <(
|
|
resolve_unique_remote_ref "${REMOTE_URL}" "${TARGET_REF}^{}" "${TARGET_REF}" || true
|
|
)
|
|
else
|
|
mapfile -t MATCHES < <(resolve_unique_remote_ref "${REMOTE_URL}" "${TARGET_REF}" || true)
|
|
fi
|
|
else
|
|
mapfile -t BRANCH_MATCHES < <(
|
|
resolve_unique_remote_ref "${REMOTE_URL}" "refs/heads/${TARGET_REF}" || true
|
|
)
|
|
mapfile -t TAG_MATCHES < <(
|
|
resolve_unique_remote_ref "${REMOTE_URL}" "refs/tags/${TARGET_REF}^{}" "refs/tags/${TARGET_REF}" || true
|
|
)
|
|
|
|
MATCH_COUNT=$(( ${#BRANCH_MATCHES[@]} + ${#TAG_MATCHES[@]} ))
|
|
if [[ "${MATCH_COUNT}" -eq 1 ]]; then
|
|
if [[ "${#BRANCH_MATCHES[@]}" -eq 1 ]]; then
|
|
MATCHES=("${BRANCH_MATCHES[0]}")
|
|
else
|
|
MATCHES=("${TAG_MATCHES[0]}")
|
|
fi
|
|
elif [[ "${MATCH_COUNT}" -eq 0 ]]; then
|
|
MATCHES=()
|
|
else
|
|
echo "Workflow ref resolved ambiguously: ${TARGET_REF}" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
case "${#MATCHES[@]}" in
|
|
1)
|
|
echo "value=${MATCHES[0]}" >> "$GITHUB_OUTPUT"
|
|
;;
|
|
0)
|
|
echo "Failed to resolve workflow ref: ${TARGET_REF}" >&2
|
|
exit 1
|
|
;;
|
|
*)
|
|
echo "Workflow ref resolved ambiguously: ${TARGET_REF}" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
- name: Checkout workflow repo
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
|
with:
|
|
repository: ${{ env.OPENCLAW_REPOSITORY }}
|
|
ref: ${{ steps.workflow_ref.outputs.value }}
|
|
path: workflow
|
|
fetch-depth: 1
|
|
persist-credentials: true
|
|
|
|
- name: Checkout public source ref
|
|
if: inputs.candidate_artifact_name == ''
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
|
with:
|
|
repository: ${{ env.OPENCLAW_REPOSITORY }}
|
|
ref: ${{ inputs.ref }}
|
|
path: source
|
|
fetch-depth: 0
|
|
persist-credentials: true
|
|
submodules: recursive
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
|
|
- name: Setup pnpm
|
|
uses: ./workflow/.github/actions/setup-pnpm-store-cache
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
package-manager-file: ${{ inputs.candidate_artifact_name == '' && 'source/package.json' || 'workflow/package.json' }}
|
|
lockfile-path: ${{ inputs.candidate_artifact_name == '' && 'source/pnpm-lock.yaml' || 'workflow/pnpm-lock.yaml' }}
|
|
use-actions-cache: ${{ inputs.candidate_artifact_name == '' && 'true' || 'false' }}
|
|
|
|
- name: Ensure pnpm store cache directory exists
|
|
run: mkdir -p "$(pnpm store path --silent)"
|
|
|
|
- name: Build candidate artifact once
|
|
if: inputs.candidate_artifact_name == ''
|
|
env:
|
|
OUTPUT_DIR: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare
|
|
run: |
|
|
bash workflow/scripts/github/run-openclaw-cross-os-release-checks.sh \
|
|
--prepare-only \
|
|
--source-dir source \
|
|
--output-dir "${OUTPUT_DIR}"
|
|
|
|
- name: Download current-run candidate artifact
|
|
if: inputs.candidate_artifact_name != '' && inputs.candidate_artifact_run_id == ''
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
name: ${{ inputs.candidate_artifact_name }}
|
|
path: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/package
|
|
|
|
- name: Download previous-run candidate artifact
|
|
if: inputs.candidate_artifact_name != '' && inputs.candidate_artifact_run_id != ''
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
name: ${{ inputs.candidate_artifact_name }}
|
|
run-id: ${{ inputs.candidate_artifact_run_id }}
|
|
github-token: ${{ github.token }}
|
|
path: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/package
|
|
|
|
- name: Capture provided candidate artifact metadata
|
|
if: inputs.candidate_artifact_name != ''
|
|
env:
|
|
PACKAGE_DIR: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/package
|
|
INPUT_CANDIDATE_FILE_NAME: ${{ inputs.candidate_file_name }}
|
|
INPUT_CANDIDATE_VERSION: ${{ inputs.candidate_version }}
|
|
INPUT_CANDIDATE_SOURCE_SHA: ${{ inputs.candidate_source_sha }}
|
|
CANDIDATE_JSON: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/candidate.json
|
|
run: |
|
|
node <<'NODE'
|
|
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
|
|
const packageDir = process.env.PACKAGE_DIR;
|
|
function resolveTarballFileName(value, label) {
|
|
const fileName = typeof value === "string" ? value.trim() : "";
|
|
if (
|
|
!fileName.endsWith(".tgz") ||
|
|
fileName.includes("\0") ||
|
|
fileName !== path.basename(fileName) ||
|
|
fileName !== path.win32.basename(fileName)
|
|
) {
|
|
throw new Error(`${label} must be a local .tgz filename.`);
|
|
}
|
|
return fileName;
|
|
}
|
|
const requestedFileName = process.env.INPUT_CANDIDATE_FILE_NAME.trim();
|
|
const files = fs.readdirSync(packageDir).filter((file) => file.endsWith(".tgz"));
|
|
const selectedCandidateFileName = requestedFileName || (files.length === 1 ? files[0] : "");
|
|
if (!selectedCandidateFileName) {
|
|
throw new Error(`Expected exactly one candidate .tgz in ${packageDir}; found ${files.length}.`);
|
|
}
|
|
const candidateFileName = resolveTarballFileName(
|
|
selectedCandidateFileName,
|
|
"candidate_file_name",
|
|
);
|
|
if (!fs.existsSync(path.join(packageDir, candidateFileName))) {
|
|
throw new Error(`Provided candidate artifact does not contain ${candidateFileName}.`);
|
|
}
|
|
const candidateVersion = process.env.INPUT_CANDIDATE_VERSION.trim();
|
|
if (!candidateVersion) {
|
|
throw new Error("candidate_version is required when candidate_artifact_name is provided.");
|
|
}
|
|
const sourceSha = process.env.INPUT_CANDIDATE_SOURCE_SHA.trim();
|
|
if (!/^[0-9a-f]{40}$/iu.test(sourceSha)) {
|
|
throw new Error("candidate_source_sha must be a full commit SHA when candidate_artifact_name is provided.");
|
|
}
|
|
fs.writeFileSync(
|
|
process.env.CANDIDATE_JSON,
|
|
`${JSON.stringify({ candidateFileName, candidateVersion, sourceSha }, null, 2)}\n`,
|
|
);
|
|
NODE
|
|
|
|
- name: Resolve baseline package spec
|
|
if: ${{ inputs.mode != 'fresh' }}
|
|
id: baseline
|
|
env:
|
|
INPUT_PREVIOUS_VERSION: ${{ inputs.previous_version }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [[ -n "${INPUT_PREVIOUS_VERSION}" ]]; then
|
|
echo "value=openclaw@${INPUT_PREVIOUS_VERSION}" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
BASELINE_VERSION="$(npm view openclaw@latest version)"
|
|
echo "value=openclaw@${BASELINE_VERSION}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Pack baseline artifact
|
|
if: ${{ inputs.mode != 'fresh' }}
|
|
env:
|
|
BASELINE_SPEC: ${{ steps.baseline.outputs.value }}
|
|
OUTPUT_DIR: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/baseline
|
|
run: |
|
|
mkdir -p "${OUTPUT_DIR}"
|
|
timeout --preserve-status 300s npm pack --ignore-scripts --json "${BASELINE_SPEC}" --pack-destination "${OUTPUT_DIR}" > "${OUTPUT_DIR}/pack.json"
|
|
|
|
- name: Capture candidate metadata
|
|
id: candidate_metadata
|
|
env:
|
|
CANDIDATE_JSON: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/candidate.json
|
|
run: |
|
|
node <<'NODE' >>"$GITHUB_OUTPUT"
|
|
const fs = require("node:fs");
|
|
const payload = JSON.parse(fs.readFileSync(process.env.CANDIDATE_JSON, "utf8"));
|
|
process.stdout.write(`file_name=${payload.candidateFileName}\n`);
|
|
process.stdout.write(`version=${payload.candidateVersion}\n`);
|
|
process.stdout.write(`source_sha=${payload.sourceSha}\n`);
|
|
NODE
|
|
|
|
- name: Capture baseline metadata
|
|
if: ${{ inputs.mode != 'fresh' }}
|
|
id: baseline_metadata
|
|
env:
|
|
BASELINE_PACK_JSON: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/baseline/pack.json
|
|
run: |
|
|
node <<'NODE' >>"$GITHUB_OUTPUT"
|
|
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
function resolveTarballFileName(value, label) {
|
|
const fileName = typeof value === "string" ? value.trim() : "";
|
|
if (
|
|
!fileName.endsWith(".tgz") ||
|
|
fileName.includes("\0") ||
|
|
fileName !== path.basename(fileName) ||
|
|
fileName !== path.win32.basename(fileName)
|
|
) {
|
|
throw new Error(`${label} must be a local .tgz filename.`);
|
|
}
|
|
return fileName;
|
|
}
|
|
const payload = JSON.parse(fs.readFileSync(process.env.BASELINE_PACK_JSON, "utf8"));
|
|
const entry = Array.isArray(payload) ? payload.at(-1) : null;
|
|
const fileName = resolveTarballFileName(entry?.filename, "Baseline npm pack filename");
|
|
process.stdout.write(`file_name=${fileName}\n`);
|
|
NODE
|
|
|
|
- name: Upload candidate artifact
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: openclaw-cross-os-release-checks-candidate-${{ github.run_id }}
|
|
path: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/package/${{ steps.candidate_metadata.outputs.file_name }}
|
|
if-no-files-found: error
|
|
|
|
- name: Upload baseline artifact
|
|
if: ${{ inputs.mode != 'fresh' }}
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: openclaw-cross-os-release-checks-baseline-${{ github.run_id }}
|
|
path: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/baseline/${{ steps.baseline_metadata.outputs.file_name }}
|
|
if-no-files-found: error
|
|
|
|
- name: Resolve runner matrix
|
|
id: matrix
|
|
env:
|
|
INPUT_REF: ${{ inputs.ref }}
|
|
INPUT_MODE: ${{ inputs.mode }}
|
|
INPUT_SUITE_FILTER: ${{ inputs.suite_filter }}
|
|
INPUT_UBUNTU_RUNNER: ${{ inputs.ubuntu_runner }}
|
|
INPUT_WINDOWS_RUNNER: ${{ inputs.windows_runner }}
|
|
INPUT_MACOS_RUNNER: ${{ inputs.macos_runner }}
|
|
VAR_UBUNTU_RUNNER: ${{ vars.OPENCLAW_RELEASE_CHECKS_UBUNTU_RUNNER }}
|
|
VAR_WINDOWS_RUNNER: ${{ vars.OPENCLAW_RELEASE_CHECKS_WINDOWS_RUNNER }}
|
|
VAR_MACOS_RUNNER: ${{ vars.OPENCLAW_RELEASE_CHECKS_MACOS_RUNNER }}
|
|
run: |
|
|
MATRIX_JSON="$(bash workflow/scripts/github/run-openclaw-cross-os-release-checks.sh \
|
|
--resolve-matrix \
|
|
--ref "${INPUT_REF}" \
|
|
--mode "${INPUT_MODE}" \
|
|
--suite-filter "${INPUT_SUITE_FILTER}" \
|
|
--ubuntu-runner "${INPUT_UBUNTU_RUNNER}" \
|
|
--windows-runner "${INPUT_WINDOWS_RUNNER}" \
|
|
--macos-runner "${INPUT_MACOS_RUNNER}")"
|
|
echo "value=${MATRIX_JSON}" >> "$GITHUB_OUTPUT"
|
|
|
|
cross_os_release_checks:
|
|
name: "${{ matrix.display_name }} / ${{ matrix.suite_label }}"
|
|
needs: prepare
|
|
continue-on-error: ${{ inputs.advisory }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix: ${{ fromJson(needs.prepare.outputs.matrix) }}
|
|
runs-on: ${{ matrix.runner }}
|
|
timeout-minutes: 60
|
|
steps:
|
|
- name: Checkout workflow repo
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
|
with:
|
|
repository: ${{ env.OPENCLAW_REPOSITORY }}
|
|
ref: ${{ needs.prepare.outputs.workflow_ref }}
|
|
path: workflow
|
|
fetch-depth: 1
|
|
persist-credentials: true
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
|
|
- name: Setup pnpm
|
|
uses: ./workflow/.github/actions/setup-pnpm-store-cache
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
package-manager-file: workflow/package.json
|
|
lockfile-path: workflow/pnpm-lock.yaml
|
|
use-actions-cache: "false"
|
|
|
|
- name: Download candidate artifact
|
|
id: download_candidate
|
|
continue-on-error: true
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
name: openclaw-cross-os-release-checks-candidate-${{ github.run_id }}
|
|
path: ${{ runner.temp }}/openclaw-cross-os-release-checks/candidate
|
|
|
|
- name: Retry candidate artifact download
|
|
if: ${{ steps.download_candidate.outcome == 'failure' }}
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
name: openclaw-cross-os-release-checks-candidate-${{ github.run_id }}
|
|
path: ${{ runner.temp }}/openclaw-cross-os-release-checks/candidate
|
|
|
|
- name: Download baseline artifact
|
|
if: ${{ matrix.suite == 'packaged-upgrade' }}
|
|
id: download_baseline
|
|
continue-on-error: true
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
name: openclaw-cross-os-release-checks-baseline-${{ github.run_id }}
|
|
path: ${{ runner.temp }}/openclaw-cross-os-release-checks/baseline
|
|
|
|
- name: Retry baseline artifact download
|
|
if: ${{ matrix.suite == 'packaged-upgrade' && steps.download_baseline.outcome == 'failure' }}
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
name: openclaw-cross-os-release-checks-baseline-${{ github.run_id }}
|
|
path: ${{ runner.temp }}/openclaw-cross-os-release-checks/baseline
|
|
|
|
- name: Verify release-check inputs
|
|
shell: bash
|
|
env:
|
|
CANDIDATE_TGZ: ${{ runner.temp }}/openclaw-cross-os-release-checks/candidate/${{ needs.prepare.outputs.candidate_file_name }}
|
|
BASELINE_TGZ: ${{ runner.temp }}/openclaw-cross-os-release-checks/baseline/${{ needs.prepare.outputs.baseline_file_name }}
|
|
OUTPUT_DIR: ${{ runner.temp }}/openclaw-cross-os-release-checks/${{ matrix.artifact_name }}-${{ matrix.suite }}
|
|
SUITE: ${{ matrix.suite }}
|
|
run: |
|
|
mkdir -p "${OUTPUT_DIR}"
|
|
if [[ ! -f "${CANDIDATE_TGZ}" ]]; then
|
|
echo "::error::candidate artifact missing: ${CANDIDATE_TGZ}"
|
|
exit 1
|
|
fi
|
|
if [[ "${SUITE}" == "packaged-upgrade" ]] && [[ ! -f "${BASELINE_TGZ}" ]]; then
|
|
echo "::error::baseline artifact missing: ${BASELINE_TGZ}"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Run cross-OS release checks
|
|
shell: bash
|
|
env:
|
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
|
|
OPENCLAW_DISCORD_SMOKE_BOT_TOKEN: ${{ secrets.OPENCLAW_DISCORD_SMOKE_BOT_TOKEN }}
|
|
OPENCLAW_DISCORD_SMOKE_GUILD_ID: ${{ secrets.OPENCLAW_DISCORD_SMOKE_GUILD_ID }}
|
|
OPENCLAW_DISCORD_SMOKE_CHANNEL_ID: ${{ secrets.OPENCLAW_DISCORD_SMOKE_CHANNEL_ID }}
|
|
OPENCLAW_RELEASE_CHECK_OS: ${{ matrix.os_id }}
|
|
OPENCLAW_RELEASE_CHECK_RUNNER: ${{ matrix.runner }}
|
|
CANDIDATE_TGZ: ${{ runner.temp }}/openclaw-cross-os-release-checks/candidate/${{ needs.prepare.outputs.candidate_file_name }}
|
|
CANDIDATE_VERSION: ${{ needs.prepare.outputs.candidate_version }}
|
|
SOURCE_SHA: ${{ needs.prepare.outputs.source_sha }}
|
|
BASELINE_SPEC: ${{ needs.prepare.outputs.baseline_spec }}
|
|
PREVIOUS_VERSION: ${{ inputs.previous_version }}
|
|
BASELINE_TGZ: ${{ runner.temp }}/openclaw-cross-os-release-checks/baseline/${{ needs.prepare.outputs.baseline_file_name }}
|
|
PROVIDER: ${{ inputs.provider }}
|
|
MODE: ${{ matrix.lane }}
|
|
SUITE: ${{ matrix.suite }}
|
|
REF: ${{ inputs.ref }}
|
|
OUTPUT_DIR: ${{ runner.temp }}/openclaw-cross-os-release-checks/${{ matrix.artifact_name }}-${{ matrix.suite }}
|
|
run: |
|
|
DISCORD_ARGS=()
|
|
if [[ -n "${OPENCLAW_DISCORD_SMOKE_BOT_TOKEN}" ]] && [[ -n "${OPENCLAW_DISCORD_SMOKE_GUILD_ID}" ]] && [[ -n "${OPENCLAW_DISCORD_SMOKE_CHANNEL_ID}" ]]; then
|
|
DISCORD_ARGS+=(--run-discord-roundtrip true)
|
|
fi
|
|
bash workflow/scripts/github/run-openclaw-cross-os-release-checks.sh \
|
|
--candidate-tgz "${CANDIDATE_TGZ}" \
|
|
--candidate-version "${CANDIDATE_VERSION}" \
|
|
--source-sha "${SOURCE_SHA}" \
|
|
--baseline-spec "${BASELINE_SPEC}" \
|
|
--previous-version "${PREVIOUS_VERSION}" \
|
|
--baseline-tgz "${BASELINE_TGZ}" \
|
|
--provider "${PROVIDER}" \
|
|
--mode "${MODE}" \
|
|
--suite "${SUITE}" \
|
|
--ref "${REF}" \
|
|
"${DISCORD_ARGS[@]}" \
|
|
--output-dir "${OUTPUT_DIR}"
|
|
|
|
- name: Summarize release checks
|
|
if: always()
|
|
shell: bash
|
|
env:
|
|
SUMMARY_PATH: ${{ runner.temp }}/openclaw-cross-os-release-checks/${{ matrix.artifact_name }}-${{ matrix.suite }}/summary.md
|
|
run: |
|
|
if [[ -f "${SUMMARY_PATH}" ]]; then
|
|
cat "${SUMMARY_PATH}" >> "$GITHUB_STEP_SUMMARY"
|
|
else
|
|
mkdir -p "$(dirname "${SUMMARY_PATH}")"
|
|
echo "No summary generated." | tee "${SUMMARY_PATH}" >> "$GITHUB_STEP_SUMMARY"
|
|
fi
|
|
|
|
- name: Upload release-check artifacts
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: openclaw-cross-os-release-checks-${{ matrix.artifact_name }}-${{ matrix.suite }}-${{ github.run_id }}
|
|
path: ${{ runner.temp }}/openclaw-cross-os-release-checks/${{ matrix.artifact_name }}-${{ matrix.suite }}
|
|
if-no-files-found: error
|