From af28a409d2b3726c00d1e9250466181ff95f04b6 Mon Sep 17 00:00:00 2001 From: Bernard <63512176+BernardJen@users.noreply.github.com> Date: Fri, 17 Apr 2026 12:18:04 +0200 Subject: [PATCH 1/2] ci: separate CI and release workflows Split workflows to prevent automatic releases on every merge: - ci.yml: Runs lint and test on all PRs and pushes to main - release.yml: Manual trigger (workflow_dispatch) for building and releasing This gives full control over when versions are released while keeping automated testing on every commit. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ci.yml | 301 +--------------------------------- .github/workflows/release.yml | 294 +++++++++++++++++++++++++++++++++ 2 files changed, 301 insertions(+), 294 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ede4530..9302e78 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,24 +1,17 @@ -name: CI/CD +name: CI on: pull_request: branches: [main] push: branches: [main] - workflow_dispatch: - inputs: - force_release: - description: 'Force release even if no version bump needed' - required: false - type: boolean - default: false jobs: # ============================================================================= - # Step 1: Lint and Test (runs on all events) + # Lint and test (runs on all events) # ============================================================================= lint: - name: Lint + name: Lint & Test runs-on: ubuntu-latest defaults: run: @@ -36,288 +29,8 @@ jobs: - name: Install dependencies run: npm ci - # ============================================================================= - # Step 2: Analyze commits (push to main only, runs early to determine version) - # ============================================================================= - analyze: - name: Analyze Commits - if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' - runs-on: ubuntu-latest - outputs: - bump: ${{ steps.analyze.outputs.bump }} - new_version: ${{ steps.new_version.outputs.version }} - should_release: ${{ steps.decide.outputs.should_release }} - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - fetch-depth: 0 - - - name: Get current version - id: current_version - run: | - if [ -f VERSION ]; then - echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT - else - echo "version=0.0.0" >> $GITHUB_OUTPUT - fi - - - name: Analyze commits for version bump - id: analyze - run: | - LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") - - if [ -z "$LAST_TAG" ]; then - COMMITS=$(git log --pretty=format:"%s" --no-merges) - else - COMMITS=$(git log ${LAST_TAG}..HEAD --pretty=format:"%s" --no-merges) - fi - - if [ -z "$COMMITS" ]; then - echo "bump=none" >> $GITHUB_OUTPUT - exit 0 - fi - - echo "Commits to analyze:" - echo "$COMMITS" - - BUMP="none" - if echo "$COMMITS" | grep -qiE "^.+!:|BREAKING CHANGE"; then - BUMP="major" - elif echo "$COMMITS" | grep -qiE "^feat(\(.+\))?:"; then - BUMP="minor" - elif echo "$COMMITS" | grep -qiE "^(fix|perf|refactor|build|ci)(\(.+\))?:"; then - BUMP="patch" - fi - - echo "bump=$BUMP" >> $GITHUB_OUTPUT - - - name: Calculate new version - id: new_version - if: steps.analyze.outputs.bump != 'none' - run: | - CURRENT="${{ steps.current_version.outputs.version }}" - BUMP="${{ steps.analyze.outputs.bump }}" - - IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT" - - case $BUMP in - major) NEW_VERSION="$((MAJOR + 1)).0.0" ;; - minor) NEW_VERSION="${MAJOR}.$((MINOR + 1)).0" ;; - patch) NEW_VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))" ;; - esac - - echo "Current: $CURRENT -> New: $NEW_VERSION ($BUMP)" - echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT - - - name: Decide if release needed - id: decide - run: | - if [ "${{ steps.analyze.outputs.bump }}" != "none" ] || [ "${{ inputs.force_release }}" == "true" ]; then - echo "should_release=true" >> $GITHUB_OUTPUT - else - echo "should_release=false" >> $GITHUB_OUTPUT - fi - - # ============================================================================= - # Step 3: Build macOS (PR: after lint, Push: after analyze with new version) - # ============================================================================= - build-mac: - name: Build macOS - needs: [lint, analyze] - if: always() && needs.lint.result == 'success' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && needs.analyze.result == 'success' - runs-on: macos-latest - defaults: - run: - working-directory: input_viewer_electron - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: '20' - cache: 'npm' - cache-dependency-path: input_viewer_electron/package-lock.json - - - name: Install dependencies - run: npm ci - - - name: Update version for release - if: needs.analyze.outputs.should_release == 'true' && needs.analyze.outputs.new_version != '' - run: | - npm version "${{ needs.analyze.outputs.new_version }}" --no-git-tag-version --allow-same-version - - - name: Build and package for macOS - run: npm run build && npx electron-builder --mac --config --publish never - env: - CSC_IDENTITY_AUTO_DISCOVERY: false - - - name: Fix latest-mac.yml URLs - run: | - if [ -f dist/latest-mac.yml ]; then - sed -i '' 's|LAB271/input-viewer/releases|LAB271/input-viewer-releases/releases|g' dist/latest-mac.yml - fi - - - name: Upload artifacts - if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: macos-build - path: | - input_viewer_electron/dist/*.dmg - input_viewer_electron/dist/*.zip - input_viewer_electron/dist/latest-mac.yml - - # ============================================================================= - # Step 3: Build Windows (PR: after lint, Push: after analyze with new version) - # ============================================================================= - build-windows: - name: Build Windows - needs: [lint, analyze] - if: always() && needs.lint.result == 'success' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && needs.analyze.result == 'success' - runs-on: windows-latest - defaults: - run: - working-directory: input_viewer_electron - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: '20' - cache: 'npm' - cache-dependency-path: input_viewer_electron/package-lock.json - - - name: Install dependencies - run: npm ci - - - name: Update version for release - if: needs.analyze.outputs.should_release == 'true' && needs.analyze.outputs.new_version != '' - run: | - npm version "${{ needs.analyze.outputs.new_version }}" --no-git-tag-version --allow-same-version - - - name: Build and package for Windows - run: npm run build && npx electron-builder --win --config --publish never - - - name: Fix latest.yml URLs - run: | - if (Test-Path dist/latest.yml) { - (Get-Content dist/latest.yml) -replace 'LAB271/input-viewer/releases', 'LAB271/input-viewer-releases/releases' | Set-Content dist/latest.yml - } - shell: pwsh - - - name: Upload artifacts - if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: windows-build - path: | - input_viewer_electron/dist/*.exe - input_viewer_electron/dist/latest.yml - - # ============================================================================= - # Step 4: Bump version and create tag - # ============================================================================= - version-bump: - name: Bump Version - needs: [analyze, build-mac, build-windows] - if: needs.analyze.outputs.should_release == 'true' && needs.analyze.outputs.bump != 'none' - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: '20' - - - name: Update VERSION and package.json - run: | - VERSION="${{ needs.analyze.outputs.new_version }}" - echo "$VERSION" > VERSION - cd input_viewer_electron - npm version "$VERSION" --no-git-tag-version --allow-same-version - - - name: Commit and tag - run: | - VERSION="${{ needs.analyze.outputs.new_version }}" - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git add VERSION input_viewer_electron/package.json - git commit -m "chore: bump version to $VERSION" - git tag -a "v${VERSION}" -m "Release v${VERSION}" - git push - git push origin "v${VERSION}" - - # ============================================================================= - # Step 5: Publish release (reuses build artifacts) - # ============================================================================= - publish: - name: Publish Release - needs: [analyze, version-bump] - if: always() && needs.analyze.outputs.should_release == 'true' && (needs.version-bump.result == 'success' || needs.version-bump.result == 'skipped') - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Download macOS artifacts - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - name: macos-build - path: ./artifacts/mac - - - name: Download Windows artifacts - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - name: windows-build - path: ./artifacts/win - - - name: List artifacts - run: | - echo "macOS artifacts:" - ls -la ./artifacts/mac/ || echo "No macOS artifacts" - echo "Windows artifacts:" - ls -la ./artifacts/win/ || echo "No Windows artifacts" - - - name: Get tag name - id: tag - run: | - echo "tag=v${{ needs.analyze.outputs.new_version }}" >> $GITHUB_OUTPUT - - - name: Create Release in source repo - uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 - with: - tag_name: ${{ steps.tag.outputs.tag }} - files: | - ./artifacts/mac/*.dmg - ./artifacts/mac/*.zip - ./artifacts/win/*.exe - generate_release_notes: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Create Release in public releases repo - uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 - with: - repository: LAB271/input-viewer-releases - tag_name: ${{ steps.tag.outputs.tag }} - name: Input Viewer ${{ steps.tag.outputs.tag }} - files: | - ./artifacts/mac/*.dmg - ./artifacts/mac/*.zip - ./artifacts/mac/*.yml - ./artifacts/win/*.exe - ./artifacts/win/*.yml - generate_release_notes: true - env: - GITHUB_TOKEN: ${{ secrets.RELEASES_REPO_TOKEN }} + - name: Check code style + run: npm run lint --if-present - - name: Summary - run: | - echo "### 🚀 Release Published" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "- **Version:** ${{ steps.tag.outputs.tag }}" >> $GITHUB_STEP_SUMMARY - echo "- **Bump type:** ${{ needs.analyze.outputs.bump }}" >> $GITHUB_STEP_SUMMARY + - name: Build app + run: npm run build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1870497 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,294 @@ +name: Release + +on: + workflow_dispatch: + inputs: + force_release: + description: 'Force release even if no version bump needed' + required: false + type: boolean + default: false + +jobs: + # ============================================================================= + # Analyze commits to determine version bump + # ============================================================================= + analyze: + name: Analyze Commits + runs-on: ubuntu-latest + outputs: + bump: ${{ steps.analyze.outputs.bump }} + new_version: ${{ steps.new_version.outputs.version }} + should_release: ${{ steps.decide.outputs.should_release }} + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + fetch-depth: 0 + + - name: Get current version + id: current_version + run: | + if [ -f VERSION ]; then + echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT + else + echo "version=0.0.0" >> $GITHUB_OUTPUT + fi + + - name: Analyze commits for version bump + id: analyze + run: | + LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + + if [ -z "$LAST_TAG" ]; then + COMMITS=$(git log --pretty=format:"%s" --no-merges) + else + COMMITS=$(git log ${LAST_TAG}..HEAD --pretty=format:"%s" --no-merges) + fi + + if [ -z "$COMMITS" ]; then + echo "bump=none" >> $GITHUB_OUTPUT + exit 0 + fi + + echo "Commits to analyze:" + echo "$COMMITS" + + BUMP="none" + if echo "$COMMITS" | grep -qiE "^.+!:|BREAKING CHANGE"; then + BUMP="major" + elif echo "$COMMITS" | grep -qiE "^feat(\(.+\))?:"; then + BUMP="minor" + elif echo "$COMMITS" | grep -qiE "^(fix|perf|refactor|build|ci)(\(.+\))?:"; then + BUMP="patch" + fi + + echo "bump=$BUMP" >> $GITHUB_OUTPUT + + - name: Calculate new version + id: new_version + if: steps.analyze.outputs.bump != 'none' + run: | + CURRENT="${{ steps.current_version.outputs.version }}" + BUMP="${{ steps.analyze.outputs.bump }}" + + IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT" + + case $BUMP in + major) NEW_VERSION="$((MAJOR + 1)).0.0" ;; + minor) NEW_VERSION="${MAJOR}.$((MINOR + 1)).0" ;; + patch) NEW_VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))" ;; + esac + + echo "Current: $CURRENT -> New: $NEW_VERSION ($BUMP)" + echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT + + - name: Decide if release needed + id: decide + run: | + if [ "${{ steps.analyze.outputs.bump }}" != "none" ] || [ "${{ inputs.force_release }}" == "true" ]; then + echo "should_release=true" >> $GITHUB_OUTPUT + else + echo "should_release=false" >> $GITHUB_OUTPUT + fi + + # ============================================================================= + # Build macOS + # ============================================================================= + build-mac: + name: Build macOS + needs: analyze + if: needs.analyze.outputs.should_release == 'true' + runs-on: macos-latest + defaults: + run: + working-directory: input_viewer_electron + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: input_viewer_electron/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Update version for release + if: needs.analyze.outputs.new_version != '' + run: | + npm version "${{ needs.analyze.outputs.new_version }}" --no-git-tag-version --allow-same-version + + - name: Build and package for macOS + run: npm run build && npx electron-builder --mac --config --publish never + env: + CSC_IDENTITY_AUTO_DISCOVERY: false + + - name: Fix latest-mac.yml URLs + run: | + if [ -f dist/latest-mac.yml ]; then + sed -i '' 's|LAB271/input-viewer/releases|LAB271/input-viewer-releases/releases|g' dist/latest-mac.yml + fi + + - name: Upload artifacts + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: macos-build + path: | + input_viewer_electron/dist/*.dmg + input_viewer_electron/dist/*.zip + input_viewer_electron/dist/latest-mac.yml + + # ============================================================================= + # Build Windows + # ============================================================================= + build-windows: + name: Build Windows + needs: analyze + if: needs.analyze.outputs.should_release == 'true' + runs-on: windows-latest + defaults: + run: + working-directory: input_viewer_electron + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: input_viewer_electron/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Update version for release + if: needs.analyze.outputs.new_version != '' + run: | + npm version "${{ needs.analyze.outputs.new_version }}" --no-git-tag-version --allow-same-version + + - name: Build and package for Windows + run: npm run build && npx electron-builder --win --config --publish never + + - name: Fix latest.yml URLs + run: | + if (Test-Path dist/latest.yml) { + (Get-Content dist/latest.yml) -replace 'LAB271/input-viewer/releases', 'LAB271/input-viewer-releases/releases' | Set-Content dist/latest.yml + } + shell: pwsh + + - name: Upload artifacts + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: windows-build + path: | + input_viewer_electron/dist/*.exe + input_viewer_electron/dist/latest.yml + + # ============================================================================= + # Bump version and create tag + # ============================================================================= + version-bump: + name: Bump Version + needs: [analyze, build-mac, build-windows] + if: needs.analyze.outputs.should_release == 'true' && needs.analyze.outputs.bump != 'none' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: '20' + + - name: Update VERSION and package.json + run: | + VERSION="${{ needs.analyze.outputs.new_version }}" + echo "$VERSION" > VERSION + cd input_viewer_electron + npm version "$VERSION" --no-git-tag-version --allow-same-version + + - name: Commit and tag + run: | + VERSION="${{ needs.analyze.outputs.new_version }}" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add VERSION input_viewer_electron/package.json + git commit -m "chore: bump version to $VERSION" + git tag -a "v${VERSION}" -m "Release v${VERSION}" + git push + git push origin "v${VERSION}" + + # ============================================================================= + # Publish release (reuses build artifacts) + # ============================================================================= + publish: + name: Publish Release + needs: [analyze, version-bump] + if: always() && needs.analyze.outputs.should_release == 'true' && (needs.version-bump.result == 'success' || needs.version-bump.result == 'skipped') + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Download macOS artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: macos-build + path: ./artifacts/mac + + - name: Download Windows artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: windows-build + path: ./artifacts/win + + - name: List artifacts + run: | + echo "macOS artifacts:" + ls -la ./artifacts/mac/ || echo "No macOS artifacts" + echo "Windows artifacts:" + ls -la ./artifacts/win/ || echo "No Windows artifacts" + + - name: Get tag name + id: tag + run: | + echo "tag=v${{ needs.analyze.outputs.new_version }}" >> $GITHUB_OUTPUT + + - name: Create Release in source repo + uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 + with: + tag_name: ${{ steps.tag.outputs.tag }} + files: | + ./artifacts/mac/*.dmg + ./artifacts/mac/*.zip + ./artifacts/win/*.exe + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create Release in public releases repo + uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 + with: + repository: LAB271/input-viewer-releases + tag_name: ${{ steps.tag.outputs.tag }} + name: Input Viewer ${{ steps.tag.outputs.tag }} + files: | + ./artifacts/mac/*.dmg + ./artifacts/mac/*.zip + ./artifacts/mac/*.yml + ./artifacts/win/*.exe + ./artifacts/win/*.yml + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.RELEASES_REPO_TOKEN }} + + - name: Summary + run: | + echo "### 🚀 Release Published" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Version:** ${{ steps.tag.outputs.tag }}" >> $GITHUB_STEP_SUMMARY + echo "- **Bump type:** ${{ needs.analyze.outputs.bump }}" >> $GITHUB_STEP_SUMMARY From 3e301527e29ab6d3bda811f7edf635a37032cc96 Mon Sep 17 00:00:00 2001 From: Bernard <63512176+BernardJen@users.noreply.github.com> Date: Fri, 17 Apr 2026 12:25:56 +0200 Subject: [PATCH 2/2] ci: fix status check name to match branch protection requirement --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9302e78..0968afd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: # Lint and test (runs on all events) # ============================================================================= lint: - name: Lint & Test + name: Lint runs-on: ubuntu-latest defaults: run: