github-repository-provider 7.23.43 → 7.24.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-repository-provider",
3
- "version": "7.23.43",
3
+ "version": "7.24.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -30,19 +30,19 @@
30
30
  "lint:docs": "documentation lint ./src/**/*.mjs"
31
31
  },
32
32
  "dependencies": {
33
- "content-entry": "^3.0.0",
33
+ "content-entry": "^3.0.1",
34
34
  "fetch-link-util": "^1.0.4",
35
35
  "fetch-rate-limit-util": "^1.1.6",
36
36
  "matching-iterator": "^2.0.0",
37
37
  "node-fetch": "3.1.0",
38
38
  "one-time-execution-method": "^2.0.9",
39
- "repository-provider": "^26.0.1"
39
+ "repository-provider": "^26.0.2"
40
40
  },
41
41
  "devDependencies": {
42
42
  "ava": "^3.15.0",
43
43
  "c8": "^7.10.0",
44
44
  "documentation": "^13.2.5",
45
- "repository-provider-test-support": "^1.8.10",
45
+ "repository-provider-test-support": "^1.8.11",
46
46
  "semantic-release": "^18.0.1"
47
47
  },
48
48
  "engines": {
@@ -154,17 +154,22 @@ export class GithubBranch extends Branch {
154
154
  const url = `repos/${this.slug}/git/trees/${treeSha}?recursive=1`;
155
155
 
156
156
  let res;
157
- for (const i = 0; i < 3; i++) {
158
- res = await this.provider.fetch(url);
159
- if (res.ok) {
160
- const json = await res.json();
161
- return json.tree;
157
+ for (let i = 0; i < 3; i++) {
158
+ try {
159
+ res = await this.provider.fetch(url);
160
+ if (res.ok) {
161
+ const json = await res.json();
162
+ return json.tree;
163
+ }
164
+ } catch (e) {
165
+ // errno: 'ERR_STREAM_PREMATURE_CLOSE',
166
+ // code: 'ERR_STREAM_PREMATURE_CLOSE',
167
+
168
+ this.error(e);
162
169
  }
163
170
  }
164
171
 
165
172
  console.log(res.errno, res.code);
166
- // errno: 'ERR_STREAM_PREMATURE_CLOSE',
167
- // code: 'ERR_STREAM_PREMATURE_CLOSE',
168
173
 
169
174
  throw new Error(res.status);
170
175
  }
@@ -0,0 +1,194 @@
1
+ import { matcher } from "matching-iterator";
2
+ import { Branch } from "repository-provider";
3
+ import {
4
+ BaseCollectionEntry,
5
+ BufferContentEntry,
6
+ BufferContentEntryMixin,
7
+ ContentEntry
8
+ } from "content-entry";
9
+
10
+ /**
11
+ * Branch on GitHub.
12
+ */
13
+ export class GithubBranch extends Branch {
14
+ /**
15
+ * Writes content into the branch
16
+ * {@link https://developer.github.com/v3/git/blobs/#get-a-blob}
17
+ * @param {ConentEntry} entry
18
+ * @return {Promise<Entry>} written content with sha values set
19
+ */
20
+ async writeEntry(entry) {
21
+ const res = await this.provider.fetch(`repos/${this.slug}/git/blobs`, {
22
+ method: "POST",
23
+ body: JSON.stringify({
24
+ content: await entry.string,
25
+ encoding: "utf8"
26
+ })
27
+ });
28
+ const json = await res.json();
29
+
30
+ entry.sha = json.sha;
31
+
32
+ return entry;
33
+ }
34
+
35
+ /**
36
+ * {@link https://developer.github.com/v3/git/commits/#get-a-commit}
37
+ * @param {string} sha
38
+ */
39
+ async baseTreeSha(sha) {
40
+ const json = await this.provider.fetchJSON(
41
+ `repos/${this.slug}/git/commits/${sha}`
42
+ );
43
+ return json.tree;
44
+ }
45
+
46
+ /**
47
+ * {@link https://developer.github.com/v3/git/trees/#create-a-tree}
48
+ * {@link https://developer.github.com/v3/git/commits/#create-a-commit}
49
+ * {@link https://developer.github.com/v3/git/refs/#update-a-reference}
50
+ * @param {string} message
51
+ * @param {ContentEntry[]} entries
52
+ * @param {Object} options
53
+ */
54
+ async commit(message, entries, options = {}) {
55
+ const updates = await Promise.all(
56
+ entries.map(entry => this.writeEntry(entry))
57
+ );
58
+
59
+ /*
60
+ * 1st. commit on empty tree
61
+ * https://stackoverflow.com/questions/9765453/is-gits-semi-secret-empty-tree-object-reliable-and-why-is-there-not-a-symbolic/9766506#9766506
62
+ * empty_tree=$(git mktree </dev/null)
63
+ * sha1:4b825dc642cb6eb9a060e54bf8d69288fbee4904
64
+ * sha256:6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321
65
+ */
66
+
67
+ const shaLatestCommit = await this.refId();
68
+ const shaBaseTree = await this.baseTreeSha(shaLatestCommit);
69
+
70
+ let json = await this.provider.fetchJSON(`repos/${this.slug}/git/trees`, {
71
+ method: "POST",
72
+ body: JSON.stringify({
73
+ base_tree: shaBaseTree,
74
+ tree: updates.map(u => {
75
+ return {
76
+ path: u.name,
77
+ sha: u.sha,
78
+ type: "blob",
79
+ mode: "100" + u.mode.toString(8)
80
+ };
81
+ })
82
+ })
83
+ });
84
+
85
+ const shaNewTree = json.sha;
86
+
87
+ json = await this.provider.fetchJSON(`repos/${this.slug}/git/commits`, {
88
+ method: "POST",
89
+ body: JSON.stringify({
90
+ message,
91
+ tree: shaNewTree,
92
+ parents: [shaLatestCommit]
93
+ })
94
+ });
95
+ const sha = json.sha;
96
+
97
+ return this.provider.fetchJSON(
98
+ `repos/${this.slug}/git/refs/heads/${this.name}`,
99
+ {
100
+ method: "PATCH",
101
+ body: JSON.stringify({
102
+ ...options,
103
+ sha
104
+ })
105
+ }
106
+ );
107
+ }
108
+
109
+ /**
110
+ * {@link https://developer.github.com/v3/repos/contents/#get-repository-content}
111
+ * @param {string} name
112
+ */
113
+ async entry(name) {
114
+ const json = await this.provider.fetchJSON(
115
+ `repos/${this.slug}/contents/${name}?ref=${this.ref}`
116
+ );
117
+
118
+ return new this.entryClass(name, Buffer.from(json.content, "base64"));
119
+ }
120
+
121
+ /** @inheritdoc */
122
+ async maybeEntry(name) {
123
+ const json = await this.provider.fetchJSON(
124
+ `repos/${this.slug}/contents/${name}?ref=${this.ref}`
125
+ );
126
+ return new this.entryClass(name, Buffer.from(json.content, "base64"));
127
+ }
128
+
129
+ /**
130
+ * @see https://developer.github.com/v3/git/trees/
131
+ * @param {string } treeSha
132
+ * @return {Object[]}
133
+ */
134
+ async tree(treeSha) {
135
+ const json = await this.provider.fetchJSON(
136
+ `repos/${this.slug}/git/trees/${treeSha}?recursive=1`
137
+ );
138
+ return json.tree;
139
+ }
140
+
141
+ async *entries(patterns) {
142
+ const shaBaseTree = await this.baseTreeSha(await this.refId());
143
+
144
+ for (const entry of matcher(await this.tree(shaBaseTree), patterns, {
145
+ name: "path"
146
+ })) {
147
+ yield entry.type === "tree"
148
+ ? new BaseCollectionEntry(entry.path)
149
+ : new LazyBufferContentEntry(entry.path, this);
150
+ }
151
+ }
152
+
153
+ /**
154
+ * https://developer.github.com/v3/repos/contents/
155
+ * @param {Iterator<ContentEntry>} entries
156
+ */
157
+ async removeEntries(entries) {
158
+ for await (const entry of entries) {
159
+ await this.provider.fetch(`repos/${this.slug}/contents/${entry.name}`, {
160
+ method: "DELETE",
161
+ body: JSON.stringify({ branch: this.name, message: "", sha: "" })
162
+ });
163
+ }
164
+ }
165
+
166
+ get entryClass() {
167
+ return BufferContentEntry;
168
+ }
169
+ }
170
+
171
+ class LazyBufferContentEntry extends BufferContentEntryMixin(ContentEntry) {
172
+ constructor(name, branch) {
173
+ super(name);
174
+ this.branch = branch;
175
+ }
176
+
177
+ get buffer() {
178
+ return this.getBuffer();
179
+ }
180
+
181
+ async getBuffer() {
182
+ if (this._buffer) {
183
+ return this._buffer;
184
+ }
185
+
186
+ const branch = this.branch;
187
+ const json = await branch.provider.fetchJSON(
188
+ `repos/${branch.slug}/contents/${this.name}?ref=${branch.ref}`
189
+ );
190
+
191
+ this._buffer = Buffer.from(json.content, "base64");
192
+ return this._buffer;
193
+ }
194
+ }
@@ -42,7 +42,7 @@ export class GithubProvider extends MultiGroupProvider {
42
42
  * @return {string} default instance environment name prefix
43
43
  */
44
44
  static get instanceIdentifier() {
45
- return "GITHUB_"
45
+ return "GITHUB_";
46
46
  }
47
47
 
48
48
  static get attributes() {
@@ -116,16 +116,37 @@ export class GithubProvider extends MultiGroupProvider {
116
116
  );
117
117
  }
118
118
 
119
+ async fetchJSON(url, options) {
120
+ for (let i = 0; i < 3; i++) {
121
+ try {
122
+ const response = await this.fetch(url, options);
123
+ if (!response.ok) {
124
+ this.error(`Unable to fetch ${response.status} ${response.url}`);
125
+ throw new Error(`Unable to fetch ${response.status} ${response.url}`);
126
+ //break;
127
+ }
128
+ return await response.json();
129
+ } catch (e) {
130
+ this.error(e);
131
+ throw e;
132
+ }
133
+ }
134
+ }
135
+
119
136
  /**
120
137
  * {@link https://developer.github.com/v3/repos/#list-repositories-for-the-authenticated-user}
121
138
  */
122
139
  async initializeRepositories() {
123
140
  for (let page = 1; ; page++) {
124
- const response = await this.fetch(`user/repos?page=${page}`, {
125
- headers: {
126
- accept: "application/vnd.github.baptiste-preview+json"
141
+ const response = await this.fetch(
142
+ `user/repos?page=${page}&per_page=30`,
143
+ {
144
+ headers: {
145
+ accept: "application/vnd.github.baptiste-preview+json"
146
+ // accept: "application/vnd.github.v3+json"
147
+ }
127
148
  }
128
- });
149
+ );
129
150
 
130
151
  if (!response.ok) {
131
152
  this.error(
@@ -134,16 +155,23 @@ export class GithubProvider extends MultiGroupProvider {
134
155
  return;
135
156
  }
136
157
 
137
- const json = await response.json();
158
+ try {
159
+ const json = await response.json();
138
160
 
139
- if (json.length === 0 || !Array.isArray(json)) {
140
- break;
141
- }
161
+ if (json.length === 0 || !Array.isArray(json)) {
162
+ break;
163
+ }
142
164
 
143
- json.forEach(r => {
144
- const [groupName, repoName] = r.full_name.split(/\//);
145
- this.addRepositoryGroup(groupName, r.owner).addRepository(repoName, r);
146
- });
165
+ json.forEach(r => {
166
+ const [groupName, repoName] = r.full_name.split(/\//);
167
+ this.addRepositoryGroup(groupName, r.owner).addRepository(
168
+ repoName,
169
+ r
170
+ );
171
+ });
172
+ } catch (e) {
173
+ this.error(e);
174
+ }
147
175
  }
148
176
  }
149
177
 
@@ -0,0 +1,199 @@
1
+ import fetch from "node-fetch";
2
+ import { replaceWithOneTimeExecutionMethod } from "one-time-execution-method";
3
+ import { rateLimitHandler, defaultWaitDecide } from "fetch-rate-limit-util";
4
+
5
+ import { MultiGroupProvider } from "repository-provider";
6
+ import { GithubRepository } from "./github-repository.mjs";
7
+ import { GithubBranch } from "./github-branch.mjs";
8
+ import { GithubOwner } from "./github-owner.mjs";
9
+ import { GithubPullRequest } from "./github-pull-request.mjs";
10
+ export { GithubRepository, GithubBranch, GithubOwner, GithubPullRequest };
11
+
12
+ const domain = "github.com";
13
+
14
+ /**
15
+ * <!-- skip-example -->
16
+ * GitHub provider.
17
+ * Lookup a repository.
18
+ * known environment variables
19
+ * - GITHUB_TOKEN or GH_TOKEN api token
20
+ * @example
21
+ * import GithubProvider from 'github-repository-provider';
22
+ *
23
+ * const ghp = new GithubProvider();
24
+ * const r1 = ghp.repository('git@github.com:arlac77/github-repository-provider.git');
25
+ * const r2 = ghp.repository('git://github.com/arlac77/github-repository-provider.git');
26
+ * const r3 = ghp.repository('git+ssh://github.com/arlac77/github-repository-provider.git');
27
+ * const r4 = ghp.repository('https://github.com/arlac77/github-repository-provider.git#master');
28
+ * const r5 = ghp.repository('git+https://github.com/arlac77/github-repository-provider.git#master');
29
+ * const r6 = ghp.repository('arlac77/github-repository-provider');
30
+ * // different ways to address the same repository
31
+ */
32
+ export class GithubProvider extends MultiGroupProvider {
33
+ /**
34
+ * We are called github.
35
+ * @return {string} github
36
+ */
37
+ static get name() {
38
+ return "github";
39
+ }
40
+
41
+ /**
42
+ * @return {string} default instance environment name prefix
43
+ */
44
+ static get instanceIdentifier() {
45
+ return "GITHUB_";
46
+ }
47
+
48
+ static get attributes() {
49
+ return {
50
+ ...super.attributes,
51
+ ssh: {
52
+ type: "url",
53
+ default: `git@${domain}:`
54
+ },
55
+ url: {
56
+ type: "url",
57
+ env: ["{{instanceIdentifier}}SERVER_URL"],
58
+ set: value => (value.endsWith("/") ? value : value + "/"),
59
+ default: `https://${domain}/`
60
+ },
61
+ api: {
62
+ type: "url",
63
+ env: ["{{instanceIdentifier}}API_URL"],
64
+ set: value => value.replace(/\/$/, ""),
65
+ default: `https://api.${domain}`
66
+ },
67
+ "authentication.token": {
68
+ type: "string",
69
+ env: ["{{instanceIdentifier}}TOKEN", "GH_TOKEN"],
70
+ additionalAttributes: { "authentication.type": "token" },
71
+ private: true,
72
+ mandatory: true
73
+ },
74
+ priority: { default: 1000.0 },
75
+ reateLimitRemaining: { writable: true, default: 5000 }
76
+ };
77
+ }
78
+
79
+ get repositoryClass() {
80
+ return GithubRepository;
81
+ }
82
+
83
+ get branchClass() {
84
+ return GithubBranch;
85
+ }
86
+
87
+ get repositoryGroupClass() {
88
+ return GithubOwner;
89
+ }
90
+
91
+ fetch(url, options = {}) {
92
+ return rateLimitHandler(
93
+ () =>
94
+ fetch(new URL(url, this.api), {
95
+ ...options,
96
+ headers: {
97
+ authorization: `token ${this.authentication.token}`,
98
+ ...options.headers
99
+ }
100
+ }),
101
+ (millisecondsToWait, rateLimitRemaining, nthTry, response) => {
102
+ this.rateLimitRemaining = rateLimitRemaining;
103
+
104
+ const msecs = defaultWaitDecide(
105
+ millisecondsToWait,
106
+ rateLimitRemaining,
107
+ nthTry,
108
+ response
109
+ );
110
+
111
+ if (msecs > 0) {
112
+ this.warn(`Rate limit reached: waiting for ${msecs / 1000}s`);
113
+ }
114
+ return msecs;
115
+ }
116
+ );
117
+ }
118
+
119
+ async fetchJSON(url, options) {
120
+ for (let i = 0; i < 3; i++) {
121
+ try {
122
+ const response = await this.fetch(url, options);
123
+ if (!response.ok) {
124
+ this.error(`Unable to fetch ${response.status} ${response.url}`);
125
+ throw new Error(`Unable to fetch ${response.status} ${response.url}`);
126
+ //break;
127
+ }
128
+ return await response.json();
129
+ } catch (e) {
130
+ this.error(e);
131
+ throw e;
132
+ }
133
+ }
134
+ }
135
+
136
+ /**
137
+ * {@link https://developer.github.com/v3/repos/#list-repositories-for-the-authenticated-user}
138
+ */
139
+ async initializeRepositories() {
140
+ for (let page = 1; ; page++) {
141
+ const json = await this.fetchJSON(
142
+ `user/repos?page=${page}`,
143
+ {
144
+ headers: {
145
+ accept: "application/vnd.github.v3+json"
146
+ }
147
+ }
148
+ );
149
+
150
+ if (json.length === 0 || !Array.isArray(json)) {
151
+ break;
152
+ }
153
+
154
+ json.forEach(r => {
155
+ const [groupName, repoName] = r.full_name.split(/\//);
156
+ this.addRepositoryGroup(groupName, r.owner).addRepository(repoName, r);
157
+ });
158
+ }
159
+ }
160
+
161
+ /**
162
+ * All possible base urls.
163
+ * - github:
164
+ * - git@github.com
165
+ * - git://github.com
166
+ * - git+ssh://github.com
167
+ * - https://github.com
168
+ * - git+https://github.com
169
+ * @return {string[]} common base urls of all repositories
170
+ */
171
+ get repositoryBases() {
172
+ return super.repositoryBases.concat([
173
+ this.url,
174
+ "git+" + this.url,
175
+ `git+ssh://${domain}`,
176
+ `git://${domain}/`,
177
+ `git@${domain}:`
178
+ ]);
179
+ }
180
+
181
+ get areRepositoryNamesCaseSensitive() {
182
+ return false;
183
+ }
184
+
185
+ get areGroupNamesCaseSensitive() {
186
+ return false;
187
+ }
188
+
189
+ get pullRequestClass() {
190
+ return GithubPullRequest;
191
+ }
192
+ }
193
+
194
+ replaceWithOneTimeExecutionMethod(
195
+ GithubProvider.prototype,
196
+ "initializeRepositories"
197
+ );
198
+
199
+ export default GithubProvider;
@@ -0,0 +1,235 @@
1
+ import { replaceWithOneTimeExecutionMethod } from "one-time-execution-method";
2
+ import { Repository } from "repository-provider";
3
+ import { getHeaderLink } from "fetch-link-util";
4
+
5
+ /**
6
+ * Repository on GitHub.
7
+ */
8
+ export class GithubRepository extends Repository {
9
+ static get attributeMapping() {
10
+ return {
11
+ ...super.attributeMapping,
12
+ archived: "isArchived",
13
+ is_template: "isTemplate",
14
+ private: "isPrivate",
15
+ fork: "isFork",
16
+ default_branch: "defaultBranchName"
17
+ };
18
+ }
19
+
20
+ static get attributes() {
21
+ return {
22
+ ...super.attributes,
23
+ auto_init: { type: "boolean", writable: true },
24
+ allow_squash_merge: { type: "boolean", writable: true },
25
+ allow_merge_commit: { type: "boolean", writable: true },
26
+ allow_rebase_merge: { type: "boolean", writable: true },
27
+ allow_auto_merge: { type: "boolean", writable: true },
28
+ delete_branch_on_merge: { type: "boolean", writable: true }
29
+ };
30
+ }
31
+
32
+ get defaultBranchName() {
33
+ return "main";
34
+ }
35
+
36
+ async _initializeSlot(typeName, addMethodName) {
37
+ let next = `repos/${this.slug}/${typeName}`;
38
+
39
+ do {
40
+ const response = await this.provider.fetch(next);
41
+
42
+ if (!response.ok) {
43
+ this.error(
44
+ `Unable to fetch ${typeName} ${response.status} ${response.url}`
45
+ );
46
+ return;
47
+ }
48
+
49
+ const json = await response.json();
50
+ json.forEach(b => this[addMethodName](b.name, b));
51
+ next = getHeaderLink(response.headers);
52
+ } while (next);
53
+ }
54
+
55
+ /**
56
+ * {@link https://developer.github.com/v3/repos/branches/#list-branches}
57
+ */
58
+ async initializeBranches() {
59
+ return this._initializeSlot("branches", "addBranch");
60
+ }
61
+
62
+ /**
63
+ * {@link https://docs.github.com/en/rest/reference/repos#list-repository-tags}
64
+ */
65
+ async initializeTags() {
66
+ return this._initializeSlot("tags", "addTag");
67
+ }
68
+
69
+ /**
70
+ * @return {string[]} github https url
71
+ */
72
+ get urls() {
73
+ return [`${this.provider.url}${this.fullName}.git`];
74
+ }
75
+
76
+ /**
77
+ * Deliver the url of issue tracking system.
78
+ * @return {string}
79
+ */
80
+ get issuesURL() {
81
+ return `${this.provider.url}${this.fullName}/issues`;
82
+ }
83
+
84
+ /**
85
+ * Deliver the url of the repositories home page.
86
+ * @return {string}
87
+ */
88
+ get homePageURL() {
89
+ return `${this.provider.url}${this.fullName}#readme`;
90
+ }
91
+
92
+ /**
93
+ * {@link https://developer.github.com/v3/repos/#update-a-repository}
94
+ */
95
+ async update() {
96
+ return this.provider.fetch(`repos/${this.slug}`, {
97
+ method: "PATCH",
98
+ body: JSON.stringify(
99
+ mapAttributesInverse(
100
+ optionJSON(this, undefined, this.constructor.writableAttributes),
101
+ this.constructor.attributeMapping
102
+ )
103
+ )
104
+ });
105
+ }
106
+
107
+ /**
108
+ * {@link https://developer.github.com/v3/git/refs/}
109
+ * @param {string} ref
110
+ * @return {string} sha of the ref
111
+ */
112
+ async refId(ref) {
113
+ ref = ref.replace(/^refs\//, "");
114
+
115
+ const json = await this.provider.fetchJSON(`repos/${this.slug}/git/ref/${ref}`);
116
+
117
+ // TODO why does this happen ?
118
+ if (!json.object.sha) {
119
+ throw new Error(`No refId for '${this.name}' '${ref}'`);
120
+ }
121
+
122
+ return json.object.sha;
123
+ }
124
+
125
+ async createBranch(name, from, options) {
126
+ const branch = this._branches.get(name);
127
+ if (branch) {
128
+ return branch;
129
+ }
130
+
131
+ let sha;
132
+
133
+ if (this._branches.size === 0) {
134
+ /*
135
+ * https://stackoverflow.com/questions/9765453/is-gits-semi-secret-empty-tree-object-reliable-and-why-is-there-not-a-symbolic/9766506#9766506
136
+ * sha1:4b825dc642cb6eb9a060e54bf8d69288fbee4904
137
+ * sha256:6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321
138
+ */
139
+ sha = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
140
+ /*const res = await this.provider.fetch(`repos/${this.slug}/git/commits`, {
141
+ method: "POST",
142
+ body: JSON.stringify({
143
+ message: "Initial commit",
144
+ tree: sha
145
+ })
146
+ });
147
+ console.log(res);
148
+ */
149
+ } else {
150
+ const json = await this.provider.fetchJSON(
151
+ `repos/${this.slug}/git/ref/heads/${
152
+ from === undefined ? this.defaultBranchName : from.name
153
+ }`
154
+ );
155
+ sha = json.object.sha;
156
+ }
157
+
158
+ const res = await this.provider.fetch(`repos/${this.slug}/git/refs`, {
159
+ method: "POST",
160
+ body: JSON.stringify({
161
+ ref: `refs/heads/${name}`,
162
+ sha
163
+ })
164
+ });
165
+
166
+ if (res.ok) {
167
+ return this.addBranch(name, options);
168
+ }
169
+ }
170
+
171
+ async deleteBranch(name) {
172
+ const res = await this.provider.fetch(
173
+ `repos/${this.slug}/git/refs/heads/${name}`,
174
+ {
175
+ method: "DELETE"
176
+ }
177
+ );
178
+
179
+ if (res.ok) {
180
+ return super.deleteBranch(name);
181
+ }
182
+ }
183
+
184
+ /**
185
+ * {@link https://developer.github.com/v3/pulls/#update-a-pull-request}
186
+ *
187
+ * @param {string} name
188
+ */
189
+ async deletePullRequest(name) {
190
+ const res = await this.provider.fetch(`repos/${this.slug}/pulls/${name}`, {
191
+ method: "PATCH",
192
+ body: JSON.stringify({ state: "closed" })
193
+ });
194
+
195
+ if (res.ok) {
196
+ this._pullRequests.delete(name);
197
+ }
198
+ }
199
+
200
+ /**
201
+ * {@link https://developer.github.com/v3/repos/hooks/}
202
+ */
203
+ async initializeHooks() {
204
+ let next = `repos/${this.slug}/hooks`;
205
+
206
+ do {
207
+ const response = await this.provider.fetch(next);
208
+
209
+ for (const h of await response.json()) {
210
+ const id = h.id;
211
+ delete h.id;
212
+ new this.hookClass(this, id, new Set(h.events), {
213
+ ...h,
214
+ ...h.config
215
+ });
216
+ }
217
+ next = getHeaderLink(response.headers);
218
+ } while (next);
219
+ }
220
+ }
221
+
222
+ replaceWithOneTimeExecutionMethod(
223
+ GithubRepository.prototype,
224
+ "initializeBranches"
225
+ );
226
+
227
+ replaceWithOneTimeExecutionMethod(
228
+ GithubRepository.prototype,
229
+ "initializeHooks"
230
+ );
231
+
232
+ replaceWithOneTimeExecutionMethod(
233
+ GithubRepository.prototype,
234
+ "initializePullRequests"
235
+ );