Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Publish Release

on:
push:
branches:
- 3.x
workflow_dispatch:

permissions:
contents: read
pull-requests: read

jobs:
publish:
name: Publish release
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: read
# Only run if this is a release commit created by script/release.
# Match either the direct commit message from script/release
# ("release X.Y.Z") or the merge-commit body containing the release
# PR title ("Release X.Y.Z"), so a normal PR merge still triggers.
# Also always run on manual workflow_dispatch.
if: |
github.event_name == 'workflow_dispatch' ||
startsWith(github.event.head_commit.message, 'release ') ||
contains(github.event.head_commit.message, 'Release ')
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
persist-credentials: true

- name: Set up Ruby
uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b
with:
ruby-version: 4.0
bundler-cache: true

- name: Harden runner
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c
with:
egress-policy: audit

- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

- name: Publish release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
script/publish
41 changes: 41 additions & 0 deletions .github/workflows/push_gem.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Push Gem

on:
push:
tags:
- v*
workflow_dispatch:

permissions:
contents: read

jobs:
push:
if: github.repository == 'ViewComponent/view_component'
runs-on: ubuntu-latest

permissions:
contents: write
id-token: write

# GitHub environment configured on RubyGems
environment: release

steps:
# Set up
- name: Harden Runner
uses: step-security/harden-runner@v2
with:
egress-policy: audit

- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
ruby-version: 4.0

# Release
- uses: rubygems/release-gem@v1
133 changes: 133 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
name: Release

on:
workflow_dispatch:
inputs:
major:
description: 'Major version'
required: true
type: string
minor:
description: 'Minor version'
required: true
type: string
patch:
description: 'Patch version'
required: true
type: string
pre:
description: 'Pre-release version (leave empty for release)'
required: false
type: string

permissions:
contents: read
pull-requests: read

jobs:
validate-inputs:
name: Validate version inputs
runs-on: ubuntu-latest
steps:
- name: Validate inputs are numeric
env:
MAJOR: ${{ inputs.major }}
MINOR: ${{ inputs.minor }}
PATCH: ${{ inputs.patch }}
PRE: ${{ inputs.pre }}
run: |
if ! [[ "$MAJOR" =~ ^[0-9]+$ ]]; then
echo "Error: major version must be numeric"
exit 1
fi
if ! [[ "$MINOR" =~ ^[0-9]+$ ]]; then
echo "Error: minor version must be numeric"
exit 1
fi
if ! [[ "$PATCH" =~ ^[0-9]+$ ]]; then
echo "Error: patch version must be numeric"
exit 1
fi
if [ -n "$PRE" ] && ! [[ "$PRE" =~ ^[a-zA-Z0-9.-]+$ ]]; then
echo "Error: pre-release version contains invalid characters"
exit 1
fi

