@horizon36596/zenith-github 0.1.1
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/LICENSE +21 -0
- package/NOTICE +18 -0
- package/README.md +108 -0
- package/dist/allowlist.d.ts +14 -0
- package/dist/allowlist.d.ts.map +1 -0
- package/dist/allowlist.js +42 -0
- package/dist/allowlist.js.map +1 -0
- package/dist/auth.d.ts +69 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +118 -0
- package/dist/auth.js.map +1 -0
- package/dist/base64.d.ts +10 -0
- package/dist/base64.d.ts.map +1 -0
- package/dist/base64.js +20 -0
- package/dist/base64.js.map +1 -0
- package/dist/client.d.ts +95 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +265 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +60 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +90 -0
- package/dist/errors.js.map +1 -0
- package/dist/http.d.ts +50 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +150 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/project.d.ts +54 -0
- package/dist/project.d.ts.map +1 -0
- package/dist/project.js +109 -0
- package/dist/project.js.map +1 -0
- package/dist/propose.d.ts +38 -0
- package/dist/propose.d.ts.map +1 -0
- package/dist/propose.js +52 -0
- package/dist/propose.js.map +1 -0
- package/dist/review.d.ts +29 -0
- package/dist/review.d.ts.map +1 -0
- package/dist/review.js +73 -0
- package/dist/review.js.map +1 -0
- package/dist/types.d.ts +65 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +58 -0
package/dist/client.js
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { assertPathAllowed } from "./allowlist.js";
|
|
2
|
+
import { decodeBase64Utf8, encodeBase64Utf8 } from "./base64.js";
|
|
3
|
+
import { BaseBranchWriteError, MergeConflictError, NotFoundError } from "./errors.js";
|
|
4
|
+
import { HttpClient } from "./http.js";
|
|
5
|
+
function toRepo(raw) {
|
|
6
|
+
return {
|
|
7
|
+
owner: raw.owner.login,
|
|
8
|
+
repo: raw.name,
|
|
9
|
+
fullName: raw.full_name,
|
|
10
|
+
defaultBranch: raw.default_branch,
|
|
11
|
+
private: raw.private,
|
|
12
|
+
htmlUrl: raw.html_url,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function toBranch(raw) {
|
|
16
|
+
return { name: raw.name, sha: raw.commit.sha, protected: raw.protected };
|
|
17
|
+
}
|
|
18
|
+
function toPull(raw) {
|
|
19
|
+
return {
|
|
20
|
+
number: raw.number,
|
|
21
|
+
url: raw.url,
|
|
22
|
+
htmlUrl: raw.html_url,
|
|
23
|
+
state: raw.state,
|
|
24
|
+
title: raw.title,
|
|
25
|
+
body: raw.body,
|
|
26
|
+
head: { ref: raw.head.ref, sha: raw.head.sha },
|
|
27
|
+
base: { ref: raw.base.ref, sha: raw.base.sha },
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function toPullFile(raw) {
|
|
31
|
+
return { filename: raw.filename, status: raw.status, sha: raw.sha, patch: raw.patch };
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* An isomorphic (browser + Node), `fetch`-based GitHub client: no Octokit, no Node-only APIs. One
|
|
35
|
+
* instance is bound to an `AuthProvider`, not to a repository — `GitHubProject` is the layer bound
|
|
36
|
+
* to `{owner, repo, base}` (site/docs/github.md).
|
|
37
|
+
*/
|
|
38
|
+
export class GitHubClient {
|
|
39
|
+
http;
|
|
40
|
+
constructor(auth, options = {}) {
|
|
41
|
+
this.http = new HttpClient(auth, options);
|
|
42
|
+
}
|
|
43
|
+
/** The most recent `x-ratelimit-*` snapshot seen, or `null` before the first request. */
|
|
44
|
+
get rateLimit() {
|
|
45
|
+
return this.http.rateLimit;
|
|
46
|
+
}
|
|
47
|
+
repos = {
|
|
48
|
+
/** Repositories the authenticated account can push to, across personal and org affiliation. */
|
|
49
|
+
listWritable: async () => {
|
|
50
|
+
const repos = await this.paginate("/user/repos", {
|
|
51
|
+
affiliation: "owner,collaborator,organization_member",
|
|
52
|
+
per_page: 100,
|
|
53
|
+
});
|
|
54
|
+
return repos.filter((repo) => repo.permissions?.push).map(toRepo);
|
|
55
|
+
},
|
|
56
|
+
get: async (owner, repo) => {
|
|
57
|
+
const raw = await this.http.request(`/repos/${owner}/${repo}`);
|
|
58
|
+
return toRepo(raw);
|
|
59
|
+
},
|
|
60
|
+
/**
|
|
61
|
+
* Merges `head` into `base` (`POST /repos/{owner}/{repo}/merges`) — the "update the work
|
|
62
|
+
* branch from the base branch" step of propose (06 §3 step 2). On conflict, recovers the
|
|
63
|
+
* files in play by comparing both branches against their merge base; see `MergeConflictError`.
|
|
64
|
+
* `guard`, when given, refuses the call outright when `base` (the ref the merge commit lands
|
|
65
|
+
* on) is the project's protected base branch (finding 12).
|
|
66
|
+
*/
|
|
67
|
+
merge: async (owner, repo, base, head, guard) => {
|
|
68
|
+
if (guard !== undefined && base === guard.base) {
|
|
69
|
+
throw new BaseBranchWriteError(base);
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const raw = await this.http.request(`/repos/${owner}/${repo}/merges`, {
|
|
73
|
+
method: "POST",
|
|
74
|
+
body: { base, head, commit_message: `Merge ${head} into ${base}` },
|
|
75
|
+
});
|
|
76
|
+
if (raw === null)
|
|
77
|
+
return { commitSha: null, merged: false };
|
|
78
|
+
return { commitSha: raw.sha, merged: true };
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
if (error instanceof Error && error.name === "ConflictError") {
|
|
82
|
+
const files = await this.conflictingFiles(owner, repo, base, head);
|
|
83
|
+
throw new MergeConflictError(`"${head}" cannot be merged into "${base}" without a conflict in ${files.join(", ") || "an unknown file"}.`, files);
|
|
84
|
+
}
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
branches = {
|
|
90
|
+
list: async (owner, repo) => {
|
|
91
|
+
const raw = await this.paginate(`/repos/${owner}/${repo}/branches`, { per_page: 100 });
|
|
92
|
+
return raw.map(toBranch);
|
|
93
|
+
},
|
|
94
|
+
get: async (owner, repo, branch) => {
|
|
95
|
+
const raw = await this.http.request(`/repos/${owner}/${repo}/branches/${encodeURIComponent(branch)}`);
|
|
96
|
+
return toBranch(raw);
|
|
97
|
+
},
|
|
98
|
+
/** Creates `name` pointing at `from`'s current sha. Never touches an existing ref (no force-push). */
|
|
99
|
+
create: async (owner, repo, name, from) => {
|
|
100
|
+
const source = await this.branches.get(owner, repo, from.branch);
|
|
101
|
+
await this.http.request(`/repos/${owner}/${repo}/git/refs`, {
|
|
102
|
+
method: "POST",
|
|
103
|
+
body: { ref: `refs/heads/${name}`, sha: source.sha },
|
|
104
|
+
});
|
|
105
|
+
return { name, sha: source.sha, protected: false };
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
contents = {
|
|
109
|
+
get: async (owner, repo, path, ref) => {
|
|
110
|
+
const raw = await this.http.request(`/repos/${owner}/${repo}/contents/${encodePath(path)}`, {
|
|
111
|
+
query: { ref },
|
|
112
|
+
});
|
|
113
|
+
if (raw.type !== "file") {
|
|
114
|
+
throw new NotFoundError(`"${path}" at ${ref} is a ${raw.type}, not a file.`);
|
|
115
|
+
}
|
|
116
|
+
return { path: raw.path, sha: raw.sha, text: decodeBase64Utf8(raw.content) };
|
|
117
|
+
},
|
|
118
|
+
/** `guard`, when given, refuses a write to the base branch or to a path outside `link`'s
|
|
119
|
+
* allowlist before any network call (finding 12). `GitHubProject.save` always passes one; a
|
|
120
|
+
* raw `GitHubClient` caller that cares about the same protection can pass one too. */
|
|
121
|
+
put: async (owner, repo, path, text, message, branch, sha, guard) => {
|
|
122
|
+
if (guard !== undefined) {
|
|
123
|
+
if (branch === guard.base)
|
|
124
|
+
throw new BaseBranchWriteError(branch);
|
|
125
|
+
assertPathAllowed(guard.link, path);
|
|
126
|
+
}
|
|
127
|
+
const raw = await this.http.request(`/repos/${owner}/${repo}/contents/${encodePath(path)}`, {
|
|
128
|
+
method: "PUT",
|
|
129
|
+
body: {
|
|
130
|
+
message,
|
|
131
|
+
content: encodeBase64Utf8(text),
|
|
132
|
+
branch,
|
|
133
|
+
...(sha === undefined ? {} : { sha }),
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
return { sha: raw.content.sha, commitSha: raw.commit.sha };
|
|
137
|
+
},
|
|
138
|
+
list: async (owner, repo, dir, ref) => {
|
|
139
|
+
const raw = await this.http.request(`/repos/${owner}/${repo}/contents/${encodePath(dir)}`, {
|
|
140
|
+
query: { ref },
|
|
141
|
+
});
|
|
142
|
+
return raw.map((entry) => ({ name: entry.name, path: entry.path, sha: entry.sha, type: entry.type }));
|
|
143
|
+
},
|
|
144
|
+
/** Same guard as `put` (site/docs/github.md): the "never write base, only inside
|
|
145
|
+
* the allowlist" rule covers a delete exactly as much as it covers a write. */
|
|
146
|
+
deleteFile: async (owner, repo, path, message, branch, sha, guard) => {
|
|
147
|
+
if (guard !== undefined) {
|
|
148
|
+
if (branch === guard.base)
|
|
149
|
+
throw new BaseBranchWriteError(branch);
|
|
150
|
+
assertPathAllowed(guard.link, path);
|
|
151
|
+
}
|
|
152
|
+
await this.http.request(`/repos/${owner}/${repo}/contents/${encodePath(path)}`, {
|
|
153
|
+
method: "DELETE",
|
|
154
|
+
body: { message, sha, branch },
|
|
155
|
+
});
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
git = {
|
|
159
|
+
/** The commit both `base` and `head` descend from, via the compare API's `merge_base_commit`. */
|
|
160
|
+
mergeBase: async (owner, repo, base, head) => {
|
|
161
|
+
try {
|
|
162
|
+
const raw = await this.http.request(`/repos/${owner}/${repo}/compare/${encodeURIComponent(base)}...${encodeURIComponent(head)}`);
|
|
163
|
+
return raw.merge_base_commit.sha;
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
if (error instanceof NotFoundError)
|
|
167
|
+
return null;
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
pulls = {
|
|
173
|
+
create: async (owner, repo, input) => {
|
|
174
|
+
const raw = await this.http.request(`/repos/${owner}/${repo}/pulls`, {
|
|
175
|
+
method: "POST",
|
|
176
|
+
body: { title: input.title, head: input.head, base: input.base, body: input.body },
|
|
177
|
+
});
|
|
178
|
+
return toPull(raw);
|
|
179
|
+
},
|
|
180
|
+
update: async (owner, repo, number, input) => {
|
|
181
|
+
const raw = await this.http.request(`/repos/${owner}/${repo}/pulls/${number}`, {
|
|
182
|
+
method: "PATCH",
|
|
183
|
+
body: input,
|
|
184
|
+
});
|
|
185
|
+
return toPull(raw);
|
|
186
|
+
},
|
|
187
|
+
get: async (owner, repo, number) => {
|
|
188
|
+
const raw = await this.http.request(`/repos/${owner}/${repo}/pulls/${number}`);
|
|
189
|
+
return toPull(raw);
|
|
190
|
+
},
|
|
191
|
+
listForBranch: async (owner, repo, branch, state = "open") => {
|
|
192
|
+
const raw = await this.paginate(`/repos/${owner}/${repo}/pulls`, {
|
|
193
|
+
head: `${owner}:${branch}`,
|
|
194
|
+
state,
|
|
195
|
+
per_page: 100,
|
|
196
|
+
});
|
|
197
|
+
return raw.map(toPull);
|
|
198
|
+
},
|
|
199
|
+
files: async (owner, repo, number) => {
|
|
200
|
+
const raw = await this.paginate(`/repos/${owner}/${repo}/pulls/${number}/files`, {
|
|
201
|
+
per_page: 100,
|
|
202
|
+
});
|
|
203
|
+
return raw.map(toPullFile);
|
|
204
|
+
},
|
|
205
|
+
requestReviewers: async (owner, repo, number, reviewers) => {
|
|
206
|
+
if (reviewers.length === 0)
|
|
207
|
+
return;
|
|
208
|
+
await this.http.request(`/repos/${owner}/${repo}/pulls/${number}/requested_reviewers`, {
|
|
209
|
+
method: "POST",
|
|
210
|
+
body: { reviewers },
|
|
211
|
+
});
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
users = {
|
|
215
|
+
me: async () => {
|
|
216
|
+
const raw = await this.http.request("/user");
|
|
217
|
+
return { login: raw.login, id: raw.id, name: raw.name };
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
/** Follows `Link: rel="next"` headers to collect every page. */
|
|
221
|
+
async paginate(path, query) {
|
|
222
|
+
const results = [];
|
|
223
|
+
let next = path;
|
|
224
|
+
let nextQuery = query;
|
|
225
|
+
while (next) {
|
|
226
|
+
const { body, headers } = await this.http.requestRaw(next, { query: nextQuery });
|
|
227
|
+
results.push(...body);
|
|
228
|
+
next = nextLinkFrom(headers.get("link"));
|
|
229
|
+
nextQuery = undefined;
|
|
230
|
+
}
|
|
231
|
+
return results;
|
|
232
|
+
}
|
|
233
|
+
/** Files touched on both sides since the merge base: a conservative guess at the conflict set. */
|
|
234
|
+
async conflictingFiles(owner, repo, base, head) {
|
|
235
|
+
const mergeBase = await this.git.mergeBase(owner, repo, base, head);
|
|
236
|
+
if (mergeBase === null)
|
|
237
|
+
return [];
|
|
238
|
+
const [baseSide, headSide] = await Promise.all([
|
|
239
|
+
this.comparedFiles(owner, repo, mergeBase, base),
|
|
240
|
+
this.comparedFiles(owner, repo, mergeBase, head),
|
|
241
|
+
]);
|
|
242
|
+
return [...baseSide].filter((path) => headSide.has(path));
|
|
243
|
+
}
|
|
244
|
+
async comparedFiles(owner, repo, from, to) {
|
|
245
|
+
const raw = await this.http.request(`/repos/${owner}/${repo}/compare/${encodeURIComponent(from)}...${encodeURIComponent(to)}`);
|
|
246
|
+
return new Set((raw.files ?? []).map((file) => file.filename));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function encodePath(path) {
|
|
250
|
+
return path
|
|
251
|
+
.split("/")
|
|
252
|
+
.map((segment) => encodeURIComponent(segment))
|
|
253
|
+
.join("/");
|
|
254
|
+
}
|
|
255
|
+
function nextLinkFrom(header) {
|
|
256
|
+
if (!header)
|
|
257
|
+
return undefined;
|
|
258
|
+
for (const part of header.split(",")) {
|
|
259
|
+
const match = /<([^>]+)>;\s*rel="next"/.exec(part.trim());
|
|
260
|
+
if (match)
|
|
261
|
+
return match[1];
|
|
262
|
+
}
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACtF,OAAO,EAAE,UAAU,EAAoB,MAAM,WAAW,CAAC;AAoFzD,SAAS,MAAM,CAAC,GAAY;IAC1B,OAAO;QACL,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,KAAK;QACtB,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,QAAQ,EAAE,GAAG,CAAC,SAAS;QACvB,aAAa,EAAE,GAAG,CAAC,cAAc;QACjC,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,OAAO,EAAE,GAAG,CAAC,QAAQ;KACtB,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,GAAc;IAC9B,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC;AAC3E,CAAC;AAED,SAAS,MAAM,CAAC,GAAY;IAC1B,OAAO;QACL,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,OAAO,EAAE,GAAG,CAAC,QAAQ;QACrB,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,IAAI,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE;QAC9C,IAAI,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE;KAC/C,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,GAAgB;IAClC,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,MAA4B,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC;AAC9G,CAAC;AAqBD;;;;GAIG;AACH,MAAM,OAAO,YAAY;IACN,IAAI,CAAa;IAElC,YAAY,IAAkB,EAAE,UAA+B,EAAE;QAC/D,IAAI,CAAC,IAAI,GAAG,IAAI,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC5C,CAAC;IAED,yFAAyF;IACzF,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC;IAC7B,CAAC;IAEQ,KAAK,GAAG;QACf,+FAA+F;QAC/F,YAAY,EAAE,KAAK,IAA4B,EAAE;YAC/C,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAU,aAAa,EAAE;gBACxD,WAAW,EAAE,wCAAwC;gBACrD,QAAQ,EAAE,GAAG;aACd,CAAC,CAAC;YACH,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACpE,CAAC;QAED,GAAG,EAAE,KAAK,EAAE,KAAa,EAAE,IAAY,EAAiB,EAAE;YACxD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAU,UAAU,KAAK,IAAI,IAAI,EAAE,CAAC,CAAC;YACxE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC;QAED;;;;;;WAMG;QACH,KAAK,EAAE,KAAK,EAAE,KAAa,EAAE,IAAY,EAAE,IAAY,EAAE,IAAY,EAAE,KAAuB,EAAwB,EAAE;YACtH,IAAI,KAAK,KAAK,SAAS,IAAI,IAAI,KAAK,KAAK,CAAC,IAAI,EAAE,CAAC;gBAC/C,MAAM,IAAI,oBAAoB,CAAC,IAAI,CAAC,CAAC;YACvC,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAkB,UAAU,KAAK,IAAI,IAAI,SAAS,EAAE;oBACrF,MAAM,EAAE,MAAM;oBACd,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,SAAS,IAAI,SAAS,IAAI,EAAE,EAAE;iBACnE,CAAC,CAAC;gBACH,IAAI,GAAG,KAAK,IAAI;oBAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;gBAC5D,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;YAC9C,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;oBAC7D,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;oBACnE,MAAM,IAAI,kBAAkB,CAC1B,IAAI,IAAI,4BAA4B,IAAI,2BAA2B,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,iBAAiB,GAAG,EAC3G,KAAK,CACN,CAAC;gBACJ,CAAC;gBACD,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;KACF,CAAC;IAEO,QAAQ,GAAG;QAClB,IAAI,EAAE,KAAK,EAAE,KAAa,EAAE,IAAY,EAAqB,EAAE;YAC7D,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAY,UAAU,KAAK,IAAI,IAAI,WAAW,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;YAClG,OAAO,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC3B,CAAC;QAED,GAAG,EAAE,KAAK,EAAE,KAAa,EAAE,IAAY,EAAE,MAAc,EAAmB,EAAE;YAC1E,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CACjC,UAAU,KAAK,IAAI,IAAI,aAAa,kBAAkB,CAAC,MAAM,CAAC,EAAE,CACjE,CAAC;YACF,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC;QACvB,CAAC;QAED,sGAAsG;QACtG,MAAM,EAAE,KAAK,EAAE,KAAa,EAAE,IAAY,EAAE,IAAY,EAAE,IAAwB,EAAmB,EAAE;YACrG,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;YACjE,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,KAAK,IAAI,IAAI,WAAW,EAAE;gBAC1D,MAAM,EAAE,MAAM;gBACd,IAAI,EAAE,EAAE,GAAG,EAAE,cAAc,IAAI,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE;aACrD,CAAC,CAAC;YACH,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;QACrD,CAAC;KACF,CAAC;IAEO,QAAQ,GAAG;QAClB,GAAG,EAAE,KAAK,EAAE,KAAa,EAAE,IAAY,EAAE,IAAY,EAAE,GAAW,EAAwB,EAAE;YAC1F,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAiB,UAAU,KAAK,IAAI,IAAI,aAAa,UAAU,CAAC,IAAI,CAAC,EAAE,EAAE;gBAC1G,KAAK,EAAE,EAAE,GAAG,EAAE;aACf,CAAC,CAAC;YACH,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBACxB,MAAM,IAAI,aAAa,CAAC,IAAI,IAAI,QAAQ,GAAG,SAAS,GAAG,CAAC,IAAI,eAAe,CAAC,CAAC;YAC/E,CAAC;YACD,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,gBAAgB,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QAC/E,CAAC;QAED;;8FAEsF;QACtF,GAAG,EAAE,KAAK,EACR,KAAa,EACb,IAAY,EACZ,IAAY,EACZ,IAAY,EACZ,OAAe,EACf,MAAc,EACd,GAAY,EACZ,KAAyB,EACL,EAAE;YACtB,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACxB,IAAI,MAAM,KAAK,KAAK,CAAC,IAAI;oBAAE,MAAM,IAAI,oBAAoB,CAAC,MAAM,CAAC,CAAC;gBAClE,iBAAiB,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACtC,CAAC;YACD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CACjC,UAAU,KAAK,IAAI,IAAI,aAAa,UAAU,CAAC,IAAI,CAAC,EAAE,EACtD;gBACE,MAAM,EAAE,KAAK;gBACb,IAAI,EAAE;oBACJ,OAAO;oBACP,OAAO,EAAE,gBAAgB,CAAC,IAAI,CAAC;oBAC/B,MAAM;oBACN,GAAG,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC;iBACtC;aACF,CACF,CAAC;YACF,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;QAC7D,CAAC;QAED,IAAI,EAAE,KAAK,EAAE,KAAa,EAAE,IAAY,EAAE,GAAW,EAAE,GAAW,EAA2B,EAAE;YAC7F,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAoB,UAAU,KAAK,IAAI,IAAI,aAAa,UAAU,CAAC,GAAG,CAAC,EAAE,EAAE;gBAC5G,KAAK,EAAE,EAAE,GAAG,EAAE;aACf,CAAC,CAAC;YACH,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACxG,CAAC;QAED;uFAC+E;QAC/E,UAAU,EAAE,KAAK,EACf,KAAa,EACb,IAAY,EACZ,IAAY,EACZ,OAAe,EACf,MAAc,EACd,GAAW,EACX,KAAyB,EACV,EAAE;YACjB,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACxB,IAAI,MAAM,KAAK,KAAK,CAAC,IAAI;oBAAE,MAAM,IAAI,oBAAoB,CAAC,MAAM,CAAC,CAAC;gBAClE,iBAAiB,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACtC,CAAC;YACD,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,KAAK,IAAI,IAAI,aAAa,UAAU,CAAC,IAAI,CAAC,EAAE,EAAE;gBAC9E,MAAM,EAAE,QAAQ;gBAChB,IAAI,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE;aAC/B,CAAC,CAAC;QACL,CAAC;KACF,CAAC;IAEO,GAAG,GAAG;QACb,iGAAiG;QACjG,SAAS,EAAE,KAAK,EAAE,KAAa,EAAE,IAAY,EAAE,IAAY,EAAE,IAAY,EAA0B,EAAE;YACnG,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CACjC,UAAU,KAAK,IAAI,IAAI,YAAY,kBAAkB,CAAC,IAAI,CAAC,MAAM,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAC5F,CAAC;gBACF,OAAO,GAAG,CAAC,iBAAiB,CAAC,GAAG,CAAC;YACnC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,KAAK,YAAY,aAAa;oBAAE,OAAO,IAAI,CAAC;gBAChD,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;KACF,CAAC;IAEO,KAAK,GAAG;QACf,MAAM,EAAE,KAAK,EACX,KAAa,EACb,IAAY,EACZ,KAAkE,EAC5C,EAAE;YACxB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAU,UAAU,KAAK,IAAI,IAAI,QAAQ,EAAE;gBAC5E,MAAM,EAAE,MAAM;gBACd,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE;aACnF,CAAC,CAAC;YACH,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC;QAED,MAAM,EAAE,KAAK,EACX,KAAa,EACb,IAAY,EACZ,MAAc,EACd,KAAmE,EAC7C,EAAE;YACxB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAU,UAAU,KAAK,IAAI,IAAI,UAAU,MAAM,EAAE,EAAE;gBACtF,MAAM,EAAE,OAAO;gBACf,IAAI,EAAE,KAAK;aACZ,CAAC,CAAC;YACH,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC;QAED,GAAG,EAAE,KAAK,EAAE,KAAa,EAAE,IAAY,EAAE,MAAc,EAAwB,EAAE;YAC/E,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAU,UAAU,KAAK,IAAI,IAAI,UAAU,MAAM,EAAE,CAAC,CAAC;YACxF,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC;QAED,aAAa,EAAE,KAAK,EAClB,KAAa,EACb,IAAY,EACZ,MAAc,EACd,QAAmC,MAAM,EACjB,EAAE;YAC1B,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAU,UAAU,KAAK,IAAI,IAAI,QAAQ,EAAE;gBACxE,IAAI,EAAE,GAAG,KAAK,IAAI,MAAM,EAAE;gBAC1B,KAAK;gBACL,QAAQ,EAAE,GAAG;aACd,CAAC,CAAC;YACH,OAAO,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACzB,CAAC;QAED,KAAK,EAAE,KAAK,EAAE,KAAa,EAAE,IAAY,EAAE,MAAc,EAAuB,EAAE;YAChF,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAc,UAAU,KAAK,IAAI,IAAI,UAAU,MAAM,QAAQ,EAAE;gBAC5F,QAAQ,EAAE,GAAG;aACd,CAAC,CAAC;YACH,OAAO,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC7B,CAAC;QAED,gBAAgB,EAAE,KAAK,EAAE,KAAa,EAAE,IAAY,EAAE,MAAc,EAAE,SAAmB,EAAiB,EAAE;YAC1G,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO;YACnC,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,KAAK,IAAI,IAAI,UAAU,MAAM,sBAAsB,EAAE;gBACrF,MAAM,EAAE,MAAM;gBACd,IAAI,EAAE,EAAE,SAAS,EAAE;aACpB,CAAC,CAAC;QACL,CAAC;KACF,CAAC;IAEO,KAAK,GAAG;QACf,EAAE,EAAE,KAAK,IAAmB,EAAE;YAC5B,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAU,OAAO,CAAC,CAAC;YACtD,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC;QAC1D,CAAC;KACF,CAAC;IAEF,gEAAgE;IACxD,KAAK,CAAC,QAAQ,CAAI,IAAY,EAAE,KAAkD;QACxF,MAAM,OAAO,GAAQ,EAAE,CAAC;QACxB,IAAI,IAAI,GAAuB,IAAI,CAAC;QACpC,IAAI,SAAS,GAA4D,KAAK,CAAC;QAC/E,OAAO,IAAI,EAAE,CAAC;YACZ,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;YACjF,OAAO,CAAC,IAAI,CAAC,GAAI,IAAY,CAAC,CAAC;YAC/B,IAAI,GAAG,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;YACzC,SAAS,GAAG,SAAS,CAAC;QACxB,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,kGAAkG;IAC1F,KAAK,CAAC,gBAAgB,CAAC,KAAa,EAAE,IAAY,EAAE,IAAY,EAAE,IAAY;QACpF,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QACpE,IAAI,SAAS,KAAK,IAAI;YAAE,OAAO,EAAE,CAAC;QAClC,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YAC7C,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC;YAChD,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC;SACjD,CAAC,CAAC;QACH,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5D,CAAC;IAEO,KAAK,CAAC,aAAa,CAAC,KAAa,EAAE,IAAY,EAAE,IAAY,EAAE,EAAU;QAC/E,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CACjC,UAAU,KAAK,IAAI,IAAI,YAAY,kBAAkB,CAAC,IAAI,CAAC,MAAM,kBAAkB,CAAC,EAAE,CAAC,EAAE,CAC1F,CAAC;QACF,OAAO,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IACjE,CAAC;CACF;AAED,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,IAAI;SACR,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;SAC7C,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC;AAED,SAAS,YAAY,CAAC,MAAqB;IACzC,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAC9B,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,yBAAyB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAC1D,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed errors for the GitHub client (site/docs/github.md).
|
|
3
|
+
*
|
|
4
|
+
* Every network or protocol failure the client can distinguish gets its own class so callers (the
|
|
5
|
+
* CLI, the MCP server, the editor) can branch on `instanceof` instead of parsing messages.
|
|
6
|
+
*/
|
|
7
|
+
/** Base class for anything the GitHub API itself reported. Carries the HTTP status when known. */
|
|
8
|
+
export declare class GitHubError extends Error {
|
|
9
|
+
readonly status?: number | undefined;
|
|
10
|
+
readonly body?: unknown | undefined;
|
|
11
|
+
constructor(message: string, status?: number | undefined, body?: unknown | undefined);
|
|
12
|
+
}
|
|
13
|
+
/** 401, or 403 for a reason that is not a rate limit: the token is missing, bad or lacks scope. */
|
|
14
|
+
export declare class AuthError extends GitHubError {
|
|
15
|
+
constructor(message: string, status?: number, body?: unknown);
|
|
16
|
+
}
|
|
17
|
+
/** 404: the repository, branch, path or pull request does not exist, or the token cannot see it. */
|
|
18
|
+
export declare class NotFoundError extends GitHubError {
|
|
19
|
+
constructor(message: string, status?: number, body?: unknown);
|
|
20
|
+
}
|
|
21
|
+
/** 409, or 422 for a stale `sha`: someone else moved the ref or the file since we last read it. */
|
|
22
|
+
export declare class ConflictError extends GitHubError {
|
|
23
|
+
constructor(message: string, status?: number, body?: unknown);
|
|
24
|
+
}
|
|
25
|
+
/** 429, or 403 with `x-ratelimit-remaining: 0` / a secondary-rate-limit message, after retries. */
|
|
26
|
+
export declare class RateLimitError extends GitHubError {
|
|
27
|
+
readonly retryAfterMs: number | undefined;
|
|
28
|
+
constructor(message: string, retryAfterMs: number | undefined, status?: number, body?: unknown);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The work branch could not be brought up to date with the base branch because the two sides
|
|
32
|
+
* touched the same lines. `files` names the paths in conflict so the caller can tell the user
|
|
33
|
+
* exactly what to resolve (06 §3 step 2).
|
|
34
|
+
*
|
|
35
|
+
* GitHub's merge API (`POST /repos/{owner}/{repo}/merges`) reports a 409 on conflict but does not
|
|
36
|
+
* itself enumerate the files involved. `GitHubClient.repos.merge` recovers the list by comparing
|
|
37
|
+
* both branches against their merge base and intersecting the changed paths — a conservative
|
|
38
|
+
* superset of the true conflicts, not an exact diff3 result.
|
|
39
|
+
*/
|
|
40
|
+
export declare class MergeConflictError extends GitHubError {
|
|
41
|
+
readonly files: string[];
|
|
42
|
+
constructor(message: string, files: string[], status?: number, body?: unknown);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A write targeted a path outside `zenith.json`'s `autosDir`, `deploy.dir` or `codegen.dir`
|
|
46
|
+
* (site/docs/github.md). Never sent to GitHub.
|
|
47
|
+
*/
|
|
48
|
+
export declare class PathNotAllowedError extends Error {
|
|
49
|
+
readonly path: string;
|
|
50
|
+
constructor(path: string);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A write targeted the base branch. The app never writes to base (06 §7); this guard makes that a
|
|
54
|
+
* thrown error instead of a silent commit. Never sent to GitHub.
|
|
55
|
+
*/
|
|
56
|
+
export declare class BaseBranchWriteError extends Error {
|
|
57
|
+
readonly branch: string;
|
|
58
|
+
constructor(branch: string);
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,kGAAkG;AAClG,qBAAa,WAAY,SAAQ,KAAK;IAGlC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO;gBAFvB,OAAO,EAAE,MAAM,EACN,MAAM,CAAC,EAAE,MAAM,YAAA,EACf,IAAI,CAAC,EAAE,OAAO,YAAA;CAK1B;AAED,mGAAmG;AACnG,qBAAa,SAAU,SAAQ,WAAW;gBAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO;CAI7D;AAED,oGAAoG;AACpG,qBAAa,aAAc,SAAQ,WAAW;gBAChC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO;CAI7D;AAED,mGAAmG;AACnG,qBAAa,aAAc,SAAQ,WAAW;gBAChC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO;CAI7D;AAED,mGAAmG;AACnG,qBAAa,cAAe,SAAQ,WAAW;IAG3C,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS;gBADzC,OAAO,EAAE,MAAM,EACN,YAAY,EAAE,MAAM,GAAG,SAAS,EACzC,MAAM,CAAC,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,OAAO;CAKjB;AAED;;;;;;;;;GASG;AACH,qBAAa,kBAAmB,SAAQ,WAAW;IAG/C,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE;gBADxB,OAAO,EAAE,MAAM,EACN,KAAK,EAAE,MAAM,EAAE,EACxB,MAAM,CAAC,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,OAAO;CAKjB;AAED;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM;gBAAZ,IAAI,EAAE,MAAM;CAIlC;AAED;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM;CAIpC"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed errors for the GitHub client (site/docs/github.md).
|
|
3
|
+
*
|
|
4
|
+
* Every network or protocol failure the client can distinguish gets its own class so callers (the
|
|
5
|
+
* CLI, the MCP server, the editor) can branch on `instanceof` instead of parsing messages.
|
|
6
|
+
*/
|
|
7
|
+
/** Base class for anything the GitHub API itself reported. Carries the HTTP status when known. */
|
|
8
|
+
export class GitHubError extends Error {
|
|
9
|
+
status;
|
|
10
|
+
body;
|
|
11
|
+
constructor(message, status, body) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.status = status;
|
|
14
|
+
this.body = body;
|
|
15
|
+
this.name = "GitHubError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** 401, or 403 for a reason that is not a rate limit: the token is missing, bad or lacks scope. */
|
|
19
|
+
export class AuthError extends GitHubError {
|
|
20
|
+
constructor(message, status, body) {
|
|
21
|
+
super(message, status, body);
|
|
22
|
+
this.name = "AuthError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** 404: the repository, branch, path or pull request does not exist, or the token cannot see it. */
|
|
26
|
+
export class NotFoundError extends GitHubError {
|
|
27
|
+
constructor(message, status, body) {
|
|
28
|
+
super(message, status, body);
|
|
29
|
+
this.name = "NotFoundError";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** 409, or 422 for a stale `sha`: someone else moved the ref or the file since we last read it. */
|
|
33
|
+
export class ConflictError extends GitHubError {
|
|
34
|
+
constructor(message, status, body) {
|
|
35
|
+
super(message, status, body);
|
|
36
|
+
this.name = "ConflictError";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** 429, or 403 with `x-ratelimit-remaining: 0` / a secondary-rate-limit message, after retries. */
|
|
40
|
+
export class RateLimitError extends GitHubError {
|
|
41
|
+
retryAfterMs;
|
|
42
|
+
constructor(message, retryAfterMs, status, body) {
|
|
43
|
+
super(message, status, body);
|
|
44
|
+
this.retryAfterMs = retryAfterMs;
|
|
45
|
+
this.name = "RateLimitError";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The work branch could not be brought up to date with the base branch because the two sides
|
|
50
|
+
* touched the same lines. `files` names the paths in conflict so the caller can tell the user
|
|
51
|
+
* exactly what to resolve (06 §3 step 2).
|
|
52
|
+
*
|
|
53
|
+
* GitHub's merge API (`POST /repos/{owner}/{repo}/merges`) reports a 409 on conflict but does not
|
|
54
|
+
* itself enumerate the files involved. `GitHubClient.repos.merge` recovers the list by comparing
|
|
55
|
+
* both branches against their merge base and intersecting the changed paths — a conservative
|
|
56
|
+
* superset of the true conflicts, not an exact diff3 result.
|
|
57
|
+
*/
|
|
58
|
+
export class MergeConflictError extends GitHubError {
|
|
59
|
+
files;
|
|
60
|
+
constructor(message, files, status, body) {
|
|
61
|
+
super(message, status, body);
|
|
62
|
+
this.files = files;
|
|
63
|
+
this.name = "MergeConflictError";
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* A write targeted a path outside `zenith.json`'s `autosDir`, `deploy.dir` or `codegen.dir`
|
|
68
|
+
* (site/docs/github.md). Never sent to GitHub.
|
|
69
|
+
*/
|
|
70
|
+
export class PathNotAllowedError extends Error {
|
|
71
|
+
path;
|
|
72
|
+
constructor(path) {
|
|
73
|
+
super(`"${path}" is outside the allowed autos/deploy/codegen directories; refusing to write it.`);
|
|
74
|
+
this.path = path;
|
|
75
|
+
this.name = "PathNotAllowedError";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* A write targeted the base branch. The app never writes to base (06 §7); this guard makes that a
|
|
80
|
+
* thrown error instead of a silent commit. Never sent to GitHub.
|
|
81
|
+
*/
|
|
82
|
+
export class BaseBranchWriteError extends Error {
|
|
83
|
+
branch;
|
|
84
|
+
constructor(branch) {
|
|
85
|
+
super(`Refusing to write to "${branch}": it is the base branch, and Zenith never writes there.`);
|
|
86
|
+
this.branch = branch;
|
|
87
|
+
this.name = "BaseBranchWriteError";
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,kGAAkG;AAClG,MAAM,OAAO,WAAY,SAAQ,KAAK;IAGzB;IACA;IAHX,YACE,OAAe,EACN,MAAe,EACf,IAAc;QAEvB,KAAK,CAAC,OAAO,CAAC,CAAC;QAHN,WAAM,GAAN,MAAM,CAAS;QACf,SAAI,GAAJ,IAAI,CAAU;QAGvB,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF;AAED,mGAAmG;AACnG,MAAM,OAAO,SAAU,SAAQ,WAAW;IACxC,YAAY,OAAe,EAAE,MAAe,EAAE,IAAc;QAC1D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;IAC1B,CAAC;CACF;AAED,oGAAoG;AACpG,MAAM,OAAO,aAAc,SAAQ,WAAW;IAC5C,YAAY,OAAe,EAAE,MAAe,EAAE,IAAc;QAC1D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC9B,CAAC;CACF;AAED,mGAAmG;AACnG,MAAM,OAAO,aAAc,SAAQ,WAAW;IAC5C,YAAY,OAAe,EAAE,MAAe,EAAE,IAAc;QAC1D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC9B,CAAC;CACF;AAED,mGAAmG;AACnG,MAAM,OAAO,cAAe,SAAQ,WAAW;IAGlC;IAFX,YACE,OAAe,EACN,YAAgC,EACzC,MAAe,EACf,IAAc;QAEd,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;QAJpB,iBAAY,GAAZ,YAAY,CAAoB;QAKzC,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAED;;;;;;;;;GASG;AACH,MAAM,OAAO,kBAAmB,SAAQ,WAAW;IAGtC;IAFX,YACE,OAAe,EACN,KAAe,EACxB,MAAe,EACf,IAAc;QAEd,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;QAJpB,UAAK,GAAL,KAAK,CAAU;QAKxB,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IACvB;IAArB,YAAqB,IAAY;QAC/B,KAAK,CAAC,IAAI,IAAI,kFAAkF,CAAC,CAAC;QAD/E,SAAI,GAAJ,IAAI,CAAQ;QAE/B,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IACxB;IAArB,YAAqB,MAAc;QACjC,KAAK,CAAC,yBAAyB,MAAM,0DAA0D,CAAC,CAAC;QAD9E,WAAM,GAAN,MAAM,CAAQ;QAEjC,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { AuthProvider } from "./auth.js";
|
|
2
|
+
/** The last rate-limit snapshot the client saw, read off `x-ratelimit-*` response headers. */
|
|
3
|
+
export interface RateLimitSnapshot {
|
|
4
|
+
limit: number;
|
|
5
|
+
remaining: number;
|
|
6
|
+
/** Epoch milliseconds. */
|
|
7
|
+
resetAt: number;
|
|
8
|
+
}
|
|
9
|
+
export interface HttpOptions {
|
|
10
|
+
apiBase?: string;
|
|
11
|
+
fetchImpl?: typeof fetch;
|
|
12
|
+
/** Retries for 429s and secondary-rate-limit 403s before giving up with `RateLimitError`. */
|
|
13
|
+
maxRetries?: number;
|
|
14
|
+
/** Base delay for the backoff used when GitHub gives no `Retry-After` header. */
|
|
15
|
+
retryDelayMs?: number;
|
|
16
|
+
/** Injectable so tests do not actually wait. Defaults to a real `setTimeout`. */
|
|
17
|
+
sleep?: (ms: number) => Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
export interface RequestOptions {
|
|
20
|
+
method?: string;
|
|
21
|
+
body?: unknown;
|
|
22
|
+
query?: Record<string, string | number | undefined>;
|
|
23
|
+
/** Extra headers, e.g. an `Accept` override for a raw-content fetch. */
|
|
24
|
+
headers?: Record<string, string>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The one place every `GitHubClient` method funnels through: attaches the bearer token, retries
|
|
28
|
+
* 429s and secondary rate limits with backoff, and turns a non-2xx response into a typed error
|
|
29
|
+
* (site/docs/github.md). Isomorphic: `fetch`, no Octokit, no Node APIs.
|
|
30
|
+
*/
|
|
31
|
+
export declare class HttpClient {
|
|
32
|
+
readonly apiBase: string;
|
|
33
|
+
private readonly auth;
|
|
34
|
+
private readonly fetchImpl;
|
|
35
|
+
private readonly maxRetries;
|
|
36
|
+
private readonly retryDelayMs;
|
|
37
|
+
private readonly sleep;
|
|
38
|
+
private lastRateLimit;
|
|
39
|
+
constructor(auth: AuthProvider, options?: HttpOptions);
|
|
40
|
+
get rateLimit(): RateLimitSnapshot | null;
|
|
41
|
+
request<T>(path: string, options?: RequestOptions): Promise<T>;
|
|
42
|
+
/** Like `request`, but also returns the response headers (pagination, ETags, raw byte counts). */
|
|
43
|
+
requestRaw(path: string, options?: RequestOptions): Promise<{
|
|
44
|
+
status: number;
|
|
45
|
+
body: unknown;
|
|
46
|
+
headers: Headers;
|
|
47
|
+
}>;
|
|
48
|
+
private buildUrl;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAG9C,8FAA8F;AAC9F,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,0BAA0B;IAC1B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,6FAA6F;IAC7F,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC,CAAC;IACpD,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAwCD;;;;GAIG;AACH,qBAAa,UAAU;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAe;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgC;IACtD,OAAO,CAAC,aAAa,CAAkC;gBAE3C,IAAI,EAAE,YAAY,EAAE,OAAO,GAAE,WAAgB;IASzD,IAAI,SAAS,IAAI,iBAAiB,GAAG,IAAI,CAExC;IAEK,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,CAAC,CAAC;IAKxE,kGAAkG;IAC5F,UAAU,CACd,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAkD/D,OAAO,CAAC,QAAQ;CAkBjB"}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { AuthError, ConflictError, GitHubError, NotFoundError, RateLimitError } from "./errors.js";
|
|
2
|
+
const DEFAULT_API_BASE = "https://api.github.com";
|
|
3
|
+
function defaultSleep(ms) {
|
|
4
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
5
|
+
}
|
|
6
|
+
function isSecondaryRateLimitBody(body) {
|
|
7
|
+
if (typeof body !== "object" || body === null)
|
|
8
|
+
return false;
|
|
9
|
+
const message = body.message;
|
|
10
|
+
return typeof message === "string" && /secondary rate limit/i.test(message);
|
|
11
|
+
}
|
|
12
|
+
function parseRateLimit(headers) {
|
|
13
|
+
const limit = headers.get("x-ratelimit-limit");
|
|
14
|
+
const remaining = headers.get("x-ratelimit-remaining");
|
|
15
|
+
const reset = headers.get("x-ratelimit-reset");
|
|
16
|
+
if (limit === null || remaining === null || reset === null)
|
|
17
|
+
return null;
|
|
18
|
+
return {
|
|
19
|
+
limit: Number(limit),
|
|
20
|
+
remaining: Number(remaining),
|
|
21
|
+
resetAt: Number(reset) * 1000,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function retryDelayFromHeaders(headers, fallbackMs) {
|
|
25
|
+
const retryAfter = headers.get("retry-after");
|
|
26
|
+
if (retryAfter !== null) {
|
|
27
|
+
const seconds = Number(retryAfter);
|
|
28
|
+
if (Number.isFinite(seconds))
|
|
29
|
+
return Math.max(0, seconds * 1000);
|
|
30
|
+
}
|
|
31
|
+
const reset = headers.get("x-ratelimit-reset");
|
|
32
|
+
if (reset !== null) {
|
|
33
|
+
const resetAt = Number(reset) * 1000;
|
|
34
|
+
if (Number.isFinite(resetAt))
|
|
35
|
+
return Math.max(0, resetAt - Date.now());
|
|
36
|
+
}
|
|
37
|
+
return fallbackMs;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The one place every `GitHubClient` method funnels through: attaches the bearer token, retries
|
|
41
|
+
* 429s and secondary rate limits with backoff, and turns a non-2xx response into a typed error
|
|
42
|
+
* (site/docs/github.md). Isomorphic: `fetch`, no Octokit, no Node APIs.
|
|
43
|
+
*/
|
|
44
|
+
export class HttpClient {
|
|
45
|
+
apiBase;
|
|
46
|
+
auth;
|
|
47
|
+
fetchImpl;
|
|
48
|
+
maxRetries;
|
|
49
|
+
retryDelayMs;
|
|
50
|
+
sleep;
|
|
51
|
+
lastRateLimit = null;
|
|
52
|
+
constructor(auth, options = {}) {
|
|
53
|
+
this.auth = auth;
|
|
54
|
+
this.apiBase = options.apiBase ?? DEFAULT_API_BASE;
|
|
55
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
56
|
+
this.maxRetries = options.maxRetries ?? 3;
|
|
57
|
+
this.retryDelayMs = options.retryDelayMs ?? 500;
|
|
58
|
+
this.sleep = options.sleep ?? defaultSleep;
|
|
59
|
+
}
|
|
60
|
+
get rateLimit() {
|
|
61
|
+
return this.lastRateLimit;
|
|
62
|
+
}
|
|
63
|
+
async request(path, options = {}) {
|
|
64
|
+
const { body } = await this.requestRaw(path, options);
|
|
65
|
+
return body;
|
|
66
|
+
}
|
|
67
|
+
/** Like `request`, but also returns the response headers (pagination, ETags, raw byte counts). */
|
|
68
|
+
async requestRaw(path, options = {}) {
|
|
69
|
+
const token = await this.auth.token();
|
|
70
|
+
const url = this.buildUrl(path, options.query);
|
|
71
|
+
let attempt = 0;
|
|
72
|
+
for (;;) {
|
|
73
|
+
const response = await this.fetchImpl(url, {
|
|
74
|
+
method: options.method ?? "GET",
|
|
75
|
+
headers: {
|
|
76
|
+
Authorization: `Bearer ${token}`,
|
|
77
|
+
Accept: "application/vnd.github+json",
|
|
78
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
79
|
+
...(options.body !== undefined ? { "Content-Type": "application/json" } : {}),
|
|
80
|
+
...options.headers,
|
|
81
|
+
},
|
|
82
|
+
body: options.body === undefined ? undefined : JSON.stringify(options.body),
|
|
83
|
+
});
|
|
84
|
+
const rateLimit = parseRateLimit(response.headers);
|
|
85
|
+
if (rateLimit)
|
|
86
|
+
this.lastRateLimit = rateLimit;
|
|
87
|
+
const parsedBody = await parseBody(response);
|
|
88
|
+
const isRateLimited = response.status === 429 ||
|
|
89
|
+
(response.status === 403 && (rateLimit?.remaining === 0 || isSecondaryRateLimitBody(parsedBody)));
|
|
90
|
+
if (isRateLimited) {
|
|
91
|
+
if (attempt >= this.maxRetries) {
|
|
92
|
+
throw new RateLimitError("GitHub's rate limit was hit and the retry budget ran out.", retryDelayFromHeaders(response.headers, this.retryDelayMs), response.status, parsedBody);
|
|
93
|
+
}
|
|
94
|
+
const delay = retryDelayFromHeaders(response.headers, this.retryDelayMs * 2 ** attempt);
|
|
95
|
+
await this.sleep(delay);
|
|
96
|
+
attempt += 1;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (response.ok) {
|
|
100
|
+
return { status: response.status, body: parsedBody, headers: response.headers };
|
|
101
|
+
}
|
|
102
|
+
throw errorFor(response.status, path, parsedBody);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
buildUrl(path, query) {
|
|
106
|
+
const url = new URL(path.startsWith("http") ? path : `${this.apiBase}${path}`);
|
|
107
|
+
// `path` can be an absolute URL taken straight from a response's `Link: rel="next"` header
|
|
108
|
+
// (`GitHubClient`'s `paginate`); the bearer token is attached to every request this method
|
|
109
|
+
// builds a URL for, so an attacker-controlled `Link` header pointing off-host must never reach
|
|
110
|
+
// `fetch` (site/docs/github.md).
|
|
111
|
+
if (url.origin !== new URL(this.apiBase).origin) {
|
|
112
|
+
throw new GitHubError(`Refusing to follow a URL whose origin (${url.origin}) is not this client's API base (${new URL(this.apiBase).origin}).`);
|
|
113
|
+
}
|
|
114
|
+
if (query) {
|
|
115
|
+
for (const [key, value] of Object.entries(query)) {
|
|
116
|
+
if (value !== undefined)
|
|
117
|
+
url.searchParams.set(key, String(value));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return url.toString();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async function parseBody(response) {
|
|
124
|
+
const text = await response.text();
|
|
125
|
+
if (text.length === 0)
|
|
126
|
+
return null;
|
|
127
|
+
try {
|
|
128
|
+
return JSON.parse(text);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return text;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function messageOf(body, fallback) {
|
|
135
|
+
if (typeof body === "object" && body !== null && typeof body.message === "string") {
|
|
136
|
+
return body.message;
|
|
137
|
+
}
|
|
138
|
+
return fallback;
|
|
139
|
+
}
|
|
140
|
+
function errorFor(status, path, body) {
|
|
141
|
+
const message = messageOf(body, `GitHub returned ${status} for ${path}.`);
|
|
142
|
+
if (status === 401 || status === 403)
|
|
143
|
+
return new AuthError(message, status, body);
|
|
144
|
+
if (status === 404)
|
|
145
|
+
return new NotFoundError(message, status, body);
|
|
146
|
+
if (status === 409 || status === 422)
|
|
147
|
+
return new ConflictError(message, status, body);
|
|
148
|
+
return new GitHubError(message, status, body);
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=http.js.map
|