Pulse/.github/workflows/create-release.yml

701 lines
27 KiB
YAML

name: Pulse Release Pipeline
# Optimized: parallel jobs, fast prerelease path
on:
workflow_dispatch:
inputs:
version:
description: 'Version number (e.g., 4.30.0)'
required: true
type: string
release_notes:
description: 'Release notes (markdown) - generated by Claude'
required: true
type: string
promoted_from_tag:
description: 'Stable only: prerelease tag being promoted (for example 6.0.0-rc.2)'
required: false
type: string
rollback_version:
description: 'Required: prior stable version to pin for rollback (for example 5.1.14 or v5.1.14)'
required: true
type: string
ga_date:
description: 'First stable v6.0.0 GA only: exact GA publish date (YYYY-MM-DD)'
required: false
type: string
v5_eos_date:
description: 'First stable v6.0.0 GA only: Pulse v5 end-of-support date (YYYY-MM-DD)'
required: false
type: string
hotfix_exception:
description: 'Stable only: bypass the 72-hour prerelease soak for urgent customer harm'
required: false
type: boolean
default: false
hotfix_reason:
description: 'Stable only: reason for hotfix soak exception'
required: false
type: string
draft_only:
description: 'Create draft release only (do not publish)'
required: false
type: boolean
default: false
concurrency:
group: release-${{ github.event.inputs.version || github.ref || github.run_id }}
cancel-in-progress: false
jobs:
# Combined version extraction and validation (saves a checkout)
prepare:
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
version: ${{ steps.extract.outputs.version }}
tag: ${{ steps.extract.outputs.tag }}
is_prerelease: ${{ steps.extract.outputs.is_prerelease }}
source_branch: ${{ steps.extract.outputs.source_branch }}
required_branch: ${{ steps.branch_policy.outputs.required_branch }}
promoted_from_tag: ${{ steps.promotion.outputs.promoted_from_tag }}
rollback_tag: ${{ steps.promotion.outputs.rollback_tag }}
rollback_command: ${{ steps.promotion.outputs.rollback_command }}
ga_date: ${{ steps.promotion.outputs.ga_date }}
v5_eos_date: ${{ steps.promotion.outputs.v5_eos_date }}
hotfix_exception: ${{ steps.promotion.outputs.hotfix_exception }}
hotfix_reason: ${{ steps.promotion.outputs.hotfix_reason }}
steps:
- name: Extract version
id: extract
run: |
VERSION=$(jq -r '.inputs.version // ""' "$GITHUB_EVENT_PATH" 2>/dev/null || echo "")
if [ -z "$VERSION" ]; then
echo "::error::workflow_dispatch must include a version input"
exit 1
fi
TAG="v${VERSION}"
IS_PRERELEASE="false"
if [[ "$VERSION" =~ -rc\.[0-9]+$ ]] || [[ "$VERSION" =~ -alpha\.[0-9]+$ ]] || [[ "$VERSION" =~ -beta\.[0-9]+$ ]]; then
IS_PRERELEASE="true"
echo "Detected prerelease version: ${VERSION}"
fi
if [[ "${GITHUB_REF}" != refs/heads/* ]]; then
echo "::error::Release workflow must be dispatched from a branch ref (current ref: ${GITHUB_REF})."
exit 1
fi
SOURCE_BRANCH="${GITHUB_REF_NAME}"
echo "tag=${TAG}" >> $GITHUB_OUTPUT
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "is_prerelease=${IS_PRERELEASE}" >> $GITHUB_OUTPUT
echo "source_branch=${SOURCE_BRANCH}" >> $GITHUB_OUTPUT
echo "Version: ${VERSION}, Tag: ${TAG}, Prerelease: ${IS_PRERELEASE}, Branch: ${SOURCE_BRANCH}"
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
sparse-checkout: |
VERSION
docs/release-control/control_plane.json
scripts/release_control/control_plane.py
scripts/release_control/repo_file_io.py
- name: Resolve required release branch
id: branch_policy
run: |
REQUIRED_BRANCH="$(python3 scripts/release_control/control_plane.py --branch-for-version "${{ steps.extract.outputs.version }}")"
if [ "${{ steps.extract.outputs.source_branch }}" != "$REQUIRED_BRANCH" ]; then
echo "::error::Invalid release line. Version ${{ steps.extract.outputs.version }} must run from ${REQUIRED_BRANCH}, but workflow ref is ${{ steps.extract.outputs.source_branch }}."
exit 1
fi
echo "required_branch=${REQUIRED_BRANCH}" >> "$GITHUB_OUTPUT"
echo "[OK] Governed release branch for ${{ steps.extract.outputs.version }} is ${REQUIRED_BRANCH}"
- name: Validate VERSION file
run: |
FILE_VERSION=$(cat VERSION | tr -d '\n')
REQUESTED_VERSION="${{ steps.extract.outputs.version }}"
if [ "$FILE_VERSION" != "$REQUESTED_VERSION" ]; then
echo "::error::VERSION file ($FILE_VERSION) does not match requested version ($REQUESTED_VERSION)."
echo "The VERSION file must be updated and committed before running release."
exit 1
fi
echo "[OK] VERSION file matches requested version ($REQUESTED_VERSION)"
- name: Validate promotion policy
id: promotion
env:
VERSION: ${{ steps.extract.outputs.version }}
TAG: ${{ steps.extract.outputs.tag }}
IS_PRERELEASE: ${{ steps.extract.outputs.is_prerelease }}
PROMOTED_FROM_TAG_INPUT: ${{ github.event.inputs.promoted_from_tag }}
ROLLBACK_VERSION_INPUT: ${{ github.event.inputs.rollback_version }}
GA_DATE_INPUT: ${{ github.event.inputs.ga_date }}
V5_EOS_DATE_INPUT: ${{ github.event.inputs.v5_eos_date }}
HOTFIX_EXCEPTION_INPUT: ${{ github.event.inputs.hotfix_exception }}
HOTFIX_REASON_INPUT: ${{ github.event.inputs.hotfix_reason }}
run: |
set -euo pipefail
git fetch --prune origin main "${REQUIRED_BRANCH}" --tags
RELEASE_NOTES_INPUT="$(jq -r '.inputs.release_notes // ""' "$GITHUB_EVENT_PATH")"
NOTES_FILE="$(mktemp)"
printf '%s\n' "$RELEASE_NOTES_INPUT" > "$NOTES_FILE"
HELPER_ARGS=(
--version "${VERSION}"
--promoted-from-tag "${PROMOTED_FROM_TAG_INPUT:-}"
--rollback-version "${ROLLBACK_VERSION_INPUT:-}"
--ga-date "${GA_DATE_INPUT:-}"
--v5-eos-date "${V5_EOS_DATE_INPUT:-}"
--hotfix-reason "${HOTFIX_REASON_INPUT:-}"
--release-notes-file "$NOTES_FILE"
)
if [ "${HOTFIX_EXCEPTION_INPUT:-false}" = "true" ]; then
HELPER_ARGS+=(--hotfix-exception)
fi
python3 scripts/release_control/resolve_release_promotion.py "${HELPER_ARGS[@]}" > "$RUNNER_TEMP/promotion-metadata.out"
rm -f "$NOTES_FILE"
{
cat "$RUNNER_TEMP/promotion-metadata.out"
} >> "$GITHUB_OUTPUT"
echo "[OK] Promotion policy validated for ${TAG}"
# Frontend checks run in parallel with backend tests
frontend_checks:
needs: prepare
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: 'frontend-modern/package-lock.json'
- name: Install dependencies
run: npm --prefix frontend-modern ci
- name: Lint frontend
run: npm --prefix frontend-modern run lint
- name: Audit header composition
run: npm --prefix frontend-modern run lint:headers
- name: Check frontend copy-paste duplication
run: npm --prefix frontend-modern run lint:cpd
# Backend tests run in parallel with frontend checks
backend_tests:
needs: prepare
runs-on: ubuntu-latest
timeout-minutes: 30
env:
FRONTEND_DIST: frontend-modern/dist
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: 'frontend-modern/package-lock.json'
- name: Restore frontend build cache
id: frontend-cache
uses: actions/cache@v4
with:
path: frontend-modern/dist
key: frontend-build-${{ hashFiles('frontend-modern/package-lock.json', 'frontend-modern/src/**/*', 'frontend-modern/index.html', 'frontend-modern/postcss.config.cjs', 'frontend-modern/tailwind.config.cjs') }}
- name: Build frontend (if not cached)
if: steps.frontend-cache.outputs.cache-hit != 'true'
run: |
npm --prefix frontend-modern ci
npm --prefix frontend-modern run build
- name: Copy frontend to embed location
run: |
rm -rf internal/api/frontend-modern
mkdir -p internal/api/frontend-modern
cp -r frontend-modern/dist internal/api/frontend-modern/
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.25.7'
cache: true
- name: Run backend tests
env:
PULSE_DATA_DIR: /tmp/pulse-test-data
run: make test
# Docker build - amd64 only for prereleases, multi-arch for stable
docker_build:
needs: prepare
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up QEMU
if: needs.prepare.outputs.is_prerelease != 'true'
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker image (verify only)
uses: docker/build-push-action@v6
with:
context: .
target: runtime
# amd64 only for prereleases (faster), multi-arch for stable releases
platforms: ${{ needs.prepare.outputs.is_prerelease == 'true' && 'linux/amd64' || 'linux/amd64,linux/arm64' }}
push: false # Don't push staging images, just verify build
provenance: false
cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/pulse:buildcache
cache-to: type=registry,ref=ghcr.io/${{ github.repository_owner }}/pulse:buildcache,mode=max
build-args: |
PULSE_LICENSE_PUBLIC_KEY=${{ secrets.PULSE_LICENSE_PUBLIC_KEY }}
VERSION=${{ needs.prepare.outputs.tag }}
- name: Build Pulse agent image (verify only)
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
target: agent_runtime
platforms: ${{ needs.prepare.outputs.is_prerelease == 'true' && 'linux/amd64' || 'linux/amd64,linux/arm64' }}
push: false
provenance: false
cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/pulse-agent:buildcache
cache-to: type=registry,ref=ghcr.io/${{ github.repository_owner }}/pulse-agent:buildcache,mode=max
build-args: |
PULSE_LICENSE_PUBLIC_KEY=${{ secrets.PULSE_LICENSE_PUBLIC_KEY }}
VERSION=${{ needs.prepare.outputs.tag }}
# Integration tests - skipped for prereleases (they've been tested in CI)
integration_tests:
needs:
- prepare
- backend_tests
if: ${{ needs.prepare.outputs.is_prerelease != 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 45
env:
FRONTEND_DIST: frontend-modern/dist
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: 'frontend-modern/package-lock.json'
- name: Restore frontend build cache
uses: actions/cache@v4
with:
path: frontend-modern/dist
key: frontend-build-${{ hashFiles('frontend-modern/package-lock.json', 'frontend-modern/src/**/*', 'frontend-modern/index.html', 'frontend-modern/postcss.config.cjs', 'frontend-modern/tailwind.config.cjs') }}
- name: Copy frontend to embed location
run: |
rm -rf internal/api/frontend-modern
mkdir -p internal/api/frontend-modern
cp -r frontend-modern/dist internal/api/frontend-modern/
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.25.7'
cache: true
- name: Build Pulse Docker image for integration tests
run: docker build -t pulse:test --target runtime .
- name: Build mock GitHub server
run: docker build -t pulse-mock-github:test tests/integration/mock-github-server
- name: Install integration test dependencies
working-directory: tests/integration
run: |
npm ci
npx playwright install --with-deps chromium
- name: Run integration tests
working-directory: tests/integration
env:
MOCK_CHECKSUM_ERROR: "false"
MOCK_NETWORK_ERROR: "false"
MOCK_RATE_LIMIT: "false"
MOCK_STALE_RELEASE: "false"
PULSE_MULTI_TENANT_ENABLED: "true"
PULSE_E2E_ENTITLEMENT_PROFILE: "multi-tenant"
PULSE_E2E_BOOTSTRAP_TOKEN: 0123456789abcdef0123456789abcdef0123456789abcdef
run: |
docker compose -f docker-compose.test.yml up -d
echo "Waiting for services to be healthy..."
timeout 60 sh -c 'until docker inspect --format="{{json .State.Health.Status}}" pulse-mock-github | grep -q "healthy"; do sleep 2; done'
timeout 60 sh -c 'until docker inspect --format="{{json .State.Health.Status}}" pulse-test-server | grep -q "healthy"; do sleep 2; done'
for i in 1 2 3 4 5; do
if curl -f -s http://localhost:7655/api/health > /dev/null 2>&1; then
echo "Pulse server is reachable"
break
elif [ $i -eq 5 ]; then
docker logs pulse-test-server || true
exit 1
fi
sleep 2
done
node scripts/apply-entitlement-profile.mjs
echo "Running update API route smoke check..."
STATUS=$(curl -s -o /tmp/update-status.json -w "%{http_code}" http://localhost:7655/api/updates/status || true)
echo "Update status endpoint returned HTTP ${STATUS}"
case "${STATUS}" in
200|401|403)
;;
*)
echo "Unexpected response from /api/updates/status"
cat /tmp/update-status.json || true
exit 1
;;
esac
echo "Running multi-tenant E2E suite..."
npx playwright test tests/03-multi-tenant.spec.ts --project=chromium --reporter=list
docker compose -f docker-compose.test.yml down -v
- name: Cleanup
if: always()
working-directory: tests/integration
run: docker compose -f docker-compose.test.yml down -v || true
# Create release after all checks pass
create_release:
needs:
- prepare
- frontend_checks
- backend_tests
- docker_build
- integration_tests
# Run if integration_tests passed OR was skipped (prereleases)
if: ${{ always() && needs.frontend_checks.result == 'success' && needs.backend_tests.result == 'success' && needs.docker_build.result == 'success' && (needs.integration_tests.result == 'success' || needs.integration_tests.result == 'skipped') }}
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
outputs:
release_id: ${{ steps.create_release.outputs.release_id }}
release_url: ${{ steps.create_release.outputs.release_url }}
target_commitish: ${{ github.sha }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.25.7'
cache: true
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: 'frontend-modern/package-lock.json'
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y zip
- name: Set up Helm
uses: azure/setup-helm@v4
with:
version: 'v3.15.2'
- name: Build release artifacts
run: |
echo "Building release ${{ needs.prepare.outputs.tag }}..."
./scripts/build-release.sh ${{ needs.prepare.outputs.version }}
env:
PULSE_LICENSE_PUBLIC_KEY: ${{ secrets.PULSE_LICENSE_PUBLIC_KEY }}
- name: Post-build health check
run: |
if [ -x ./pulse ]; then
./pulse --version
elif [ -x ./cmd/pulse/pulse ]; then
./cmd/pulse/pulse --version
fi
- name: Prepare release notes
id: generate_notes
run: |
VERSION="${{ needs.prepare.outputs.version }}"
RELEASE_NOTES_INPUT=$(jq -r '.inputs.release_notes // ""' "$GITHUB_EVENT_PATH" 2>/dev/null || echo "")
NOTES_FILE=$(mktemp)
if [ -n "$RELEASE_NOTES_INPUT" ]; then
printf "%s\n" "$RELEASE_NOTES_INPUT" > "$NOTES_FILE"
else
echo "Release $VERSION" > "$NOTES_FILE"
echo "" >> "$NOTES_FILE"
echo "See commit history for changes." >> "$NOTES_FILE"
fi
{
echo ""
echo "## Installation"
echo ""
echo "**Docker (recommended):**"
echo '```bash'
echo "docker pull rcourtman/pulse:${VERSION}"
echo '```'
echo ""
echo "**Docker Compose:**"
echo "Update your \`docker-compose.yml\` to use \`rcourtman/pulse:${VERSION}\`"
echo ""
echo "See the [Installation Guide](https://github.com/rcourtman/Pulse#installation) for complete setup instructions."
echo ""
echo "## Promotion Metadata"
echo ""
echo "- Promotion channel: ${{ needs.prepare.outputs.is_prerelease == 'true' && 'rc' || 'stable' }}"
echo "- Candidate stable tag: ${{ needs.prepare.outputs.tag }}"
if [ -n "${{ needs.prepare.outputs.promoted_from_tag }}" ]; then
echo "- Promoted prerelease tag: ${{ needs.prepare.outputs.promoted_from_tag }}"
else
echo "- Promoted prerelease tag: n/a"
fi
echo "- Rollback target: ${{ needs.prepare.outputs.rollback_tag }}"
echo "- Rollback command: \`${{ needs.prepare.outputs.rollback_command }}\`"
if [ -n "${{ needs.prepare.outputs.ga_date }}" ]; then
echo "- Planned GA date: ${{ needs.prepare.outputs.ga_date }}"
fi
if [ -n "${{ needs.prepare.outputs.v5_eos_date }}" ]; then
echo "- Planned v5 end-of-support date: ${{ needs.prepare.outputs.v5_eos_date }}"
fi
echo "- Hotfix exception: ${{ needs.prepare.outputs.hotfix_exception }}"
if [ -n "${{ needs.prepare.outputs.hotfix_reason }}" ]; then
echo "- Hotfix reason: ${{ needs.prepare.outputs.hotfix_reason }}"
fi
} >> "$NOTES_FILE"
echo "notes_file=${NOTES_FILE}" >> $GITHUB_OUTPUT
- name: Create tag
env:
GH_TOKEN: ${{ github.token }}
run: |
TAG="${{ needs.prepare.outputs.tag }}"
HEAD_SHA=$(git rev-parse HEAD)
REMOTE_TAG_SHA=$(git ls-remote --tags origin "refs/tags/${TAG}" | awk '{print $1}')
if [ -n "$REMOTE_TAG_SHA" ]; then
REMOTE_COMMIT_SHA=$(git ls-remote --tags origin "refs/tags/${TAG}^{}" | awk '{print $1}')
[ -z "$REMOTE_COMMIT_SHA" ] && REMOTE_COMMIT_SHA="$REMOTE_TAG_SHA"
if [ "$REMOTE_COMMIT_SHA" = "$HEAD_SHA" ]; then
echo "Tag ${TAG} already exists and points to HEAD - continuing"
else
echo "::error::Tag ${TAG} already exists but points to ${REMOTE_COMMIT_SHA}, not HEAD (${HEAD_SHA}). Delete the tag first: git push origin --delete ${TAG}"
exit 1
fi
else
echo "Creating tag ${TAG}..."
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -a "${TAG}" -m "Release ${TAG}"
git push origin "${TAG}"
fi
- name: Create draft release
id: create_release
env:
GH_TOKEN: ${{ github.token }}
run: |
TAG="${{ needs.prepare.outputs.tag }}"
NOTES_FILE="${{ steps.generate_notes.outputs.notes_file }}"
IS_PRERELEASE="${{ needs.prepare.outputs.is_prerelease }}"
EXISTING_RELEASE=$(gh api "repos/${{ github.repository }}/releases/tags/${TAG}" 2>/dev/null || echo "")
RELEASE_ID=$(echo "$EXISTING_RELEASE" | jq -r '.id // empty')
if [ -n "$RELEASE_ID" ]; then
RELEASE_URL=$(echo "$EXISTING_RELEASE" | jq -r '.html_url')
IS_DRAFT=$(echo "$EXISTING_RELEASE" | jq -r '.draft')
if [ "$IS_DRAFT" = "true" ]; then
echo "Updating existing draft release for ${TAG}"
gh api "repos/${{ github.repository }}/releases/${RELEASE_ID}" \
-X PATCH \
-F body="$(cat $NOTES_FILE)" \
-F prerelease=${IS_PRERELEASE} > /dev/null
else
echo "::error::Published release already exists for ${TAG}."
exit 1
fi
else
echo "Creating draft release for ${TAG}..."
RELEASE_JSON=$(gh api "repos/${{ github.repository }}/releases" \
-X POST \
-F tag_name="${TAG}" \
-F name="Pulse ${TAG}" \
-F body="$(cat $NOTES_FILE)" \
-F draft=true \
-F prerelease=${IS_PRERELEASE})
RELEASE_ID=$(echo "$RELEASE_JSON" | jq -r '.id')
RELEASE_URL=$(echo "$RELEASE_JSON" | jq -r '.html_url')
fi
rm -f "$NOTES_FILE"
echo "release_url=${RELEASE_URL}" >> $GITHUB_OUTPUT
echo "release_id=${RELEASE_ID}" >> $GITHUB_OUTPUT
echo "[OK] Draft release: ${TAG} (ID: ${RELEASE_ID})"
- name: Upload checksums
env:
GH_TOKEN: ${{ github.token }}
run: |
TAG="${{ needs.prepare.outputs.tag }}"
gh release upload "${TAG}" release/checksums.txt --clobber
gh release upload "${TAG}" release/*.sha256 --clobber
- name: Upload release assets
env:
GH_TOKEN: ${{ github.token }}
run: |
TAG="${{ needs.prepare.outputs.tag }}"
gh release upload "${TAG}" release/*.tar.gz --clobber
gh release upload "${TAG}" release/*.zip --clobber
if ls release/*.tgz 1> /dev/null 2>&1; then
gh release upload "${TAG}" release/*.tgz --clobber
fi
for bare_agent in \
release/pulse-agent-linux-amd64 \
release/pulse-agent-linux-arm64 \
release/pulse-agent-linux-armv7 \
release/pulse-agent-linux-armv6 \
release/pulse-agent-linux-386 \
release/pulse-agent-freebsd-amd64 \
release/pulse-agent-freebsd-arm64 \
release/pulse-agent-windows-amd64.exe \
release/pulse-agent-windows-arm64.exe \
release/pulse-agent-windows-386.exe; do
if [ -f "${bare_agent}" ]; then
gh release upload "${TAG}" "${bare_agent}" --clobber
fi
done
gh release upload "${TAG}" release/install.sh --clobber
if [ -f release/install.ps1 ]; then
gh release upload "${TAG}" release/install.ps1 --clobber
fi
gh release upload "${TAG}" release/install-docker.sh --clobber
gh release upload "${TAG}" release/pulse-auto-update.sh --clobber
- name: Publish release
if: ${{ github.event.inputs.draft_only != 'true' }}
env:
GH_TOKEN: ${{ github.token }}
run: |
TAG="${{ needs.prepare.outputs.tag }}"
RELEASE_ID="${{ steps.create_release.outputs.release_id }}"
IS_PRERELEASE="${{ needs.prepare.outputs.is_prerelease }}"
if [ "$IS_PRERELEASE" = "true" ]; then
gh api "repos/${{ github.repository }}/releases/${RELEASE_ID}" \
-X PATCH -F draft=false -F make_latest=false
echo "[OK] Published as prerelease: ${TAG}"
else
gh api "repos/${{ github.repository }}/releases/${RELEASE_ID}" \
-X PATCH -F draft=false -F make_latest=true
echo "[OK] Published as latest: ${TAG}"
fi
- name: Skip publish (draft only)
if: ${{ github.event.inputs.draft_only == 'true' }}
run: 'echo "Draft-only mode: ${{ steps.create_release.outputs.release_url }}"'
- name: Trigger Docker image publish
if: ${{ github.event.inputs.draft_only != 'true' }}
continue-on-error: true
env:
GH_TOKEN: ${{ secrets.WORKFLOW_PAT }}
run: |
gh workflow run publish-docker.yml -f tag="${{ needs.prepare.outputs.tag }}"
echo "[OK] Docker publish workflow dispatched"
- name: Trigger demo server update
if: ${{ github.event.inputs.draft_only != 'true' && needs.prepare.outputs.is_prerelease != 'true' }}
continue-on-error: true
env:
GH_TOKEN: ${{ secrets.WORKFLOW_PAT }}
run: |
gh workflow run update-demo-server.yml -f tag="${{ needs.prepare.outputs.tag }}"
echo "[OK] Demo server update dispatched"
- name: Skip demo server update for prerelease
if: ${{ github.event.inputs.draft_only != 'true' && needs.prepare.outputs.is_prerelease == 'true' }}
run: echo "Skipping demo server update for prerelease tag ${{ needs.prepare.outputs.tag }}"
- name: Summary
run: |
echo "[SUCCESS] Release published!"
echo "Release: ${{ needs.prepare.outputs.tag }}"
echo "URL: ${{ steps.create_release.outputs.release_url }}"
validate_release_assets:
needs:
- prepare
- create_release
uses: ./.github/workflows/validate-release-assets.yml
secrets: inherit
with:
tag: ${{ needs.prepare.outputs.tag }}
version: ${{ needs.prepare.outputs.version }}
release_id: ${{ needs.create_release.outputs.release_id }}
draft: false
target_commitish: ${{ needs.create_release.outputs.target_commitish }}