Let Plus devices share a short restore code or QR #126
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: ✍️ CLA | |
| on: | |
| pull_request: | |
| branches: | |
| - main | |
| types: | |
| - opened | |
| - reopened | |
| - synchronize | |
| issue_comment: | |
| types: | |
| - created | |
| - edited | |
| permissions: | |
| contents: read | |
| jobs: | |
| cla: | |
| if: github.event_name == 'pull_request' | |
| runs-on: ubuntu-latest | |
| name: ✍️ CLA | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - name: 📦 Checkout base | |
| uses: actions/checkout@v6.0.2 | |
| with: | |
| ref: ${{ github.event.pull_request.base.ref }} | |
| persist-credentials: false | |
| - name: 🟢 Setup Node | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24 | |
| - name: 🧾 Collect identities | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 | |
| with: | |
| script: | | |
| const identities = [ | |
| { | |
| githubLogin: context.payload.pull_request.user.login, | |
| name: context.payload.pull_request.user.login, | |
| email: null, | |
| }, | |
| ] | |
| const commits = await github.paginate( | |
| github.rest.pulls.listCommits, | |
| { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: context.payload.pull_request.number, | |
| per_page: 100, | |
| }, | |
| ) | |
| for (const commit of commits) { | |
| identities.push({ | |
| githubLogin: commit.author?.login ?? null, | |
| name: commit.commit.author?.name ?? null, | |
| email: commit.commit.author?.email ?? null, | |
| }) | |
| } | |
| const fs = await import('node:fs/promises') | |
| await fs.writeFile( | |
| 'cla-identities.json', | |
| `${JSON.stringify(identities, null, '\t')}\n`, | |
| ) | |
| - name: ✍️ Check CLA | |
| id: check | |
| run: | | |
| if [ ! -f tools/ci/check-cla.mjs ] || [ ! -f .github/cla-signers.json ]; then | |
| echo "CLA checker is not on the base branch yet." | |
| exit 0 | |
| fi | |
| set +e | |
| node tools/ci/check-cla.mjs \ | |
| --signers .github/cla-signers.json \ | |
| --identities-json cla-identities.json | |
| code=$? | |
| echo "exit_code=$code" >> "$GITHUB_OUTPUT" | |
| if [ "$code" -ne 0 ] && [ "$code" -ne 1 ]; then | |
| exit "$code" | |
| fi | |
| - name: 💬 Comment when unsigned | |
| if: steps.check.outputs.exit_code == '1' | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 | |
| with: | |
| script: | | |
| const marker = '<!-- kody-cla-check -->' | |
| const body = `${marker} | |
| Unsigned contributions cannot merge. | |
| 1. Read the [Individual CLA](https://github.com/${{ github.repository }}/blob/main/docs/legal/individual-cla.md) (or the [Entity CLA](https://github.com/${{ github.repository }}/blob/main/docs/legal/entity-cla.md) if an organization owns the work). | |
| 2. Comment exactly: \`I have read the CLA and I hereby sign the CLA\` | |
| 3. The CLA workflow records your GitHub username on \`main\` and re-runs this check. See [Inbound contributions](https://github.com/${{ github.repository }}/blob/main/docs/contribute/inbound-contributions.md). | |
| Adding your own username on this branch does not pass the check.` | |
| const comments = await github.paginate( | |
| github.rest.issues.listComments, | |
| { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.payload.pull_request.number, | |
| per_page: 100, | |
| }, | |
| ) | |
| const existing = comments.find((comment) => | |
| comment.body?.includes(marker), | |
| ) | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existing.id, | |
| body, | |
| }) | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.payload.pull_request.number, | |
| body, | |
| }) | |
| } | |
| - name: ❌ Fail when unsigned | |
| if: steps.check.outputs.exit_code == '1' | |
| run: exit 1 | |
| record: | |
| if: > | |
| github.event_name == 'issue_comment' && github.event.issue.pull_request && | |
| github.event.comment.user.type == 'User' && | |
| contains(github.event.comment.body, 'I hereby sign the CLA') | |
| runs-on: ubuntu-latest | |
| name: 📝 Record CLA | |
| timeout-minutes: 5 | |
| concurrency: | |
| group: cla-signers-main | |
| cancel-in-progress: false | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| actions: write | |
| steps: | |
| - name: 📦 Checkout main | |
| uses: actions/checkout@v6.0.2 | |
| with: | |
| ref: main | |
| - name: 🟢 Setup Node | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24 | |
| - name: 📝 Record signer | |
| id: record | |
| env: | |
| CLA_COMMENT: ${{ github.event.comment.body }} | |
| CLA_LOGIN: ${{ github.event.comment.user.login }} | |
| CLA_SIGNED_AT: ${{ github.event.comment.created_at }} | |
| run: | | |
| if [ ! -f tools/ci/check-cla.mjs ] || [ ! -f .github/cla-signers.json ]; then | |
| echo "skipped=true" >> "$GITHUB_OUTPUT" | |
| echo "added=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| printf '%s' "$CLA_COMMENT" > cla-comment.txt | |
| node tools/ci/check-cla.mjs \ | |
| --signers .github/cla-signers.json \ | |
| --record-signer "$CLA_LOGIN" \ | |
| --signed-at "${CLA_SIGNED_AT%%T*}" \ | |
| --comment-file cla-comment.txt > cla-record-output.txt | |
| cat cla-record-output.txt | |
| grep -E '^(skipped|added|github)=' cla-record-output.txt >> "$GITHUB_OUTPUT" | |
| - name: 💾 Commit signer on main | |
| if: steps.record.outputs.added == 'true' | |
| env: | |
| CLA_GITHUB: ${{ steps.record.outputs.github }} | |
| run: | | |
| git config user.name 'github-actions[bot]' | |
| git config user.email '41898282+github-actions[bot]@users.noreply.github.com' | |
| git add .github/cla-signers.json | |
| git commit -m "Record @${CLA_GITHUB} as an individual CLA signer" | |
| git pull --rebase origin main | |
| git push origin HEAD:main | |
| - name: 💬 Comment and re-run CLA | |
| if: steps.record.outputs.skipped == 'false' | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 | |
| env: | |
| CLA_ADDED: ${{ steps.record.outputs.added }} | |
| CLA_GITHUB: ${{ steps.record.outputs.github }} | |
| with: | |
| script: | | |
| const login = process.env.CLA_GITHUB | |
| const added = process.env.CLA_ADDED === 'true' | |
| const marker = '<!-- kody-cla-recorded -->' | |
| const owner = context.repo.owner | |
| const repo = context.repo.repo | |
| const pull = await github.rest.pulls.get({ | |
| owner, | |
| repo, | |
| pull_number: context.payload.issue.number, | |
| }) | |
| const headSha = pull.data.head.sha | |
| async function latestClaRun() { | |
| const runs = await github.rest.actions.listWorkflowRuns({ | |
| owner, | |
| repo, | |
| workflow_id: 'cla.yml', | |
| event: 'pull_request', | |
| head_sha: headSha, | |
| per_page: 5, | |
| }) | |
| return runs.data.workflow_runs[0] ?? null | |
| } | |
| const deadline = Date.now() + 180_000 | |
| let latest = await latestClaRun() | |
| while (latest && latest.status !== 'completed' && Date.now() < deadline) { | |
| await new Promise((resolve) => setTimeout(resolve, 10_000)) | |
| latest = await latestClaRun() | |
| } | |
| let reran = false | |
| if (latest?.status === 'completed') { | |
| try { | |
| await github.rest.actions.reRunWorkflow({ | |
| owner, | |
| repo, | |
| run_id: latest.id, | |
| }) | |
| reran = true | |
| } catch (error) { | |
| core.warning( | |
| `Could not re-run CLA: ${error instanceof Error ? error.message : String(error)}`, | |
| ) | |
| } | |
| } | |
| const followUp = reran | |
| ? 'The CLA check will re-run.' | |
| : 'The next CLA check will read the updated signers file.' | |
| const body = added | |
| ? `${marker} | |
| Recorded @${login} as an individual CLA signer on \`main\`. ${followUp}` | |
| : `${marker} | |
| @${login} is already recorded as an individual CLA signer. ${followUp}` | |
| const comments = await github.paginate( | |
| github.rest.issues.listComments, | |
| { | |
| owner, | |
| repo, | |
| issue_number: context.payload.issue.number, | |
| per_page: 100, | |
| }, | |
| ) | |
| const existing = comments.find((comment) => | |
| comment.body?.includes(marker), | |
| ) | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner, | |
| repo, | |
| comment_id: existing.id, | |
| body, | |
| }) | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: context.payload.issue.number, | |
| body, | |
| }) | |
| } |