ocs_changelog_update #107
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Workflow: Update Changelog and Docs from OCS PR | |
| # | |
| # This workflow automatically updates changelog and documentation when PRs are merged | |
| # in the open-chat-studio repository. It can be triggered via repository_dispatch events | |
| # or manually using workflow_dispatch. | |
| # | |
| # See README-changelog-automation.md for more. | |
| name: Update Changelog and Docs from OCS PR | |
| on: | |
| repository_dispatch: | |
| types: [ocs_changelog_update] | |
| workflow_dispatch: | |
| inputs: | |
| pr_number: | |
| description: 'OCS PR number' | |
| required: true | |
| type: number | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| issues: write | |
| id-token: write | |
| # Prevent concurrent runs for the same PR to avoid conflicts | |
| concurrency: | |
| group: changelog-update-${{ github.event_name == 'repository_dispatch' && github.event.client_payload.pr_number || inputs.pr_number }} | |
| cancel-in-progress: false # Let current run finish | |
| jobs: | |
| update-changelog-and-docs: | |
| runs-on: ubuntu-latest | |
| env: | |
| WIDGET_PATH_PREFIX: "components/" | |
| WIDGET_CHANGELOG: "docs/chat_widget/changelog.md" | |
| MAIN_CHANGELOG: "docs/changelog.md" | |
| SOURCE_REPO: "dimagi/open-chat-studio" | |
| steps: | |
| - name: Determine base branch | |
| id: determine_branch | |
| run: | | |
| # Determine base branch before checkout | |
| if [ "${{ github.event_name }}" == "repository_dispatch" ]; then | |
| IS_WIDGET="${{ github.event.client_payload.is_widget_change }}" | |
| else | |
| echo "Fetching PR data to determine widget status..." | |
| FILES=$(gh api "repos/${{ env.SOURCE_REPO }}/pulls/${{ inputs.pr_number }}/files" --paginate --jq '.[].filename') || { echo "Failed to fetch PR data"; exit 1; } | |
| WIDGET_PREFIX="${{ env.WIDGET_PATH_PREFIX }}" | |
| if echo "$FILES" | grep -q "^$WIDGET_PREFIX"; then | |
| IS_WIDGET="true" | |
| else | |
| IS_WIDGET="false" | |
| fi | |
| fi | |
| if [ "$IS_WIDGET" == "true" ]; then | |
| BASE_BRANCH="widget-develop" | |
| # Check if widget-develop exists on remote | |
| if git ls-remote --exit-code --heads https://github.com/${{ github.repository }}.git widget-develop > /dev/null 2>&1; then | |
| CHECKOUT_REF="widget-develop" | |
| CREATE_WIDGET_BRANCH="false" | |
| else | |
| echo "widget-develop branch does not exist, will create from main" | |
| CHECKOUT_REF="main" | |
| CREATE_WIDGET_BRANCH="true" | |
| fi | |
| else | |
| BASE_BRANCH="main" | |
| CHECKOUT_REF="main" | |
| CREATE_WIDGET_BRANCH="false" | |
| fi | |
| echo "base_branch=$BASE_BRANCH" >> $GITHUB_OUTPUT | |
| echo "checkout_ref=$CHECKOUT_REF" >> $GITHUB_OUTPUT | |
| echo "create_widget_branch=$CREATE_WIDGET_BRANCH" >> $GITHUB_OUTPUT | |
| echo "is_widget_change=$IS_WIDGET" >> $GITHUB_OUTPUT | |
| echo "Using base branch: $BASE_BRANCH, checkout ref: $CHECKOUT_REF (widget change: $IS_WIDGET)" | |
| env: | |
| GH_TOKEN: ${{ secrets.OCS_DOCS_PAT }} | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ steps.determine_branch.outputs.checkout_ref }} | |
| fetch-depth: 1 | |
| - name: Configure git | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| - name: Set variables from dispatch or manual input | |
| id: set_vars | |
| # Payload fields are passed via env (not ${{ }} interpolation in the script body) | |
| # so PR titles/bodies containing backticks, quotes, parens, or $ cannot break the shell. | |
| env: | |
| GH_TOKEN: ${{ secrets.OCS_DOCS_PAT }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| PAYLOAD_PR_NUMBER: ${{ github.event.client_payload.pr_number }} | |
| PAYLOAD_PR_TITLE: ${{ github.event.client_payload.pr_title }} | |
| PAYLOAD_PR_URL: ${{ github.event.client_payload.pr_url }} | |
| PAYLOAD_PR_AUTHOR: ${{ github.event.client_payload.pr_author }} | |
| PAYLOAD_PR_BODY: ${{ github.event.client_payload.pr_body }} | |
| INPUT_PR_NUMBER: ${{ inputs.pr_number }} | |
| DETERMINED_BASE_BRANCH: ${{ steps.determine_branch.outputs.base_branch }} | |
| DETERMINED_IS_WIDGET: ${{ steps.determine_branch.outputs.is_widget_change }} | |
| run: | | |
| if [ "$EVENT_NAME" == "repository_dispatch" ]; then | |
| echo "pr_number=$PAYLOAD_PR_NUMBER" >> $GITHUB_OUTPUT | |
| { | |
| echo "pr_title<<OCS_DOCS_EOF" | |
| echo "$PAYLOAD_PR_TITLE" | |
| echo "OCS_DOCS_EOF" | |
| } >> $GITHUB_OUTPUT | |
| { | |
| echo "pr_url<<OCS_DOCS_EOF" | |
| echo "$PAYLOAD_PR_URL" | |
| echo "OCS_DOCS_EOF" | |
| } >> $GITHUB_OUTPUT | |
| { | |
| echo "pr_author<<OCS_DOCS_EOF" | |
| echo "$PAYLOAD_PR_AUTHOR" | |
| echo "OCS_DOCS_EOF" | |
| } >> $GITHUB_OUTPUT | |
| { | |
| echo "pr_body<<OCS_DOCS_EOF" | |
| echo "$PAYLOAD_PR_BODY" | |
| echo "OCS_DOCS_EOF" | |
| } >> $GITHUB_OUTPUT | |
| else | |
| echo "pr_number=$INPUT_PR_NUMBER" >> $GITHUB_OUTPUT | |
| # Fetch PR details using GitHub API | |
| PR_DATA=$(gh api "repos/${SOURCE_REPO}/pulls/${INPUT_PR_NUMBER}") || { echo "Failed to fetch PR data"; exit 1; } | |
| # Use multiline outputs for safe escaping | |
| { | |
| echo "pr_title<<OCS_DOCS_EOF" | |
| echo "$PR_DATA" | jq -r '.title' | |
| echo "OCS_DOCS_EOF" | |
| } >> $GITHUB_OUTPUT | |
| { | |
| echo "pr_url<<OCS_DOCS_EOF" | |
| echo "$PR_DATA" | jq -r '.html_url' | |
| echo "OCS_DOCS_EOF" | |
| } >> $GITHUB_OUTPUT | |
| { | |
| echo "pr_author<<OCS_DOCS_EOF" | |
| echo "$PR_DATA" | jq -r '.user.login' | |
| echo "OCS_DOCS_EOF" | |
| } >> $GITHUB_OUTPUT | |
| { | |
| echo "pr_body<<OCS_DOCS_EOF" | |
| echo "$PR_DATA" | jq -r '.body' | |
| echo "OCS_DOCS_EOF" | |
| } >> $GITHUB_OUTPUT | |
| fi | |
| # Use base branch from determine_branch step (already determined correctly) | |
| echo "base_branch=$DETERMINED_BASE_BRANCH" >> $GITHUB_OUTPUT | |
| echo "is_widget_change=$DETERMINED_IS_WIDGET" >> $GITHUB_OUTPUT | |
| - name: Create branch for changelog update | |
| run: | | |
| # Create widget-develop from main if needed | |
| if [ "${{ steps.determine_branch.outputs.create_widget_branch }}" == "true" ]; then | |
| echo "Creating widget-develop branch from main..." | |
| git push origin HEAD:widget-develop | |
| echo "Created widget-develop branch" | |
| fi | |
| BRANCH_NAME="changelog-pr-${{ steps.set_vars.outputs.pr_number }}-${{ github.run_number }}" | |
| git checkout -b $BRANCH_NAME | |
| echo "branch_name=$BRANCH_NAME" >> $GITHUB_ENV | |
| - name: Create instruction file for Claude | |
| id: create_instructions | |
| run: | | |
| # Export environment variables for template substitution | |
| export PR_NUMBER="${{ steps.set_vars.outputs.pr_number }}" | |
| export PR_URL="${{ steps.set_vars.outputs.pr_url }}" | |
| export PR_AUTHOR="${{ steps.set_vars.outputs.pr_author }}" | |
| export PR_MERGED_AT="${{ steps.set_vars.outputs.pr_merged_at }}" | |
| export IS_WIDGET_CHANGE="${{ steps.set_vars.outputs.is_widget_change }}" | |
| export BASE_BRANCH="${{ steps.set_vars.outputs.base_branch }}" | |
| export WIDGET_CHANGELOG="${{ env.WIDGET_CHANGELOG }}" | |
| export MAIN_CHANGELOG="${{ env.MAIN_CHANGELOG }}" | |
| export WIDGET_PATH_PREFIX="${{ env.WIDGET_PATH_PREFIX }}" | |
| # Use heredoc for variables that may contain quotes and special characters | |
| PR_TITLE=$(cat <<'EOF' | |
| ${{ steps.set_vars.outputs.pr_title }} | |
| EOF | |
| ) | |
| export PR_TITLE | |
| PR_LABELS=$(cat <<'EOF' | |
| ${{ steps.set_vars.outputs.pr_labels }} | |
| EOF | |
| ) | |
| export PR_LABELS | |
| PR_BODY=$(cat <<'EOF' | |
| ${{ steps.set_vars.outputs.pr_body }} | |
| EOF | |
| ) | |
| export PR_BODY | |
| # Determine context message and load appropriate changelog section template | |
| if [ "${{ steps.set_vars.outputs.is_widget_change }}" == "true" ]; then | |
| export CONTEXT_MESSAGE="**This PR modifies the chat widget** (files in $WIDGET_PATH_PREFIX folder). Widget documentation should be updated in the chat_widget/ directory." | |
| export CHANGELOG_INSTRUCTIONS=$(envsubst < .github/templates/widget-changelog-section.md) | |
| else | |
| export CONTEXT_MESSAGE="This PR modifies the main Open Chat Studio application." | |
| export CHANGELOG_INSTRUCTIONS=$(envsubst < .github/templates/main-changelog-section.md) | |
| fi | |
| # Use envsubst to replace variables in main template and write to GITHUB_OUTPUT | |
| { | |
| echo "instructions<<EOF" | |
| envsubst < .github/templates/changelog-instructions.md | |
| echo "EOF" | |
| } >> $GITHUB_OUTPUT | |
| - name: Run Claude Code to generate changelog and update docs | |
| id: claude | |
| timeout-minutes: 10 | |
| uses: anthropics/claude-code-action@v1 | |
| with: | |
| anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} | |
| github_token: ${{ secrets.OCS_DOCS_PAT }} | |
| prompt: ${{ steps.create_instructions.outputs.instructions }} | |
| claude_args: | | |
| --allowedTools "Read,Write,Edit,Glob,Grep,Task,Bash(git add:*),Bash(git commit:*)" | |
| - name: Check for changes and push | |
| id: check_changes | |
| run: | | |
| BASE_BRANCH="${{ steps.set_vars.outputs.base_branch }}" | |
| if git diff --quiet origin/$BASE_BRANCH; then | |
| echo "has_changes=false" >> $GITHUB_OUTPUT | |
| echo "No changes made by Claude" | |
| else | |
| echo "has_changes=true" >> $GITHUB_OUTPUT | |
| # Determine which changelog file to check based on widget change flag | |
| if [ "${{ steps.set_vars.outputs.is_widget_change }}" == "true" ]; then | |
| CHANGELOG_FILE="${{ env.WIDGET_CHANGELOG }}" | |
| else | |
| CHANGELOG_FILE="${{ env.MAIN_CHANGELOG }}" | |
| fi | |
| # Check what was changed | |
| CHANGELOG_CHANGED=$(git diff --name-only origin/$BASE_BRANCH | grep -c "$CHANGELOG_FILE" || true) | |
| DOCS_CHANGED=$(git diff --name-only origin/$BASE_BRANCH | grep -v "$CHANGELOG_FILE" | grep -c "docs/" || true) | |
| # Ensure we have numeric values (grep -c returns 0 when no matches, but with exit code 1) | |
| CHANGELOG_CHANGED=${CHANGELOG_CHANGED:-0} | |
| DOCS_CHANGED=${DOCS_CHANGED:-0} | |
| echo "changelog_changed=$CHANGELOG_CHANGED" >> $GITHUB_OUTPUT | |
| echo "docs_changed=$DOCS_CHANGED" >> $GITHUB_OUTPUT | |
| echo "changelog_file=$CHANGELOG_FILE" >> $GITHUB_OUTPUT | |
| echo "Pushing changes to origin/${{ env.branch_name }}..." | |
| git push origin ${{ env.branch_name }} || { echo "Failed to push changes"; exit 1; } | |
| fi | |
| - name: Create Pull Request | |
| if: steps.check_changes.outputs.has_changes == 'true' | |
| run: | | |
| # Store source PR title safely (may contain special characters) | |
| SOURCE_PR_TITLE=$(cat <<'EOF' | |
| ${{ steps.set_vars.outputs.pr_title }} | |
| EOF | |
| ) | |
| # Determine PR title prefix based on what changed | |
| if [ "${{ steps.check_changes.outputs.docs_changed }}" != "0" ]; then | |
| PR_PREFIX="Changelog + Docs" | |
| CHANGES_DESC="Automated changelog and documentation updates" | |
| else | |
| PR_PREFIX="Changelog" | |
| CHANGES_DESC="Automated changelog update" | |
| fi | |
| # Add widget context to prefix if applicable | |
| if [ "${{ steps.set_vars.outputs.is_widget_change }}" == "true" ]; then | |
| PR_PREFIX="[Widget] $PR_PREFIX" | |
| CHANGELOG_DESC="Widget changelog (\`${{ env.WIDGET_CHANGELOG }}\`)" | |
| else | |
| CHANGELOG_DESC="Main changelog (\`${{ env.MAIN_CHANGELOG }}\`)" | |
| fi | |
| PR_TITLE="$PR_PREFIX: $SOURCE_PR_TITLE (OCS #${{ steps.set_vars.outputs.pr_number }})" | |
| echo "Creating pull request..." | |
| gh pr create \ | |
| --title "$PR_TITLE" \ | |
| --body "## Summary | |
| $CHANGES_DESC based on merged PR in open-chat-studio. | |
| **Source PR:** ${{ steps.set_vars.outputs.pr_url }} | |
| **PR Title:** $SOURCE_PR_TITLE | |
| **Author:** @${{ steps.set_vars.outputs.pr_author }} | |
| **Base Branch:** \`${{ steps.set_vars.outputs.base_branch }}\` | |
| **Widget Change:** ${{ steps.set_vars.outputs.is_widget_change == 'true' && '✅ Yes' || '❌ No' }} | |
| ### Changes Made | |
| - Changelog ($CHANGELOG_DESC): ${{ steps.check_changes.outputs.changelog_changed == '1' && '✅ Updated' || '⏭️ Skipped' }} | |
| - Documentation: ${{ steps.check_changes.outputs.docs_changed != '0' && '✅ Updated' || '⏭️ No updates needed' }} | |
| --- | |
| 🤖 This PR was automatically generated using Claude to analyze the source PR and update the changelog and documentation accordingly." \ | |
| --label "automated" \ | |
| --base "${{ steps.set_vars.outputs.base_branch }}" \ | |
| --head "${{ env.branch_name }}" || { echo "Failed to create pull request"; exit 1; } | |
| # Try to assign after PR creation (won't fail the workflow if user doesn't exist) | |
| gh pr edit "${{ env.branch_name }}" --add-assignee "${{ steps.set_vars.outputs.pr_author }}" || echo "Could not assign @${{ steps.set_vars.outputs.pr_author }} (user may not have access to this repo)" | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Generate workflow summary | |
| if: always() | |
| run: | | |
| # Store pr_title safely | |
| SUMMARY_PR_TITLE=$(cat <<'EOF' | |
| ${{ steps.set_vars.outputs.pr_title }} | |
| EOF | |
| ) | |
| echo "## Changelog Update Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY | |
| echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| **PR Number** | #${{ steps.set_vars.outputs.pr_number }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| **PR Title** | $SUMMARY_PR_TITLE |" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Base Branch** | \`${{ steps.set_vars.outputs.base_branch }}\` |" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Widget Change** | ${{ steps.set_vars.outputs.is_widget_change == 'true' && '✅ Yes' || '❌ No' }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| **Changes Made** | ${{ steps.check_changes.outputs.has_changes == 'true' && '✅ Yes' || '❌ No' }} |" >> $GITHUB_STEP_SUMMARY | |
| if [ "${{ steps.check_changes.outputs.has_changes }}" == "true" ]; then | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Files Changed" >> $GITHUB_STEP_SUMMARY | |
| echo "- Changelog: ${{ steps.check_changes.outputs.changelog_changed == '1' && '✅ Updated' || '⏭️ Skipped' }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- Documentation: ${{ steps.check_changes.outputs.docs_changed != '0' && '✅ Updated' || '⏭️ No updates needed' }}" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| - name: Notify on failure | |
| if: failure() | |
| run: | | |
| # Comment on the source PR to notify about failure | |
| gh issue comment ${{ steps.set_vars.outputs.pr_number }} \ | |
| --repo ${{ env.SOURCE_REPO }} \ | |
| --body "⚠️ Automated changelog update failed for this PR. | |
| **Workflow Run:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| Please check the logs or manually update the changelog in the docs repository." || echo "Failed to post comment (PR may be inaccessible)" | |
| env: | |
| GH_TOKEN: ${{ secrets.OCS_DOCS_PAT }} |