# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
# ══════════════════════════════════════════════════════════════════════
# Reusable composite action: Run a releasekit command
#
# Builds the CLI invocation from structured inputs, runs it with
# proper error handling, and parses outputs into GITHUB_OUTPUT.
#
# Supports all three pipeline stages: prepare, release, publish.
# All user-controlled inputs are passed via env vars to prevent
# GitHub Actions script injection.
#
# Outputs:
# has_bumps — "true" if prepare found version bumps
# pr_url — URL of the created/updated Release PR
# release_url — URL of the created GitHub Release
#
# Usage:
#
# - uses: ./.github/actions/run-releasekit
# id: prepare
# with:
# command: prepare
# workspace: py
# releasekit-dir: py/tools/releasekit
# group: ${{ inputs.group }}
# force: ${{ inputs.force_prepare }}
#
# ══════════════════════════════════════════════════════════════════════
name: Run ReleaseKit
description: >-
Run a releasekit command (prepare, release, or publish) with
structured inputs and parsed outputs.
inputs:
command:
description: >-
The releasekit command to run: prepare, release, or publish.
required: true
workspace:
description: >-
Workspace label (e.g. "py", "js", "go").
required: true
releasekit-dir:
description: >-
Path to the releasekit tool directory (relative to repo root).
required: false
default: py/tools/releasekit
dry-run:
description: >-
Set to "true" to pass --dry-run.
required: false
default: "false"
force:
description: >-
Set to "true" to pass --force (prepare and publish).
required: false
default: "false"
group:
description: >-
Release group to target (empty for all).
required: false
default: ""
bump-type:
description: >-
Override bump type: auto, patch, minor, major (prepare only).
required: false
default: "auto"
prerelease:
description: >-
Prerelease suffix, e.g. "rc.1" (prepare only).
required: false
default: ""
concurrency:
description: >-
Max parallel publish jobs, 0 = auto (publish only).
required: false
default: "0"
max-retries:
description: >-
Max retries for failed publishes (publish only).
required: false
default: "0"
check-url:
description: >-
Registry URL for version existence checks (publish only).
required: false
default: ""
index-url:
description: >-
Registry URL for uploading packages (publish only).
required: false
default: ""
show-plan:
description: >-
Show the execution plan before running the command.
required: false
default: "false"
outputs:
has_bumps:
description: "'true' if prepare found version bumps."
value: ${{ steps.run.outputs.has_bumps }}
pr_url:
description: URL of the Release PR (prepare only).
value: ${{ steps.run.outputs.pr_url }}
release_url:
description: URL of the GitHub Release (release only).
value: ${{ steps.run.outputs.release_url }}
runs:
using: composite
steps:
# ── Optional: show execution plan ───────────────────────────────
- name: Preview execution plan
if: inputs.show-plan == 'true'
shell: bash
env:
RK_DIR: ${{ inputs.releasekit-dir }}
RK_WORKSPACE: ${{ inputs.workspace }}
RK_DRY_RUN: ${{ inputs.dry-run }}
run: |
echo "::group::Execution Plan"
uv run --directory "$RK_DIR" \
releasekit --workspace "$RK_WORKSPACE" plan --format full 2>&1 || true
echo "::endgroup::"
if [ "$RK_DRY_RUN" = "true" ]; then
echo "::notice::DRY RUN — no side effects"
else
echo "::notice::LIVE RUN"
fi
# ── Build and run the command ───────────────────────────────────
- name: Run releasekit ${{ inputs.command }}
id: run
shell: bash
env:
RK_COMMAND: ${{ inputs.command }}
RK_WORKSPACE: ${{ inputs.workspace }}
RK_DIR: ${{ inputs.releasekit-dir }}
RK_DRY_RUN: ${{ inputs.dry-run }}
RK_FORCE: ${{ inputs.force }}
RK_GROUP: ${{ inputs.group }}
RK_BUMP_TYPE: ${{ inputs.bump-type }}
RK_PRERELEASE: ${{ inputs.prerelease }}
RK_CONCURRENCY: ${{ inputs.concurrency }}
RK_MAX_RETRIES: ${{ inputs.max-retries }}
RK_CHECK_URL: ${{ inputs.check-url }}
RK_INDEX_URL: ${{ inputs.index-url }}
run: |
set -euo pipefail
cmd=(uv run --directory "$RK_DIR" releasekit --workspace "$RK_WORKSPACE" "$RK_COMMAND")
# ── Common flags ──────────────────────────────────────────
if [ "$RK_DRY_RUN" = "true" ]; then
cmd+=(--dry-run)
fi
if [ "$RK_FORCE" = "true" ]; then
cmd+=(--force)
fi
if [ -n "$RK_GROUP" ]; then
cmd+=(--group "$RK_GROUP")
fi
# ── prepare-specific flags ────────────────────────────────
if [ "$RK_COMMAND" = "prepare" ]; then
if [ "$RK_BUMP_TYPE" != "auto" ] && [ -n "$RK_BUMP_TYPE" ]; then
cmd+=(--bump "$RK_BUMP_TYPE")
fi
if [ -n "$RK_PRERELEASE" ]; then
cmd+=(--prerelease "$RK_PRERELEASE")
fi
fi
# ── publish-specific flags ────────────────────────────────
if [ "$RK_COMMAND" = "publish" ]; then
if [ -n "$RK_CHECK_URL" ]; then
cmd+=(--check-url "$RK_CHECK_URL")
fi
if [ -n "$RK_INDEX_URL" ]; then
cmd+=(--index-url "$RK_INDEX_URL")
fi
if [ -n "$RK_CONCURRENCY" ] && [ "$RK_CONCURRENCY" != "0" ]; then
cmd+=(--concurrency "$RK_CONCURRENCY")
fi
if [ -n "$RK_MAX_RETRIES" ] && [ "$RK_MAX_RETRIES" != "0" ]; then
cmd+=(--max-retries "$RK_MAX_RETRIES")
fi
fi
# ── Execute ───────────────────────────────────────────────
echo "::group::Running: ${cmd[*]}"
OUTPUT=$("${cmd[@]}" 2>&1) || EXIT_CODE=$?
echo "$OUTPUT"
echo "::endgroup::"
if [ "${EXIT_CODE:-0}" -ne 0 ]; then
echo "::error::releasekit $RK_COMMAND failed with exit code $EXIT_CODE"
exit "$EXIT_CODE"
fi
# ── Parse outputs ─────────────────────────────────────────
if [ "$RK_COMMAND" = "prepare" ]; then
PR_URL=$(echo "$OUTPUT" | sed -n 's/.*Release PR: //p' | tail -1)
if [ -n "$PR_URL" ]; then
echo "has_bumps=true" >> "$GITHUB_OUTPUT"
echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT"
else
echo "has_bumps=false" >> "$GITHUB_OUTPUT"
fi
fi
if [ "$RK_COMMAND" = "release" ]; then
RELEASE_URL=$(echo "$OUTPUT" | sed -n 's/.*release_url=//p' | tail -1)
if [ -n "$RELEASE_URL" ]; then
echo "release_url=$RELEASE_URL" >> "$GITHUB_OUTPUT"
fi
fi