clients/.github/workflows/sdk-update.yml
Todd Martin 66d69a366f
fix(workflow): [PM-32677] Fix changelog generation to correctly render sdk-internal PRs
* Fixed changelog generation.

* Quote the variable for security.
2026-04-14 09:58:50 -04:00

457 lines
17 KiB
YAML

name: SDK Update
run-name: "SDK ${{ inputs.run-mode }} - ${{ inputs.base-branch }} @ ${{ inputs.sdk-version }}"
on:
workflow_dispatch:
inputs:
run-mode:
description: "Run Mode"
type: choice
options:
- Update # opens a PR in this repo updating the SDK
- Test # validates SDK update without creating PR
default: Update
sdk-version:
description: "SDK Version"
type: string
required: true
base-branch:
description: "Base branch for PR (branch to merge into)"
type: string
default: "main"
triggering-run-id:
description: "Run ID of the workflow that triggered this update (e.g. sdk-internal publish run)"
type: string
env:
_BOT_NAME: "bw-ghapp[bot]"
_BOT_EMAIL: "178206702+bw-ghapp[bot]@users.noreply.github.com"
defaults:
run:
shell: bash
jobs:
update:
name: Update and PR
if: ${{ inputs.run-mode == 'Update' }}
runs-on: ubuntu-24.04
permissions:
id-token: write
steps:
- name: Log in to Azure
uses: bitwarden/gh-actions/azure-login@main
with:
subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
tenant_id: ${{ secrets.AZURE_TENANT_ID }}
client_id: ${{ secrets.AZURE_CLIENT_ID }}
- name: Get Azure Key Vault secrets
id: get-kv-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@main
with:
keyvault: gh-org-bitwarden
secrets: "BW-GHAPP-ID,BW-GHAPP-KEY"
- name: Log out from Azure
uses: bitwarden/gh-actions/azure-logout@main
- name: Generate GH App token
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
id: app-token
with:
app-id: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-ID }}
private-key: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-KEY }}
permission-pull-requests: write
permission-actions: read
permission-contents: write
- name: Log inputs to job summary
env:
RUN_MODE: ${{ inputs.run-mode }}
SDK_VERSION: ${{ inputs.sdk-version }}
BASE_BRANCH: ${{ inputs.base-branch }}
run: |
{
echo "## 📋 Workflow Inputs"
echo "- Run Mode: $RUN_MODE"
echo "- SDK Version: $SDK_VERSION"
echo "- Base Branch: $BASE_BRANCH"
} >> $GITHUB_STEP_SUMMARY
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
token: ${{ steps.app-token.outputs.token }}
ref: ${{ inputs.base-branch }}
fetch-depth: 0
persist-credentials: true
- name: Get Node Version
id: retrieve-node-version
run: |
NODE_NVMRC=$(cat .nvmrc)
NODE_VERSION=${NODE_NVMRC/v/''}
echo "node_version=$NODE_VERSION" >> "$GITHUB_OUTPUT"
- name: Set up Node
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
node-version: ${{ steps.retrieve-node-version.outputs.node_version }}
- name: Print environment
run: |
node --version
npm --version
- name: Get current SDK version from base branch
id: get-current-sdk
env:
BASE_BRANCH: ${{ inputs.base-branch }}
run: |
CURRENT_SDK_VERSION=$(git show origin/$BASE_BRANCH:package.json | jq -r '.dependencies."@bitwarden/sdk-internal" // empty')
CURRENT_COMMERCIAL_VERSION=$(git show origin/$BASE_BRANCH:package.json | jq -r '.dependencies."@bitwarden/commercial-sdk-internal" // empty')
if [ -z "$CURRENT_SDK_VERSION" ]; then
echo "::warning::Could not find current @bitwarden/sdk-internal version"
CURRENT_SDK_VERSION="unknown"
fi
if [ -z "$CURRENT_COMMERCIAL_VERSION" ]; then
echo "::warning::Could not find current @bitwarden/commercial-sdk-internal version"
CURRENT_COMMERCIAL_VERSION="unknown"
fi
echo "current-sdk-version=$CURRENT_SDK_VERSION" >> "$GITHUB_OUTPUT"
echo "current-commercial-version=$CURRENT_COMMERCIAL_VERSION" >> "$GITHUB_OUTPUT"
echo "📋 Current SDK version: $CURRENT_SDK_VERSION"
echo "📋 Current Commercial SDK version: $CURRENT_COMMERCIAL_VERSION"
- name: Validate SDK version
id: validate-version
env:
SDK_VERSION: ${{ inputs.sdk-version }}
CURRENT_VERSION: ${{ steps.get-current-sdk.outputs.current-sdk-version }}
run: |
echo "🔍 Validating SDK version: $SDK_VERSION"
if ! npm view @bitwarden/sdk-internal@$SDK_VERSION version 2>/dev/null; then
echo "::error::SDK version $SDK_VERSION not found in npm registry"
exit 1
fi
if ! npm view @bitwarden/commercial-sdk-internal@$SDK_VERSION version 2>/dev/null; then
echo "::error::Commercial SDK version $SDK_VERSION not found in npm registry"
exit 1
fi
echo "✅ SDK versions exist in npm registry"
if [ "$SDK_VERSION" = "$CURRENT_VERSION" ]; then
echo "::error::Provided SDK version is the same as current version ($CURRENT_VERSION)"
exit 1
fi
- name: Check for existing branch
id: check-branch
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
BASE_BRANCH: ${{ inputs.base-branch }}
run: |
BRANCH_NAME="sdlc/sdk-update"
echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT"
if git ls-remote --exit-code --heads origin "$BRANCH_NAME" > /dev/null 2>&1; then
echo "branch_exists=true" >> "$GITHUB_OUTPUT"
echo "📋 Branch $BRANCH_NAME exists on remote"
else
echo "branch_exists=false" >> "$GITHUB_OUTPUT"
echo "📋 Branch $BRANCH_NAME does not exist"
fi
- name: Prevent updating branch with manual changes
if: ${{ steps.check-branch.outputs.branch_exists == 'true' }}
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
_BRANCH_NAME: ${{ steps.check-branch.outputs.branch_name }}
BASE_BRANCH: ${{ inputs.base-branch }}
run: |
git fetch origin "$_BRANCH_NAME"
LATEST_COMMIT_AUTHOR=$(git log -1 --format='%ae' "origin/$_BRANCH_NAME")
echo "Latest commit author in branch ($_BRANCH_NAME): $LATEST_COMMIT_AUTHOR"
echo "Expected bot email: $_BOT_EMAIL"
if [ "$LATEST_COMMIT_AUTHOR" != "$_BOT_EMAIL" ]; then
echo "::error::Branch $_BRANCH_NAME has a commit not made by the bot." \
"This indicates manual changes have been made to the branch," \
"PR has to be merged or closed before running this workflow again."
echo "👀 Fetching existing PR..."
EXISTING_PR=$(gh pr list --head "$_BRANCH_NAME" --base "$BASE_BRANCH" --state open --json number --jq '.[0].number // empty')
if [ -z "$EXISTING_PR" ]; then
echo "::error::Couldn't find an existing PR for branch $_BRANCH_NAME."
exit 1
fi
PR_URL="https://github.com/${{ github.repository }}/pull/$EXISTING_PR"
echo "## ❌ Merge or close: $PR_URL" >> "$GITHUB_STEP_SUMMARY"
exit 1
fi
echo "✅ Branch tip commit was made by the bot. Safe to proceed."
- name: Check for stop-updates label
if: ${{ steps.check-branch.outputs.branch_exists == 'true' }}
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
_BRANCH_NAME: ${{ steps.check-branch.outputs.branch_name }}
BASE_BRANCH: ${{ inputs.base-branch }}
run: |
EXISTING_PR=$(gh pr list --head "$_BRANCH_NAME" --base "$BASE_BRANCH" --state open --json number,labels --jq '.[0] // empty')
if [ -n "$EXISTING_PR" ]; then
HAS_STOP_LABEL=$(echo "$EXISTING_PR" | jq -r '.labels | map(select(.name == "stop-updates")) | length > 0')
if [ "$HAS_STOP_LABEL" = "true" ]; then
PR_NUMBER=$(echo "$EXISTING_PR" | jq -r '.number')
PR_URL="https://github.com/${{ github.repository }}/pull/$PR_NUMBER"
echo "::error::PR #$PR_NUMBER has the 'stop-updates' label. Remove the label to resume automated updates."
echo "## ⛔ Blocked by stop-updates label: $PR_URL" >> "$GITHUB_STEP_SUMMARY"
exit 1
fi
fi
echo "✅ No stop-updates label found. Proceeding."
- name: Configure git identity
run: |
git config user.name "$_BOT_NAME"
git config user.email "$_BOT_EMAIL"
- name: Create fresh branch from base
env:
_BRANCH_NAME: ${{ steps.check-branch.outputs.branch_name }}
BASE_BRANCH: ${{ inputs.base-branch }}
run: |
# Delete local branch if it exists (from fetch)
git branch -D "$_BRANCH_NAME" 2>/dev/null || true
# Create fresh branch from base
echo "📝 Creating fresh branch $_BRANCH_NAME from $BASE_BRANCH"
git switch -c "$_BRANCH_NAME"
- name: Update SDK versions
env:
SDK_VERSION: ${{ inputs.sdk-version }}
run: |
echo "📦 Installing SDK version: $SDK_VERSION"
npm install --save-exact @bitwarden/sdk-internal@$SDK_VERSION
npm install --save-exact @bitwarden/commercial-sdk-internal@$SDK_VERSION
echo "✅ SDK packages updated in package.json"
- name: Generate changelog
id: changelog
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
OLD_VERSION: ${{ steps.get-current-sdk.outputs.current-sdk-version }}
NEW_VERSION: ${{ inputs.sdk-version }}
run: |
# Look up source commits from npm package metadata
OLD_REF=$(npm view "@bitwarden/sdk-internal@$OLD_VERSION" gitHead 2>/dev/null || echo "")
NEW_REF=$(npm view "@bitwarden/sdk-internal@$NEW_VERSION" gitHead 2>/dev/null || echo "")
if [ -n "$OLD_REF" ] && [ -n "$NEW_REF" ]; then
echo "Fetching changelog for sdk-internal: $OLD_REF...$NEW_REF"
CHANGELOG=$(./scripts/get-repo-changelog.sh "bitwarden/sdk-internal" "$OLD_REF" "$NEW_REF" 2>/dev/null || echo "")
if [ -n "$CHANGELOG" ]; then
echo "changelog<<__CHANGELOG_END__" >> "$GITHUB_OUTPUT"
echo "$CHANGELOG" >> "$GITHUB_OUTPUT"
echo "__CHANGELOG_END__" >> "$GITHUB_OUTPUT"
echo "✅ Generated changelog"
else
echo "::warning::Could not generate changelog"
fi
else
echo "::warning::Could not resolve source commits from npm for changelog (old: $OLD_VERSION, new: $NEW_VERSION)"
fi
- name: Generate validation summary
env:
SDK_VERSION: ${{ inputs.sdk-version }}
run: |
{
echo "## 📊 Validation Summary"
echo ""
echo "- SDK Version: $SDK_VERSION"
echo ""
echo "### Changed Files"
echo '```'
git diff --stat package.json package-lock.json
echo '```'
} >> $GITHUB_STEP_SUMMARY
- name: Commit changes
env:
SDK_VERSION: ${{ inputs.sdk-version }}
BRANCH_NAME: ${{ steps.check-branch.outputs.branch_name }}
run: |
echo "👀 Committing SDK version update..."
git add package.json package-lock.json
if git diff --cached --quiet; then
echo "::warning::No changes to commit — branch already contains SDK $SDK_VERSION"
else
git commit -m "Update sdk-internal to $SDK_VERSION"
fi
git push --force-with-lease origin "$BRANCH_NAME"
- name: Create or Update Pull Request
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
BRANCH_NAME: ${{ steps.check-branch.outputs.branch_name }}
SDK_VERSION: ${{ inputs.sdk-version }}
OLD_SDK_VERSION: ${{ steps.get-current-sdk.outputs.current-sdk-version }}
OLD_COMMERCIAL_VERSION: ${{ steps.get-current-sdk.outputs.current-commercial-version }}
CHANGELOG: ${{ steps.changelog.outputs.changelog }}
BASE_BRANCH: ${{ inputs.base-branch }}
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_SERVER_URL: ${{ github.server_url }}
GITHUB_RUN_ID: ${{ github.run_id }}
TRIGGERING_RUN_ID: ${{ inputs.triggering-run-id }}
run: |
# Build PR body
PR_TITLE="Update sdk-internal to $SDK_VERSION"
{
echo "## 🤖 Automated SDK Update"
echo ""
echo "This PR updates the SDK packages to version \`$SDK_VERSION\`."
echo ""
echo "### Changes"
echo "- \`@bitwarden/sdk-internal\`: \`$OLD_SDK_VERSION\` → \`$SDK_VERSION\`"
echo "- \`@bitwarden/commercial-sdk-internal\`: \`$OLD_COMMERCIAL_VERSION\` → \`$SDK_VERSION\`"
if [ -n "$CHANGELOG" ]; then
echo ""
echo "### What's Changed in SDK"
echo "$CHANGELOG"
fi
echo ""
echo "### Links"
echo "- [Workflow Run]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID)"
if [ -n "$TRIGGERING_RUN_ID" ]; then
echo "- [Triggering SDK Publish Run](https://github.com/bitwarden/sdk-internal/actions/runs/$TRIGGERING_RUN_ID)"
fi
echo "- [SDK Releases](https://github.com/bitwarden/sdk-internal/releases)"
} > /tmp/pr-body.md
# Check for existing PR
EXISTING_PR=$(gh pr list --head "$BRANCH_NAME" --base "$BASE_BRANCH" --state open --json number --jq '.[0].number // empty')
if [ -n "$EXISTING_PR" ]; then
echo "🔄 Updating existing PR #$EXISTING_PR..."
gh pr edit "$EXISTING_PR" \
--title "$PR_TITLE" \
--body-file /tmp/pr-body.md
PR_URL="https://github.com/$GITHUB_REPOSITORY/pull/$EXISTING_PR"
echo "## ✅ Updated PR: $PR_URL" >> "$GITHUB_STEP_SUMMARY"
else
echo "📝 Creating new PR..."
LABELS="automated pr"
PR_URL=$(gh pr create \
--title "$PR_TITLE" \
--body-file /tmp/pr-body.md \
--base "$BASE_BRANCH" \
--head "$BRANCH_NAME" \
--label "$LABELS")
echo "## 🚀 Created PR: $PR_URL" >> "$GITHUB_STEP_SUMMARY"
fi
test:
name: Test SDK Update
if: ${{ inputs.run-mode == 'Test' }}
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- name: Log inputs to job summary
env:
RUN_MODE: ${{ inputs.run-mode }}
SDK_VERSION: ${{ inputs.sdk-version }}
BASE_BRANCH: ${{ inputs.base-branch }}
run: |
{
echo "## 📋 Workflow Inputs (Test Mode)"
echo "- Run Mode: $RUN_MODE"
echo "- SDK Version: $SDK_VERSION"
echo "- Base Branch: $BASE_BRANCH"
} >> $GITHUB_STEP_SUMMARY
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ inputs.base-branch }}
persist-credentials: false
- name: Get Node Version
id: retrieve-node-version
run: |
NODE_NVMRC=$(cat .nvmrc)
NODE_VERSION=${NODE_NVMRC/v/''}
echo "node_version=$NODE_VERSION" >> "$GITHUB_OUTPUT"
- name: Set up Node
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
node-version: ${{ steps.retrieve-node-version.outputs.node_version }}
- name: Print environment
run: |
node --version
npm --version
- name: Validate SDK version exists
env:
SDK_VERSION: ${{ inputs.sdk-version }}
run: |
echo "🔍 Validating SDK version: $SDK_VERSION"
if ! npm view @bitwarden/sdk-internal@$SDK_VERSION version 2>/dev/null; then
echo "::error::SDK version $SDK_VERSION not found in npm registry"
exit 1
fi
if ! npm view @bitwarden/commercial-sdk-internal@$SDK_VERSION version 2>/dev/null; then
echo "::error::Commercial SDK version $SDK_VERSION not found in npm registry"
exit 1
fi
echo "✅ SDK versions exist in npm registry"
- name: Update SDK versions
env:
SDK_VERSION: ${{ inputs.sdk-version }}
run: |
echo "📦 Installing SDK version: $SDK_VERSION"
npm install --save-exact @bitwarden/sdk-internal@$SDK_VERSION
npm install --save-exact @bitwarden/commercial-sdk-internal@$SDK_VERSION
echo "✅ SDK packages updated in package.json"
- name: Test summary
env:
SDK_VERSION: ${{ inputs.sdk-version }}
run: |
{
echo "## 📊 Test Results"
echo ""
echo "- SDK Version: $SDK_VERSION"
echo "- ✅ SDK packages updated successfully"
} >> $GITHUB_STEP_SUMMARY