Skip to content

Commit 235c6d1

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 0364659 commit 235c6d1

File tree

2 files changed

+324
-0
lines changed

2 files changed

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

0 commit comments

Comments
 (0)