1
+ name : Invite Recent Contributors to organization
2
+ on :
3
+ schedule :
4
+ - cron : ' 0 0 * * 0' # Weekly
5
+ workflow_dispatch :
6
+ inputs :
7
+ dry-run :
8
+ description : ' Run without actually inviting users'
9
+ required : false
10
+ default : ' false'
11
+
12
+ jobs :
13
+ check-eligibility :
14
+ runs-on : ubuntu-latest
15
+ steps :
16
+ - name : Collect PR authors from multiple Armbian repositories
17
+ id : get-contributors
18
+ uses : actions/github-script@v7
19
+ with :
20
+ github-token : ${{ secrets.GITHUB_TOKEN }}
21
+ script : |
22
+ const org = 'armbian';
23
+ const repos = ['build', 'firmware', 'documentation', 'configng', 'os', 'armbian.github.io','linux-rockchip', 'apa'];
24
+ const since = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(); // last ~6 months
25
+ //const since = new Date(Date.now() - 182 * 24 * 60 * 60 * 1000).toISOString(); // last ~6 months
26
+ const isBot = (username) => username.endsWith('[bot]');
27
+ const contributors = new Set();
28
+
29
+ for (const repo of repos) {
30
+ console.log(`🔍 Searching PRs created since ${since} in ${org}/${repo}`);
31
+ let page = 1;
32
+ let results;
33
+
34
+ do {
35
+ results = await github.rest.search.issuesAndPullRequests({
36
+ q: `is:pr repo:${org}/${repo} created:>=${since}`,
37
+ per_page: 100,
38
+ page,
39
+ });
40
+
41
+ for (const pr of results.data.items) {
42
+ if (pr.user && pr.user.login) {
43
+ const username = pr.user.login;
44
+ if (!isBot(username)) {
45
+ contributors.add(username);
46
+ console.log(`✅ ${username} opened a PR in ${repo}`);
47
+ } else {
48
+ console.log(`🤖 Skipping bot: ${username}`);
49
+ }
50
+ }
51
+ }
52
+
53
+ page++;
54
+ } while (results.data.items.length === 100);
55
+ }
56
+
57
+ const usernames = Array.from(contributors);
58
+ console.log("🧾 Unique PR authors from all repos:", usernames);
59
+ core.setOutput("usernames", JSON.stringify(usernames));
60
+
61
+
62
+ - name : Check if users are already members of Armbian org (display eligible users)
63
+ uses : actions/github-script@v7
64
+ with :
65
+ github-token : ${{ secrets.ORG_INVITE }}
66
+ script : |
67
+ const dryRun = '${{ github.event.inputs.dry-run }}' === 'true';
68
+ const usernames = JSON.parse('${{ steps.get-contributors.outputs.usernames }}');
69
+ const org = 'armbian';
70
+ const eligibleUsers = [];
71
+
72
+ let tableMarkdown = '\n| Username | Full Name |\n|----------|-----------|\n';
73
+
74
+ for (const username of usernames) {
75
+ try {
76
+ await github.rest.orgs.getMembershipForUser({ org, username });
77
+ console.log(`ℹ️ ${username} is already a member of ${org}`);
78
+ } catch (error) {
79
+ if (error.status === 404) {
80
+ try {
81
+ const { data: user } = await github.rest.users.getByUsername({ username });
82
+ const name = user.name || '(no name provided)';
83
+ eligibleUsers.push({ username, name });
84
+
85
+ tableMarkdown += `| [${username}](https://github.com/${username}) | ${name} |\n`;
86
+
87
+ if (dryRun) {
88
+ console.log(`🛑 Dry-run: Would invite ${username} to ${org}`);
89
+ } else {
90
+ await github.rest.orgs.createInvitation({
91
+ org,
92
+ invitee_id: user.id,
93
+ role: 'direct_member',
94
+ });
95
+ invited = '✅';
96
+ console.log(`✅ Invited ${username} to ${org}`);
97
+ }
98
+ } catch (err) {
99
+ console.log(`⚠️ Error fetching details for ${username}: ${err.message}`);
100
+ }
101
+ } else {
102
+ console.log(`⚠️ Error checking membership for ${username}: ${error.message}`);
103
+ }
104
+ }
105
+ }
106
+
107
+ if (eligibleUsers.length === 0) {
108
+ console.log(`No users eligible for invitation.`);
109
+ await core.summary
110
+ .addHeading('Eligible Users')
111
+ .addRaw('No eligible users found.')
112
+ .write();
113
+ } else {
114
+ console.log(`Eligible users to invite: ${eligibleUsers.map(u => u.username).join(', ')}`);
115
+ await core.summary
116
+ .addHeading('Eligible Users to Invite')
117
+ .addRaw(tableMarkdown, true)
118
+ .write();
119
+ }
0 commit comments