Skip to content

Commit 204dc59

Browse files
committed
feat: Add cross-repo breaking change detection system
- Extend build-wasm-internal.yml with trigger-breaking-change-check job - Add consolidated comment strategy for real-time status updates - Implement synchronous workflow coordination with 10min timeout - Add comprehensive error handling and retry mechanisms - Include Azure Key Vault PAT token integration - Add Breaking Changes section to PR template - Support for matrix strategy (ready for mobile expansion) Implements comprehensive SDK CI breaking change detection per PM-22218
1 parent d553d37 commit 204dc59

File tree

2 files changed

+299
-0
lines changed

2 files changed

+299
-0
lines changed

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,20 @@
66

77
<!-- Describe what the purpose of this PR is, for example what bug you're fixing or new feature you're adding. -->
88

9+
## 🚨 Breaking Changes
10+
11+
<!-- Does this PR introduce any breaking changes? If so, please describe the impact and migration path for clients.
12+
13+
If you're unsure, the automated TypeScript compatibility check will run when you open/update this PR and provide feedback.
14+
15+
For breaking changes:
16+
1. Describe what changed in the client interface
17+
2. Explain why the change was necessary
18+
3. Provide migration steps for client developers
19+
4. Link to any paired client PRs if needed
20+
21+
Otherwise, you can remove this section. -->
22+
923
## ⏰ Reminders before review
1024

1125
- Contributor guidelines followed