prepare-release:
name: Get current version
needs: validate-inputs
runs-on: ubuntu-latest
outputs:
current-major: ${{ steps.current-version.outputs.major }}
current-minor: ${{ steps.current-version.outputs.minor }}
current-patch: ${{ steps.current-version.outputs.patch }}
current-pre: ${{ steps.current-version.outputs.pre }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false

- name: Read current version
id: current-version
run: |
major=$(grep "MAJOR = " lib/view_component/version.rb | grep -o "[0-9]\+")
minor=$(grep "MINOR = " lib/view_component/version.rb | grep -o "[0-9]\+")
patch=$(grep "PATCH = " lib/view_component/version.rb | grep -o "[0-9]\+")
pre=$(grep "PRE = " lib/view_component/version.rb | grep -o "\"[^\"]*\"" | tr -d '"')
echo "major=$major" >> $GITHUB_OUTPUT
echo "minor=$minor" >> $GITHUB_OUTPUT
echo "patch=$patch" >> $GITHUB_OUTPUT
echo "pre=$pre" >> $GITHUB_OUTPUT

- name: Show current version
env:
CURRENT_MAJOR: ${{ steps.current-version.outputs.major }}
CURRENT_MINOR: ${{ steps.current-version.outputs.minor }}
CURRENT_PATCH: ${{ steps.current-version.outputs.patch }}
CURRENT_PRE: ${{ steps.current-version.outputs.pre }}
INPUT_MAJOR: ${{ inputs.major }}
INPUT_MINOR: ${{ inputs.minor }}
INPUT_PATCH: ${{ inputs.patch }}
INPUT_PRE: ${{ inputs.pre }}
run: |
current="$CURRENT_MAJOR.$CURRENT_MINOR.$CURRENT_PATCH"
[ -n "$CURRENT_PRE" ] && current="$current-$CURRENT_PRE"
echo "Current version: $current"
release="$INPUT_MAJOR.$INPUT_MINOR.$INPUT_PATCH"
[ -n "$INPUT_PRE" ] && release="$release-$INPUT_PRE"
echo "Release version: $release"

release:
name: Create release
needs: [prepare-release, validate-inputs]
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
persist-credentials: true

- name: Set up Ruby
uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b
with:
ruby-version: 4.0
bundler-cache: true

- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

- name: Create release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MAJOR: ${{ inputs.major }}
MINOR: ${{ inputs.minor }}
PATCH: ${{ inputs.patch }}
PRE: ${{ inputs.pre }}
run: |
script/release "$MAJOR" "$MINOR" "$PATCH" "$PRE"
94 changes: 88 additions & 6 deletions script/publish
Original file line number Diff line number Diff line change
@@ -1,10 +1,92 @@
#!/usr/bin/env bash

set -e

# Check GitHub permissions
check_github_permissions() {
if ! gh auth status > /dev/null 2>&1; then
echo "Error: not authenticated with GitHub. Run 'gh auth login'"
exit 1
fi

# Check if user has push access to the repo
if ! gh repo view ViewComponent/view_component > /dev/null 2>&1; then
echo "Error: cannot access ViewComponent/view_component repository"
exit 1
fi

# Skip the push-permission probe when running in GitHub Actions.
# GET /repos/:owner/:repo only returns `.permissions` for user/OAuth
# tokens — for the workflow's GITHUB_TOKEN the field is absent, so
# the probe would incorrectly report no push access even when the
# workflow has `contents: write`. In CI we rely on the job's declared
# permissions; failures will surface from the actual git push / gh
# release create calls below with clear messages.
if [ -n "${GITHUB_ACTIONS:-}" ]; then
return 0
fi

# Check if user can create releases (requires write access)
# Attempt a dry-run by checking repo permissions
local perms=$(gh api repos/ViewComponent/view_component -q '.permissions.push // false')
if [ "$perms" != "true" ]; then
echo "Error: insufficient permissions to create releases in ViewComponent/view_component"
echo "You need at least 'push' (write) access to the repository"
exit 1
fi
}

# Get version from version.rb
major=$(grep "MAJOR = " lib/view_component/version.rb | grep -o "[0-9]\+")
minor=$(grep "MINOR = " lib/view_component/version.rb | grep -o "[0-9]\+")
patch=$(grep "PATCH = " lib/view_component/version.rb | grep -o "[0-9]\+")
version="$major.$minor.$patch"
tag="v$version"

# Check permissions before proceeding
check_github_permissions

# Create and push git tag (idempotent so re-running after a partial
# release doesn't abort on an already-existing tag)
if ! git rev-parse --quiet --verify "refs/tags/$tag" > /dev/null; then
git tag "$tag"
fi
git push origin "$tag" || true

# Extract changelog for this version
# Find the section for this version and capture until the next ## heading
changelog=$(sed -n "/^## $version$/,/^## /p" docs/CHANGELOG.md | sed '$ d')

# Publish gem
bundle exec rake release
# this step has been replaced by .github/workflows/push_gem.yml

# Publish updated docs — but only from main. gh-pages hosts a single
# docs site, so publishing from 3.x would clobber the current (4.x)
# docs. 3.x is a maintenance branch: skip gh-pages here.
current_branch=$(git symbolic-ref --short HEAD 2>/dev/null || echo "")
if [ "$current_branch" = "main" ]; then
git branch -D gh-pages 2>/dev/null || true
git checkout -b gh-pages main
git push origin gh-pages --force
git checkout main
else
echo "Skipping gh-pages publish (current branch is '$current_branch', not 'main')"
fi

# Create GitHub release
gh release create "$tag" \
--title "$version" \
--notes "$changelog" \
--repo ViewComponent/view_component

# Publish updated docs
git branch -D gh-pages
git checkout -b gh-pages main
git push origin gh-pages --force
git checkout main
# Kick off the Push Gem workflow explicitly. GitHub Actions does not
# trigger downstream workflows for events driven by GITHUB_TOKEN — so
# the tag push above will not fire push_gem.yml on its own when this
# script runs inside the publish-release workflow. Dispatch it here so
# 'script/publish' always results in a gem being pushed to RubyGems.
#
# Dispatch against the tag ref (not main) because the 'release'
# environment restricts deployments to v* tags.
gh workflow run push_gem.yml \
--repo ViewComponent/view_component \
--ref "$tag" || true
Loading
Loading