Skip to content

Commit 507708e

Browse files
authored
chore: breakage check (#992)
1 parent ef92005 commit 507708e

1 file changed

Lines changed: 168 additions & 0 deletions

File tree

.github/workflows/changeset.yaml

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# Copyright 2025 LiveKit, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
name: Changeset Check
16+
17+
on:
18+
pull_request:
19+
branches: [main]
20+
21+
permissions:
22+
contents: read
23+
pull-requests: write
24+
25+
jobs:
26+
changeset:
27+
name: Changeset & Breaking Change Check
28+
runs-on: ubuntu-latest
29+
steps:
30+
- uses: actions/checkout@v4
31+
with:
32+
fetch-depth: 0
33+
34+
- name: Check for changeset entries
35+
id: changeset
36+
run: |
37+
count=$(find .changes -maxdepth 1 -type f ! -name '.*' 2>/dev/null | wc -l | tr -d ' ')
38+
echo "count=$count" >> "$GITHUB_OUTPUT"
39+
40+
has_major=false
41+
if [ "$count" -gt 0 ]; then
42+
if grep -rq '^major ' .changes/ 2>/dev/null; then
43+
has_major=true
44+
fi
45+
fi
46+
echo "has_major=$has_major" >> "$GITHUB_OUTPUT"
47+
48+
- uses: ./.github/actions/setup-flutter
49+
50+
- name: Detect breaking changes
51+
id: breaking
52+
run: |
53+
echo "Comparing public API against main"
54+
55+
dart pub global activate dart_apitool
56+
BASE_REF="git://${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}:main"
57+
58+
DIFF_OUTPUT=$(dart-apitool diff \
59+
--old "$BASE_REF" \
60+
--new . \
61+
--force-use-flutter 2>&1) || true
62+
63+
echo "--- dart-apitool output ---"
64+
echo "$DIFF_OUTPUT"
65+
echo "---"
66+
67+
# Extract breaking changes from the output
68+
BREAKING_LINES=$(echo "$DIFF_OUTPUT" | grep -i "breaking" || true)
69+
70+
if [ -n "$BREAKING_LINES" ]; then
71+
echo "has_breaking=true" >> "$GITHUB_OUTPUT"
72+
echo "$DIFF_OUTPUT" > "$RUNNER_TEMP/breaking_changes.txt"
73+
else
74+
echo "has_breaking=false" >> "$GITHUB_OUTPUT"
75+
echo "No breaking changes detected"
76+
fi
77+
78+
- name: Manage PR comments
79+
uses: actions/github-script@v7
80+
with:
81+
script: |
82+
const fs = require('fs');
83+
const path = require('path');
84+
85+
const changesetCount = parseInt('${{ steps.changeset.outputs.count }}');
86+
const hasMajor = '${{ steps.changeset.outputs.has_major }}' === 'true';
87+
const hasBreaking = '${{ steps.breaking.outputs.has_breaking }}' === 'true';
88+
89+
let details = '';
90+
if (hasBreaking) {
91+
const detailsPath = path.join(process.env.RUNNER_TEMP, 'breaking_changes.txt');
92+
try { details = fs.readFileSync(detailsPath, 'utf8').trim(); } catch {}
93+
}
94+
95+
const { data: comments } = await github.rest.issues.listComments({
96+
owner: context.repo.owner,
97+
repo: context.repo.repo,
98+
issue_number: context.issue.number,
99+
});
100+
101+
// Minimize a comment via GraphQL (collapses with "resolved" reason)
102+
async function minimizeComment(commentId) {
103+
await github.graphql(`
104+
mutation($id: ID!) {
105+
minimizeComment(input: { subjectId: $id, classifier: RESOLVED }) {
106+
minimizedComment { isMinimized }
107+
}
108+
}
109+
`, { id: commentId });
110+
}
111+
112+
// Create or update a comment; minimize the existing one if resolved
113+
async function upsertOrResolve(marker, shouldShow, bodyLines) {
114+
const existing = comments.find(c => c.body.includes(marker));
115+
if (shouldShow) {
116+
const body = [marker, ...bodyLines].join('\n');
117+
if (existing) {
118+
await github.rest.issues.updateComment({
119+
owner: context.repo.owner,
120+
repo: context.repo.repo,
121+
comment_id: existing.id,
122+
body,
123+
});
124+
} else {
125+
await github.rest.issues.createComment({
126+
owner: context.repo.owner,
127+
repo: context.repo.repo,
128+
issue_number: context.issue.number,
129+
body,
130+
});
131+
}
132+
} else if (existing) {
133+
await minimizeComment(existing.node_id);
134+
}
135+
}
136+
137+
// --- Changeset missing ---
138+
await upsertOrResolve('<!-- changeset-check -->', changesetCount === 0, [
139+
'> [!WARNING]',
140+
'> **No changeset found**',
141+
'>',
142+
'> If this PR includes user-facing changes, please add a changeset file in `.changes/`',
143+
'',
144+
'**Format:** `level type="kind" "description"`',
145+
'',
146+
'```',
147+
'patch type="fixed" "Fix audio frame generation"',
148+
'minor type="added" "Add support for custom audio processing"',
149+
'major type="changed" "Breaking: Rename Room.connect() to Room.join()"',
150+
'```',
151+
]);
152+
153+
// --- Breaking change without major changeset ---
154+
await upsertOrResolve('<!-- breaking-change-check -->', hasBreaking && !hasMajor, [
155+
'> [!CAUTION]',
156+
'> **Breaking change detected without major changeset**',
157+
'',
158+
'`dart-apitool` detected the following breaking changes:',
159+
'',
160+
'```',
161+
details,
162+
'```',
163+
'',
164+
'If this is intentional, please add a changeset with `major` level in `.changes/`:',
165+
'```',
166+
'major type="changed" "Description of breaking change"',
167+
'```',
168+
]);

0 commit comments

Comments
 (0)