Skip to content

test: fix ReleaseWorkflowTests to handle run steps without uses key #15

test: fix ReleaseWorkflowTests to handle run steps without uses key

test: fix ReleaseWorkflowTests to handle run steps without uses key #15

Workflow file for this run

# Release Workflow
#
# This workflow automates the release process when semantic version tags are pushed.
# It reuses build artifacts from the build workflow (must merge to main first), validates
# the version, creates GitHub releases, and publishes to package managers.
#
# IMPORTANT: Tags must be on commits that have been merged to main and built by build.yml
#
# Triggers: Push of semantic version tag (e.g., v1.2.3)
# Performance Target: ≤10 minutes total (excluding manual approval)
# Artifacts: Reuses build artifacts, creates release packages
#
# Manual Testing on PR:
# 1. Run Build workflow manually on your PR branch first
# 2. Note the commit SHA from the build run
# 3. Use Actions tab → Release → Run workflow
# - Select your PR branch
# - Enter test version (e.g., v0.0.1-test)
# - Check 'Skip Homebrew' to avoid tap updates
# - Check 'Dry run' to skip actual release creation
# 4. Review logs to verify workflow logic
#
# Production Release:
# 1. Merge PR to main → triggers build.yml automatically
# 2. Wait for build to complete successfully
# 3. Tag the merge commit: git tag v1.0.0 && git push origin v1.0.0
# 4. Release workflow triggers automatically
name: Release
on:
push:
tags:
- 'v*.*.*' # Semantic version tags only (e.g., v1.2.3, v0.1.0)
workflow_dispatch: # Allow manual triggering for testing
inputs:
tag:
description: 'Version tag to release (e.g., v0.0.1-test for testing)'
required: true
type: string
skip_homebrew:
description: 'Skip Homebrew publication (for PR testing)'
required: false
type: boolean
default: false
dry_run:
description: 'Dry run - skip release creation (logs only)'
required: false
type: boolean
default: false
# Prevent concurrent releases to avoid conflicts
# Do not cancel in-progress releases as they involve publishing to external services
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
# Workflow-level permissions
permissions:
contents: write # Required for creating releases and reading repo
actions: read # Required for downloading artifacts from build workflow
packages: write # Required for GitHub Packages (bottles)
issues: write # Required for creating Winget/Chocolatey publication issues
jobs:
select-runner:
name: Select Linux Runner
runs-on: ubuntu-latest
outputs:
linux: ${{ steps.detect.outputs.linux }}
steps:
- name: Detect available self-hosted runners
id: detect
env:
GH_TOKEN: ${{ secrets.RUNNER_QUERY_TOKEN }}
run: |
echo "🔍 Checking for online self-hosted Linux runners"
# Fallback to ubuntu-latest if token not available or API fails
if [ -z "$GH_TOKEN" ]; then
echo "⚠️ RUNNER_QUERY_TOKEN secret not configured; using ubuntu-latest"
echo "linux=ubuntu-latest" >> "$GITHUB_OUTPUT"
exit 0
fi
if ! RUNNERS=$(gh api repos/${{ github.repository }}/actions/runners --paginate 2>&1); then
echo "⚠️ Unable to query runner API; using ubuntu-latest"
echo "Error: $RUNNERS"
echo "linux=ubuntu-latest" >> "$GITHUB_OUTPUT"
exit 0
fi
# Debug: show what we got
echo "📊 Runner API response:"
echo "$RUNNERS" | jq -r '.runners[]? | " - \(.name): status=\(.status), os=\(.os), labels=\(.labels | map(.name) | join(","))"'
# Match runners with status=online, os=Linux (case-insensitive), and self-hosted label
LINUX_COUNT=$(echo "$RUNNERS" | jq '[.runners[]? | select(.status == "online" and (.os | ascii_downcase) == "linux" and (.labels[]?.name == "self-hosted"))] | length')
if [ "$LINUX_COUNT" -gt 0 ]; then
echo "🤖 Found $LINUX_COUNT online self-hosted Linux runner(s); selecting self-hosted"
echo "linux=self-hosted" >> "$GITHUB_OUTPUT"
else
echo "☁️ No online self-hosted Linux runners found; using ubuntu-latest"
echo "linux=ubuntu-latest" >> "$GITHUB_OUTPUT"
fi
# Job 1: Validate Version and Find Artifacts
# Extract/validate version, ensure tag is on main, find build artifacts
# Performance Target: <2 minutes
validate-version:
name: Validate Version & Find Artifacts
needs: select-runner
runs-on: ${{ needs.select-runner.outputs.linux }}
outputs:
version: ${{ steps.extract.outputs.version }}
commit-sha: ${{ steps.validate.outputs.commit-sha }}
build-run-id: ${{ steps.find-artifacts.outputs.run-id }}
steps:
- name: Install GitHub CLI (self-hosted runner)
run: |
if ! command -v gh &> /dev/null; then
echo "📦 Installing GitHub CLI..."
type -p curl >/dev/null || (sudo apt update && sudo apt install curl -y)
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
sudo apt update
sudo apt install gh -y
echo "✅ GitHub CLI installed"
else
echo "✅ GitHub CLI already installed"
fi
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch all history for release notes
- name: Extract version from tag
id: extract
run: |
# Get tag name (either from push event or manual input)
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
TAG_NAME="${{ github.event.inputs.tag }}"
else
TAG_NAME="${GITHUB_REF#refs/tags/}"
fi
# Remove 'v' prefix to get version
VERSION="${TAG_NAME#v}"
echo "📦 Tag: $TAG_NAME"
echo "🔢 Version: $VERSION"
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Validate semantic version format
run: |
VERSION="${{ steps.extract.outputs.version }}"
# Allow semver with optional pre-release/build metadata
# Valid formats: 1.2.3, 1.2.3-beta.1, 1.2.3+build.123, 1.2.3-rc.1+build.456
if ! echo "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$'; then
echo "❌ Error: Invalid semantic version format: $VERSION"
echo "Expected format: MAJOR.MINOR.PATCH with optional pre-release and build metadata"
echo "Examples: 1.2.3, 0.1.0-beta.1, 1.0.0-rc.1+build.123"
exit 1
fi
echo "✅ Valid version format: $VERSION"
- name: Validate tag is on main branch
id: validate
run: |
# Get the commit SHA for this tag
COMMIT_SHA=$(git rev-list -n 1 ${{ github.ref }})
echo "commit-sha=$COMMIT_SHA" >> $GITHUB_OUTPUT
echo "📍 Tag commit: $COMMIT_SHA"
# For manual testing, allow any branch
# For production tag push, enforce main branch only
if [ "${{ github.event_name }}" = "push" ]; then
# Check if this commit is on main branch
if ! git branch -r --contains $COMMIT_SHA | grep -q "origin/main"; then
echo "❌ Error: Tag must be on a commit that exists on main branch"
echo "Please merge to main first, then tag the merge commit"
exit 1
fi
echo "✅ Tag is on main branch"
else
echo "⚠️ Manual dispatch: Skipping main branch check"
echo "✅ Running on branch: ${{ github.ref_name }}"
fi
- name: Check for duplicate version in releases
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION="${{ steps.extract.outputs.version }}"
# Check if this version already exists as a release
if gh release view "v$VERSION" &>/dev/null; then
# For test versions, just warn instead of failing
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && echo "$VERSION" | grep -q -- "-"; then
echo "⚠️ Warning: Release v$VERSION already exists (test version)"
echo "💡 Consider using a different test version or deleting the existing one"
else
echo "❌ Error: Release v$VERSION already exists"
echo "Please use a new version number"
exit 1
fi
else
echo "✅ Version v$VERSION is unique"
fi
- name: Find build workflow artifacts
id: find-artifacts
env:
GH_TOKEN: ${{ github.token }}
run: |
COMMIT_SHA="${{ steps.validate.outputs.commit-sha }}"
echo "🔍 Looking for build workflow run for commit $COMMIT_SHA..."
# Find the build workflow run for this commit
RUN_ID=$(gh run list \
--workflow=build.yml \
--commit=$COMMIT_SHA \
--status=success \
--json databaseId \
--jq '.[0].databaseId')
if [ -z "$RUN_ID" ] || [ "$RUN_ID" = "null" ]; then
echo "❌ Error: No successful build workflow run found for commit $COMMIT_SHA"
echo ""
echo "The release workflow reuses artifacts from the build workflow to avoid redundancy."
echo "Please ensure:"
echo " 1. Code has been merged to main branch"
echo " 2. Build workflow has completed successfully"
echo " 3. Tag is created on the same commit that was built"
echo ""
echo "Typical workflow:"
echo " 1. Merge PR to main → triggers build.yml"
echo " 2. Wait for build to complete"
echo " 3. Tag the merge commit: git tag v1.0.0 && git push origin v1.0.0"
exit 1
fi
echo "run-id=$RUN_ID" >> $GITHUB_OUTPUT
echo "✅ Found build workflow run: $RUN_ID"
echo "🔗 https://github.com/${{ github.repository }}/actions/runs/$RUN_ID"
# Job 2: Download Build Artifacts
# Reuse artifacts from the build workflow to avoid redundant builds
# Performance Target: <1 minute
download-artifacts:
name: Download Build Artifacts
runs-on: ${{ needs.select-runner.outputs.linux }}
needs: [select-runner, validate-version]
steps:
- name: Install GitHub CLI (self-hosted runner)
run: |
if ! command -v gh &> /dev/null; then
echo "📦 Installing GitHub CLI..."
type -p curl >/dev/null || (sudo apt update && sudo apt install curl -y)
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
sudo apt update
sudo apt install gh -y
echo "✅ GitHub CLI installed"
else
echo "✅ GitHub CLI already installed"
fi
- name: Download artifacts from build workflow
env:
GH_TOKEN: ${{ github.token }}
run: |
RUN_ID="${{ needs.validate-version.outputs.build-run-id }}"
echo "📥 Downloading artifacts from build run $RUN_ID..."
# Download all three platform artifacts
gh run download $RUN_ID \
--repo ${{ github.repository }} \
--dir ./artifacts
echo "✅ Downloaded artifacts:"
ls -lah ./artifacts/
# Verify all expected artifacts exist
for artifact in ten-second-tom-osx-x64 ten-second-tom-osx-arm64 ten-second-tom-win-x64; do
if [ ! -d "./artifacts/$artifact" ]; then
echo "❌ Error: Expected artifact '$artifact' not found"
exit 1
fi
echo " ✅ $artifact"
done
- name: Upload artifacts for release jobs
uses: actions/upload-artifact@v4
with:
name: release-artifacts
path: ./artifacts/
retention-days: 90
# Job 3: Create GitHub Release
# Generate release notes and create GitHub release with all binaries
# Performance Target: ≤2 minutes
create-github-release:
name: Create GitHub Release
runs-on: ${{ needs.select-runner.outputs.linux }}
needs: [select-runner, validate-version, download-artifacts]
outputs:
release-id: ${{ steps.create-release.outputs.id }}
upload-url: ${{ steps.create-release.outputs.upload_url }}
steps:
- name: Install GitHub CLI (self-hosted runner)
run: |
if ! command -v gh &> /dev/null; then
echo "📦 Installing GitHub CLI..."
type -p curl >/dev/null || (sudo apt update && sudo apt install curl -y)
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
sudo apt update
sudo apt install gh -y
echo "✅ GitHub CLI installed"
else
echo "✅ GitHub CLI already installed"
fi
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch all history for release notes
- name: Download release artifacts
uses: actions/download-artifact@v4
with:
name: release-artifacts
path: ./artifacts
- name: Generate release notes
id: release-notes
run: |
VERSION="v${{ needs.validate-version.outputs.version }}"
# Get previous tag for changelog
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
if [ -z "$PREV_TAG" ]; then
echo "📝 First release - no previous tag found"
NOTES="## 🎉 Initial Release
This is the first release of Ten Second Tom.
### 📦 Downloads
Choose the appropriate binary for your platform:
- **macOS (Intel)**: \`tom\` (from ten-second-tom-osx-x64)
- **macOS (Apple Silicon)**: \`tom\` (from ten-second-tom-osx-arm64)
- **Windows**: \`tom.exe\` (from ten-second-tom-win-x64)
### ✅ Verification
Use \`shasum -a 256\` or \`Get-FileHash\` to verify the downloaded binaries."
else
echo "📝 Generating changelog from $PREV_TAG to $VERSION"
# Generate commit log
COMMITS=$(git log $PREV_TAG..HEAD --pretty=format:"- %s (%h)" --no-merges)
NOTES="## 📋 Changes
$COMMITS
### 📦 Downloads
Choose the appropriate binary for your platform:
- **macOS (Intel)**: \`tom\` (from ten-second-tom-osx-x64)
- **macOS (Apple Silicon)**: \`tom\` (from ten-second-tom-osx-arm64)
- **Windows**: \`tom.exe\` (from ten-second-tom-win-x64)
### ✅ Verification
Use \`shasum -a 256\` or \`Get-FileHash\` to verify the downloaded binaries."
fi
# Save notes to file (handle multiline)
echo "$NOTES" > release-notes.md
cat release-notes.md
- name: Create GitHub Release
id: create-release
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION="v${{ needs.validate-version.outputs.version }}"
# Check if this is a dry run
if [ "${{ github.event.inputs.dry_run }}" = "true" ]; then
echo "🧪 DRY RUN - Would create release $VERSION"
echo "📝 Release notes:"
cat release-notes.md
echo "⏭️ Skipping actual release creation"
exit 0
fi
# Create release with notes
gh release create "$VERSION" \
--title "Release $VERSION" \
--notes-file release-notes.md \
--draft=false \
--latest
echo "✅ GitHub release created: $VERSION"
# Check if this is a dry run
if [ "${{ github.event.inputs.dry_run }}" = "true" ]; then
echo "🧪 DRY RUN - Skipping artifact uploads"
exit 0
fi
# Upload all artifacts
echo "📤 Uploading artifacts..."
# Upload macOS x64
gh release upload "$VERSION" \
./artifacts/ten-second-tom-osx-x64/tom \
--clobber
# Upload macOS ARM64
gh release upload "$VERSION" \
./artifacts/ten-second-tom-osx-arm64/tom \
--clobber
# Upload Windows x64
gh release upload "$VERSION" \
./artifacts/ten-second-tom-win-x64/tom.exe \
--clobber
echo "✅ All artifacts uploaded"
# Job 4: Publish to Homebrew with Bottles
# Create Homebrew bottles and upload to GitHub Packages, then update tap formula
# Performance Target: ≤7 minutes
# Requires: HOMEBREW_TAP_TOKEN secret configured in 'production' environment
# Note: VS Code may show "'production' is not valid" - this is a false positive
# Can be skipped with workflow_dispatch skip_homebrew input for PR testing
publish-homebrew:
name: Publish to Homebrew
runs-on: macos-latest # Changed to macOS for bottle creation
needs: [validate-version, create-github-release]
if: ${{ github.event.inputs.skip_homebrew != 'true' && github.event.inputs.dry_run != 'true' }}
environment:
name: production # Configured in GitHub repository settings
permissions:
contents: write # Required for uploading bottles to release
packages: write # Required for GitHub Packages (bottles)
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download release artifacts
uses: actions/download-artifact@v4
with:
name: release-artifacts
path: ./artifacts
- name: Create Homebrew bottles
id: bottles
run: |
VERSION="${{ needs.validate-version.outputs.version }}"
REPO_OWNER="${{ github.repository_owner }}"
echo "🍾 Creating Homebrew bottles for version $VERSION"
# Determine macOS version for bottle tag
MACOS_VERSION=$(sw_vers -productVersion | cut -d '.' -f 1)
case $MACOS_VERSION in
12) MACOS_TAG="monterey" ;;
13) MACOS_TAG="ventura" ;;
14) MACOS_TAG="sonoma" ;;
15) MACOS_TAG="sequoia" ;;
*) MACOS_TAG="monterey" ;; # Default fallback
esac
echo "📦 Building bottles for macOS $MACOS_TAG (both architectures)"
# Create ARM64 bottle
BOTTLE_TAG_ARM64="arm64_${MACOS_TAG}"
BOTTLE_NAME_ARM64="ten-second-tom--${VERSION}.${BOTTLE_TAG_ARM64}.bottle.tar.gz"
BOTTLE_DIR_ARM64="ten-second-tom/${VERSION}/bin"
mkdir -p "$BOTTLE_DIR_ARM64"
cp "./artifacts/ten-second-tom-osx-arm64/tom" "$BOTTLE_DIR_ARM64/tom"
chmod +x "$BOTTLE_DIR_ARM64/tom"
tar czf "$BOTTLE_NAME_ARM64" ten-second-tom
BOTTLE_SHA_ARM64=$(shasum -a 256 "$BOTTLE_NAME_ARM64" | cut -d ' ' -f 1)
echo "✅ ARM64 bottle: $BOTTLE_NAME_ARM64"
echo " SHA256: $BOTTLE_SHA_ARM64"
# Clean up for x64 bottle
rm -rf ten-second-tom
# Create x64 bottle
BOTTLE_TAG_X64="${MACOS_TAG}"
BOTTLE_NAME_X64="ten-second-tom--${VERSION}.${BOTTLE_TAG_X64}.bottle.tar.gz"
BOTTLE_DIR_X64="ten-second-tom/${VERSION}/bin"
mkdir -p "$BOTTLE_DIR_X64"
cp "./artifacts/ten-second-tom-osx-x64/tom" "$BOTTLE_DIR_X64/tom"
chmod +x "$BOTTLE_DIR_X64/tom"
tar czf "$BOTTLE_NAME_X64" ten-second-tom
BOTTLE_SHA_X64=$(shasum -a 256 "$BOTTLE_NAME_X64" | cut -d ' ' -f 1)
echo "✅ x64 bottle: $BOTTLE_NAME_X64"
echo " SHA256: $BOTTLE_SHA_X64"
# Output variables for later steps
echo "bottle-name-arm64=$BOTTLE_NAME_ARM64" >> $GITHUB_OUTPUT
echo "bottle-name-x64=$BOTTLE_NAME_X64" >> $GITHUB_OUTPUT
echo "bottle-tag-arm64=$BOTTLE_TAG_ARM64" >> $GITHUB_OUTPUT
echo "bottle-tag-x64=$BOTTLE_TAG_X64" >> $GITHUB_OUTPUT
echo "bottle-sha-arm64=$BOTTLE_SHA_ARM64" >> $GITHUB_OUTPUT
echo "bottle-sha-x64=$BOTTLE_SHA_X64" >> $GITHUB_OUTPUT
- name: Upload bottles to GitHub release
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION="${{ needs.validate-version.outputs.version }}"
BOTTLE_NAME_ARM64="${{ steps.bottles.outputs.bottle-name-arm64 }}"
BOTTLE_NAME_X64="${{ steps.bottles.outputs.bottle-name-x64 }}"
echo "📤 Uploading bottles to GitHub release"
# Upload both bottles to GitHub release
gh release upload "v${VERSION}" "$BOTTLE_NAME_ARM64" "$BOTTLE_NAME_X64" --clobber
echo "✅ Bottles uploaded to release assets"
echo "📦 ARM64: https://github.com/${{ github.repository }}/releases/download/v${VERSION}/${BOTTLE_NAME_ARM64}"
echo "📦 x64: https://github.com/${{ github.repository }}/releases/download/v${VERSION}/${BOTTLE_NAME_X64}"
- name: Generate Homebrew formula with bottle blocks
id: formula
run: |
VERSION="${{ needs.validate-version.outputs.version }}"
REPO_OWNER="${{ github.repository_owner }}"
REPO_NAME="${{ github.event.repository.name }}"
BOTTLE_TAG_ARM64="${{ steps.bottles.outputs.bottle-tag-arm64 }}"
BOTTLE_TAG_X64="${{ steps.bottles.outputs.bottle-tag-x64 }}"
BOTTLE_SHA_ARM64="${{ steps.bottles.outputs.bottle-sha-arm64 }}"
BOTTLE_SHA_X64="${{ steps.bottles.outputs.bottle-sha-x64 }}"
echo "📝 Generating Homebrew formula for version $VERSION with bottle support"
# Download source tarball to calculate SHA256
SOURCE_TARBALL_URL="https://github.com/${REPO_OWNER}/${REPO_NAME}/archive/refs/tags/v${VERSION}.tar.gz"
curl -sSL "$SOURCE_TARBALL_URL" -o source.tar.gz
SOURCE_SHA=$(shasum -a 256 source.tar.gz | cut -d ' ' -f 1)
echo "Source tarball SHA256: $SOURCE_SHA"
# Create formula file with bottle block using echo to avoid heredoc YAML issues
# Calculate spacing for bottle digest alignment
ARM64_LEN=${#BOTTLE_TAG_ARM64}
X64_LEN=${#BOTTLE_TAG_X64}
# Add spaces to shorter tag to align the SHA256 values
if [ $ARM64_LEN -gt $X64_LEN ]; then
SPACES=$((ARM64_LEN - X64_LEN))
X64_PADDING=$(printf '%*s' $SPACES '')
ARM64_PADDING=""
else
SPACES=$((X64_LEN - ARM64_LEN))
ARM64_PADDING=$(printf '%*s' $SPACES '')
X64_PADDING=""
fi
{
echo "class TenSecondTom < Formula"
echo " desc \"CLI tool for daily work summaries using Claude AI\""
echo " homepage \"https://github.com/${REPO_OWNER}/${REPO_NAME}\""
echo " url \"https://github.com/${REPO_OWNER}/${REPO_NAME}/archive/refs/tags/v${VERSION}.tar.gz\""
echo " sha256 \"${SOURCE_SHA}\""
echo " license \"MIT\""
echo ""
echo " # Bottles (pre-built binaries) for fast installation"
echo " bottle do"
echo " root_url \"https://github.com/${REPO_OWNER}/${REPO_NAME}/releases/download/v${VERSION}\""
echo " sha256 cellar: :any_skip_relocation, ${BOTTLE_TAG_ARM64}:${ARM64_PADDING} \"${BOTTLE_SHA_ARM64}\""
echo " sha256 cellar: :any_skip_relocation, ${BOTTLE_TAG_X64}:${X64_PADDING} \"${BOTTLE_SHA_X64}\""
echo " end"
echo ""
echo " def install"
echo " bin.install \"tom\""
echo " end"
echo ""
echo " test do"
echo " system \"#{bin}/tom\", \"--version\""
echo " end"
echo "end"
} > ten-second-tom.rb
cat ten-second-tom.rb
- name: Validate formula syntax
run: |
echo "✅ Formula syntax validation (basic check)"
# Basic syntax validation - full validation requires Homebrew installation
if ! grep -q "class TenSecondTom" ten-second-tom.rb; then
echo "❌ Error: Invalid formula structure"
exit 1
fi
# Check that source URL contains the version
if ! grep -q "archive/refs/tags/v${{ needs.validate-version.outputs.version }}.tar.gz" ten-second-tom.rb; then
echo "❌ Error: Version not found in source URL"
exit 1
fi
# Check that source has SHA256
if ! grep -q "sha256 \"" ten-second-tom.rb; then
echo "❌ Error: Source SHA256 not found in formula"
exit 1
fi
if ! grep -q "bottle do" ten-second-tom.rb; then
echo "❌ Error: Bottle block not found in formula"
exit 1
fi
echo "✅ Basic formula validation passed (including bottle block)"
- name: Push formula to Homebrew tap
env:
# Note: This secret is configured in the 'production' environment
# Go to Settings → Environments → production → Environment secrets
TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
run: |
if [ -z "$TAP_TOKEN" ]; then
echo "❌ Error: HOMEBREW_TAP_TOKEN secret not configured"
echo "Please configure the secret in the 'production' environment"
echo "Settings → Environments → production → Environment secrets"
exit 1
fi
VERSION="${{ needs.validate-version.outputs.version }}"
REPO_OWNER="${{ github.repository_owner }}"
TAP_REPO="homebrew-ten-second-tom"
echo "📤 Pushing formula with bottles to ${REPO_OWNER}/${TAP_REPO}"
# Clone tap repository
git clone "https://x-access-token:${TAP_TOKEN}@github.com/${REPO_OWNER}/${TAP_REPO}.git" tap-repo
cd tap-repo
# Configure git
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Copy formula
mkdir -p Formula
cp ../ten-second-tom.rb Formula/ten-second-tom.rb
# Commit and push
git add Formula/ten-second-tom.rb
git commit -m "Release version ${VERSION} with bottles"
git push origin main
echo "✅ Formula with bottle support published to Homebrew tap"
# Job 5: Document Winget Publication
# Generate Winget manifest and create issue for manual publication (Phase 1)
# Performance Target: <1 minute
# Note: Phase 2 will automate the publication process
document-winget:
name: Document Winget Publication
runs-on: ${{ needs.select-runner.outputs.linux }}
needs: [select-runner, validate-version, create-github-release]
if: success() # Run even if other jobs fail
steps:
- name: Install GitHub CLI (self-hosted runner)
run: |
if ! command -v gh &> /dev/null; then
echo "📦 Installing GitHub CLI..."
type -p curl >/dev/null || (sudo apt update && sudo apt install curl -y)
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
sudo apt update
sudo apt install gh -y
echo "✅ GitHub CLI installed"
else
echo "✅ GitHub CLI already installed"
fi
- name: Generate Winget manifest template
run: |
VERSION="${{ needs.validate-version.outputs.version }}"
REPO_OWNER="${{ github.repository_owner }}"
REPO_NAME="${{ github.event.repository.name }}"
cat > winget-manifest.yaml <<EOF
# Winget Package Manifest
# This manifest should be submitted to microsoft/winget-pkgs repository
# Manual submission process (Phase 1) - will be automated in Phase 2
PackageIdentifier: ${REPO_OWNER}.TenSecondTom
PackageVersion: ${VERSION}
PackageName: Ten Second Tom
Publisher: ${REPO_OWNER}
License: MIT
ShortDescription: CLI tool for daily work summaries using Claude AI
PackageUrl: https://github.com/${REPO_OWNER}/${REPO_NAME}
Installers:
- Architecture: x64
InstallerType: zip
InstallerUrl: https://github.com/${REPO_OWNER}/${REPO_NAME}/releases/download/v${VERSION}/tom.exe
# TODO: Add SHA256 checksum from release artifact
ManifestType: singleton
ManifestVersion: 1.0.0
EOF
cat winget-manifest.yaml
- name: Create GitHub issue for manual publication
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION="${{ needs.validate-version.outputs.version }}"
gh issue create \
--repo ${{ github.repository }} \
--title "📦 Publish v${VERSION} to Winget" \
--body "## Manual Winget Publication Required
Version **v${VERSION}** has been released and needs to be published to Winget.
### Steps:
1. Fork microsoft/winget-pkgs repository
2. Use the generated manifest template (see workflow artifacts)
3. Create a PR to microsoft/winget-pkgs
4. Wait for validation and merge
### References:
- [Winget Package Manifest](https://github.com/microsoft/winget-pkgs)
- [Release v${VERSION}](https://github.com/${{ github.repository }}/releases/tag/v${VERSION})
### Automation:
Phase 2 will automate this process." \
--label "release,winget" || echo "⚠️ Issue creation skipped (may already exist)"
# Job 6: Document Chocolatey Publication
# Generate Chocolatey nuspec and create issue for manual publication (Phase 1)
# Performance Target: <1 minute
# Note: Phase 2 will automate the publication process
document-chocolatey:
name: Document Chocolatey Publication
runs-on: ${{ needs.select-runner.outputs.linux }}
needs: [select-runner, validate-version, create-github-release]
if: success() # Run even if other jobs fail
steps:
- name: Install GitHub CLI (self-hosted runner)
run: |
if ! command -v gh &> /dev/null; then
echo "📦 Installing GitHub CLI..."
type -p curl >/dev/null || (sudo apt update && sudo apt install curl -y)
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
sudo apt update
sudo apt install gh -y
echo "✅ GitHub CLI installed"
else
echo "✅ GitHub CLI already installed"
fi
- name: Generate Chocolatey nuspec template
run: |
VERSION="${{ needs.validate-version.outputs.version }}"
REPO_OWNER="${{ github.repository_owner }}"
REPO_NAME="${{ github.event.repository.name }}"
cat > ten-second-tom.nuspec <<EOF
<?xml version="1.0" encoding="utf-8"?>
<!-- Chocolatey Package Specification -->
<!-- Manual submission process (Phase 1) - will be automated in Phase 2 -->
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
<metadata>
<id>ten-second-tom</id>
<version>${VERSION}</version>
<title>Ten Second Tom</title>
<authors>${REPO_OWNER}</authors>
<projectUrl>https://github.com/${REPO_OWNER}/${REPO_NAME}</projectUrl>
<licenseUrl>https://github.com/${REPO_OWNER}/${REPO_NAME}/blob/main/LICENSE</licenseUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>CLI tool for daily work summaries using Claude AI</description>
<summary>Daily work summary CLI powered by Claude AI</summary>
<tags>cli productivity ai claude summary</tags>
</metadata>
<files>
<file src="tools\**" target="tools" />
</files>
</package>
EOF
cat ten-second-tom.nuspec
- name: Create GitHub issue for manual publication
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION="${{ needs.validate-version.outputs.version }}"
gh issue create \
--repo ${{ github.repository }} \
--title "📦 Publish v${VERSION} to Chocolatey" \
--body "## Manual Chocolatey Publication Required
Version **v${VERSION}** has been released and needs to be published to Chocolatey.
### Steps:
1. Create Chocolatey account (if not exists)
2. Use the generated nuspec template (see workflow artifacts)
3. Package and test locally: \`choco pack\`
4. Publish: \`choco push ten-second-tom.${VERSION}.nupkg --source https://push.chocolatey.org/\`
### References:
- [Chocolatey Package Docs](https://docs.chocolatey.org/en-us/create/create-packages)
- [Release v${VERSION}](https://github.com/${{ github.repository }}/releases/tag/v${VERSION})
### Automation:
Phase 2 will automate this process." \
--label "release,chocolatey" || echo "⚠️ Issue creation skipped (may already exist)"