.github/workflows/build-wasm-internal.yml

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,288 @@ jobs:
138138
workflow_id: 'publish-wasm-internal.yml',
139139
ref: 'main',
140140
})
141+
trigger-breaking-change-check:
142+
name: Trigger client breaking change checks
143+
if: github.event_name == 'pull_request'
144+
runs-on: ubuntu-24.04
145+
needs: build
146+
permissions:
147+
contents: read
148+
actions: write
149+
pull-requests: write
150+
id-token: write
151+
strategy:
152+
matrix:
153+
client:
154+
- repo: "bitwarden/clients"
155+
event_type: "sdk-breaking-change-check"
156+
label: "typescript"
157+
workflow: "sdk-breaking-change-check.yml"
158+
env:
159+
CLIENT_REPO: ${{ matrix.client.repo }}
160+
EVENT_TYPE: ${{ matrix.client.event_type }}
161+
CLIENT_LABEL: ${{ matrix.client.label }}
162+
WORKFLOW_NAME: ${{ matrix.client.workflow }}
163+
MAX_RETRIES: 3
164+
165+
steps:
166+
- name: Download SDK artifacts
167+
uses: actions/download-artifact@v4
168+
with:
169+
name: sdk-internal
170+
path: ./sdk-artifacts
171+
172+
- name: Prepare dispatch payload
173+
id: payload
174+
run: |
175+
SDK_VERSION="${{ github.event.pull_request.head.ref }} (${{ github.event.pull_request.head.sha:0:7 }})"
176+
177+
# Create payload JSON
178+
PAYLOAD=$(cat << EOF
179+
{
180+
"pr_number": "${{ github.event.number }}",
181+
"sdk_version": "$SDK_VERSION",
182+
"source_repo": "${{ github.repository }}",
183+
"workflow_context": "$WORKFLOW_NAME",
184+
"client_label": "$CLIENT_LABEL",
185+
"pr_head_sha": "${{ github.event.pull_request.head.sha }}",
186+
"pr_base_ref": "${{ github.event.pull_request.base.ref }}",
187+
"comment_id": "${{ steps.consolidated-comment.outputs.comment_id }}",
188+
"artifacts_info": {
189+
"run_id": "${{ github.run_id }}",
190+
"artifact_name": "sdk-internal"
191+
}
192+
}
193+
EOF
194+
)
195+
196+
echo "payload=$PAYLOAD" >> $GITHUB_OUTPUT
197+
echo "sdk_version=$SDK_VERSION" >> $GITHUB_OUTPUT
198+
199+
- name: Create or update consolidated status comment
200+
id: consolidated-comment
201+
run: |
202+
echo "💬 Creating/updating consolidated breaking change status comment..."
203+
204+
COMMENT_HEADER="<!-- SDK-BREAKING-CHANGE-CHECK -->"
205+
SDK_VERSION="${{ steps.payload.outputs.sdk_version }}"
206+
TIMESTAMP=$(date -u '+%Y-%m-%d %H:%M:%S UTC')
207+
208+
# Expected clients list - expand when adding mobile
209+
EXPECTED_CLIENTS=("typescript")
210+
211+
# Build status table
212+
STATUS_TABLE="| Client | Status | Details |
213+
|--------|---------|---------|"
214+
215+
for CLIENT in "${EXPECTED_CLIENTS[@]}"; do
216+
STATUS_TABLE+="
217+
|$CLIENT|⏳ Pending|Type checking in progress...|"
218+
done
219+
220+
CONSOLIDATED_COMMENT=$(cat << EOF
221+
$COMMENT_HEADER
222+
## 🔍 SDK Breaking Change Detection Status
223+
224+
**SDK Version:** \`$SDK_VERSION\`
225+
**Triggered:** $TIMESTAMP
226+
**Progress:** Dispatching client workflows...
227+
228+
$STATUS_TABLE
229+
230+
---
231+
*This status updates automatically as client workflows complete. [View SDK workflow](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})*
232+
EOF
233+
)
234+
235+
# Check for existing consolidated comment
236+
EXISTING_COMMENT=$(gh api repos/${{ github.repository }}/issues/${{ github.event.number }}/comments \\
237+
--jq '.[] | select(.body | contains("'$COMMENT_HEADER'")) | .id' | head -1)
238+
239+
if [ -n "$EXISTING_COMMENT" ]; then
240+
echo "Updating existing consolidated comment ID: $EXISTING_COMMENT"
241+
gh api --method PATCH repos/${{ github.repository }}/issues/comments/$EXISTING_COMMENT \\
242+
--field body="$CONSOLIDATED_COMMENT"
243+
echo "comment_id=$EXISTING_COMMENT" >> $GITHUB_OUTPUT
244+
else
245+
echo "Creating new consolidated comment"
246+
COMMENT_ID=$(gh api --method POST repos/${{ github.repository }}/issues/${{ github.event.number }}/comments \\
247+
--field body="$CONSOLIDATED_COMMENT" --jq '.id')
248+
echo "comment_id=$COMMENT_ID" >> $GITHUB_OUTPUT
249+
fi
250+
251+
echo "✅ Consolidated comment created/updated"
252+
- name: Log in to Azure
253+
uses: bitwarden/gh-actions/azure-login@main
254+
with:
255+
subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
256+
tenant_id: ${{ secrets.AZURE_TENANT_ID }}
257+
client_id: ${{ secrets.AZURE_CLIENT_ID }}
258+
- name: Retrieve GitHub PAT secrets
259+
id: retrieve-secret-pat
260+
uses: bitwarden/gh-actions/get-keyvault-secrets@main
261+
with:
262+
keyvault: "bitwarden-ci"
263+
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
264+
- name: Log out from Azure
265+
uses: bitwarden/gh-actions/azure-logout@main
266+
- name: Trigger client repository dispatch
267+
run: |
268+
echo "🚀 Triggering $WORKFLOW_NAME in $CLIENT_REPO..."
269+
270+
RETRY_COUNT=0
271+
DISPATCH_SUCCESS=false
272+
273+
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
274+
RETRY_COUNT=$((RETRY_COUNT + 1))
275+
echo "🔄 Attempt $RETRY_COUNT of $MAX_RETRIES for $CLIENT_REPO..."
276+
277+
if GH_TOKEN="${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }}" gh api repos/$CLIENT_REPO/dispatches \
278+
--method POST \
279+
--field event_type="$EVENT_TYPE" \
280+
--raw-field client_payload='${{ steps.payload.outputs.payload }}'; then
281+
282+
echo "✅ Successfully triggered $CLIENT_REPO ($CLIENT_LABEL)"
283+
echo "📋 Results will be posted to PR #${{ github.event.number }} when ready"
284+
echo "✅ **$CLIENT_REPO**: $WORKFLOW_NAME triggered - [Monitor Progress](https://github.com/$CLIENT_REPO/actions)" >> $GITHUB_STEP_SUMMARY
285+
DISPATCH_SUCCESS=true
286+
break
287+
else
288+
echo "⚠️ $CLIENT_REPO dispatch attempt $RETRY_COUNT failed"
289+
[ $RETRY_COUNT -lt $MAX_RETRIES ] && sleep 5
290+
fi
291+
done
292+
293+
if [ "$DISPATCH_SUCCESS" = "false" ]; then
294+
echo "::error::Failed to trigger $CLIENT_REPO after $MAX_RETRIES attempts"
295+
echo "::warning::$CLIENT_LABEL breaking change detection will be skipped"
296+
echo "❌ **$CLIENT_REPO**: Failed to trigger - [Manual Check Required](https://github.com/$CLIENT_REPO)" >> $GITHUB_STEP_SUMMARY
297+
fi
298+
299+
- name: Wait for client workflow completion
300+
timeout-minutes: 12
301+
run: |
302+
echo "⏳ Waiting for all client type checking workflows to complete..."
303+
304+
WAIT_START_TIME=$(date +%s)
305+
MAX_WAIT_SECONDS=600 # 10 minutes max wait
306+
POLL_INTERVAL=30 # Check every 30 seconds
307+
308+
# Expected clients list - expand when adding mobile
309+
EXPECTED_CLIENTS=("typescript")
310+
COMPLETED_CLIENTS=()
311+
312+
while true; do
313+
CURRENT_TIME=$(date +%s)
314+
ELAPSED=$((CURRENT_TIME - WAIT_START_TIME))
315+
316+
# Check timeout
317+
if [ $ELAPSED -gt $MAX_WAIT_SECONDS ]; then
318+
echo "⏰ Timeout reached after ${ELAPSED}s - proceeding with available results"
319+
echo "::warning::Some client workflows may still be running"
320+
break
321+
fi
322+
323+
echo "🔍 Polling for completion markers (${ELAPSED}s elapsed)..."
324+
325+
# Check for completion markers in PR comments
326+
COMMENTS=$(gh api repos/${{ github.repository }}/issues/${{ github.event.number }}/comments \
327+
--jq '.[] | select(.body | contains("<!-- SDK-BREAKING-CHANGE-COMPLETION:")) | .body')
328+
329+
# Reset completed clients for this check
330+
COMPLETED_CLIENTS=()
331+
332+
# Check each expected client
333+
for CLIENT in "${EXPECTED_CLIENTS[@]}"; do
334+
if echo "$COMMENTS" | grep -q "<!-- SDK-BREAKING-CHANGE-COMPLETION:$CLIENT -->"; then
335+
COMPLETED_CLIENTS+=("$CLIENT")
336+
echo "✅ $CLIENT workflow completed"
337+
else
338+
echo "⏳ $CLIENT workflow still running..."
339+
fi
340+
done
341+
342+
# Check if all clients completed
343+
if [ ${#COMPLETED_CLIENTS[@]} -eq ${#EXPECTED_CLIENTS[@]} ]; then
344+
echo "🎉 All client workflows completed!"
345+
break
346+
fi
347+
348+
# Wait before next poll
349+
echo "⏸️ Waiting ${POLL_INTERVAL}s before next check..."
350+
sleep $POLL_INTERVAL
351+
done
352+
353+
# Summary
354+
echo ""
355+
echo "=== Final Status ==="
356+
echo "Completed clients: ${COMPLETED_CLIENTS[*]}"
357+
echo "Total wait time: ${ELAPSED}s"
358+
359+
if [ ${#COMPLETED_CLIENTS[@]} -eq ${#EXPECTED_CLIENTS[@]} ]; then
360+
echo "✅ All expected client workflows completed successfully"
361+
echo "status=complete" >> $GITHUB_OUTPUT
362+
else
363+
MISSING_CLIENTS=()
364+
for CLIENT in "${EXPECTED_CLIENTS[@]}"; do
365+
if [[ ! " ${COMPLETED_CLIENTS[*]} " =~ " ${CLIENT} " ]]; then
366+
MISSING_CLIENTS+=("$CLIENT")
367+
fi
368+
done
369+
echo "⚠️ Some workflows incomplete: ${MISSING_CLIENTS[*]}"
370+
echo "status=partial" >> $GITHUB_OUTPUT
371+
fi
372+
373+
# Update GitHub step summary
374+
echo "⏱️ **Synchronization Complete**: Waited ${ELAPSED}s for client workflows" >> $GITHUB_STEP_SUMMARY
375+
echo "📊 **Completed**: ${#COMPLETED_CLIENTS[@]}/${#EXPECTED_CLIENTS[@]} clients" >> $GITHUB_STEP_SUMMARY
376+
- name: Report final workflow status
377+
run: |
378+
echo "📊 Final SDK workflow status summary"
379+
380+
# Analyze all PR comments for breaking change results
381+
COMMENTS=$(gh api repos/${{ github.repository }}/issues/${{ github.event.number }}/comments \
382+
--jq '.[] | select(.body | contains("SDK-BREAKING-CHANGE-CHECK")) | .body')
383+
384+
BREAKING_DETECTED=false
385+
CLIENT_RESULTS=()
386+
387+
for CLIENT in typescript; do # Expand when adding mobile
388+
if echo "$COMMENTS" | grep -q "❌ TypeScript Breaking Changes Detected" && [[ "$CLIENT" == "typescript" ]]; then
389+
BREAKING_DETECTED=true
390+
CLIENT_RESULTS+=("❌ $CLIENT: Breaking changes detected")
391+
elif echo "$COMMENTS" | grep -q "✅ TypeScript Compatibility Check Passed" && [[ "$CLIENT" == "typescript" ]]; then
392+
CLIENT_RESULTS+=("✅ $CLIENT: No breaking changes")
393+
else
394+
CLIENT_RESULTS+=("⚠️ $CLIENT: Status unclear or incomplete")
395+
fi
396+
done
397+
398+
echo ""
399+
echo "=== Breaking Change Detection Results ==="
400+
for RESULT in "${CLIENT_RESULTS[@]}"; do
401+
echo "$RESULT"
402+
done
403+
404+
# Set overall status
405+
if [ "$BREAKING_DETECTED" = "true" ]; then
406+
echo ""
407+
echo "🚨 BREAKING CHANGES DETECTED - Review PR comments for details"
408+
echo "⚠️ This is non-blocking - PR can still be merged if changes are intentional"
409+
echo "status=breaking-changes" >> $GITHUB_OUTPUT
410+
else
411+
echo ""
412+
echo "✅ No breaking changes detected across all clients"
413+
echo "status=clean" >> $GITHUB_OUTPUT
414+
fi
415+
416+
# Add results to job summary
417+
echo "## 🔍 Breaking Change Detection Results" >> $GITHUB_STEP_SUMMARY
418+
for RESULT in "${CLIENT_RESULTS[@]}"; do
419+
echo "- $RESULT" >> $GITHUB_STEP_SUMMARY
420+
done
421+
422+
if [ "$BREAKING_DETECTED" = "true" ]; then
423+
echo "" >> $GITHUB_STEP_SUMMARY
424+
echo "⚠️ **Breaking changes detected** - See PR comments for migration guidance" >> $GITHUB_STEP_SUMMARY
425+
fi

0 commit comments

Comments
 (0)