gh-api-client 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,971 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ CommitResource: () => CommitResource,
24
+ GitHubApiError: () => GitHubApiError,
25
+ GitHubClient: () => GitHubClient,
26
+ OrganizationResource: () => OrganizationResource,
27
+ PullRequestResource: () => PullRequestResource,
28
+ RepositoryResource: () => RepositoryResource,
29
+ Security: () => Security,
30
+ UserResource: () => UserResource
31
+ });
32
+ module.exports = __toCommonJS(index_exports);
33
+
34
+ // src/security/Security.ts
35
+ var Security = class {
36
+ /**
37
+ * Creates a new Security instance with a GitHub personal access token.
38
+ *
39
+ * @param token - A GitHub personal access token (e.g., `ghp_...`) or OAuth token
40
+ * @param apiUrl - The base URL of the GitHub API. Defaults to `'https://api.github.com'`.
41
+ * Must be a valid URL; throws if it cannot be parsed.
42
+ *
43
+ * @throws {TypeError} If `apiUrl` is not a valid URL
44
+ */
45
+ constructor(token, apiUrl = "https://api.github.com") {
46
+ if (!URL.canParse(apiUrl)) {
47
+ throw new TypeError(`Invalid apiUrl: "${apiUrl}" is not a valid URL`);
48
+ }
49
+ this.apiUrl = apiUrl.replace(/\/$/, "");
50
+ this.token = token;
51
+ }
52
+ /**
53
+ * Returns the base URL of the GitHub API, without a trailing slash.
54
+ *
55
+ * @returns The API base URL
56
+ */
57
+ getApiUrl() {
58
+ return this.apiUrl;
59
+ }
60
+ /**
61
+ * Returns the value of the `Authorization` header for Bearer authentication.
62
+ *
63
+ * @returns The Authorization header value in the format `Bearer <token>`
64
+ */
65
+ getAuthorizationHeader() {
66
+ return `Bearer ${this.token}`;
67
+ }
68
+ /**
69
+ * Returns the full set of HTTP headers required for authenticated GitHub API requests.
70
+ *
71
+ * @returns An object containing `Authorization`, `Accept`, `Content-Type`, and `X-GitHub-Api-Version` headers
72
+ */
73
+ getHeaders() {
74
+ return {
75
+ Authorization: `Bearer ${this.token}`,
76
+ Accept: "application/vnd.github+json",
77
+ "Content-Type": "application/json",
78
+ "X-GitHub-Api-Version": "2022-11-28"
79
+ };
80
+ }
81
+ /**
82
+ * Returns headers for raw file content requests.
83
+ *
84
+ * @returns Headers with `Accept: application/vnd.github.raw+json`
85
+ */
86
+ getRawHeaders() {
87
+ return {
88
+ ...this.getHeaders(),
89
+ Accept: "application/vnd.github.raw+json"
90
+ };
91
+ }
92
+ };
93
+
94
+ // src/errors/GitHubApiError.ts
95
+ var GitHubApiError = class extends Error {
96
+ constructor(status, statusText) {
97
+ super(`GitHub API error: ${status} ${statusText}`);
98
+ this.name = "GitHubApiError";
99
+ this.status = status;
100
+ this.statusText = statusText;
101
+ }
102
+ };
103
+
104
+ // src/resources/PullRequestResource.ts
105
+ var PullRequestResource = class {
106
+ /** @internal */
107
+ constructor(request, requestList, owner, repo, pullNumber) {
108
+ this.request = request;
109
+ this.requestList = requestList;
110
+ this.basePath = `/repos/${owner}/${repo}/pulls/${pullNumber}`;
111
+ }
112
+ /**
113
+ * Allows the resource to be awaited directly, resolving with the pull request info.
114
+ * Delegates to {@link PullRequestResource.get}.
115
+ */
116
+ then(onfulfilled, onrejected) {
117
+ return this.get().then(onfulfilled, onrejected);
118
+ }
119
+ /**
120
+ * Fetches the pull request details.
121
+ *
122
+ * `GET /repos/{owner}/{repo}/pulls/{pull_number}`
123
+ *
124
+ * @returns The pull request object
125
+ */
126
+ async get() {
127
+ return this.request(this.basePath);
128
+ }
129
+ /**
130
+ * Fetches the commits included in this pull request.
131
+ *
132
+ * `GET /repos/{owner}/{repo}/pulls/{pull_number}/commits`
133
+ *
134
+ * @param params - Optional pagination: `per_page`, `page`
135
+ * @returns A paged response of commits
136
+ */
137
+ async commits(params) {
138
+ return this.requestList(
139
+ `${this.basePath}/commits`,
140
+ params
141
+ );
142
+ }
143
+ /**
144
+ * Fetches the files changed by this pull request.
145
+ *
146
+ * `GET /repos/{owner}/{repo}/pulls/{pull_number}/files`
147
+ *
148
+ * @param params - Optional pagination: `per_page`, `page`
149
+ * @returns A paged response of changed files
150
+ */
151
+ async files(params) {
152
+ return this.requestList(
153
+ `${this.basePath}/files`,
154
+ params
155
+ );
156
+ }
157
+ /**
158
+ * Fetches the reviews submitted on this pull request.
159
+ *
160
+ * `GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews`
161
+ *
162
+ * @param params - Optional pagination: `per_page`, `page`
163
+ * @returns A paged response of reviews
164
+ */
165
+ async reviews(params) {
166
+ return this.requestList(
167
+ `${this.basePath}/reviews`,
168
+ params
169
+ );
170
+ }
171
+ /**
172
+ * Fetches the inline review comments on this pull request's diff.
173
+ *
174
+ * `GET /repos/{owner}/{repo}/pulls/{pull_number}/comments`
175
+ *
176
+ * @param params - Optional filters: `sort`, `direction`, `since`, `per_page`, `page`
177
+ * @returns A paged response of review comments
178
+ */
179
+ async reviewComments(params) {
180
+ return this.requestList(
181
+ `${this.basePath}/comments`,
182
+ params
183
+ );
184
+ }
185
+ /**
186
+ * Checks whether the pull request has been merged.
187
+ *
188
+ * `GET /repos/{owner}/{repo}/pulls/{pull_number}/merge`
189
+ *
190
+ * @returns `true` if merged (HTTP 204), `false` if not merged (HTTP 404)
191
+ */
192
+ async isMerged() {
193
+ try {
194
+ await this.request(`${this.basePath}/merge`);
195
+ return true;
196
+ } catch {
197
+ return false;
198
+ }
199
+ }
200
+ };
201
+
202
+ // src/resources/CommitResource.ts
203
+ var CommitResource = class {
204
+ /** @internal */
205
+ constructor(request, requestList, owner, repo, ref) {
206
+ this.request = request;
207
+ this.requestList = requestList;
208
+ this.basePath = `/repos/${owner}/${repo}/commits/${ref}`;
209
+ this.ref = ref;
210
+ }
211
+ /**
212
+ * Allows the resource to be awaited directly, resolving with the commit info.
213
+ * Delegates to {@link CommitResource.get}.
214
+ */
215
+ then(onfulfilled, onrejected) {
216
+ return this.get().then(onfulfilled, onrejected);
217
+ }
218
+ /**
219
+ * Fetches the commit details, including stats and changed files.
220
+ *
221
+ * `GET /repos/{owner}/{repo}/commits/{ref}`
222
+ *
223
+ * @returns The commit object with stats and files
224
+ */
225
+ async get() {
226
+ return this.request(this.basePath);
227
+ }
228
+ /**
229
+ * Fetches the individual commit statuses (from CI/CD systems via the Statuses API).
230
+ *
231
+ * `GET /repos/{owner}/{repo}/statuses/{sha}`
232
+ *
233
+ * @param params - Optional pagination: `per_page`, `page`
234
+ * @returns A paged response of commit statuses
235
+ */
236
+ async statuses(params) {
237
+ const repoPath = this.basePath.replace(`/commits/${this.ref}`, "");
238
+ return this.requestList(
239
+ `${repoPath}/statuses/${this.ref}`,
240
+ params
241
+ );
242
+ }
243
+ /**
244
+ * Fetches the combined commit status — an aggregation of all statuses for this ref.
245
+ *
246
+ * `GET /repos/{owner}/{repo}/commits/{ref}/status`
247
+ *
248
+ * @returns The combined status object
249
+ */
250
+ async combinedStatus() {
251
+ return this.request(`${this.basePath}/status`);
252
+ }
253
+ /**
254
+ * Fetches GitHub Actions check runs for this commit.
255
+ *
256
+ * `GET /repos/{owner}/{repo}/commits/{ref}/check-runs`
257
+ *
258
+ * @param params - Optional filters: `check_name`, `status`, `app_id`, `per_page`, `page`
259
+ * @returns A paged response of check runs
260
+ */
261
+ async checkRuns(params) {
262
+ const raw = await this.request(
263
+ `${this.basePath}/check-runs`,
264
+ params
265
+ );
266
+ return {
267
+ values: raw.check_runs,
268
+ hasNextPage: false
269
+ };
270
+ }
271
+ };
272
+
273
+ // src/resources/RepositoryResource.ts
274
+ var RepositoryResource = class {
275
+ /** @internal */
276
+ constructor(request, requestList, requestText, owner, repo) {
277
+ this.request = request;
278
+ this.requestList = requestList;
279
+ this.requestText = requestText;
280
+ this.owner = owner;
281
+ this.repo = repo;
282
+ this.basePath = `/repos/${owner}/${repo}`;
283
+ }
284
+ /**
285
+ * Allows the resource to be awaited directly, resolving with the repository info.
286
+ * Delegates to {@link RepositoryResource.get}.
287
+ */
288
+ then(onfulfilled, onrejected) {
289
+ return this.get().then(onfulfilled, onrejected);
290
+ }
291
+ /**
292
+ * Fetches the repository details.
293
+ *
294
+ * `GET /repos/{owner}/{repo}`
295
+ *
296
+ * @returns The repository object
297
+ */
298
+ async get() {
299
+ return this.request(this.basePath);
300
+ }
301
+ /**
302
+ * Fetches pull requests for this repository.
303
+ *
304
+ * `GET /repos/{owner}/{repo}/pulls`
305
+ *
306
+ * @param params - Optional filters: `state`, `head`, `base`, `sort`, `direction`, `per_page`, `page`
307
+ * @returns A paged response of pull requests
308
+ */
309
+ async pullRequests(params) {
310
+ return this.requestList(
311
+ `${this.basePath}/pulls`,
312
+ params
313
+ );
314
+ }
315
+ /**
316
+ * Returns a {@link PullRequestResource} for a given pull request number.
317
+ *
318
+ * The returned resource can be awaited directly to fetch pull request info,
319
+ * or chained to access nested resources.
320
+ *
321
+ * @param pullNumber - The pull request number (not the ID)
322
+ * @returns A chainable pull request resource
323
+ *
324
+ * @example
325
+ * ```typescript
326
+ * const pr = await gh.org('github').repo('linguist').pullRequest(42);
327
+ * const files = await gh.org('github').repo('linguist').pullRequest(42).files();
328
+ * const reviews = await gh.org('github').repo('linguist').pullRequest(42).reviews();
329
+ * ```
330
+ */
331
+ pullRequest(pullNumber) {
332
+ return new PullRequestResource(
333
+ this.request,
334
+ this.requestList,
335
+ this.owner,
336
+ this.repo,
337
+ pullNumber
338
+ );
339
+ }
340
+ /**
341
+ * Fetches commits for this repository.
342
+ *
343
+ * `GET /repos/{owner}/{repo}/commits`
344
+ *
345
+ * @param params - Optional filters: `sha`, `path`, `author`, `since`, `until`, `per_page`, `page`
346
+ * @returns A paged response of commits
347
+ */
348
+ async commits(params) {
349
+ return this.requestList(
350
+ `${this.basePath}/commits`,
351
+ params
352
+ );
353
+ }
354
+ /**
355
+ * Returns a {@link CommitResource} for a given commit ref (SHA, branch, or tag).
356
+ *
357
+ * The returned resource can be awaited directly to fetch commit info,
358
+ * or chained to access nested resources.
359
+ *
360
+ * @param ref - Commit SHA, branch name, or tag name
361
+ * @returns A chainable commit resource
362
+ *
363
+ * @example
364
+ * ```typescript
365
+ * const commit = await gh.org('github').repo('linguist').commit('abc123');
366
+ * const statuses = await gh.org('github').repo('linguist').commit('abc123').statuses();
367
+ * ```
368
+ */
369
+ commit(ref) {
370
+ return new CommitResource(
371
+ this.request,
372
+ this.requestList,
373
+ this.owner,
374
+ this.repo,
375
+ ref
376
+ );
377
+ }
378
+ /**
379
+ * Fetches branches for this repository.
380
+ *
381
+ * `GET /repos/{owner}/{repo}/branches`
382
+ *
383
+ * @param params - Optional filters: `protected`, `per_page`, `page`
384
+ * @returns A paged response of branches
385
+ */
386
+ async branches(params) {
387
+ return this.requestList(
388
+ `${this.basePath}/branches`,
389
+ params
390
+ );
391
+ }
392
+ /**
393
+ * Fetches a specific branch by name.
394
+ *
395
+ * `GET /repos/{owner}/{repo}/branches/{branch}`
396
+ *
397
+ * @param name - The branch name (e.g., `'main'`)
398
+ * @returns The branch object
399
+ */
400
+ async branch(name) {
401
+ return this.request(`${this.basePath}/branches/${encodeURIComponent(name)}`);
402
+ }
403
+ /**
404
+ * Fetches tags for this repository.
405
+ *
406
+ * `GET /repos/{owner}/{repo}/tags`
407
+ *
408
+ * @param params - Optional pagination: `per_page`, `page`
409
+ * @returns A paged response of tags
410
+ */
411
+ async tags(params) {
412
+ return this.requestList(
413
+ `${this.basePath}/tags`,
414
+ params
415
+ );
416
+ }
417
+ /**
418
+ * Fetches releases for this repository.
419
+ *
420
+ * `GET /repos/{owner}/{repo}/releases`
421
+ *
422
+ * @param params - Optional pagination: `per_page`, `page`
423
+ * @returns A paged response of releases
424
+ */
425
+ async releases(params) {
426
+ return this.requestList(
427
+ `${this.basePath}/releases`,
428
+ params
429
+ );
430
+ }
431
+ /**
432
+ * Fetches the latest published release for this repository.
433
+ *
434
+ * `GET /repos/{owner}/{repo}/releases/latest`
435
+ *
436
+ * @returns The latest release object
437
+ */
438
+ async latestRelease() {
439
+ return this.request(`${this.basePath}/releases/latest`);
440
+ }
441
+ /**
442
+ * Fetches the forks of this repository.
443
+ *
444
+ * `GET /repos/{owner}/{repo}/forks`
445
+ *
446
+ * @param params - Optional filters: `sort`, `per_page`, `page`
447
+ * @returns A paged response of forked repositories
448
+ */
449
+ async forks(params) {
450
+ return this.requestList(
451
+ `${this.basePath}/forks`,
452
+ params
453
+ );
454
+ }
455
+ /**
456
+ * Fetches webhooks configured on this repository.
457
+ *
458
+ * `GET /repos/{owner}/{repo}/hooks`
459
+ *
460
+ * @param params - Optional pagination: `per_page`, `page`
461
+ * @returns A paged response of webhooks
462
+ */
463
+ async webhooks(params) {
464
+ return this.requestList(
465
+ `${this.basePath}/hooks`,
466
+ params
467
+ );
468
+ }
469
+ /**
470
+ * Fetches the contents of a file or directory in this repository.
471
+ *
472
+ * `GET /repos/{owner}/{repo}/contents/{path}`
473
+ *
474
+ * Returns a single {@link GitHubContent} for files, or an array for directories.
475
+ *
476
+ * @param path - Path to the file or directory (e.g., `'src/index.ts'` or `'src'`). Omit for root.
477
+ * @param params - Optional: `ref` (branch, tag, or commit SHA)
478
+ * @returns File content object or array of directory entries
479
+ */
480
+ async contents(path, params) {
481
+ const contentPath = path ? `${this.basePath}/contents/${path}` : `${this.basePath}/contents`;
482
+ return this.request(
483
+ contentPath,
484
+ params
485
+ );
486
+ }
487
+ /**
488
+ * Fetches the raw text content of a file in this repository.
489
+ *
490
+ * Uses `Accept: application/vnd.github.raw+json` to retrieve the file directly
491
+ * without base64 encoding.
492
+ *
493
+ * `GET /repos/{owner}/{repo}/contents/{filePath}`
494
+ *
495
+ * @param filePath - Path to the file (e.g., `'src/index.ts'`)
496
+ * @param params - Optional: `ref` (branch, tag, or commit SHA)
497
+ * @returns The raw file content as a string
498
+ */
499
+ async raw(filePath, params) {
500
+ return this.requestText(
501
+ `${this.basePath}/contents/${filePath}`,
502
+ params
503
+ );
504
+ }
505
+ /**
506
+ * Fetches the repository topics.
507
+ *
508
+ * `GET /repos/{owner}/{repo}/topics`
509
+ *
510
+ * @returns An array of topic strings
511
+ */
512
+ async topics() {
513
+ const data = await this.request(`${this.basePath}/topics`);
514
+ return data.names;
515
+ }
516
+ /**
517
+ * Fetches contributors to this repository.
518
+ *
519
+ * `GET /repos/{owner}/{repo}/contributors`
520
+ *
521
+ * @param params - Optional filters: `anon` (include anonymous contributors), `per_page`, `page`
522
+ * @returns A paged response of contributors
523
+ */
524
+ async contributors(params) {
525
+ return this.requestList(
526
+ `${this.basePath}/contributors`,
527
+ params
528
+ );
529
+ }
530
+ };
531
+
532
+ // src/resources/OrganizationResource.ts
533
+ var OrganizationResource = class {
534
+ /** @internal */
535
+ constructor(request, requestList, requestText, org) {
536
+ this.request = request;
537
+ this.requestList = requestList;
538
+ this.requestText = requestText;
539
+ this.org = org;
540
+ }
541
+ /**
542
+ * Allows the resource to be awaited directly, resolving with the organization info.
543
+ * Delegates to {@link OrganizationResource.get}.
544
+ */
545
+ then(onfulfilled, onrejected) {
546
+ return this.get().then(onfulfilled, onrejected);
547
+ }
548
+ /**
549
+ * Fetches the organization details.
550
+ *
551
+ * `GET /orgs/{org}`
552
+ *
553
+ * @returns The organization object
554
+ */
555
+ async get() {
556
+ return this.request(`/orgs/${this.org}`);
557
+ }
558
+ /**
559
+ * Fetches repositories belonging to this organization.
560
+ *
561
+ * `GET /orgs/{org}/repos`
562
+ *
563
+ * @param params - Optional filters: `type`, `sort`, `direction`, `per_page`, `page`
564
+ * @returns A paged response of repositories
565
+ */
566
+ async repos(params) {
567
+ return this.requestList(
568
+ `/orgs/${this.org}/repos`,
569
+ params
570
+ );
571
+ }
572
+ /**
573
+ * Returns a {@link RepositoryResource} for a given repository name within this organization.
574
+ *
575
+ * The returned resource can be awaited directly to fetch repository info,
576
+ * or chained to access nested resources.
577
+ *
578
+ * @param name - The repository name (e.g., `'linguist'`)
579
+ * @returns A chainable repository resource
580
+ *
581
+ * @example
582
+ * ```typescript
583
+ * const repo = await gh.org('github').repo('linguist');
584
+ * const prs = await gh.org('github').repo('linguist').pullRequests({ state: 'open' });
585
+ * const commits = await gh.org('github').repo('linguist').commits({ per_page: 10 });
586
+ * ```
587
+ */
588
+ repo(name) {
589
+ return new RepositoryResource(
590
+ this.request,
591
+ this.requestList,
592
+ this.requestText,
593
+ this.org,
594
+ name
595
+ );
596
+ }
597
+ /**
598
+ * Fetches members of this organization.
599
+ *
600
+ * `GET /orgs/{org}/members`
601
+ *
602
+ * @param params - Optional filters: `role`, `filter`, `per_page`, `page`
603
+ * @returns A paged response of users
604
+ */
605
+ async members(params) {
606
+ return this.requestList(
607
+ `/orgs/${this.org}/members`,
608
+ params
609
+ );
610
+ }
611
+ };
612
+
613
+ // src/resources/UserResource.ts
614
+ var UserResource = class {
615
+ /** @internal */
616
+ constructor(request, requestList, requestText, login) {
617
+ this.request = request;
618
+ this.requestList = requestList;
619
+ this.requestText = requestText;
620
+ this.login = login;
621
+ this.basePath = `/users/${login}`;
622
+ }
623
+ /**
624
+ * Allows the resource to be awaited directly, resolving with the user info.
625
+ * Delegates to {@link UserResource.get}.
626
+ */
627
+ then(onfulfilled, onrejected) {
628
+ return this.get().then(onfulfilled, onrejected);
629
+ }
630
+ /**
631
+ * Fetches the user details.
632
+ *
633
+ * `GET /users/{username}`
634
+ *
635
+ * @returns The user object
636
+ */
637
+ async get() {
638
+ return this.request(this.basePath);
639
+ }
640
+ /**
641
+ * Fetches public repositories for this user.
642
+ *
643
+ * `GET /users/{username}/repos`
644
+ *
645
+ * @param params - Optional filters: `type`, `sort`, `direction`, `per_page`, `page`
646
+ * @returns A paged response of repositories
647
+ */
648
+ async repos(params) {
649
+ return this.requestList(
650
+ `${this.basePath}/repos`,
651
+ params
652
+ );
653
+ }
654
+ /**
655
+ * Returns a {@link RepositoryResource} for a given repository under this user,
656
+ * providing access to all repository sub-resources.
657
+ *
658
+ * @param name - The repository name
659
+ * @returns A chainable repository resource
660
+ *
661
+ * @example
662
+ * ```typescript
663
+ * const repo = await gh.user('octocat').repo('Hello-World');
664
+ * const content = await gh.user('octocat').repo('Hello-World').raw('README.md');
665
+ * ```
666
+ */
667
+ repo(name) {
668
+ return new RepositoryResource(
669
+ this.request,
670
+ this.requestList,
671
+ this.requestText,
672
+ this.login,
673
+ name
674
+ );
675
+ }
676
+ /**
677
+ * Fetches the users this user is following.
678
+ *
679
+ * `GET /users/{username}/following`
680
+ *
681
+ * @returns A paged response of users
682
+ */
683
+ async following(params) {
684
+ return this.requestList(
685
+ `${this.basePath}/following`,
686
+ params
687
+ );
688
+ }
689
+ /**
690
+ * Fetches the users following this user.
691
+ *
692
+ * `GET /users/{username}/followers`
693
+ *
694
+ * @returns A paged response of users
695
+ */
696
+ async followers(params) {
697
+ return this.requestList(
698
+ `${this.basePath}/followers`,
699
+ params
700
+ );
701
+ }
702
+ };
703
+
704
+ // src/GitHubClient.ts
705
+ var GitHubClient = class {
706
+ /**
707
+ * @param options - Authentication and connection options
708
+ * @throws {TypeError} If `apiUrl` is not a valid URL
709
+ */
710
+ constructor({ token, apiUrl }) {
711
+ this.listeners = /* @__PURE__ */ new Map();
712
+ this.security = new Security(token, apiUrl);
713
+ }
714
+ /**
715
+ * Subscribes to a client event.
716
+ *
717
+ * @example
718
+ * ```typescript
719
+ * gh.on('request', (event) => {
720
+ * console.log(`${event.method} ${event.url} — ${event.durationMs}ms`);
721
+ * if (event.error) console.error('Request failed:', event.error);
722
+ * });
723
+ * ```
724
+ */
725
+ on(event, callback) {
726
+ const callbacks = this.listeners.get(event) ?? [];
727
+ callbacks.push(callback);
728
+ this.listeners.set(event, callbacks);
729
+ return this;
730
+ }
731
+ emit(event, payload) {
732
+ const callbacks = this.listeners.get(event) ?? [];
733
+ for (const cb of callbacks) {
734
+ cb(payload);
735
+ }
736
+ }
737
+ /**
738
+ * Performs an authenticated GET request returning a single JSON object.
739
+ * @internal
740
+ */
741
+ async request(path, params, options) {
742
+ const base = `${this.security.getApiUrl()}${path}`;
743
+ const url = buildUrl(base, params);
744
+ const startedAt = /* @__PURE__ */ new Date();
745
+ let statusCode;
746
+ try {
747
+ const headers = options?.headers ?? this.security.getHeaders();
748
+ const response = await fetch(url, { headers });
749
+ statusCode = response.status;
750
+ if (!response.ok) {
751
+ throw new GitHubApiError(response.status, response.statusText);
752
+ }
753
+ const data = await response.json();
754
+ this.emit("request", { url, method: "GET", startedAt, finishedAt: /* @__PURE__ */ new Date(), durationMs: Date.now() - startedAt.getTime(), statusCode });
755
+ return data;
756
+ } catch (err) {
757
+ const finishedAt = /* @__PURE__ */ new Date();
758
+ this.emit("request", { url, method: "GET", startedAt, finishedAt, durationMs: finishedAt.getTime() - startedAt.getTime(), statusCode, error: err instanceof Error ? err : new Error(String(err)) });
759
+ throw err;
760
+ }
761
+ }
762
+ /**
763
+ * Performs an authenticated GET request returning a paginated list.
764
+ * Parses the `Link` response header to determine if more pages exist.
765
+ * @internal
766
+ */
767
+ async requestList(path, params) {
768
+ const base = `${this.security.getApiUrl()}${path}`;
769
+ const url = buildUrl(base, params);
770
+ const startedAt = /* @__PURE__ */ new Date();
771
+ let statusCode;
772
+ try {
773
+ const response = await fetch(url, { headers: this.security.getHeaders() });
774
+ statusCode = response.status;
775
+ if (!response.ok) {
776
+ throw new GitHubApiError(response.status, response.statusText);
777
+ }
778
+ const data = await response.json();
779
+ const linkHeader = response.headers.get("Link");
780
+ const nextPage = parseNextPage(linkHeader);
781
+ this.emit("request", { url, method: "GET", startedAt, finishedAt: /* @__PURE__ */ new Date(), durationMs: Date.now() - startedAt.getTime(), statusCode });
782
+ return {
783
+ values: data,
784
+ hasNextPage: nextPage !== void 0,
785
+ nextPage
786
+ };
787
+ } catch (err) {
788
+ const finishedAt = /* @__PURE__ */ new Date();
789
+ this.emit("request", { url, method: "GET", startedAt, finishedAt, durationMs: finishedAt.getTime() - startedAt.getTime(), statusCode, error: err instanceof Error ? err : new Error(String(err)) });
790
+ throw err;
791
+ }
792
+ }
793
+ /**
794
+ * Performs a GET request returning raw text content.
795
+ * Uses `Accept: application/vnd.github.raw+json` to retrieve file content directly.
796
+ * @internal
797
+ */
798
+ async requestText(path, params) {
799
+ const base = `${this.security.getApiUrl()}${path}`;
800
+ const url = buildUrl(base, params);
801
+ const startedAt = /* @__PURE__ */ new Date();
802
+ let statusCode;
803
+ try {
804
+ const response = await fetch(url, { headers: this.security.getRawHeaders() });
805
+ statusCode = response.status;
806
+ if (!response.ok) {
807
+ throw new GitHubApiError(response.status, response.statusText);
808
+ }
809
+ const text = await response.text();
810
+ this.emit("request", { url, method: "GET", startedAt, finishedAt: /* @__PURE__ */ new Date(), durationMs: Date.now() - startedAt.getTime(), statusCode });
811
+ return text;
812
+ } catch (err) {
813
+ const finishedAt = /* @__PURE__ */ new Date();
814
+ this.emit("request", { url, method: "GET", startedAt, finishedAt, durationMs: finishedAt.getTime() - startedAt.getTime(), statusCode, error: err instanceof Error ? err : new Error(String(err)) });
815
+ throw err;
816
+ }
817
+ }
818
+ makeRequestFn() {
819
+ return (path, params) => this.request(path, params);
820
+ }
821
+ makeRequestListFn() {
822
+ return (path, params) => this.requestList(path, params);
823
+ }
824
+ makeRequestTextFn() {
825
+ return (path, params) => this.requestText(path, params);
826
+ }
827
+ /**
828
+ * Fetches the authenticated user's profile.
829
+ *
830
+ * `GET /user`
831
+ *
832
+ * @returns The authenticated user object
833
+ *
834
+ * @example
835
+ * ```typescript
836
+ * const me = await gh.currentUser();
837
+ * console.log(me.login); // 'octocat'
838
+ * ```
839
+ */
840
+ async currentUser() {
841
+ return this.request("/user");
842
+ }
843
+ /**
844
+ * Returns a {@link UserResource} for a given GitHub login, providing access
845
+ * to user data and their repositories.
846
+ *
847
+ * The returned resource can be awaited directly to fetch user info,
848
+ * or chained to access nested resources.
849
+ *
850
+ * @param login - The user's login name (e.g., `'octocat'`)
851
+ * @returns A chainable user resource
852
+ *
853
+ * @example
854
+ * ```typescript
855
+ * const user = await gh.user('octocat');
856
+ * const repos = await gh.user('octocat').repos({ sort: 'updated' });
857
+ * const pr = await gh.user('octocat').repo('Hello-World').pullRequest(1).files();
858
+ * ```
859
+ */
860
+ user(login) {
861
+ return new UserResource(
862
+ this.makeRequestFn(),
863
+ this.makeRequestListFn(),
864
+ this.makeRequestTextFn(),
865
+ login
866
+ );
867
+ }
868
+ /**
869
+ * Returns an {@link OrganizationResource} for a given GitHub organization, providing
870
+ * access to organization data and its repositories.
871
+ *
872
+ * The returned resource can be awaited directly to fetch organization info,
873
+ * or chained to access nested resources.
874
+ *
875
+ * @param name - The organization's login name (e.g., `'github'`)
876
+ * @returns A chainable organization resource
877
+ *
878
+ * @example
879
+ * ```typescript
880
+ * const org = await gh.org('github');
881
+ * const repos = await gh.org('github').repos({ type: 'public' });
882
+ * const prs = await gh.org('github').repo('linguist').pullRequests({ state: 'open' });
883
+ * ```
884
+ */
885
+ org(name) {
886
+ return new OrganizationResource(
887
+ this.makeRequestFn(),
888
+ this.makeRequestListFn(),
889
+ this.makeRequestTextFn(),
890
+ name
891
+ );
892
+ }
893
+ /**
894
+ * Returns a {@link RepositoryResource} for a given owner and repository name.
895
+ *
896
+ * Shortcut that works for both user repositories and organization repositories.
897
+ *
898
+ * @param owner - The owner login (user or organization)
899
+ * @param name - The repository name
900
+ * @returns A chainable repository resource
901
+ *
902
+ * @example
903
+ * ```typescript
904
+ * const repo = await gh.repo('octocat', 'Hello-World');
905
+ * const prs = await gh.repo('octocat', 'Hello-World').pullRequests();
906
+ * ```
907
+ */
908
+ repo(owner, name) {
909
+ return new RepositoryResource(
910
+ this.makeRequestFn(),
911
+ this.makeRequestListFn(),
912
+ this.makeRequestTextFn(),
913
+ owner,
914
+ name
915
+ );
916
+ }
917
+ /**
918
+ * Searches for repositories using GitHub's search syntax.
919
+ *
920
+ * `GET /search/repositories`
921
+ *
922
+ * @param params - Search query and optional filters. `q` is required.
923
+ * @returns A paged response of repositories with `totalCount`
924
+ *
925
+ * @example
926
+ * ```typescript
927
+ * const results = await gh.searchRepos({ q: 'language:typescript stars:>1000', sort: 'stars' });
928
+ * console.log(`Found ${results.totalCount} repositories`);
929
+ * ```
930
+ */
931
+ async searchRepos(params) {
932
+ const base = `${this.security.getApiUrl()}/search/repositories`;
933
+ const url = buildUrl(base, params);
934
+ const startedAt = /* @__PURE__ */ new Date();
935
+ let statusCode;
936
+ try {
937
+ const response = await fetch(url, { headers: this.security.getHeaders() });
938
+ statusCode = response.status;
939
+ if (!response.ok) {
940
+ throw new GitHubApiError(response.status, response.statusText);
941
+ }
942
+ const data = await response.json();
943
+ const linkHeader = response.headers.get("Link");
944
+ const nextPage = parseNextPage(linkHeader);
945
+ this.emit("request", { url, method: "GET", startedAt, finishedAt: /* @__PURE__ */ new Date(), durationMs: Date.now() - startedAt.getTime(), statusCode });
946
+ return {
947
+ values: data.items,
948
+ hasNextPage: nextPage !== void 0,
949
+ nextPage,
950
+ totalCount: data.total_count
951
+ };
952
+ } catch (err) {
953
+ const finishedAt = /* @__PURE__ */ new Date();
954
+ this.emit("request", { url, method: "GET", startedAt, finishedAt, durationMs: finishedAt.getTime() - startedAt.getTime(), statusCode, error: err instanceof Error ? err : new Error(String(err)) });
955
+ throw err;
956
+ }
957
+ }
958
+ };
959
+ function buildUrl(base, params) {
960
+ if (!params) return base;
961
+ const entries = Object.entries(params).filter(([, v]) => v !== void 0);
962
+ if (entries.length === 0) return base;
963
+ const search = new URLSearchParams(entries.map(([k, v]) => [k, String(v)]));
964
+ return `${base}?${search.toString()}`;
965
+ }
966
+ function parseNextPage(linkHeader) {
967
+ if (!linkHeader) return void 0;
968
+ const match = linkHeader.match(/<[^>]*[?&]page=(\d+)[^>]*>;\s*rel="next"/);
969
+ return match ? parseInt(match[1], 10) : void 0;
970
+ }
971
+ //# sourceMappingURL=index.js.map