From 6614d225e312fab8b726f5d566fe49885ef46317 Mon Sep 17 00:00:00 2001 From: Julian Pawlowski Date: Sun, 9 Nov 2025 16:14:07 +0000 Subject: [PATCH] fix(release): add validation/lint checks before release and clean output Added mandatory validation steps to release workflow: - Job 1: validate (hassfest + HACS) - runs before release - Job 2: lint (Ruff check + format) - runs before release - Job 3: sync-manifest - only runs if validation passes - Job 4: release-notes - only runs if all previous jobs pass This ensures no release is created if code doesn't pass validation. Fixed generate-release-notes script to suppress colored output in CI: - Added log_info() wrapper that sends output to stderr in CI - Keeps release notes clean (only markdown, no ANSI codes) - Local execution still shows colored output for better UX Impact: Release workflow now fails fast if validation fails. Release notes are clean without shell output artifacts. --- .github/workflows/release.yml | 54 ++++++++++++++++++++++++++++++++++ scripts/generate-release-notes | 47 ++++++++++++++++++----------- 2 files changed, 84 insertions(+), 17 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 84a2055..4a451fa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,12 +4,66 @@ on: push: tags: - 'v*.*.*' # Triggers on version tags like v1.0.0, v2.1.3, etc. + workflow_dispatch: + inputs: + tag: + description: 'Tag version to release (e.g., v0.3.0)' + required: true + type: string permissions: contents: write # Needed to create/update releases and push commits jobs: + # Run validation first - release only proceeds if this passes + validate: + name: Validate before release + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v5 + with: + ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }} + + - name: Run hassfest validation + uses: home-assistant/actions/hassfest@master + + - name: Run HACS validation + uses: hacs/action@main + with: + category: integration + + # Run linting - release only proceeds if this passes + lint: + name: Lint before release + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v5 + with: + ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }} + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.13" + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + version: "0.9.3" + + - name: Install dependencies + run: uv sync --frozen + + - name: Run Ruff check + run: uv run ruff check . + + - name: Run Ruff format check + run: uv run ruff format --check . + sync-manifest: + needs: [validate, lint] # Only runs if validation and linting pass name: Sync manifest.json with tag version runs-on: ubuntu-latest outputs: diff --git a/scripts/generate-release-notes b/scripts/generate-release-notes index aeaf9eb..b3ea02a 100755 --- a/scripts/generate-release-notes +++ b/scripts/generate-release-notes @@ -30,6 +30,19 @@ YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color +# Detect if running in CI (suppress colored output to stdout) +if [ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ]; then + # In CI, send info messages to stderr to keep release notes clean + log_info() { + echo "$@" >&2 + } +else + # Local execution, show colored output + log_info() { + echo "$@" + } +fi + # Configuration BACKEND="${RELEASE_NOTES_BACKEND:-auto}" USE_AI="${USE_AI:-true}" @@ -40,13 +53,13 @@ FROM_TAG="${1:-$(git describe --tags --abbrev=0 2>/dev/null || echo "")}" TO_TAG="${2:-HEAD}" if [ -z "$FROM_TAG" ]; then - echo "${RED}Error: No tags found in repository${NC}" - echo "Usage: $0 [FROM_TAG] [TO_TAG]" + echo "${RED}Error: No tags found in repository${NC}" >&2 + echo "Usage: $0 [FROM_TAG] [TO_TAG]" >&2 exit 1 fi -echo "${BLUE}==> Generating release notes: ${FROM_TAG}..${TO_TAG}${NC}" -echo "" +log_info "${BLUE}==> Generating release notes: ${FROM_TAG}..${TO_TAG}${NC}" +log_info "" # Detect available backends detect_backend() { @@ -82,20 +95,20 @@ detect_backend() { } BACKEND=$(detect_backend) -echo "${GREEN}Using backend: ${BACKEND}${NC}" -echo "" +log_info "${GREEN}Using backend: ${BACKEND}${NC}" +log_info "" # Backend: GitHub Copilot CLI (AI-powered) generate_with_copilot() { - echo "${BLUE}==> Generating with GitHub Copilot CLI (AI-powered)${NC}" - echo "${YELLOW}Note: This will use one premium request from your monthly quota${NC}" - echo "" + log_info "${BLUE}==> Generating with GitHub Copilot CLI (AI-powered)${NC}" + log_info "${YELLOW}Note: This will use one premium request from your monthly quota${NC}" + log_info "" # Get commit log for the range COMMITS=$(git log --pretty=format:"%h | %s%n%b%n---" "${FROM_TAG}..${TO_TAG}") if [ -z "$COMMITS" ]; then - echo "${YELLOW}No commits found between ${FROM_TAG} and ${TO_TAG}${NC}" + log_info "${YELLOW}No commits found between ${FROM_TAG} and ${TO_TAG}${NC}" exit 0 fi @@ -145,8 +158,8 @@ Generate the release notes now:" # Call copilot CLI (it will handle authentication interactively) copilot < "$TEMP_PROMPT" || { echo "" - echo "${YELLOW}Warning: GitHub Copilot CLI failed or was not authenticated${NC}" - echo "${YELLOW}Falling back to git-cliff${NC}" + log_info "${YELLOW}Warning: GitHub Copilot CLI failed or was not authenticated${NC}" + log_info "${YELLOW}Falling back to git-cliff${NC}" rm -f "$TEMP_PROMPT" if command -v git-cliff >/dev/null 2>&1; then generate_with_gitcliff @@ -159,9 +172,9 @@ Generate the release notes now:" rm -f "$TEMP_PROMPT" } -# Backend: git-cliff (fast Rust tool) +# Backend: git-cliff (template-based) generate_with_gitcliff() { - echo "${BLUE}==> Generating with git-cliff${NC}" + log_info "${BLUE}==> Generating with git-cliff${NC}" # Create temporary cliff.toml if not exists if [ ! -f "cliff.toml" ]; then @@ -210,12 +223,12 @@ EOF # Backend: Manual parsing (fallback) generate_with_manual() { - echo "${BLUE}==> Generating with manual parsing${NC}" + log_info "${BLUE}==> Generating with manual parsing${NC}" echo "" # Check if we have commits if ! git log --oneline "${FROM_TAG}..${TO_TAG}" >/dev/null 2>&1; then - echo "${YELLOW}No commits found between ${FROM_TAG} and ${TO_TAG}${NC}" + log_info "${YELLOW}No commits found between ${FROM_TAG} and ${TO_TAG}${NC}" exit 0 fi @@ -351,4 +364,4 @@ case "$BACKEND" in esac echo "" -echo "${GREEN}==> Release notes generated successfully!${NC}" +log_info "${GREEN}==> Release notes generated successfully!${NC}"