mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 20:40:43 +00:00
319 lines
12 KiB
YAML
319 lines
12 KiB
YAML
name: OpenClaw Release Publish
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
tag:
|
|
description: Release tag to publish, for example v2026.5.1-alpha.1 or v2026.5.1-beta.1
|
|
required: true
|
|
type: string
|
|
preflight_run_id:
|
|
description: Successful OpenClaw NPM Release preflight run id, required when publish_openclaw_npm=true
|
|
required: false
|
|
type: string
|
|
npm_dist_tag:
|
|
description: npm dist-tag for the OpenClaw package
|
|
required: true
|
|
default: beta
|
|
type: choice
|
|
options:
|
|
- alpha
|
|
- beta
|
|
- latest
|
|
plugin_publish_scope:
|
|
description: Plugin publish scope to run before OpenClaw publish
|
|
required: true
|
|
default: all-publishable
|
|
type: choice
|
|
options:
|
|
- selected
|
|
- all-publishable
|
|
plugins:
|
|
description: Comma-separated plugin package names when plugin_publish_scope=selected
|
|
required: false
|
|
type: string
|
|
publish_openclaw_npm:
|
|
description: Publish the OpenClaw npm package after plugin npm succeeds; ClawHub may still run
|
|
required: true
|
|
default: true
|
|
type: boolean
|
|
|
|
permissions:
|
|
actions: write
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: openclaw-release-publish-${{ inputs.tag }}
|
|
cancel-in-progress: false
|
|
|
|
env:
|
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
|
NODE_VERSION: "24.x"
|
|
PNPM_VERSION: "10.32.1"
|
|
|
|
jobs:
|
|
resolve_release_target:
|
|
name: Resolve release target
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
outputs:
|
|
sha: ${{ steps.ref.outputs.sha }}
|
|
steps:
|
|
- name: Validate inputs
|
|
env:
|
|
RELEASE_TAG: ${{ inputs.tag }}
|
|
PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }}
|
|
PUBLISH_OPENCLAW_NPM: ${{ inputs.publish_openclaw_npm && 'true' || 'false' }}
|
|
PLUGIN_PUBLISH_SCOPE: ${{ inputs.plugin_publish_scope }}
|
|
PLUGINS: ${{ inputs.plugins }}
|
|
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
|
|
WORKFLOW_REF: ${{ github.ref }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-(alpha|beta)\.[1-9][0-9]*)|(-[1-9][0-9]*))?$ ]]; then
|
|
echo "Invalid release tag: ${RELEASE_TAG}" >&2
|
|
exit 1
|
|
fi
|
|
if [[ "${RELEASE_TAG}" == *"-alpha."* && "${RELEASE_NPM_DIST_TAG}" != "alpha" ]]; then
|
|
echo "Alpha prerelease tags must publish OpenClaw to npm dist-tag alpha." >&2
|
|
exit 1
|
|
fi
|
|
if [[ "${RELEASE_TAG}" == *"-beta."* && "${RELEASE_NPM_DIST_TAG}" != "beta" ]]; then
|
|
echo "Beta prerelease tags must publish OpenClaw to npm dist-tag beta." >&2
|
|
exit 1
|
|
fi
|
|
if [[ "${PUBLISH_OPENCLAW_NPM}" == "true" && -z "${PREFLIGHT_RUN_ID}" ]]; then
|
|
echo "publish_openclaw_npm=true requires preflight_run_id." >&2
|
|
exit 1
|
|
fi
|
|
if [[ "${PUBLISH_OPENCLAW_NPM}" == "true" && "${WORKFLOW_REF}" != "refs/heads/main" && ! "${WORKFLOW_REF}" =~ ^refs/heads/release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$ ]]; then
|
|
echo "publish_openclaw_npm=true requires dispatching this workflow from main or release/YYYY.M.D." >&2
|
|
exit 1
|
|
fi
|
|
if [[ "${PLUGIN_PUBLISH_SCOPE}" == "selected" && -z "${PLUGINS}" ]]; then
|
|
echo "plugin_publish_scope=selected requires plugins." >&2
|
|
exit 1
|
|
fi
|
|
if [[ "${PLUGIN_PUBLISH_SCOPE}" == "all-publishable" && -n "${PLUGINS}" ]]; then
|
|
echo "plugin_publish_scope=all-publishable must not include plugins." >&2
|
|
exit 1
|
|
fi
|
|
|
|
- name: Checkout release tag
|
|
uses: actions/checkout@v6
|
|
with:
|
|
ref: refs/tags/${{ inputs.tag }}
|
|
fetch-depth: 0
|
|
persist-credentials: false
|
|
|
|
- name: Setup Node environment
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
pnpm-version: ${{ env.PNPM_VERSION }}
|
|
install-bun: "false"
|
|
|
|
- name: Resolve checked-out release ref
|
|
id: ref
|
|
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Validate release tag is reachable from main or release branch
|
|
run: |
|
|
set -euo pipefail
|
|
git fetch --no-tags origin \
|
|
+refs/heads/main:refs/remotes/origin/main \
|
|
'+refs/heads/release/*:refs/remotes/origin/release/*'
|
|
if git merge-base --is-ancestor HEAD origin/main; then
|
|
exit 0
|
|
fi
|
|
while IFS= read -r release_ref; do
|
|
if git merge-base --is-ancestor HEAD "${release_ref}"; then
|
|
exit 0
|
|
fi
|
|
done < <(git for-each-ref --format='%(refname)' refs/remotes/origin/release)
|
|
echo "Release tag must point to a commit reachable from main or release/*." >&2
|
|
exit 1
|
|
|
|
- name: Verify plugin versions were synced for this release
|
|
run: pnpm plugins:sync:check
|
|
|
|
- name: Summarize release target
|
|
env:
|
|
RELEASE_TAG: ${{ inputs.tag }}
|
|
TARGET_SHA: ${{ steps.ref.outputs.sha }}
|
|
run: |
|
|
{
|
|
echo "### Release target"
|
|
echo
|
|
echo "- Tag: \`${RELEASE_TAG}\`"
|
|
echo "- SHA: \`${TARGET_SHA}\`"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
publish:
|
|
name: Publish plugins, then OpenClaw
|
|
needs: [resolve_release_target]
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 360
|
|
steps:
|
|
- name: Dispatch publish workflows
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
TARGET_SHA: ${{ needs.resolve_release_target.outputs.sha }}
|
|
CHILD_WORKFLOW_REF: ${{ github.ref_name }}
|
|
RELEASE_TAG: ${{ inputs.tag }}
|
|
PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }}
|
|
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
|
|
PLUGIN_PUBLISH_SCOPE: ${{ inputs.plugin_publish_scope }}
|
|
PLUGINS: ${{ inputs.plugins }}
|
|
PUBLISH_OPENCLAW_NPM: ${{ inputs.publish_openclaw_npm && 'true' || 'false' }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
dispatch_workflow() {
|
|
local workflow="$1"
|
|
shift
|
|
|
|
local before_json dispatch_output run_id
|
|
before_json="$(gh run list --repo "$GITHUB_REPOSITORY" --workflow "$workflow" --event workflow_dispatch --limit 100 --json databaseId --jq '[.[].databaseId]')"
|
|
|
|
dispatch_output="$(gh workflow run --repo "$GITHUB_REPOSITORY" "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@" 2>&1)"
|
|
printf '%s\n' "$dispatch_output" >&2
|
|
run_id="$(
|
|
printf '%s\n' "$dispatch_output" |
|
|
sed -nE 's#.*actions/runs/([0-9]+).*#\1#p' |
|
|
tail -n 1
|
|
)"
|
|
|
|
if [[ -z "$run_id" ]]; then
|
|
for _ in $(seq 1 60); do
|
|
run_id="$(
|
|
BEFORE_IDS="$before_json" gh run list --repo "$GITHUB_REPOSITORY" --workflow "$workflow" --event workflow_dispatch --limit 50 --json databaseId,createdAt \
|
|
--jq 'map(select(.databaseId as $id | (env.BEFORE_IDS | fromjson | index($id) | not))) | sort_by(.createdAt) | reverse | .[0].databaseId // empty'
|
|
)"
|
|
if [[ -n "$run_id" ]]; then
|
|
break
|
|
fi
|
|
sleep 5
|
|
done
|
|
fi
|
|
|
|
if [[ -z "${run_id:-}" ]]; then
|
|
echo "Could not find dispatched run for ${workflow}." >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Dispatched ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}" >&2
|
|
printf '%s\n' "${run_id}"
|
|
}
|
|
|
|
wait_for_run() {
|
|
local workflow="$1"
|
|
local run_id="$2"
|
|
local status conclusion url
|
|
|
|
while true; do
|
|
status="$(gh run view --repo "$GITHUB_REPOSITORY" "$run_id" --json status --jq '.status')"
|
|
if [[ "$status" == "completed" ]]; then
|
|
break
|
|
fi
|
|
sleep 30
|
|
done
|
|
|
|
conclusion="$(gh run view --repo "$GITHUB_REPOSITORY" "$run_id" --json conclusion --jq '.conclusion')"
|
|
url="$(gh run view --repo "$GITHUB_REPOSITORY" "$run_id" --json url --jq '.url')"
|
|
echo "${workflow} finished with ${conclusion}: ${url}"
|
|
{
|
|
echo "- ${workflow}: ${conclusion} (${url})"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
if [[ "$conclusion" != "success" ]]; then
|
|
gh run view --repo "$GITHUB_REPOSITORY" "$run_id" --json jobs --jq '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | {name, conclusion, url}' || true
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
wait_for_run_background() {
|
|
local workflow="$1"
|
|
local run_id="$2"
|
|
local result_file="$3"
|
|
(
|
|
if wait_for_run "${workflow}" "${run_id}"; then
|
|
printf 'success\n' > "${result_file}"
|
|
else
|
|
printf 'failure\n' > "${result_file}"
|
|
fi
|
|
) &
|
|
wait_run_pid="$!"
|
|
}
|
|
|
|
{
|
|
echo "### Publish sequence"
|
|
echo
|
|
echo "- Workflow ref: \`${CHILD_WORKFLOW_REF}\`"
|
|
echo "- Release tag: \`${RELEASE_TAG}\`"
|
|
echo "- Release SHA: \`${TARGET_SHA}\`"
|
|
echo "- Plugin npm and ClawHub publish: dispatched in parallel"
|
|
if [[ "${PUBLISH_OPENCLAW_NPM}" == "true" ]]; then
|
|
echo "- OpenClaw npm publish: starts after plugin npm succeeds; ClawHub may still be running"
|
|
else
|
|
echo "- OpenClaw npm publish: skipped by input"
|
|
fi
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
npm_args=(-f publish_scope="${PLUGIN_PUBLISH_SCOPE}" -f ref="${TARGET_SHA}")
|
|
clawhub_args=(-f publish_scope="${PLUGIN_PUBLISH_SCOPE}" -f ref="${TARGET_SHA}")
|
|
if [[ -n "${PLUGINS}" ]]; then
|
|
npm_args+=(-f plugins="${PLUGINS}")
|
|
clawhub_args+=(-f plugins="${PLUGINS}")
|
|
fi
|
|
|
|
plugin_npm_run_id="$(dispatch_workflow plugin-npm-release.yml "${npm_args[@]}")"
|
|
plugin_clawhub_run_id="$(dispatch_workflow plugin-clawhub-release.yml "${clawhub_args[@]}")"
|
|
|
|
if ! wait_for_run plugin-npm-release.yml "${plugin_npm_run_id}"; then
|
|
echo "Plugin npm publish failed; cancelling ClawHub publish child ${plugin_clawhub_run_id}." >&2
|
|
gh run cancel --repo "$GITHUB_REPOSITORY" "${plugin_clawhub_run_id}" >/dev/null 2>&1 || true
|
|
exit 1
|
|
fi
|
|
|
|
openclaw_npm_run_id=""
|
|
if [[ "${PUBLISH_OPENCLAW_NPM}" == "true" ]]; then
|
|
openclaw_npm_run_id="$(dispatch_workflow openclaw-npm-release.yml \
|
|
-f tag="${RELEASE_TAG}" \
|
|
-f preflight_only=false \
|
|
-f preflight_run_id="${PREFLIGHT_RUN_ID}" \
|
|
-f npm_dist_tag="${RELEASE_NPM_DIST_TAG}")"
|
|
else
|
|
echo "- OpenClaw npm publish: skipped by input" >> "$GITHUB_STEP_SUMMARY"
|
|
fi
|
|
|
|
clawhub_result="$RUNNER_TEMP/clawhub-result.txt"
|
|
wait_run_pid=""
|
|
wait_for_run_background plugin-clawhub-release.yml "${plugin_clawhub_run_id}" "${clawhub_result}"
|
|
clawhub_pid="${wait_run_pid}"
|
|
|
|
openclaw_result=""
|
|
openclaw_pid=""
|
|
if [[ -n "${openclaw_npm_run_id}" ]]; then
|
|
openclaw_result="$RUNNER_TEMP/openclaw-npm-result.txt"
|
|
wait_run_pid=""
|
|
wait_for_run_background openclaw-npm-release.yml "${openclaw_npm_run_id}" "${openclaw_result}"
|
|
openclaw_pid="${wait_run_pid}"
|
|
fi
|
|
|
|
failed=0
|
|
if ! wait "${clawhub_pid}"; then
|
|
failed=1
|
|
fi
|
|
if [[ -n "${openclaw_pid}" ]] && ! wait "${openclaw_pid}"; then
|
|
failed=1
|
|
fi
|
|
if [[ -f "${clawhub_result}" && "$(cat "${clawhub_result}")" != "success" ]]; then
|
|
failed=1
|
|
fi
|
|
if [[ -n "${openclaw_result}" && -f "${openclaw_result}" && "$(cat "${openclaw_result}")" != "success" ]]; then
|
|
failed=1
|
|
fi
|
|
if [[ "${failed}" != "0" ]]; then
|
|
exit 1
|
|
fi
|