forked from jorgeteixe/github-members-export
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.ts
More file actions
155 lines (127 loc) · 3.77 KB
/
main.ts
File metadata and controls
155 lines (127 loc) · 3.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#!/usr/bin/env -S deno run --allow-net --allow-write
import { Octokit, RestEndpointMethodTypes } from "@octokit/rest"
import { parseArgs } from "@std/cli/parse-args"
type OrgMember =
RestEndpointMethodTypes["orgs"]["listMembers"]["response"]["data"][0]
class ProgressIndicator {
private totalMembers = 0
private currentPage = 0
private isRunning = false
start() {
this.isRunning = true
this.render()
}
update(page: number, pageMembers: number) {
this.currentPage = page
this.totalMembers += pageMembers
this.render()
}
finish(filename: string) {
this.isRunning = false
this.clearLine()
console.log(`\n✅ Successfully exported ${this.totalMembers} members to ${filename}`)
}
error(message: string) {
this.isRunning = false
this.clearLine()
console.error(`\n❌ ${message}`)
}
private render() {
if (!this.isRunning) return
this.clearLine()
const spinner = this.getSpinner()
const message = `${spinner} Fetching page ${this.currentPage} | ${this.totalMembers} members collected`
Deno.stdout.writeSync(new TextEncoder().encode(`\r${message}`))
}
private clearLine() {
Deno.stdout.writeSync(new TextEncoder().encode('\r\x1b[K'))
}
private getSpinner() {
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
return frames[Math.floor(Date.now() / 100) % frames.length]
}
}
async function* fetchOrgMembers(
org: string,
token: string,
progress: ProgressIndicator,
): AsyncGenerator<OrgMember[], void, unknown> {
const octokit = new Octokit({
auth: token,
})
let page = 1
const perPage = 100
while (true) {
try {
const response = await octokit.rest.orgs.listMembers({
org,
per_page: perPage,
page,
})
if (response.data.length === 0) {
break
}
progress.update(page, response.data.length)
yield response.data
if (response.data.length < perPage) {
break
}
page++
} catch (error) {
progress.error(`Error fetching members: ${error}`)
throw error
}
}
}
async function exportToJsonlStream(
membersGenerator: AsyncGenerator<OrgMember[], void, unknown>,
filename: string,
): Promise<void> {
const file = await Deno.open(filename, { write: true, create: true, truncate: true })
const encoder = new TextEncoder()
try {
for await (const membersBatch of membersGenerator) {
const jsonlContent = membersBatch
.map((member) => JSON.stringify(member))
.join("\n") + "\n"
await file.write(encoder.encode(jsonlContent))
}
} finally {
file.close()
}
}
async function main() {
const args = parseArgs(Deno.args, {
string: ["token", "org"],
alias: {
t: "token",
o: "org",
},
})
const { org, token } = args
if (!org || !token) {
const scriptName = import.meta.url.split("/").pop()
console.error(`Usage: deno run --allow-net --allow-write ${scriptName} --org <organization> --token <github_token>`)
console.error("Options:")
console.error(" --org, -o GitHub organization name")
console.error(" --token, -t GitHub personal access token")
console.error("\nExample:")
console.error(` deno run --allow-net --allow-write ${scriptName} --org microsoft --token ghp_xxxxxxxxxxxx`)
Deno.exit(1)
}
try {
const progress = new ProgressIndicator()
console.log(`🚀 Fetching members for organization: ${org}`)
progress.start()
const membersGenerator = fetchOrgMembers(org, token, progress)
const filename = `${org}-members.jsonl`
await exportToJsonlStream(membersGenerator, filename)
progress.finish(filename)
} catch (error) {
console.error(`\n❌ Error: ${error}`)
Deno.exit(1)
}
}
if (import.meta.main) {
main()
}