From 0027409812467c5a3cd71d4e35b636d6be6e61c7 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Fri, 13 Feb 2026 01:24:13 +0900 Subject: [PATCH 1/3] Update changeset.yaml --- .github/workflows/changeset.yaml | 191 +++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 .github/workflows/changeset.yaml diff --git a/.github/workflows/changeset.yaml b/.github/workflows/changeset.yaml new file mode 100644 index 00000000..fddcff4d --- /dev/null +++ b/.github/workflows/changeset.yaml @@ -0,0 +1,191 @@ +# Copyright 2025 LiveKit, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: Changeset Check + +on: + pull_request: + branches: [main] + +permissions: + contents: read + pull-requests: write + +jobs: + changeset: + name: Changeset & Breaking Change Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check for changeset entries + id: changeset + run: | + count=$(find .changes -maxdepth 1 -type f ! -name '.*' 2>/dev/null | wc -l | tr -d ' ') + echo "count=$count" >> "$GITHUB_OUTPUT" + + has_major=false + if [ "$count" -gt 0 ]; then + if grep -rq '^major ' .changes/ 2>/dev/null; then + has_major=true + fi + fi + echo "has_major=$has_major" >> "$GITHUB_OUTPUT" + + - uses: ./.github/actions/setup-flutter + + - name: Detect breaking changes + id: breaking + run: | + BASE_TAG=$(git describe --tags --abbrev=0 origin/main 2>/dev/null || echo "") + if [ -z "$BASE_TAG" ]; then + echo "No base tag found, skipping breaking change detection" + echo "has_breaking=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "Comparing public API against $BASE_TAG" + + dart pub global activate dart_apitool + REPO_URL="git://${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}:${BASE_TAG}" + + DIFF_OUTPUT=$(dart-apitool diff \ + --old "$REPO_URL" \ + --new . \ + --force-use-flutter 2>&1) || true + + echo "--- dart-apitool output ---" + echo "$DIFF_OUTPUT" + echo "---" + + # Extract breaking changes from the output + BREAKING_LINES=$(echo "$DIFF_OUTPUT" | grep -i "breaking" || true) + + if [ -n "$BREAKING_LINES" ]; then + echo "has_breaking=true" >> "$GITHUB_OUTPUT" + { + echo "details<> "$GITHUB_OUTPUT" + else + echo "has_breaking=false" >> "$GITHUB_OUTPUT" + echo "No breaking changes detected" + fi + + - name: Manage PR comments + uses: actions/github-script@v7 + with: + script: | + const changesetCount = parseInt('${{ steps.changeset.outputs.count }}'); + const hasMajor = '${{ steps.changeset.outputs.has_major }}' === 'true'; + const hasBreaking = '${{ steps.breaking.outputs.has_breaking }}' === 'true'; + const details = `${{ steps.breaking.outputs.details }}`.trim(); + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + + // --- Changeset missing comment --- + const changesetMarker = ''; + const existingChangeset = comments.find(c => c.body.includes(changesetMarker)); + + if (changesetCount === 0) { + const body = [ + changesetMarker, + '> [!WARNING]', + '> **No changeset found**', + '>', + '> If this PR includes user-facing changes, please add a changeset file in `.changes/`', + '', + '**Format:** `level type="kind" "description"`', + '', + '```', + 'patch type="fixed" "Fix audio frame generation"', + 'minor type="added" "Add support for custom audio processing"', + 'major type="changed" "Breaking: Rename Room.connect() to Room.join()"', + '```', + ].join('\n'); + + if (existingChangeset) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existingChangeset.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body, + }); + } + } else if (existingChangeset) { + await github.rest.issues.deleteComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existingChangeset.id, + }); + } + + // --- Breaking change without major changeset comment --- + const breakingMarker = ''; + const existingBreaking = comments.find(c => c.body.includes(breakingMarker)); + + if (hasBreaking && !hasMajor) { + const body = [ + breakingMarker, + '> [!CAUTION]', + '> **Breaking change detected without major changeset**', + '', + '`dart-apitool` detected the following breaking changes:', + '', + '```', + details, + '```', + '', + 'If this is intentional, please add a changeset with `major` level in `.changes/`:', + '```', + 'major type="changed" "Description of breaking change"', + '```', + ].join('\n'); + + if (existingBreaking) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existingBreaking.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body, + }); + } + } else if (existingBreaking) { + await github.rest.issues.deleteComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existingBreaking.id, + }); + } From 4eb4aa251a3067038fc00bd576deefd865fdb6b3 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Fri, 13 Feb 2026 01:35:36 +0900 Subject: [PATCH 2/3] Update changeset.yaml --- .github/workflows/changeset.yaml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/changeset.yaml b/.github/workflows/changeset.yaml index fddcff4d..028e95c3 100644 --- a/.github/workflows/changeset.yaml +++ b/.github/workflows/changeset.yaml @@ -76,11 +76,7 @@ jobs: if [ -n "$BREAKING_LINES" ]; then echo "has_breaking=true" >> "$GITHUB_OUTPUT" - { - echo "details<> "$GITHUB_OUTPUT" + echo "$DIFF_OUTPUT" > "$RUNNER_TEMP/breaking_changes.txt" else echo "has_breaking=false" >> "$GITHUB_OUTPUT" echo "No breaking changes detected" @@ -90,10 +86,18 @@ jobs: uses: actions/github-script@v7 with: script: | + const fs = require('fs'); + const path = require('path'); + const changesetCount = parseInt('${{ steps.changeset.outputs.count }}'); const hasMajor = '${{ steps.changeset.outputs.has_major }}' === 'true'; const hasBreaking = '${{ steps.breaking.outputs.has_breaking }}' === 'true'; - const details = `${{ steps.breaking.outputs.details }}`.trim(); + + let details = ''; + if (hasBreaking) { + const detailsPath = path.join(process.env.RUNNER_TEMP, 'breaking_changes.txt'); + try { details = fs.readFileSync(detailsPath, 'utf8').trim(); } catch {} + } const { data: comments } = await github.rest.issues.listComments({ owner: context.repo.owner, From 0334114dc776c6819425853fe4f4942eef174f4f Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Fri, 13 Feb 2026 14:58:03 +0900 Subject: [PATCH 3/3] Update changeset.yaml --- .github/workflows/changeset.yaml | 148 +++++++++++++------------------ 1 file changed, 64 insertions(+), 84 deletions(-) diff --git a/.github/workflows/changeset.yaml b/.github/workflows/changeset.yaml index 028e95c3..47d424ce 100644 --- a/.github/workflows/changeset.yaml +++ b/.github/workflows/changeset.yaml @@ -105,91 +105,71 @@ jobs: issue_number: context.issue.number, }); - // --- Changeset missing comment --- - const changesetMarker = ''; - const existingChangeset = comments.find(c => c.body.includes(changesetMarker)); - - if (changesetCount === 0) { - const body = [ - changesetMarker, - '> [!WARNING]', - '> **No changeset found**', - '>', - '> If this PR includes user-facing changes, please add a changeset file in `.changes/`', - '', - '**Format:** `level type="kind" "description"`', - '', - '```', - 'patch type="fixed" "Fix audio frame generation"', - 'minor type="added" "Add support for custom audio processing"', - 'major type="changed" "Breaking: Rename Room.connect() to Room.join()"', - '```', - ].join('\n'); - - if (existingChangeset) { - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existingChangeset.id, - body, - }); - } else { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body, - }); - } - } else if (existingChangeset) { - await github.rest.issues.deleteComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existingChangeset.id, - }); + // Minimize a comment via GraphQL (collapses with "resolved" reason) + async function minimizeComment(commentId) { + await github.graphql(` + mutation($id: ID!) { + minimizeComment(input: { subjectId: $id, classifier: RESOLVED }) { + minimizedComment { isMinimized } + } + } + `, { id: commentId }); } - // --- Breaking change without major changeset comment --- - const breakingMarker = ''; - const existingBreaking = comments.find(c => c.body.includes(breakingMarker)); - - if (hasBreaking && !hasMajor) { - const body = [ - breakingMarker, - '> [!CAUTION]', - '> **Breaking change detected without major changeset**', - '', - '`dart-apitool` detected the following breaking changes:', - '', - '```', - details, - '```', - '', - 'If this is intentional, please add a changeset with `major` level in `.changes/`:', - '```', - 'major type="changed" "Description of breaking change"', - '```', - ].join('\n'); - - if (existingBreaking) { - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existingBreaking.id, - body, - }); - } else { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body, - }); + // Create or update a comment; minimize the existing one if resolved + async function upsertOrResolve(marker, shouldShow, bodyLines) { + const existing = comments.find(c => c.body.includes(marker)); + if (shouldShow) { + const body = [marker, ...bodyLines].join('\n'); + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body, + }); + } + } else if (existing) { + await minimizeComment(existing.node_id); } - } else if (existingBreaking) { - await github.rest.issues.deleteComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existingBreaking.id, - }); } + + // --- Changeset missing --- + await upsertOrResolve('', changesetCount === 0, [ + '> [!WARNING]', + '> **No changeset found**', + '>', + '> If this PR includes user-facing changes, please add a changeset file in `.changes/`', + '', + '**Format:** `level type="kind" "description"`', + '', + '```', + 'patch type="fixed" "Fix audio frame generation"', + 'minor type="added" "Add support for custom audio processing"', + 'major type="changed" "Breaking: Rename Room.connect() to Room.join()"', + '```', + ]); + + // --- Breaking change without major changeset --- + await upsertOrResolve('', hasBreaking && !hasMajor, [ + '> [!CAUTION]', + '> **Breaking change detected without major changeset**', + '', + '`dart-apitool` detected the following breaking changes:', + '', + '```', + details, + '```', + '', + 'If this is intentional, please add a changeset with `major` level in `.changes/`:', + '```', + 'major type="changed" "Description of breaking change"', + '```', + ]);