@theholocron/github-client 1.46.0 → 1.47.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/README.md CHANGED
@@ -50,7 +50,7 @@ const blob = await client.git.createBlob("owner/name", "file contents");
50
50
  | `labels` | `listLabels`, `createLabel`, `updateLabel`, `deleteLabel` |
51
51
  | `topics` | `setTopics` |
52
52
  | `properties` | `setProperties` |
53
- | `git` | `getRef`, `getCommit`, `getTree`, `getContents`, `createBlob`, `createTree`, `createCommit`, `createRef`, `updateRef`, `createPull` |
53
+ | `git` | `getRef`, `getCommit`, `getTree`, `getContents`, `putContents`, `createBlob`, `createTree`, `createCommit`, `createRef`, `updateRef`, `createPull` |
54
54
  | `checks` | `createCheckRun` |
55
55
 
56
56
  GitHub accepts at most 50 annotations per check-run request. Slice with the
package/dist/index.d.mts CHANGED
@@ -131,6 +131,29 @@ interface GitContents {
131
131
  name: string;
132
132
  path: string;
133
133
  }
134
+ /** What `git.putContents` returns: the file's new blob sha (the `sha` to pass next time) and the commit it made. */
135
+ interface PutContentsResult {
136
+ content: {
137
+ sha: string;
138
+ path: string;
139
+ };
140
+ commit: {
141
+ sha: string;
142
+ };
143
+ }
144
+ interface PutContentsInput {
145
+ message: string;
146
+ /** The new file text (UTF-8); the client encodes it. */
147
+ content: string;
148
+ /**
149
+ * The blob `sha` of the file being replaced, from `getContents`. Required to update an existing file,
150
+ * and omitted to create one. This is what makes the write safe: if someone changed the file since it was
151
+ * read, GitHub refuses (409) instead of overwriting, so the caller can read again and retry.
152
+ */
153
+ sha?: string;
154
+ /** Defaults to the repo's default branch. */
155
+ branch?: string;
156
+ }
134
157
  interface GitBlob {
135
158
  sha: string;
136
159
  url: string;
@@ -361,6 +384,12 @@ interface CodeScanningSetupResult {
361
384
  //#endregion
362
385
  //#region ../../../../../src/teams/teams.d.ts
363
386
  type TeamPermission = "pull" | "triage" | "push" | "maintain" | "admin";
387
+ /** A member of a team, as GitHub lists it. `id` is the numeric user id, which survives a rename; `login` does not. */
388
+ interface TeamMember {
389
+ id: number;
390
+ login: string;
391
+ type?: string;
392
+ }
364
393
  //#endregion
365
394
  //#region ../../../../../src/user/user.d.ts
366
395
  interface GitHubUser {
@@ -502,6 +531,7 @@ declare function createGitHubClient(opts: GitHubClientOptions): {
502
531
  getCommit: (repo: string, sha: string) => Promise<GitCommit>;
503
532
  getTree: (repo: string, sha: string, recursive?: boolean) => Promise<GitTree>;
504
533
  getContents: (repo: string, path: string, ref?: string) => Promise<GitContents>;
534
+ putContents: (repo: string, path: string, input: PutContentsInput) => Promise<PutContentsResult>;
505
535
  createBlob: (repo: string, content: string, encoding?: string) => Promise<GitBlob>;
506
536
  createTree: (repo: string, tree: Array<{
507
537
  path: string;
@@ -581,6 +611,7 @@ declare function createGitHubClient(opts: GitHubClientOptions): {
581
611
  disableDefaultCodeScanning: (repo: string) => Promise<void>;
582
612
  };
583
613
  teams: {
614
+ members: (org: string, teamSlug: string) => Promise<TeamMember[]>;
584
615
  addRepo: (org: string, teamSlug: string, owner: string, repo: string, permission: TeamPermission) => Promise<void>;
585
616
  };
586
617
  topics: {
@@ -597,4 +628,4 @@ declare function createGitHubClient(opts: GitHubClientOptions): {
597
628
  };
598
629
  type GitHubClient = ReturnType<typeof createGitHubClient>;
599
630
  //#endregion
600
- export { type CheckRunAnnotation, type CheckRunAnnotationLevel, type CheckRunConclusion, type CheckRunOutput, type CheckRunStatus, type CodeScanningSetupResult, type CreateCheckRunInput, type CreatePagesPayload, type CreatePullInput, type CreateReviewComment, type CreateReviewInput, type GitBlob, type GitCommit, type GitContents, type GitHubAppCredentials, type GitHubCheckRun, GitHubClient, type GitHubClientOptions, type GitHubContents, type GitHubEnvironment, type GitHubInstallationWebhookPayload, type GitHubIssue, type GitHubLabel, type GitHubMilestone, type GitHubPages, type GitHubPublicKey, type GitHubPullRequest, type GitHubPullRequestCommit, type GitHubPullRequestFile, type GitHubPullRequestReview, type GitHubPullRequestWebhookPayload, type GitHubPushWebhookPayload, type GitHubRepo, type GitHubRuleset, type GitHubUser, type GitHubWebhookHeaders, type GitHubWorkflowRun, type GitPull, type GitRef, type GitTree, type GitTreeItem, type InstallationAccessToken, type IssueSearchParams, MAX_CHECK_RUN_ANNOTATIONS, type PagesBuildStatus, type PagesBuildType, type ReviewThread, type SecretScope, type TeamPermission, type UpdateCheckRunInput, type UpdatePagesPayload, type WorkflowRunFilter, createAppJWT, createGitHubClient, createInstallationClient, getInstallationAccessToken, parseGitHubWebhookHeaders, verifyGitHubWebhookSignature };
631
+ export { type CheckRunAnnotation, type CheckRunAnnotationLevel, type CheckRunConclusion, type CheckRunOutput, type CheckRunStatus, type CodeScanningSetupResult, type CreateCheckRunInput, type CreatePagesPayload, type CreatePullInput, type CreateReviewComment, type CreateReviewInput, type GitBlob, type GitCommit, type GitContents, type GitHubAppCredentials, type GitHubCheckRun, GitHubClient, type GitHubClientOptions, type GitHubContents, type GitHubEnvironment, type GitHubInstallationWebhookPayload, type GitHubIssue, type GitHubLabel, type GitHubMilestone, type GitHubPages, type GitHubPublicKey, type GitHubPullRequest, type GitHubPullRequestCommit, type GitHubPullRequestFile, type GitHubPullRequestReview, type GitHubPullRequestWebhookPayload, type GitHubPushWebhookPayload, type GitHubRepo, type GitHubRuleset, type GitHubUser, type GitHubWebhookHeaders, type GitHubWorkflowRun, type GitPull, type GitRef, type GitTree, type GitTreeItem, type InstallationAccessToken, type IssueSearchParams, MAX_CHECK_RUN_ANNOTATIONS, type PagesBuildStatus, type PagesBuildType, type PutContentsInput, type PutContentsResult, type ReviewThread, type SecretScope, type TeamMember, type TeamPermission, type UpdateCheckRunInput, type UpdatePagesPayload, type WorkflowRunFilter, createAppJWT, createGitHubClient, createInstallationClient, getInstallationAccessToken, parseGitHubWebhookHeaders, verifyGitHubWebhookSignature };
package/dist/index.mjs CHANGED
@@ -58,6 +58,24 @@ function splitRepo(repo) {
58
58
  const [owner, name] = repo.split("/", 2);
59
59
  return [owner ?? "", name ?? ""];
60
60
  }
61
+ /**
62
+ * One URL path segment from a caller-supplied name (an org, a team slug). `.` and `..` are refused because
63
+ * `new URL` would resolve them, sending the authenticated request to a different resource.
64
+ */
65
+ function segment(value) {
66
+ if (value === "" || value === "." || value === "..") throw new Error(`invalid path segment: ${JSON.stringify(value)}`);
67
+ return encodeURIComponent(value);
68
+ }
69
+ /** A repo file path with each segment encoded, so `?` and `#` stay part of the name and `..` cannot climb out. */
70
+ function filePath(path) {
71
+ return path.split("/").map(segment).join("/");
72
+ }
73
+ /** UTF-8 text as base64 without `Buffer`, so it runs in Workers and browsers as well as Node. */
74
+ function toBase64(text) {
75
+ let binary = "";
76
+ for (const byte of new TextEncoder().encode(text)) binary += String.fromCharCode(byte);
77
+ return btoa(binary);
78
+ }
61
79
  //#endregion
62
80
  //#region ../../../../../src/branches/branches.ts
63
81
  function branches(rest) {
@@ -119,6 +137,19 @@ function git(rest) {
119
137
  * (D6-amended, `tech-sentinel-ci-runner.spec.md`), just never a fork's.
120
138
  */
121
139
  getContents: (repo, path, ref) => rest.request(`${repoBase(repo)}/contents/${path}`, ref ? { query: { ref } } : void 0),
140
+ /**
141
+ * `PUT /repos/:repo/contents/:path` — creates or updates one file in a single commit. Pass `sha` when
142
+ * updating so a concurrent change is refused (409) and not silently overwritten.
143
+ */
144
+ putContents: (repo, path, input) => rest.request(`${repoBase(repo)}/contents/${filePath(path)}`, {
145
+ method: "PUT",
146
+ body: {
147
+ message: input.message,
148
+ content: toBase64(input.content),
149
+ ...input.sha ? { sha: input.sha } : {},
150
+ ...input.branch ? { branch: input.branch } : {}
151
+ }
152
+ }),
122
153
  createBlob: (repo, content, encoding = "utf-8") => rest.request(`${repoBase(repo)}/git/blobs`, {
123
154
  method: "POST",
124
155
  body: {
@@ -456,11 +487,35 @@ function security(rest) {
456
487
  }
457
488
  //#endregion
458
489
  //#region ../../../../../src/teams/teams.ts
490
+ const PAGE = 100;
491
+ /** A guard so a runaway listing cannot loop forever: 50 pages of 100 is far more than any team. */
492
+ const MAX_PAGES = 50;
459
493
  function teams(rest) {
460
- return { addRepo: (org, teamSlug, owner, repo, permission) => rest.request(`/orgs/${org}/teams/${teamSlug}/repos/${owner}/${repo}`, {
461
- method: "PUT",
462
- body: { permission }
463
- }) };
494
+ return {
495
+ /**
496
+ * Every member of a team (maintainers included), read page by page. Members of child teams are
497
+ * included, as GitHub does. A 404 means the team does not exist or the token cannot see it.
498
+ */
499
+ members: async (org, teamSlug) => {
500
+ const path = `/orgs/${segment(org)}/teams/${segment(teamSlug)}/members`;
501
+ const read = (page) => rest.request(path, { query: {
502
+ per_page: String(PAGE),
503
+ page: String(page)
504
+ } });
505
+ const all = [];
506
+ for (let page = 1; page <= MAX_PAGES; page++) {
507
+ const batch = await read(page);
508
+ all.push(...batch);
509
+ if (batch.length < PAGE) return all;
510
+ }
511
+ if ((await read(51)).length > 0) throw new Error(`team ${org}/${teamSlug} has more than 5000 members; refusing to read them all`);
512
+ return all;
513
+ },
514
+ addRepo: (org, teamSlug, owner, repo, permission) => rest.request(`/orgs/${org}/teams/${teamSlug}/repos/${owner}/${repo}`, {
515
+ method: "PUT",
516
+ body: { permission }
517
+ })
518
+ };
464
519
  }
465
520
  //#endregion
466
521
  //#region ../../../../../src/topics/topics.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theholocron/github-client",
3
- "version": "1.46.0",
3
+ "version": "1.47.0",
4
4
  "description": "A TypeScript client for the GitHub REST API",
5
5
  "keywords": [
6
6
  "github",
@@ -33,7 +33,7 @@
33
33
  ],
34
34
  "dependencies": {
35
35
  "@theholocron/observability": "^0.3.0",
36
- "@theholocron/http-client": "^1.46.0"
36
+ "@theholocron/http-client": "^1.47.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@theholocron/cli": "5.0.0-alpha.79",