name: Create GitHub Release on: workflow_dispatch: inputs: artifact-run-id: description: "GitHub Action Run ID containing artifacts" required: true type: string release-ticket-id: description: "Release Ticket ID - e.g. RELEASE-1762" required: true type: string env: ARTIFACTS_PATH: artifacts jobs: create-release: name: Create GitHub Release runs-on: ubuntu-24.04 permissions: contents: write id-token: write steps: - name: Check out repository uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: fetch-depth: 0 persist-credentials: true - name: Log inputs to job summary uses: ./.github/actions/log-inputs with: inputs: ${{ toJson(inputs) }} - name: Get branch from workflow run id: get_release_branch env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} ARTIFACT_RUN_ID: ${{ inputs.artifact-run-id }} run: | workflow_data=$(gh run view "$ARTIFACT_RUN_ID" --json headBranch,workflowName) release_branch=$(echo "$workflow_data" | jq -r .headBranch) workflow_name=$(echo "$workflow_data" | jq -r .workflowName) # branch protection check if [[ "$release_branch" != "main" && ! "$release_branch" =~ ^release/ ]]; then echo "::error::Branch '$release_branch' is not 'main' or a release branch starting with 'release/'. Releases must be created from protected branches." exit 1 fi echo "🔖 Release branch: $release_branch" echo "🔖 Workflow name: $workflow_name" echo "release_branch=$release_branch" >> "$GITHUB_OUTPUT" echo "workflow_name=$workflow_name" >> "$GITHUB_OUTPUT" case "$workflow_name" in *"Password Manager"* | "Build") app_name="Password Manager" app_name_suffix="bwpm" ;; *"Authenticator"*) app_name="Authenticator" app_name_suffix="bwa" ;; *) echo "::error::Unknown workflow name: $workflow_name" exit 1 ;; esac echo "🔖 App name: $app_name" echo "🔖 App name suffix: $app_name_suffix" echo "app_name=$app_name" >> "$GITHUB_OUTPUT" echo "app_name_suffix=$app_name_suffix" >> "$GITHUB_OUTPUT" - name: Get version info from run logs and set release tag name id: get_release_info env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} ARTIFACT_RUN_ID: ${{ inputs.artifact-run-id }} _APP_NAME_SUFFIX: ${{ steps.get_release_branch.outputs.app_name_suffix }} run: | workflow_log=$(gh run view "$ARTIFACT_RUN_ID" --log) version_number_with_trailing_dot=$(grep -m 1 "Setting version code to" <<< "$workflow_log" | sed 's/.*Setting version code to //') version_number=${version_number_with_trailing_dot%.} # remove trailing dot version_name_with_trailing_dot=$(grep -m 1 "Setting version name to" <<< "$workflow_log" | sed 's/.*Setting version name to //') version_name=${version_name_with_trailing_dot%.} # remove trailing dot if [[ -z "$version_name" ]]; then echo "::warning::Version name not found. Using default value - 0.0.0" version_name="0.0.0" else echo "✅ Found version name: $version_name" fi if [[ -z "$version_number" ]]; then echo "::warning::Version number not found. Using default value - 0" version_number="0" else echo "✅ Found version number: $version_number" fi echo "version_number=$version_number" >> "$GITHUB_OUTPUT" echo "version_name=$version_name" >> "$GITHUB_OUTPUT" tag_name="v$version_name-$_APP_NAME_SUFFIX" # e.g. v2025.6.0-bwpm echo "🔖 New tag name: $tag_name" echo "tag_name=$tag_name" >> "$GITHUB_OUTPUT" last_release_tag=$(git tag -l --sort=-authordate | grep "$_APP_NAME_SUFFIX" | head -n 1) echo "🔖 Last release tag: $last_release_tag" echo "last_release_tag=$last_release_tag" >> "$GITHUB_OUTPUT" - name: Download artifacts env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} ARTIFACT_RUN_ID: ${{ inputs.artifact-run-id }} run: | gh run download "$ARTIFACT_RUN_ID" -D "$ARTIFACTS_PATH" file_count=$(find "$ARTIFACTS_PATH" -type f | wc -l) echo "Downloaded $file_count file(s)." if [ "$file_count" -gt 0 ]; then echo "Downloaded files:" find "$ARTIFACTS_PATH" -type f fi # Files that won't be included in any release files_to_remove=( "com.x8bit.bitwarden.aab" "com.x8bit.bitwarden.aab-sha256.txt" "com.x8bit.bitwarden.beta.apk" "com.x8bit.bitwarden.beta.apk-sha256.txt" "com.x8bit.bitwarden.beta.aab" "com.x8bit.bitwarden.beta.aab-sha256.txt" "com.x8bit.bitwarden.beta-fdroid.apk" "com.x8bit.bitwarden.beta-fdroid.apk-sha256.txt" "com.x8bit.bitwarden.dev.apk" "com.x8bit.bitwarden.dev.apk-sha256.txt" "com.bitwarden.authenticator.aab" "authenticator-android-aab-sha256.txt" ) for file in "${files_to_remove[@]}"; do find "$ARTIFACTS_PATH" -name "$file" -type f -delete done echo "🔖 Removed internal artifacts." echo "" echo "🔖 Files to be included in the release:" find "$ARTIFACTS_PATH" -type f - 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-android secrets: "JIRA-API-EMAIL,JIRA-API-TOKEN" - name: Log out from Azure uses: bitwarden/gh-actions/azure-logout@main - name: Get product release notes id: get_release_notes env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} ARTIFACT_RUN_ID: ${{ inputs.artifact-run-id }} _VERSION_NAME: ${{ steps.get_release_info.outputs.version_name }} _RELEASE_TICKET_ID: ${{ inputs.release-ticket-id }} _JIRA_API_EMAIL: ${{ steps.get-kv-secrets.outputs.JIRA-API-EMAIL }} _JIRA_API_TOKEN: ${{ steps.get-kv-secrets.outputs.JIRA-API-TOKEN }} run: | echo "Getting product release notes..." # capture output and exit code so this step continues even if we can't retrieve release notes. script_exit_code=0 product_release_notes=$(python .github/scripts/jira-get-release-notes/jira_release_notes.py "$_RELEASE_TICKET_ID" "$_JIRA_API_EMAIL" "$_JIRA_API_TOKEN") || script_exit_code=$? echo "--------------------------------" if [[ $script_exit_code -ne 0 || -z "$product_release_notes" ]]; then echo "Script Output: $product_release_notes" echo "::warning::Failed to fetch release notes from Jira. Check script logs for more details." product_release_notes="" else echo "✅ Product release notes:" echo "$product_release_notes" fi echo "$product_release_notes" > product_release_notes.txt - name: Create Release id: create_release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} _APP_NAME: ${{ steps.get_release_branch.outputs.app_name }} _VERSION_NAME: ${{ steps.get_release_info.outputs.version_name }} _VERSION_NUMBER: ${{ steps.get_release_info.outputs.version_number }} _TARGET_COMMIT: ${{ steps.get_release_branch.outputs.release_branch }} _TAG_NAME: ${{ steps.get_release_info.outputs.tag_name }} _LAST_RELEASE_TAG: ${{ steps.get_release_info.outputs.last_release_tag }} run: | is_latest_release=false if [[ "$_APP_NAME" == "Password Manager" ]]; then is_latest_release=true fi echo "⌛️ Creating release for $_APP_NAME $_VERSION_NAME ($_VERSION_NUMBER) on $_TARGET_COMMIT" release_url=$(gh release create "$_TAG_NAME" \ --title "$_APP_NAME $_VERSION_NAME ($_VERSION_NUMBER)" \ --target "$_TARGET_COMMIT" \ --generate-notes \ --notes-start-tag "$_LAST_RELEASE_TAG" \ --latest=$is_latest_release \ --draft \ "$ARTIFACTS_PATH/*/*") # Extract release tag from URL release_id_from_url=$(echo "$release_url" | sed 's/.*\/tag\///') echo "release_id_from_url=$release_id_from_url" >> "$GITHUB_OUTPUT" echo "url=$release_url" >> "$GITHUB_OUTPUT" echo "✅ Release created: $release_url" echo "🔖 Release ID from URL: $release_id_from_url" - name: Update Release Description id: update_release_description env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} ARTIFACT_RUN_ID: ${{ inputs.artifact-run-id }} _VERSION_NAME: ${{ steps.get_release_info.outputs.version_name }} _RELEASE_ID: ${{ steps.create_release.outputs.release_id_from_url }} run: | echo "Getting current release body. Release ID: $_RELEASE_ID" current_body=$(gh release view "$_RELEASE_ID" --json body --jq .body) product_release_notes=$(cat product_release_notes.txt) # Update release description with product release notes and builds source updated_body="# Overview ${product_release_notes} ${current_body} **Builds Source:** https://github.com/${{ github.repository }}/actions/runs/$ARTIFACT_RUN_ID" new_release_url=$(gh release edit "$_RELEASE_ID" --notes "$updated_body") # draft release links change after editing echo "release_url=$new_release_url" >> "$GITHUB_OUTPUT" - name: Add Release Summary env: _RELEASE_TAG: ${{ steps.get_release_info.outputs.tag_name }} _LAST_RELEASE_TAG: ${{ steps.get_release_info.outputs.last_release_tag }} _VERSION_NAME: ${{ steps.get_release_info.outputs.version_name }} _VERSION_NUMBER: ${{ steps.get_release_info.outputs.version_number }} _RELEASE_BRANCH: ${{ steps.get_release_branch.outputs.release_branch }} _RELEASE_URL: ${{ steps.update_release_description.outputs.release_url }} run: | { echo "# :fish_cake: Release ready at:" echo "$_RELEASE_URL" echo "" } >> "$GITHUB_STEP_SUMMARY" if [[ "$_VERSION_NAME" == "0.0.0" || "$_VERSION_NUMBER" == "0" ]]; then { echo "> [!CAUTION]" echo "> Version name or number wasn't previously found and a default value was used. You'll need to manually update the release Title, Tag and Description, specifically, the \"Full Changelog\" link." echo "" } >> "$GITHUB_STEP_SUMMARY" fi { echo ":clipboard: Confirm that the defined GitHub Release options are correct:" echo " * :bookmark: New tag name: \`$_RELEASE_TAG\`" echo " * :palm_tree: Target branch: \`$_RELEASE_BRANCH\`" echo " * :ocean: Previous tag set in the description \"Full Changelog\" link: \`$_LAST_RELEASE_TAG\`" echo " * :white_check_mark: Description has automated release notes and they match the commits in the release branch" echo "> [!NOTE]" echo "> Commits directly pushed to branches without a Pull Request won't appear in the automated release notes." } >> "$GITHUB_STEP_SUMMARY"