github-repository-provider 7.23.46 → 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 +2 -2
- package/src/github-branch.mjs +2 -3
- package/src/github-branch.mjs.x +194 -0
- package/src/github-provider.mjs +41 -13
- package/src/github-provider.mjs.x +199 -0
- package/src/github-repository.mjs.x +235 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "github-repository-provider",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.24.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"lint:docs": "documentation lint ./src/**/*.mjs"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"content-entry": "^3.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",
|
package/src/github-branch.mjs
CHANGED
|
@@ -163,9 +163,8 @@ export class GithubBranch extends Branch {
|
|
|
163
163
|
}
|
|
164
164
|
} catch (e) {
|
|
165
165
|
// errno: 'ERR_STREAM_PREMATURE_CLOSE',
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
console.error(e);
|
|
166
|
+
// code: 'ERR_STREAM_PREMATURE_CLOSE',
|
|
167
|
+
|
|
169
168
|
this.error(e);
|
|
170
169
|
}
|
|
171
170
|
}
|
|
@@ -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
|
+
}
|
package/src/github-provider.mjs
CHANGED
|
@@ -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(
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
158
|
+
try {
|
|
159
|
+
const json = await response.json();
|
|
138
160
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
161
|
+
if (json.length === 0 || !Array.isArray(json)) {
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
142
164
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
+
);
|