Skip to content

chore: rerun compliance checks #2

chore: rerun compliance checks

chore: rerun compliance checks #2

name: PR Compliance Reusable

Check failure on line 1 in .github/workflows/pr-compliance-reusable.yml

View workflow run for this annotation

GitHub Actions / .github/workflows/pr-compliance-reusable.yml

Invalid workflow file

(Line: 38, Col: 13): Unrecognized named-value: 'secrets'. Located at position 24 within expression: inputs.app-id != '' && secrets.CLA_APP_PRIVATE_KEY != ''
on:
workflow_call:
inputs:
pr-number:
required: true
type: number
pr-body:
required: false
default: ""
type: string
pr-author-login:
required: true
type: string
default-branch:
required: true
type: string
compliance-profile:
required: false
default: bsl-change-license-commercial
type: string
usage-license-confirmation-label:
required: false
default: ""
type: string
app-id:
required: false
default: ""
type: string
jobs:
validate-pr-metadata:
runs-on: ubuntu-latest
steps:
- name: Create GitHub App token
id: app_token
if: ${{ inputs.app-id != '' && secrets.CLA_APP_PRIVATE_KEY != '' }}
uses: actions/create-github-app-token@v2
with:
app-id: ${{ inputs.app-id }}
private-key: ${{ secrets.CLA_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- name: Resolve compliance profile
id: resolve_profile
uses: actions/github-script@v8
with:
github-token: ${{ steps.app_token.outputs.token || secrets.CLA_BOT_TOKEN || secrets.GITHUB_TOKEN }}
script: |
const profiles = {
"bsl-change-license-commercial": {
usageLicenseConfirmationLabel:
"I understand accepted contributions may be used under the repository's documented BSL, change-license, and commercial licensing model."
},
"apache-2.0": {
usageLicenseConfirmationLabel:
"I understand accepted contributions may be distributed under the repository's documented Apache 2.0 licensing and distribution model and the contributor agreement terms described in CLA.md."
},
"mit": {
usageLicenseConfirmationLabel:
"I understand accepted contributions may be distributed under the repository's documented MIT licensing and distribution model and the contributor agreement terms described in CLA.md."
},
"bsd-3-clause": {
usageLicenseConfirmationLabel:
"I understand accepted contributions may be distributed under the repository's documented BSD-3-Clause licensing and distribution model and the contributor agreement terms described in CLA.md."
},
"gpl-2.0-or-later": {
usageLicenseConfirmationLabel:
"I understand accepted contributions may be distributed under the repository's documented GPL-2.0-or-later licensing and distribution model and the contributor agreement terms described in CLA.md."
},
"gpl-3.0-or-later": {
usageLicenseConfirmationLabel:
"I understand accepted contributions may be distributed under the repository's documented GPL-3.0-or-later licensing and distribution model and the contributor agreement terms described in CLA.md."
}
};
const profileName = process.env.COMPLIANCE_PROFILE?.trim() || "bsl-change-license-commercial";
const profile = profiles[profileName];
if (!profile) {
core.setFailed(`Unknown compliance profile: ${profileName}`);
return;
}
const usageLicenseConfirmationLabel =
process.env.USAGE_LICENSE_CONFIRMATION_LABEL_OVERRIDE?.trim() ||
profile.usageLicenseConfirmationLabel;
core.setOutput("usage_license_confirmation_label", usageLicenseConfirmationLabel);
env:
COMPLIANCE_PROFILE: ${{ inputs.compliance-profile }}
USAGE_LICENSE_CONFIRMATION_LABEL_OVERRIDE: ${{ inputs.usage-license-confirmation-label }}
- name: Validate PR declarations
uses: actions/github-script@v8
with:
github-token: ${{ steps.app_token.outputs.token || secrets.CLA_BOT_TOKEN || secrets.GITHUB_TOKEN }}
script: |
const prNumber = Number(${{ inputs.pr-number }});
const body = ${{ toJson(inputs.pr-body) }};
const prAuthorLogin = ${{ toJson(inputs.pr-author-login) }};
const defaultBranch = ${{ toJson(inputs.default-branch) }};
const marker = "<!-- golutra-pr-compliance -->";
const issues = [];
const normalizeValue = (value) => value.trim().toLowerCase();
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const isChecked = (label) => {
const pattern = new RegExp(`- \\[x\\] ${escapeRegExp(label)}`, "i");
return pattern.test(body);
};
const extractLine = (label) => {
const pattern = new RegExp(`^${escapeRegExp(label)}:\\s*(.+)$`, "mi");
const match = body.match(pattern);
return match ? match[1].trim() : "";
};
const extractSection = (heading, nextHeading) => {
const nextPart = nextHeading ? `\\n## ${escapeRegExp(nextHeading)}` : "$";
const pattern = new RegExp(`## ${escapeRegExp(heading)}\\s*\\n([\\s\\S]*?)${nextPart}`, "i");
const match = body.match(pattern);
return match ? match[1].trim() : "";
};
const splitRegistryCell = (value) =>
value
.split(",")
.map((item) => item.trim())
.filter(Boolean);
const isEmptyRegistryValue = (value) =>
!value || /^none\.?$/i.test(value) || /^n\/a$/i.test(value) || value === "-";
const personalLabel = "This contribution is submitted in my personal capacity.";
const organizationLabel = "This contribution is submitted on behalf of an organization that already has a signed CCLA on file.";
const personalChecked = isChecked(personalLabel);
const organizationChecked = isChecked(organizationLabel);
if (personalChecked === organizationChecked) {
issues.push("必须且只能选择一种贡献身份:个人贡献或代表组织贡献。");
}
const requiredLabels = [
"I have the legal right to submit this contribution.",
${{ toJson(steps.resolve_profile.outputs.usage_license_confirmation_label) }},
"I have disclosed below any third-party, copied, or adapted code and the relevant source/license details.",
"I have reviewed any AI-assisted output included in this PR and confirmed that I have the right to submit it.",
"I understand that undisclosed or incompatible third-party code may cause this PR to be rejected or later removed."
];
for (const label of requiredLabels) {
if (!isChecked(label)) {
issues.push(`缺少必填确认项:${label}`);
}
}
const organizationName = extractLine("Organization name");
const authorizationReference = extractLine("Authorization reference");
const thirdPartyDisclosure = extractSection(
"Third-Party / Copied / Adapted Code Disclosure",
"AI Assistance Disclosure"
);
const aiDisclosure = extractSection("AI Assistance Disclosure", "Co-author Disclosure");
const coAuthorDisclosure = extractSection("Co-author Disclosure", "");
const commitLogins = new Set([prAuthorLogin.toLowerCase()]);
const coAuthorEntries = [];
const loadCommitMetadata = async () => {
const commits = await github.paginate(github.rest.pulls.listCommits, {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
per_page: 100
});
const coAuthorPattern = /^Co-authored-by:\s*(.+?)\s*<([^>]+)>$/gim;
for (const commit of commits) {
if (commit.author?.login) {
commitLogins.add(commit.author.login.toLowerCase());
}
const message = commit.commit?.message ?? "";
let match = null;
while ((match = coAuthorPattern.exec(message)) !== null) {
coAuthorEntries.push({
name: match[1].trim(),
email: match[2].trim().toLowerCase()
});
}
}
};
if (!thirdPartyDisclosure) {
issues.push("必须填写 Third-Party / Copied / Adapted Code Disclosure,若无请明确写 `None.`。");
}
if (!aiDisclosure) {
issues.push("必须填写 AI Assistance Disclosure,若无请明确写 `None.`。");
}
await loadCommitMetadata();
if (coAuthorEntries.length === 0) {
if (coAuthorDisclosure && !/^none\.?$/i.test(coAuthorDisclosure)) {
issues.push("没有检测到 `Co-authored-by:`,`Co-author Disclosure` 应填写 `None.`。");
}
} else if (!coAuthorDisclosure || /^none\.?$/i.test(coAuthorDisclosure)) {
issues.push(
"检测到 commit 中存在 `Co-authored-by:`,必须在 `Co-author Disclosure` 中列出所有共同作者。"
);
} else {
const normalizedDisclosure = coAuthorDisclosure.toLowerCase();
for (const coAuthor of coAuthorEntries) {
if (!normalizedDisclosure.includes(coAuthor.email)) {
issues.push(`Co-author Disclosure 缺少共同作者邮箱:${coAuthor.email}`);
}
}
}
if (organizationChecked) {
if (!organizationName || /^none\.?$/i.test(organizationName)) {
issues.push("代表组织贡献时,`Organization name` 不能为空。");
}
if (!authorizationReference || /^none\.?$/i.test(authorizationReference)) {
issues.push("代表组织贡献时,`Authorization reference` 必须填写已登记的授权编号。");
} else {
try {
const response = await github.rest.repos.getContent({
owner: context.repo.owner,
repo: context.repo.repo,
path: "docs/legal/corporate-authorizations.json",
ref: defaultBranch
});
const content = Buffer.from(response.data.content, "base64").toString("utf8");
const parsedRegistry = JSON.parse(content);
const registry = Array.isArray(parsedRegistry)
? parsedRegistry
: parsedRegistry.authorizations;
if (!Array.isArray(registry)) {
issues.push(
"企业授权登记文件格式无效:`docs/legal/corporate-authorizations.json` 缺少 `authorizations` 数组。"
);
throw new Error("invalid corporate authorization registry");
}
const matchedAuthorization = registry.find(
(entry) => normalizeValue(entry.authorizationReference) === normalizeValue(authorizationReference)
);
if (!matchedAuthorization) {
issues.push(
`未在 docs/legal/corporate-authorizations.json 中找到授权编号:${authorizationReference}`
);
} else {
if (
normalizeValue(matchedAuthorization.organization) !== normalizeValue(organizationName)
) {
issues.push(
`授权编号 ${authorizationReference} 对应的组织名与 PR 中填写的 \`Organization name\` 不一致。`
);
}
if (normalizeValue(matchedAuthorization.status) !== "active") {
issues.push(
`授权编号 ${authorizationReference} 当前状态不是 Active,不能用于企业贡献。`
);
}
if (!isEmptyRegistryValue(matchedAuthorization.expirationDate)) {
const expirationDate = Date.parse(matchedAuthorization.expirationDate);
if (!Number.isNaN(expirationDate) && expirationDate < Date.now()) {
issues.push(`授权编号 ${authorizationReference} 已过期。`);
}
}
const authorizedUsers = splitRegistryCell(
Array.isArray(matchedAuthorization.authorizedGitHubUsernames)
? matchedAuthorization.authorizedGitHubUsernames.join(",")
: matchedAuthorization.authorizedGitHubUsernames ?? ""
).map((item) => item.toLowerCase());
const authorizedEmails = splitRegistryCell(
Array.isArray(matchedAuthorization.authorizedEmails)
? matchedAuthorization.authorizedEmails.join(",")
: matchedAuthorization.authorizedEmails ?? ""
).map((item) => item.toLowerCase());
const wildcardAllowed = authorizedUsers.includes("*");
const wildcardEmailAllowed = authorizedEmails.includes("*");
if (!wildcardAllowed && authorizedUsers.length === 0) {
issues.push(
`授权编号 ${authorizationReference} 没有登记任何 Authorized GitHub Usernames。`
);
}
if (!wildcardAllowed) {
const unauthorizedLogins = [...commitLogins].filter(
(login) => !authorizedUsers.includes(login)
);
if (unauthorizedLogins.length > 0) {
issues.push(
`授权编号 ${authorizationReference} 未覆盖这些 GitHub 用户:${unauthorizedLogins.join(", ")}`
);
}
}
if (coAuthorEntries.length > 0 && !wildcardEmailAllowed) {
if (authorizedEmails.length === 0) {
issues.push(
`授权编号 ${authorizationReference} 检测到共同作者,但没有登记任何 Authorized Emails。`
);
} else {
const unauthorizedCoAuthorEmails = coAuthorEntries
.map((coAuthor) => coAuthor.email)
.filter((email) => !authorizedEmails.includes(email));
if (unauthorizedCoAuthorEmails.length > 0) {
issues.push(
`授权编号 ${authorizationReference} 未覆盖这些共同作者邮箱:${unauthorizedCoAuthorEmails.join(", ")}`
);
}
}
}
}
} catch (error) {
if (error.message !== "invalid corporate authorization registry") {
issues.push("无法读取企业授权登记表,请确认 docs/legal/corporate-authorizations.json 存在且可访问。");
}
}
}
} else {
if (organizationName && !/^none\.?$/i.test(organizationName)) {
issues.push("个人贡献时,`Organization name` 应填写 `None`。");
}
if (authorizationReference && !/^none\.?$/i.test(authorizationReference)) {
issues.push("个人贡献时,`Authorization reference` 应填写 `None`。");
}
}
const commentBody = issues.length === 0
? `${marker}
PR 合规检查已通过。
- 贡献身份声明完整
- 协议与使用方式确认完整
- 第三方来源、AI 与共同作者披露字段已填写`
: `${marker}
PR 合规检查未通过,请按模板补齐以下内容:
${issues.map((issue) => `- ${issue}`).join("\n")}`;
const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber
});
const existingComment = comments.find((comment) =>
comment.user?.type === "Bot" && comment.body?.includes(marker)
);
if (existingComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingComment.id,
body: commentBody
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: commentBody
});
}
if (issues.length > 0) {
core.setFailed(issues.join("\n"));
}