ci: Use cla-signed labels with CLA automations (#30234)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matsu 2026-05-11 15:48:41 +03:00 committed by GitHub
parent 5bf5f03453
commit b64a84159d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 100 additions and 3 deletions

83
.github/scripts/cla/manage-label.mjs vendored Normal file
View File

@ -0,0 +1,83 @@
// Invoked from .github/workflows/ci-cla-check.yml via actions/github-script.
//
// Adds the `cla-signed` label when every contributor has signed, and
// removes it otherwise. Idempotent: re-runs safely without duplicating
// the label or erroring if it's already in the desired state. Creates
// the label on first use so the workflow is self-contained.
/**
* @typedef { InstanceType<typeof import("@actions/github/lib/utils").GitHub> } GitHubInstance
* @typedef { import("@actions/github/lib/context").Context } Context
* @typedef { typeof import("@actions/core") } Core
*/
const LABEL_NAME = 'cla-signed';
const LABEL_COLOR = '0e8a16'; // GitHub's standard green
const LABEL_DESCRIPTION = 'All contributors on this PR have signed the CLA';
/**
* @param {{ github: GitHubInstance, context: Context, core: Core }} params
*/
export default async function manageClaLabel({ github, context, core }) {
const { owner, repo } = context.repo;
const issue_number = Number(process.env.PR_NUMBER);
const allSigned = process.env.ALL_SIGNED === 'true';
if (allSigned) {
// Make sure the label exists before trying to apply it — addLabels
// errors if the label is missing from the repo.
try {
await github.rest.issues.getLabel({ owner, repo, name: LABEL_NAME });
} catch (e) {
if (errorStatus(e) === 404) {
try {
await github.rest.issues.createLabel({
owner,
repo,
name: LABEL_NAME,
color: LABEL_COLOR,
description: LABEL_DESCRIPTION,
});
} catch (createErr) {
// 422 = race with a parallel run that just created it. Fine.
if (errorStatus(createErr) !== 422) throw createErr;
}
} else {
throw e;
}
}
await github.rest.issues.addLabels({
owner,
repo,
issue_number,
labels: [LABEL_NAME],
});
core.info(`Applied "${LABEL_NAME}" label to PR #${issue_number}`);
} else {
// 404 just means the label wasn't on the PR — nothing to undo.
try {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number,
name: LABEL_NAME,
});
core.info(`Removed "${LABEL_NAME}" label from PR #${issue_number}`);
} catch (e) {
if (errorStatus(e) !== 404) throw e;
}
}
}
/**
* Octokit's request errors carry an HTTP `status` field, but TypeScript
* sees catch parameters as `unknown`. This guard narrows safely.
* @param {unknown} e
* @returns {number | undefined}
*/
function errorStatus(e) {
return typeof e === 'object' && e !== null && 'status' in e && typeof e.status === 'number'
? e.status
: undefined;
}

View File

@ -51,9 +51,7 @@ export default async function updatePRComment({ github, context }) {
} else {
const lines = [MARKER, '## CLA signatures required', ''];
lines.push(`Thank you for your submission! We really appreciate it.
Like many open source projects, we ask that you sign our [Contributor License Agreement](${process.env.CLA_SIGN_URL}) before we can accept your contribution.
After signing, please comment \`\`\`/cla-check\`\`\` to re-check signature status.`);
Like many open source projects, we ask that you sign our [Contributor License Agreement](${process.env.CLA_SIGN_URL}) before we can accept your contribution.`);
lines.push('');
if (unsigned.length > 0) {

View File

@ -148,6 +148,22 @@ jobs:
const mod = await import('${{ github.workspace }}/.github/scripts/cla/update-pr-comment.mjs');
await mod.default({ github, context, core });
- name: Manage cla-signed label
# Skip on merge_group (no PR) and when the check produced no result.
if: >-
always() &&
steps.context.outputs.pr_number != '' &&
steps.check.outputs.all_signed != ''
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
PR_NUMBER: ${{ steps.context.outputs.pr_number }}
ALL_SIGNED: ${{ steps.check.outputs.all_signed }}
with:
github-token: ${{ steps.generate-token.outputs.token }}
script: |
const mod = await import('${{ github.workspace }}/.github/scripts/cla/manage-label.mjs');
await mod.default({ github, context, core });
- name: React to /cla-check comment
if: always() && github.event_name == 'issue_comment' && steps.check.outputs.all_signed != ''
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8