Skip to content

docs: update bedrock metadata #91

docs: update bedrock metadata

docs: update bedrock metadata #91

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})`
});