@@ -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