Add support for custom annotations in dapr sidecar service #85
Workflow file for this run
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
| name: Auto-approve & merge SDK docs PRs (per directory/team) | |
| # Run on PRs (including forks) but act with repo-level permissions. | |
| # We DO NOT check out PR code; we only read PR metadata via the API. | |
| on: | |
| pull_request_target: | |
| types: [opened, synchronize, reopened, ready_for_review, edited] | |
| # Token scopes needed: label, review, and merge. | |
| permissions: | |
| contents: write # required to merge | |
| pull-requests: write # required to approve/merge | |
| issues: write # required to create/add labels | |
| jobs: | |
| sdk-docs-automerge: | |
| runs-on: ubuntu-latest | |
| # Ignore drafts | |
| if: ${{ github.event.pull_request.draft == false }} | |
| steps: | |
| - name: Evaluate PR for SDK docs eligibility & add label | |
| id: check | |
| uses: actions/github-script@v7 | |
| with: | |
| # Optional: override org/merge method/colors via env | |
| # env: | |
| # ORG: dapr | |
| # MERGE_METHOD: squash | |
| # LABEL_COLOR_DEFAULT: '6A9286' | |
| script: | | |
| const { owner, repo } = context.repo; | |
| const pr = context.payload.pull_request; | |
| const number = pr.number; | |
| // --- Mapping: directory prefixes -> team slug + label --- | |
| // Each entry can have multiple prefixes for the same SDK if needed. | |
| const MAPPINGS = [ | |
| { label: 'automerge: dotnet', teamSlug: 'maintainers-dotnet-sdk', prefixes: ['sdkdocs/dotnet/'] }, | |
| { label: 'automerge: go', teamSlug: 'maintainers-go-sdk', prefixes: ['sdkdocs/go/'] }, | |
| { label: 'automerge: java', teamSlug: 'maintainers-java-sdk', prefixes: ['sdkdocs/java/content/en/'] }, | |
| { label: 'automerge: js', teamSlug: 'maintainers-js-sdk', prefixes: ['sdkdocs/js/'] }, | |
| { label: 'automerge: php', teamSlug: 'maintainers-php-sdk', prefixes: ['sdkdocs/php/'] }, | |
| { label: 'automerge: python', teamSlug: 'maintainers-python-sdk', prefixes: ['sdkdocs/python/'] }, | |
| { label: 'automerge: rust', teamSlug: 'maintainers-rust-sdk', prefixes: ['sdkdocs/rust/content/en/'] }, | |
| ]; | |
| const org = owner; | |
| const defaultLabelColor = '6A9286'; | |
| const username = pr.user.login; | |
| // 1) List changed files | |
| const files = await github.paginate( | |
| github.rest.pulls.listFiles, | |
| { owner, repo, pull_number: number, per_page: 100 } | |
| ); | |
| if (files.length === 0) { | |
| core.info('No files changed in PR; skipping.'); | |
| core.setOutput('eligible', 'false'); | |
| return; | |
| } | |
| // 2) Determine which single SDK mapping the PR targets | |
| // - All files must match ONE mapping's prefixes | |
| // - If files touch multiple mappings or outside any mapping, skip | |
| let currentMapping = null; // holds the mapping object we've locked onto | |
| let ineligible = false; | |
| for (const f of files) { | |
| const path = f.filename; | |
| // find the first mapping whose prefixes match this file | |
| let matched = null; | |
| for (const m of MAPPINGS) { | |
| if (m.prefixes.some(p => path.startsWith(p))) { | |
| matched = m; | |
| break; | |
| } | |
| } | |
| // if no mapping matched, we can stop: not eligible | |
| if (!matched) { | |
| ineligible = true; | |
| break; | |
| } | |
| // if we haven't locked onto a mapping yet, set it now | |
| if (!currentMapping) { | |
| currentMapping = matched; | |
| } else if (currentMapping !== matched) { | |
| // different SDK mapping from the one already selected => not eligible | |
| ineligible = true; | |
| break; | |
| } | |
| } | |
| if (ineligible || !currentMapping) { | |
| core.info('PR is not eligible: outside mapped paths or touches multiple SDK directories.'); | |
| core.setOutput('eligible', 'false'); | |
| return; | |
| } | |
| const mapping = currentMapping; | |
| const labelName = mapping.label; | |
| const teamSlug = mapping.teamSlug; | |
| const lang = mapping.label.split(': ')[1] || 'sdk'; | |
| // 3) Verify author is active in the corresponding team | |
| // teams.getMembershipForUserInOrg: GET /orgs/{org}/teams/{team_slug}/memberships/{username} | |
| // Requires team visibility to the token. [3](https://docs.github.com/rest/teams/members) | |
| try { | |
| const membership = await github.rest.teams.getMembershipForUserInOrg({ | |
| org, | |
| team_slug: teamSlug, | |
| username | |
| }); | |
| if (membership.data.state !== 'active') { | |
| core.info(`User ${username} is not active in team ${teamSlug}.`); | |
| core.setOutput('eligible', 'false'); | |
| return; | |
| } | |
| } catch (err) { | |
| if (err.status === 404) { | |
| core.info(`User ${username} is not a member of team ${teamSlug}.`); | |
| core.setOutput('eligible', 'false'); | |
| return; | |
| } | |
| throw err; | |
| } | |
| // 4) Ensure label exists; then add it to the PR | |
| try { | |
| await github.rest.issues.getLabel({ owner, repo, name: labelName }); | |
| } catch (e) { | |
| if (e.status === 404) { | |
| await github.rest.issues.createLabel({ | |
| owner, repo, name: labelName, color: defaultLabelColor, | |
| description: 'Auto-merged language-specific SDK docs' | |
| }); | |
| } else { | |
| throw e; | |
| } | |
| } | |
| await github.rest.issues.addLabels({ | |
| owner, repo, issue_number: number, labels: [labelName] | |
| }); | |
| // 5) Expose mapping for next step | |
| core.setOutput('eligible', 'true'); | |
| core.setOutput('label', labelName); | |
| core.setOutput('teamSlug', teamSlug); | |
| core.setOutput('lang', lang); | |
| - name: Auto-approve & merge (only if eligible) | |
| if: steps.check.outputs.eligible == 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const { owner, repo } = context.repo; | |
| const number = context.payload.pull_request.number; | |
| const lang = core.getInput('lang') || '${{ steps.check.outputs.lang }}'; | |
| const mergeMethod = process.env.MERGE_METHOD || 'squash'; | |
| // 6) Auto-approve review | |
| try { | |
| await github.rest.pulls.createReview({ | |
| owner, repo, pull_number: number, | |
| event: 'APPROVE', | |
| body: `Auto-approval: ${lang} SDK docs` | |
| }); | |
| } catch (e) { | |
| core.warning(`Failed to create review: ${e.message}`); | |
| } | |
| // 7) Poll until PR is mergeable (clean/unstable) | |
| const wait = ms => new Promise(r => setTimeout(r, ms)); | |
| let attempt = 0; | |
| while (attempt < 12) { // up to ~60s | |
| attempt++; | |
| const pr = await github.rest.pulls.get({ owner, repo, pull_number: number }); | |
| const state = pr.data.mergeable_state; | |
| core.info(`mergeable=${pr.data.mergeable}, mergeable_state=${state}`); | |
| if (pr.data.mergeable && (state === 'clean' || state === 'unstable')) break; | |
| await wait(5000); | |
| } | |
| // 8) Merge the PR | |
| await github.rest.pulls.merge({ | |
| owner, repo, pull_number: number, | |
| merge_method: mergeMethod, | |
| commit_title: `${lang}: ${context.payload.pull_request.title}`, | |
| commit_message: `Auto-merged by SDK maintainer merge bot (${lang})` | |
| }); |