Skip to content

Commit ae47604

Browse files
committed
feat: consolidate repository updates
0 parents  commit ae47604

19 files changed

Lines changed: 2384 additions & 0 deletions

.github/pull_request_template.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
## Contribution Capacity
2+
3+
- [ ] This contribution is submitted in my personal capacity.
4+
- [ ] This contribution is submitted on behalf of an organization that already has a signed CCLA on file.
5+
6+
Organization name: None
7+
Authorization reference: None
8+
9+
## Required Declarations
10+
11+
- [ ] I have the legal right to submit this contribution.
12+
- [ ] I understand accepted contributions may be used under the repository's documented BSL, change-license, and commercial licensing model.
13+
- [ ] I have disclosed below any third-party, copied, or adapted code and the relevant source/license details.
14+
- [ ] I have reviewed any AI-assisted output included in this PR and confirmed that I have the right to submit it.
15+
- [ ] I understand that undisclosed or incompatible third-party code may cause this PR to be rejected or later removed.
16+
17+
## Third-Party / Copied / Adapted Code Disclosure
18+
19+
None.
20+
21+
## AI Assistance Disclosure
22+
23+
None.
24+
25+
## Co-author Disclosure
26+
27+
None.

.github/workflows/cla-reusable.yml

Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
name: CLA Reusable
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
event-name:
7+
required: true
8+
type: string
9+
issue-is-pr:
10+
required: false
11+
default: false
12+
type: boolean
13+
comment-body:
14+
required: false
15+
default: ""
16+
type: string
17+
default-branch:
18+
required: true
19+
type: string
20+
compliance-profile:
21+
required: false
22+
default: bsl-change-license-commercial
23+
type: string
24+
signatures-branch:
25+
required: false
26+
default: cla-signatures
27+
type: string
28+
signatures-path:
29+
required: false
30+
default: .github/cla/signatures.json
31+
type: string
32+
icla-path:
33+
required: false
34+
default: docs/legal/ICLA.md
35+
type: string
36+
ccla-path:
37+
required: false
38+
default: docs/legal/CCLA.md
39+
type: string
40+
agreement-display-name:
41+
required: false
42+
default: ICLA
43+
type: string
44+
corporate-authorization-display-name:
45+
required: false
46+
default: CCLA / corporate authorization
47+
type: string
48+
allowlist:
49+
required: false
50+
default: dependabot[bot],github-actions[bot],bot*
51+
type: string
52+
sign-comment:
53+
required: false
54+
default: I have read the ICLA and I hereby sign this agreement.
55+
type: string
56+
app-id:
57+
required: false
58+
default: ""
59+
type: string
60+
61+
jobs:
62+
cla:
63+
if: >
64+
inputs.event-name == 'pull_request_target' ||
65+
(
66+
inputs.event-name == 'issue_comment' &&
67+
inputs.issue-is-pr &&
68+
(
69+
inputs.comment-body == inputs.sign-comment ||
70+
inputs.comment-body == 'I have read the ICLA and I hereby sign this agreement.' ||
71+
inputs.comment-body == 'recheck'
72+
)
73+
)
74+
runs-on: ubuntu-latest
75+
steps:
76+
- name: Resolve compliance profile
77+
id: resolve_profile
78+
uses: actions/github-script@v8
79+
env:
80+
COMPLIANCE_PROFILE: ${{ inputs.compliance-profile }}
81+
AGREEMENT_DISPLAY_NAME_OVERRIDE: ${{ inputs.agreement-display-name }}
82+
CORPORATE_AUTHORIZATION_DISPLAY_NAME_OVERRIDE: ${{ inputs.corporate-authorization-display-name }}
83+
SIGN_COMMENT_OVERRIDE: ${{ inputs.sign-comment }}
84+
with:
85+
script: |
86+
const profiles = {
87+
"bsl-change-license-commercial": {
88+
agreementDisplayName: "ICLA",
89+
corporateAuthorizationDisplayName: "CCLA / corporate authorization",
90+
signComment: "I have read the ICLA and I hereby sign this agreement."
91+
},
92+
"apache-2.0": {
93+
agreementDisplayName: "ICLA",
94+
corporateAuthorizationDisplayName: "CCLA / corporate authorization",
95+
signComment: "I have read the ICLA and I hereby sign this agreement."
96+
},
97+
"mit": {
98+
agreementDisplayName: "ICLA",
99+
corporateAuthorizationDisplayName: "CCLA / corporate authorization",
100+
signComment: "I have read the ICLA and I hereby sign this agreement."
101+
},
102+
"bsd-3-clause": {
103+
agreementDisplayName: "ICLA",
104+
corporateAuthorizationDisplayName: "CCLA / corporate authorization",
105+
signComment: "I have read the ICLA and I hereby sign this agreement."
106+
},
107+
"gpl-2.0-or-later": {
108+
agreementDisplayName: "ICLA",
109+
corporateAuthorizationDisplayName: "CCLA / corporate authorization",
110+
signComment: "I have read the ICLA and I hereby sign this agreement."
111+
},
112+
"gpl-3.0-or-later": {
113+
agreementDisplayName: "ICLA",
114+
corporateAuthorizationDisplayName: "CCLA / corporate authorization",
115+
signComment: "I have read the ICLA and I hereby sign this agreement."
116+
}
117+
};
118+
119+
const profileName = process.env.COMPLIANCE_PROFILE?.trim() || "bsl-change-license-commercial";
120+
const profile = profiles[profileName];
121+
122+
if (!profile) {
123+
core.setFailed(`Unknown compliance profile: ${profileName}`);
124+
return;
125+
}
126+
127+
const agreementDisplayName =
128+
process.env.AGREEMENT_DISPLAY_NAME_OVERRIDE?.trim() || profile.agreementDisplayName;
129+
const corporateAuthorizationDisplayName =
130+
process.env.CORPORATE_AUTHORIZATION_DISPLAY_NAME_OVERRIDE?.trim() ||
131+
profile.corporateAuthorizationDisplayName;
132+
const signComment =
133+
process.env.SIGN_COMMENT_OVERRIDE?.trim() || profile.signComment;
134+
135+
core.setOutput("agreement_display_name", agreementDisplayName);
136+
core.setOutput("corporate_authorization_display_name", corporateAuthorizationDisplayName);
137+
core.setOutput("sign_comment", signComment);
138+
139+
- name: Detect GitHub App token configuration
140+
id: detect_app_auth
141+
run: |
142+
if [ -n "${APP_ID}" ] && [ -n "${APP_PRIVATE_KEY}" ]; then
143+
echo "enabled=true" >> "$GITHUB_OUTPUT"
144+
else
145+
echo "enabled=false" >> "$GITHUB_OUTPUT"
146+
fi
147+
env:
148+
APP_ID: ${{ inputs.app-id }}
149+
APP_PRIVATE_KEY: ${{ secrets.CLA_APP_PRIVATE_KEY }}
150+
151+
- name: Create GitHub App token
152+
id: app_token
153+
if: ${{ steps.detect_app_auth.outputs.enabled == 'true' }}
154+
uses: actions/create-github-app-token@v2
155+
with:
156+
app-id: ${{ inputs.app-id }}
157+
private-key: ${{ secrets.CLA_APP_PRIVATE_KEY }}
158+
owner: ${{ github.repository_owner }}
159+
160+
- name: Persist signed contributors
161+
uses: actions/github-script@v8
162+
with:
163+
github-token: ${{ steps.app_token.outputs.token || secrets.CLA_BOT_TOKEN || secrets.GITHUB_TOKEN }}
164+
script: |
165+
const prNumber =
166+
context.payload.pull_request?.number ??
167+
context.payload.issue?.number ??
168+
context.issue.number;
169+
170+
if (!prNumber) {
171+
core.info("No pull request number found; skip signature persistence.");
172+
return;
173+
}
174+
175+
const owner = context.repo.owner;
176+
const repo = context.repo.repo;
177+
const signaturesBranch = ${{ toJson(inputs.signatures-branch) }};
178+
const signaturesPath = ${{ toJson(inputs.signatures-path) }};
179+
const signComment = ${{ toJson(steps.resolve_profile.outputs.sign_comment) }}.trim().toLowerCase();
180+
const acceptedBodies = new Set([
181+
signComment,
182+
"i have read the icla and i hereby sign this agreement."
183+
]);
184+
185+
const commits = await github.paginate(github.rest.pulls.listCommits, {
186+
owner,
187+
repo,
188+
pull_number: prNumber,
189+
per_page: 100
190+
});
191+
192+
const committers = new Map();
193+
for (const commit of commits) {
194+
if (commit.author?.id) {
195+
committers.set(commit.author.id, {
196+
id: commit.author.id,
197+
login: commit.author.login
198+
});
199+
}
200+
}
201+
202+
const pr = await github.rest.pulls.get({
203+
owner,
204+
repo,
205+
pull_number: prNumber
206+
});
207+
208+
if (pr.data.user?.id) {
209+
committers.set(pr.data.user.id, {
210+
id: pr.data.user.id,
211+
login: pr.data.user.login
212+
});
213+
}
214+
215+
if (committers.size === 0) {
216+
core.info("No GitHub-linked committers found; skip signature persistence.");
217+
return;
218+
}
219+
220+
const comments = await github.paginate(github.rest.issues.listComments, {
221+
owner,
222+
repo,
223+
issue_number: prNumber,
224+
per_page: 100
225+
});
226+
227+
const signerComments = new Map();
228+
for (const comment of comments) {
229+
const normalizedBody = (comment.body || "").trim().toLowerCase();
230+
if (!acceptedBodies.has(normalizedBody)) {
231+
continue;
232+
}
233+
if (!comment.user?.id || comment.user.login === "github-actions[bot]") {
234+
continue;
235+
}
236+
signerComments.set(comment.user.id, {
237+
id: comment.user.id,
238+
login: comment.user.login,
239+
comment_id: comment.id,
240+
created_at: comment.created_at
241+
});
242+
}
243+
244+
const repoId = context.payload.repository?.id ?? (await github.rest.repos.get({ owner, repo })).data.id;
245+
const signedCommitters = [];
246+
for (const [id, committer] of committers.entries()) {
247+
const signer = signerComments.get(id);
248+
if (!signer) {
249+
continue;
250+
}
251+
signedCommitters.push({
252+
name: signer.login || committer.login,
253+
id,
254+
comment_id: signer.comment_id,
255+
created_at: signer.created_at,
256+
repoId,
257+
pullRequestNo: prNumber
258+
});
259+
}
260+
261+
if (signedCommitters.length === 0) {
262+
core.info("No matching signed committers found; skip signature persistence.");
263+
return;
264+
}
265+
266+
let sha;
267+
let signatureStore = { signedContributors: [] };
268+
269+
try {
270+
const existing = await github.rest.repos.getContent({
271+
owner,
272+
repo,
273+
path: signaturesPath,
274+
ref: signaturesBranch
275+
});
276+
if (Array.isArray(existing.data)) {
277+
throw new Error(`${signaturesPath} must be a file`);
278+
}
279+
sha = existing.data.sha;
280+
signatureStore = JSON.parse(Buffer.from(existing.data.content, "base64").toString("utf8"));
281+
} catch (error) {
282+
if (error.status !== 404) {
283+
throw error;
284+
}
285+
}
286+
287+
const existingIds = new Set(
288+
Array.isArray(signatureStore.signedContributors)
289+
? signatureStore.signedContributors
290+
.map((entry) => Number(entry?.id))
291+
.filter((value) => Number.isFinite(value))
292+
: []
293+
);
294+
295+
const newEntries = signedCommitters.filter((entry) => !existingIds.has(Number(entry.id)));
296+
if (newEntries.length === 0) {
297+
core.info("Signed contributors already persisted.");
298+
return;
299+
}
300+
301+
signatureStore.signedContributors = [
302+
...(Array.isArray(signatureStore.signedContributors) ? signatureStore.signedContributors : []),
303+
...newEntries
304+
];
305+
306+
const content = Buffer.from(`${JSON.stringify(signatureStore, null, 2)}\n`).toString("base64");
307+
await github.rest.repos.createOrUpdateFileContents({
308+
owner,
309+
repo,
310+
path: signaturesPath,
311+
branch: signaturesBranch,
312+
sha,
313+
message: `docs: record CLA signature(s) for #${prNumber}`,
314+
content
315+
});
316+
317+
- name: Check and collect CLA signatures
318+
uses: contributor-assistant/github-action@v2.6.1
319+
env:
320+
GITHUB_TOKEN: ${{ steps.app_token.outputs.token || secrets.CLA_BOT_TOKEN || secrets.GITHUB_TOKEN }}
321+
with:
322+
path-to-document: https://github.com/${{ github.repository }}/blob/${{ inputs.default-branch }}/${{ inputs.icla-path }}
323+
path-to-signatures: ${{ inputs.signatures-path }}
324+
branch: ${{ inputs.signatures-branch }}
325+
allowlist: ${{ inputs.allowlist }}
326+
custom-pr-sign-comment: ${{ steps.resolve_profile.outputs.sign_comment }}
327+
custom-notsigned-prcomment: |
328+
感谢你的贡献。
329+
330+
在这个 PR 可以继续审核或合并前,请先阅读并签署 [${{ steps.resolve_profile.outputs.agreement_display_name }}](https://github.com/${{ github.repository }}/blob/${{ inputs.default-branch }}/${{ inputs.icla-path }})。
331+
332+
签署方式:
333+
在这个 PR 下直接评论下面这句完整文本:
334+
335+
`${{ steps.resolve_profile.outputs.sign_comment }}`
336+
337+
如果你代表公司、团队或其他组织提交贡献,请先确保对应的 [${{ steps.resolve_profile.outputs.corporate_authorization_display_name }}](https://github.com/${{ github.repository }}/blob/${{ inputs.default-branch }}/${{ inputs.ccla-path }}) 已由维护者登记,并在 PR 模板里填写授权编号。
338+
339+
签署后如果状态没有自动刷新,再评论 `recheck` 触发复检。
340+
custom-allsigned-prcomment: |
341+
CLA 检查已通过,当前 PR 的贡献者均已完成签署。
342+
create-file-commit-message: "chore: initialize CLA signatures store"
343+
signed-commit-message: "docs: record CLA signature for $contributorName in #$pullRequestNo"
344+
lock-pullrequest-aftermerge: true

0 commit comments

Comments
 (0)