|
| 1 | +name: Grant All-repository triage role |
| 2 | +on: |
| 3 | + schedule: |
| 4 | + - cron: '0 3 * * 1' # Weekly, Monday 03:00 UTC |
| 5 | + workflow_dispatch: |
| 6 | + inputs: |
| 7 | + dry_run: |
| 8 | + description: 'Simulate without changing roles' |
| 9 | + required: false |
| 10 | + default: 'false' |
| 11 | + exclude: |
| 12 | + description: 'Comma-separated usernames to exclude' |
| 13 | + required: false |
| 14 | + default: '' |
| 15 | + |
| 16 | +permissions: |
| 17 | + contents: read |
| 18 | + |
| 19 | +jobs: |
| 20 | + enforce-triage: |
| 21 | + name: "Enforce Triage" |
| 22 | + runs-on: ubuntu-latest |
| 23 | + concurrency: |
| 24 | + group: org-triage-${{ github.workflow }}-${{ github.ref }} |
| 25 | + cancel-in-progress: false |
| 26 | + steps: |
| 27 | + - name: Ensure all members have All-repo triage |
| 28 | + uses: actions/github-script@v7 |
| 29 | + with: |
| 30 | + github-token: ${{ secrets.ORG_INVITE }} |
| 31 | + script: | |
| 32 | + const org = 'armbian'; |
| 33 | + const dryRun = '${{ github.event.inputs.dry_run }}' === 'true'; |
| 34 | + const excludeCsv = '${{ github.event.inputs.exclude }}'.trim(); |
| 35 | + const EXCLUDE = new Set( |
| 36 | + excludeCsv ? excludeCsv.split(',').map(s => s.trim().toLowerCase()) : [] |
| 37 | + ); |
| 38 | +
|
| 39 | + // 1) Find the "All-repository triage" role |
| 40 | + let triageRole = null; |
| 41 | + try { |
| 42 | + const { data } = await github.request('GET /orgs/{org}/organization-roles', { org }); |
| 43 | + triageRole = (data.roles || []).find(r => |
| 44 | + (r.name && r.name.toLowerCase() === 'all-repository triage') || |
| 45 | + (r.slug && r.slug.toLowerCase() === 'all-repository-triage') || |
| 46 | + /all.*triage/i.test(r.name || '') |
| 47 | + ); |
| 48 | + if (!triageRole) throw new Error('Org role not found: All-repository triage'); |
| 49 | + core.info(`Found role: ${triageRole.name} (id=${triageRole.id})`); |
| 50 | + } catch (e) { |
| 51 | + core.setFailed(`Unable to list/find org roles: ${e.message}`); |
| 52 | + return; |
| 53 | + } |
| 54 | +
|
| 55 | + // 2) Get all regular members (exclude owners by default) |
| 56 | + const members = await github.paginate(github.rest.orgs.listMembers, { |
| 57 | + org, |
| 58 | + per_page: 100, |
| 59 | + role: 'member' |
| 60 | + }); |
| 61 | +
|
| 62 | + const includeOwners = false; // set true if you want owners too |
| 63 | + let owners = []; |
| 64 | + if (includeOwners) { |
| 65 | + owners = await github.paginate(github.rest.orgs.listMembers, { |
| 66 | + org, |
| 67 | + per_page: 100, |
| 68 | + role: 'admin' |
| 69 | + }); |
| 70 | + } |
| 71 | +
|
| 72 | + const targets = [...members, ...owners] |
| 73 | + .map(u => u.login.toLowerCase()) |
| 74 | + .filter(u => !EXCLUDE.has(u)); |
| 75 | +
|
| 76 | + if (!targets.length) { |
| 77 | + core.info('No eligible members to process.'); |
| 78 | + return; |
| 79 | + } |
| 80 | +
|
| 81 | + let table = '\n| User | Action |\n|---|---|\n'; |
| 82 | +
|
| 83 | + for (const username of targets) { |
| 84 | + try { |
| 85 | + // 3) Get current role assignments |
| 86 | + let currentRoles = []; |
| 87 | + try { |
| 88 | + const { data } = await github.request( |
| 89 | + 'GET /orgs/{org}/organization-roles/users/{username}', |
| 90 | + { org, username } |
| 91 | + ); |
| 92 | + currentRoles = Array.isArray(data) ? data : (data.roles || []); |
| 93 | + } catch (e) { |
| 94 | + if (e.status !== 404) { |
| 95 | + table += `| ${username} | ⚠️ read roles failed: ${e.message} |\n`; |
| 96 | + continue; |
| 97 | + } |
| 98 | + } |
| 99 | +
|
| 100 | + const hasTriage = currentRoles.some(r => (r.id || r.role_id) === triageRole.id); |
| 101 | + if (hasTriage) { |
| 102 | + table += `| ${username} | ➖ already has All-repo triage |\n`; |
| 103 | + continue; |
| 104 | + } |
| 105 | +
|
| 106 | + if (dryRun) { |
| 107 | + table += `| ${username} | 🛑 dry-run: would assign All-repo triage |\n`; |
| 108 | + continue; |
| 109 | + } |
| 110 | +
|
| 111 | + // ✅ Correct endpoint |
| 112 | + await github.request( |
| 113 | + 'PUT /orgs/{org}/organization-roles/users/{username}/{role_id}', |
| 114 | + { org, username, role_id: triageRole.id } |
| 115 | + ); |
| 116 | +
|
| 117 | + table += `| ${username} | ✅ assigned All-repo triage |\n`; |
| 118 | + } catch (e) { |
| 119 | + table += `| ${username} | ❌ assignment failed: ${e.status || ''} ${e.message} |\n`; |
| 120 | + } |
| 121 | + } |
| 122 | +
|
| 123 | + await core.summary |
| 124 | + .addHeading('All-repository triage enforcement') |
| 125 | + .addRaw(table, true) |
| 126 | + .write(); |
0 commit comments