From 451563b95089668ec0bb391c69abbefe244f3823 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 17 May 2026 07:00:45 +0100 Subject: [PATCH] ci: allow Tideclaw alpha release workflows --- .github/workflows/openclaw-npm-release.yml | 56 ++++++++++++++++++- .github/workflows/openclaw-release-checks.yml | 33 ++++++++++- .../workflows/openclaw-release-publish.yml | 25 +++++++-- .github/workflows/plugin-clawhub-release.yml | 28 +++++++++- .github/workflows/plugin-npm-release.yml | 28 +++++++++- 5 files changed, 154 insertions(+), 16 deletions(-) diff --git a/.github/workflows/openclaw-npm-release.yml b/.github/workflows/openclaw-npm-release.yml index 33ba72ce9af..12bb52a21bf 100644 --- a/.github/workflows/openclaw-npm-release.yml +++ b/.github/workflows/openclaw-npm-release.yml @@ -88,6 +88,28 @@ jobs: ref: ${{ inputs.tag }} fetch-depth: 0 + - name: Validate Tideclaw alpha preflight target + if: startsWith(github.ref, 'refs/heads/tideclaw/alpha/') + env: + RELEASE_REF: ${{ inputs.tag }} + WORKFLOW_REF: ${{ github.ref }} + run: | + set -euo pipefail + if [[ ! "${RELEASE_REF}" == *"-alpha."* && ! "${RELEASE_REF}" =~ ^[0-9a-fA-F]{40}$ ]]; then + echo "Tideclaw alpha preflight runs must target an alpha prerelease tag or SHA." >&2 + exit 1 + fi + if [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then + echo "Tideclaw alpha preflight runs must run from tideclaw/alpha/YYYY-MM-DD-HHMMZ." >&2 + exit 1 + fi + alpha_branch="${WORKFLOW_REF#refs/heads/}" + git fetch --no-tags origin "+refs/heads/${alpha_branch}:refs/remotes/origin/${alpha_branch}" + if ! git merge-base --is-ancestor HEAD "refs/remotes/origin/${alpha_branch}"; then + echo "Alpha preflight target must be reachable from ${alpha_branch}." >&2 + exit 1 + fi + - name: Setup Node environment uses: ./.github/actions/setup-node-env with: @@ -347,13 +369,19 @@ jobs: permissions: contents: read steps: - - name: Require main or release workflow ref for publish + - name: Require trusted workflow ref for publish env: + RELEASE_TAG: ${{ inputs.tag }} + RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }} WORKFLOW_REF: ${{ github.ref }} run: | set -euo pipefail - if [[ "${WORKFLOW_REF}" != "refs/heads/main" ]] && [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$ ]]; then - echo "Real publish runs must be dispatched from main or release/YYYY.M.D. Use preflight_only=true for other branch validation." + tideclaw_alpha_publish=false + if [[ "${RELEASE_TAG}" == *"-alpha."* && "${RELEASE_NPM_DIST_TAG}" == "alpha" && "${WORKFLOW_REF}" =~ ^refs/heads/tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then + tideclaw_alpha_publish=true + fi + if [[ "${WORKFLOW_REF}" != "refs/heads/main" ]] && [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$ ]] && [[ "${tideclaw_alpha_publish}" != "true" ]]; then + echo "Real publish runs must be dispatched from main, release/YYYY.M.D, or a Tideclaw alpha branch for alpha prereleases. Use preflight_only=true for other branch validation." exit 1 fi @@ -409,6 +437,28 @@ jobs: ref: refs/tags/${{ inputs.tag }} fetch-depth: 0 + - name: Validate Tideclaw alpha publish target + if: startsWith(github.ref, 'refs/heads/tideclaw/alpha/') + env: + RELEASE_TAG: ${{ inputs.tag }} + WORKFLOW_REF: ${{ github.ref }} + run: | + set -euo pipefail + if [[ ! "${RELEASE_TAG}" == *"-alpha."* ]]; then + echo "Tideclaw alpha publish runs must target an alpha prerelease tag." >&2 + exit 1 + fi + if [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then + echo "Tideclaw alpha publish runs must run from tideclaw/alpha/YYYY-MM-DD-HHMMZ." >&2 + exit 1 + fi + alpha_branch="${WORKFLOW_REF#refs/heads/}" + git fetch --no-tags origin "+refs/heads/${alpha_branch}:refs/remotes/origin/${alpha_branch}" + if ! git merge-base --is-ancestor HEAD "refs/remotes/origin/${alpha_branch}"; then + echo "Alpha publish tag must be reachable from ${alpha_branch}." >&2 + exit 1 + fi + - name: Setup Node environment uses: ./.github/actions/setup-node-env with: diff --git a/.github/workflows/openclaw-release-checks.yml b/.github/workflows/openclaw-release-checks.yml index 8e6fa376b00..394d51e0ef6 100644 --- a/.github/workflows/openclaw-release-checks.yml +++ b/.github/workflows/openclaw-release-checks.yml @@ -113,13 +113,21 @@ jobs: release_package_spec: ${{ steps.inputs.outputs.release_package_spec }} package_acceptance_package_spec: ${{ steps.inputs.outputs.package_acceptance_package_spec }} steps: - - name: Require main or release workflow ref for release checks + - name: Require trusted workflow ref for release checks env: + RELEASE_REF: ${{ inputs.ref }} WORKFLOW_REF: ${{ github.ref }} run: | set -euo pipefail - if [[ "${WORKFLOW_REF}" != "refs/heads/main" ]] && [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$ ]] && [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/release-ci/[0-9a-f]{12}-[0-9]+$ ]]; then - echo "Release checks must be dispatched from main, release/YYYY.M.D, or a Full Release Validation release-ci/- ref so workflow logic and secrets stay controlled." >&2 + tideclaw_alpha_check=false + if [[ "${WORKFLOW_REF}" =~ ^refs/heads/tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then + workflow_branch="${WORKFLOW_REF#refs/heads/}" + if [[ "${RELEASE_REF}" == *"-alpha."* || "${RELEASE_REF}" =~ ^[0-9a-fA-F]{40}$ || "${RELEASE_REF}" == "${workflow_branch}" || "${RELEASE_REF}" == "refs/heads/${workflow_branch}" ]]; then + tideclaw_alpha_check=true + fi + fi + if [[ "${WORKFLOW_REF}" != "refs/heads/main" ]] && [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$ ]] && [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/release-ci/[0-9a-f]{12}-[0-9]+$ ]] && [[ "${tideclaw_alpha_check}" != "true" ]]; then + echo "Release checks must be dispatched from main, release/YYYY.M.D, a Full Release Validation release-ci/- ref, or a Tideclaw alpha branch for alpha prereleases." >&2 exit 1 fi @@ -219,6 +227,25 @@ jobs: fi echo "sha=${selected_sha,,}" >> "$GITHUB_OUTPUT" + - name: Validate Tideclaw alpha target matches workflow branch + if: startsWith(github.ref, 'refs/heads/tideclaw/alpha/') + working-directory: workflow + env: + SELECTED_SHA: ${{ steps.ref.outputs.sha }} + WORKFLOW_REF: ${{ github.ref }} + run: | + set -euo pipefail + if [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then + echo "Tideclaw alpha release checks must run from tideclaw/alpha/YYYY-MM-DD-HHMMZ." >&2 + exit 1 + fi + alpha_branch="${WORKFLOW_REF#refs/heads/}" + git fetch --no-tags origin "+refs/heads/${alpha_branch}:refs/remotes/origin/${alpha_branch}" + if ! git merge-base --is-ancestor "${SELECTED_SHA}" "refs/remotes/origin/${alpha_branch}"; then + echo "Alpha release target ${SELECTED_SHA} must be reachable from ${alpha_branch}." >&2 + exit 1 + fi + - name: Capture selected inputs id: inputs env: diff --git a/.github/workflows/openclaw-release-publish.yml b/.github/workflows/openclaw-release-publish.yml index 5d417c746eb..91f50f74e35 100644 --- a/.github/workflows/openclaw-release-publish.yml +++ b/.github/workflows/openclaw-release-publish.yml @@ -115,8 +115,12 @@ jobs: echo "publish_openclaw_npm=true requires full_release_validation_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 + tideclaw_alpha_publish=false + if [[ "${RELEASE_TAG}" == *"-alpha."* && "${RELEASE_NPM_DIST_TAG}" == "alpha" && "${WORKFLOW_REF}" =~ ^refs/heads/tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then + tideclaw_alpha_publish=true + 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]*$ && "${tideclaw_alpha_publish}" != "true" ]]; then + echo "publish_openclaw_npm=true requires dispatching this workflow from main, release/YYYY.M.D, or a Tideclaw alpha branch for alpha prereleases." >&2 exit 1 fi if [[ "${PLUGIN_PUBLISH_SCOPE}" == "selected" && -z "${PLUGINS}" ]]; then @@ -279,7 +283,10 @@ jobs: exit 1 fi - - name: Validate release tag is reachable from main or release branch + - name: Validate release tag is reachable from a trusted release branch + env: + RELEASE_TAG: ${{ inputs.tag }} + WORKFLOW_REF_NAME: ${{ github.ref_name }} run: | set -euo pipefail git fetch --no-tags origin \ @@ -293,7 +300,17 @@ jobs: 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 + if [[ "${RELEASE_TAG}" == *"-alpha."* ]]; then + if [[ ! "${WORKFLOW_REF_NAME}" =~ ^tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then + echo "Alpha publish tags must be dispatched from tideclaw/alpha/YYYY-MM-DD-HHMMZ." >&2 + exit 1 + fi + git fetch --no-tags origin "+refs/heads/${WORKFLOW_REF_NAME}:refs/remotes/origin/${WORKFLOW_REF_NAME}" + if git merge-base --is-ancestor HEAD "refs/remotes/origin/${WORKFLOW_REF_NAME}"; then + exit 0 + fi + fi + echo "Release tag must point to a commit reachable from main, release/*, or the matching Tideclaw alpha branch for alpha prereleases." >&2 exit 1 - name: Summarize release target diff --git a/.github/workflows/plugin-clawhub-release.yml b/.github/workflows/plugin-clawhub-release.yml index 81fb1abbef2..0628d93bbcf 100644 --- a/.github/workflows/plugin-clawhub-release.yml +++ b/.github/workflows/plugin-clawhub-release.yml @@ -16,7 +16,7 @@ on: required: false type: string ref: - description: Commit SHA on main or a release branch to publish from; defaults to the workflow ref + description: Commit SHA on main, a release branch, or the matching Tideclaw alpha branch to publish from; defaults to the workflow ref required: false default: "" type: string @@ -82,7 +82,9 @@ jobs: fi echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" - - name: Validate ref is on main or a release branch + - name: Validate ref is on a trusted publish branch + env: + WORKFLOW_REF: ${{ github.ref }} run: | set -euo pipefail if git merge-base --is-ancestor HEAD origin/main; then @@ -93,7 +95,14 @@ jobs: exit 0 fi done < <(git for-each-ref --format='%(refname)' refs/remotes/origin/release) - echo "Plugin ClawHub publishes must target a commit reachable from main or release/*." >&2 + if [[ "${WORKFLOW_REF}" =~ ^refs/heads/tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then + alpha_branch="${WORKFLOW_REF#refs/heads/}" + git fetch --no-tags origin "+refs/heads/${alpha_branch}:refs/remotes/origin/${alpha_branch}" + if git merge-base --is-ancestor HEAD "refs/remotes/origin/${alpha_branch}"; then + exit 0 + fi + fi + echo "Plugin ClawHub publishes must target a commit reachable from main, release/*, or the matching Tideclaw alpha branch." >&2 exit 1 - name: Validate publishable plugin metadata @@ -168,6 +177,19 @@ jobs: echo "::error::One or more selected plugin versions already exist on ClawHub. Bump the version before running a real publish." exit 1 + - name: Validate Tideclaw alpha plugin channels + if: startsWith(github.ref, 'refs/heads/tideclaw/alpha/') + run: | + set -euo pipefail + invalid="$( + jq -r '.candidates[]? | select(.publishTag != "alpha" or .channel != "alpha") | "- \(.packageName)@\(.version) [\(.publishTag)]"' .local/plugin-clawhub-release-plan.json + )" + if [[ -n "${invalid}" ]]; then + echo "Tideclaw alpha ClawHub publishes may only publish alpha plugin versions." >&2 + printf '%s\n' "${invalid}" >&2 + exit 1 + fi + - name: Verify OpenClaw ClawHub package ownership if: steps.plan.outputs.has_candidates == 'true' env: diff --git a/.github/workflows/plugin-npm-release.yml b/.github/workflows/plugin-npm-release.yml index 7a981936952..fd158883951 100644 --- a/.github/workflows/plugin-npm-release.yml +++ b/.github/workflows/plugin-npm-release.yml @@ -25,7 +25,7 @@ on: - selected - all-publishable ref: - description: Commit SHA on main or a release branch to publish from (copy from the preview run) + description: Commit SHA on main, a release branch, or the matching Tideclaw alpha branch to publish from required: true type: string plugins: @@ -71,7 +71,9 @@ jobs: id: ref run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" - - name: Validate ref is on main or a release branch + - name: Validate ref is on a trusted publish branch + env: + WORKFLOW_REF: ${{ github.ref }} run: | set -euo pipefail git fetch --no-tags origin \ @@ -85,7 +87,14 @@ jobs: exit 0 fi done < <(git for-each-ref --format='%(refname)' refs/remotes/origin/release) - echo "Plugin npm publishes must target a commit reachable from main or release/*." >&2 + if [[ "${WORKFLOW_REF}" =~ ^refs/heads/tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then + alpha_branch="${WORKFLOW_REF#refs/heads/}" + git fetch --no-tags origin "+refs/heads/${alpha_branch}:refs/remotes/origin/${alpha_branch}" + if git merge-base --is-ancestor HEAD "refs/remotes/origin/${alpha_branch}"; then + exit 0 + fi + fi + echo "Plugin npm publishes must target a commit reachable from main, release/*, or the matching Tideclaw alpha branch." >&2 exit 1 - name: Validate publishable plugin metadata @@ -151,6 +160,19 @@ jobs: echo "Already published / skipped:" jq -r '.skippedPublished[]? | "- \(.packageName)@\(.version)"' .local/plugin-npm-release-plan.json + - name: Validate Tideclaw alpha plugin channels + if: startsWith(github.ref, 'refs/heads/tideclaw/alpha/') + run: | + set -euo pipefail + invalid="$( + jq -r '.candidates[]? | select(.publishTag != "alpha" or .channel != "alpha") | "- \(.packageName)@\(.version) [\(.publishTag)]"' .local/plugin-npm-release-plan.json + )" + if [[ -n "${invalid}" ]]; then + echo "Tideclaw alpha plugin npm publishes may only publish alpha plugin versions." >&2 + printf '%s\n' "${invalid}" >&2 + exit 1 + fi + preview_plugin_pack: needs: preview_plugins_npm if: needs.preview_plugins_npm.outputs.has_candidates == 'true'