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