@effected/github 0.25.0 → 0.26.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/DeploymentEnvironment.js +7 -2
- package/GitHubApp.js +10 -1
- package/GitHubClient.js +68 -19
- package/GitHubError.js +13 -2
- package/GitHubRelease.js +23 -6
- package/GitHubRepository.js +74 -22
- package/GraphQL.js +7 -1
- package/index.d.ts +249 -27
- package/internal/confinement.js +62 -0
- package/internal/octokit.js +95 -5
- package/internal/requestOptions.js +84 -0
- package/internal/scrub.js +113 -0
- package/internal/uploads.js +70 -0
- package/package.json +6 -5
- package/tsdoc-metadata.json +1 -1
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// src/internal/requestOptions.ts
|
|
2
|
+
/**
|
|
3
|
+
* The options `@octokit/endpoint`'s `parse` reads off the flat parameter
|
|
4
|
+
* object, plus `data`, which replaces the body.
|
|
5
|
+
*
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
const OCTOKIT_REQUEST_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
9
|
+
"method",
|
|
10
|
+
"baseUrl",
|
|
11
|
+
"url",
|
|
12
|
+
"headers",
|
|
13
|
+
"request",
|
|
14
|
+
"mediaType",
|
|
15
|
+
"data"
|
|
16
|
+
]);
|
|
17
|
+
/**
|
|
18
|
+
* The template parameters of `PATCH /repos/{owner}/{repo}`, which octokit
|
|
19
|
+
* consumes as the path and never sends.
|
|
20
|
+
*
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
const REPOSITORY_PATCH_ROUTE_PARAMS = /* @__PURE__ */ new Set(["owner", "repo"]);
|
|
24
|
+
/**
|
|
25
|
+
* The template parameters of
|
|
26
|
+
* `PUT /repos/{owner}/{repo}/environments/{environment_name}`, which octokit
|
|
27
|
+
* consumes as the path and never sends.
|
|
28
|
+
*
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
const ENVIRONMENT_PUT_ROUTE_PARAMS = /* @__PURE__ */ new Set([
|
|
32
|
+
"owner",
|
|
33
|
+
"repo",
|
|
34
|
+
"environment_name"
|
|
35
|
+
]);
|
|
36
|
+
/**
|
|
37
|
+
* Why a key of an open body map may not be sent on a route whose template
|
|
38
|
+
* names `routeParams`, or `undefined` when it may.
|
|
39
|
+
*
|
|
40
|
+
* @remarks
|
|
41
|
+
* `noun` names what the map holds, with its article ("a repository setting",
|
|
42
|
+
* "an environment setting"), in the reason. `__proto__` is refused for a different reason:
|
|
43
|
+
* assigning it to a body object sets the object's prototype instead of a
|
|
44
|
+
* field, so the key would vanish from the wire while being reported sent.
|
|
45
|
+
*
|
|
46
|
+
* @internal
|
|
47
|
+
*/
|
|
48
|
+
const reservedBodyKey = (key, routeParams, noun) => OCTOKIT_REQUEST_OPTION_KEYS.has(key) ? `is an octokit request option, not ${noun}` : routeParams.has(key) ? `is the route's own path parameter, not ${noun}` : key === "__proto__" ? "would set the body's prototype instead of a field" : void 0;
|
|
49
|
+
/**
|
|
50
|
+
* Why a repository-settings key may not be sent, or `undefined` when it may.
|
|
51
|
+
*
|
|
52
|
+
* @internal
|
|
53
|
+
*/
|
|
54
|
+
const reservedSettingKey = (key) => reservedBodyKey(key, REPOSITORY_PATCH_ROUTE_PARAMS, "a repository setting");
|
|
55
|
+
/**
|
|
56
|
+
* The request options the raw surface (`request`, `requestDecoded`,
|
|
57
|
+
* `paginate`, `paginateStream`) lets a caller pass: the two typed
|
|
58
|
+
* `Rest.RequestExtras`, and `data`, which replaces the body but cannot move
|
|
59
|
+
* the request.
|
|
60
|
+
*
|
|
61
|
+
* @internal
|
|
62
|
+
*/
|
|
63
|
+
const RAW_PASSTHROUGH_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
64
|
+
"headers",
|
|
65
|
+
"mediaType",
|
|
66
|
+
"data"
|
|
67
|
+
]);
|
|
68
|
+
/**
|
|
69
|
+
* Why a parameter of a raw-surface call may not be sent, or `undefined` when
|
|
70
|
+
* it may.
|
|
71
|
+
*
|
|
72
|
+
* @remarks
|
|
73
|
+
* The rest of {@link OCTOKIT_REQUEST_OPTION_KEYS}: `baseUrl` and `url` pick
|
|
74
|
+
* the host the token goes to, `request` swaps the fetch that receives it, and
|
|
75
|
+
* `method` overrides the route's verb. None is in the raw surface's type, so
|
|
76
|
+
* one arriving here came through a cast or an untyped object. A route whose
|
|
77
|
+
* own body has a field called `url` (a webhook's configuration) sends it
|
|
78
|
+
* inside `data`, where octokit never reads it as an option.
|
|
79
|
+
*
|
|
80
|
+
* @internal
|
|
81
|
+
*/
|
|
82
|
+
const reservedRawParam = (key) => OCTOKIT_REQUEST_OPTION_KEYS.has(key) && !RAW_PASSTHROUGH_OPTION_KEYS.has(key) ? key === "url" ? "is read by octokit as the request URL; send a body field called url inside data" : "is an octokit request option the raw surface does not accept" : void 0;
|
|
83
|
+
|
|
84
|
+
export { ENVIRONMENT_PUT_ROUTE_PARAMS, OCTOKIT_REQUEST_OPTION_KEYS, RAW_PASSTHROUGH_OPTION_KEYS, REPOSITORY_PATCH_ROUTE_PARAMS, reservedBodyKey, reservedRawParam, reservedSettingKey };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { HttpRedaction } from "@effected/crypto";
|
|
2
|
+
|
|
3
|
+
// src/internal/scrub.ts
|
|
4
|
+
const REDACTED = "REDACTED";
|
|
5
|
+
const MAX_DEPTH = 8;
|
|
6
|
+
const asRecord = (value) => typeof value === "object" && value !== null ? value : void 0;
|
|
7
|
+
const define = (target, key, value) => {
|
|
8
|
+
Object.defineProperty(target, key, {
|
|
9
|
+
value,
|
|
10
|
+
enumerable: true,
|
|
11
|
+
writable: true,
|
|
12
|
+
configurable: true
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
/** Strings through `HttpRedaction.text`, numbers and booleans as they are, everything else dropped. */
|
|
16
|
+
const primitive = (value) => typeof value === "string" ? HttpRedaction.text(value) : typeof value === "number" || typeof value === "boolean" ? value : void 0;
|
|
17
|
+
/** A header record minus cookies, every sensitive value `REDACTED`. */
|
|
18
|
+
const headers = (input) => {
|
|
19
|
+
const record = asRecord(input);
|
|
20
|
+
if (record === void 0) return void 0;
|
|
21
|
+
const out = {};
|
|
22
|
+
for (const [name, value] of Object.entries(record)) {
|
|
23
|
+
const lower = name.toLowerCase();
|
|
24
|
+
if (lower === "set-cookie" || lower === "cookie") continue;
|
|
25
|
+
const kept = primitive(value);
|
|
26
|
+
if (kept === void 0) continue;
|
|
27
|
+
out[name] = HttpRedaction.isSensitiveHeader(lower) ? REDACTED : kept;
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
};
|
|
31
|
+
/** The top-level primitive fields of a record (`message`, `documentation_url`, `status`); nested values dropped. */
|
|
32
|
+
const primitives = (input) => {
|
|
33
|
+
const record = asRecord(input);
|
|
34
|
+
if (record === void 0) return void 0;
|
|
35
|
+
const out = {};
|
|
36
|
+
for (const [key, value] of Object.entries(record)) {
|
|
37
|
+
const kept = primitive(value);
|
|
38
|
+
if (kept !== void 0) out[key] = kept;
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
};
|
|
42
|
+
const optional = (key, value) => value === void 0 ? {} : { [key]: value };
|
|
43
|
+
const copy = (value, depth) => {
|
|
44
|
+
if (typeof value === "string") return HttpRedaction.text(value);
|
|
45
|
+
if (value === null || typeof value !== "object") return value;
|
|
46
|
+
if (depth >= MAX_DEPTH) return void 0;
|
|
47
|
+
const source = value;
|
|
48
|
+
const out = value instanceof Error ? Object.create(Object.getPrototypeOf(value)) : new Error(HttpRedaction.text(typeof source.message === "string" ? source.message : Object.prototype.toString.call(value)));
|
|
49
|
+
if (value instanceof Error) {
|
|
50
|
+
Object.defineProperty(out, "message", {
|
|
51
|
+
value: HttpRedaction.text(value.message),
|
|
52
|
+
writable: true,
|
|
53
|
+
configurable: true
|
|
54
|
+
});
|
|
55
|
+
if (value.stack !== void 0) Object.defineProperty(out, "stack", {
|
|
56
|
+
value: HttpRedaction.text(value.stack),
|
|
57
|
+
writable: true,
|
|
58
|
+
configurable: true
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
for (const [key, field] of Object.entries(source)) {
|
|
62
|
+
if (key === "message" || key === "stack" || key === "cause") continue;
|
|
63
|
+
const kept = primitive(field);
|
|
64
|
+
if (kept !== void 0) define(out, key, kept);
|
|
65
|
+
}
|
|
66
|
+
const request = asRecord(source.request);
|
|
67
|
+
if (request !== void 0) define(out, "request", {
|
|
68
|
+
...optional("method", typeof request.method === "string" ? request.method : void 0),
|
|
69
|
+
...optional("url", typeof request.url === "string" ? HttpRedaction.url(request.url) : void 0),
|
|
70
|
+
...optional("headers", headers(request.headers))
|
|
71
|
+
});
|
|
72
|
+
const response = asRecord(source.response);
|
|
73
|
+
if (response !== void 0) define(out, "response", {
|
|
74
|
+
...optional("url", typeof response.url === "string" ? HttpRedaction.url(response.url) : void 0),
|
|
75
|
+
...optional("status", typeof response.status === "number" ? response.status : void 0),
|
|
76
|
+
...optional("headers", headers(response.headers)),
|
|
77
|
+
...optional("data", primitives(response.data))
|
|
78
|
+
});
|
|
79
|
+
if (source.headers !== void 0) {
|
|
80
|
+
const kept = headers(source.headers);
|
|
81
|
+
if (kept !== void 0) define(out, "headers", kept);
|
|
82
|
+
}
|
|
83
|
+
if (source.cause !== void 0) {
|
|
84
|
+
const nested = copy(source.cause, depth + 1);
|
|
85
|
+
if (nested !== void 0) define(out, "cause", nested);
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Sanitize what octokit threw before keeping it as a cause.
|
|
91
|
+
*
|
|
92
|
+
* @remarks
|
|
93
|
+
* A value carrying a `request`, `response` or `headers` field — octokit's
|
|
94
|
+
* `RequestError`, a `GraphqlResponseError`, the transport's own GraphQL throw
|
|
95
|
+
* — becomes a copy on the same prototype that keeps `name`, `message`,
|
|
96
|
+
* `status` and the other primitive fields, the request's method, redacted url
|
|
97
|
+
* and headers, and the response's redacted url, status, headers minus
|
|
98
|
+
* `set-cookie` and the top-level primitive fields of its body (`message`,
|
|
99
|
+
* `documentation_url`). Every sensitive header value reads `REDACTED`. The
|
|
100
|
+
* request body, the per-request options, the GraphQL variables and partial
|
|
101
|
+
* `data`, and the nested `errors` arrays are dropped: the facts a caller
|
|
102
|
+
* routes on (`validation`, `errors`) were already read off the original.
|
|
103
|
+
*
|
|
104
|
+
* Anything else — an `AbortError`, a kit error, a plain object a test throws
|
|
105
|
+
* — is returned unchanged, by identity.
|
|
106
|
+
*/
|
|
107
|
+
const scrubThrowable = (error) => {
|
|
108
|
+
const record = asRecord(error);
|
|
109
|
+
if (record === void 0) return error;
|
|
110
|
+
return record.request !== void 0 || record.response !== void 0 || record.headers !== void 0 ? copy(error, 0) : error;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export { scrubThrowable };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// src/internal/uploads.ts
|
|
2
|
+
/** The upload host on github.com. @internal */
|
|
3
|
+
const DOTCOM_UPLOADS_BASE_URL = "https://uploads.github.com";
|
|
4
|
+
/** The REST root on github.com, and the base of a client configured with none. @internal */
|
|
5
|
+
const DOTCOM_API_BASE_URL = "https://api.github.com";
|
|
6
|
+
const GHES_REST_SUFFIX = "/api/v3";
|
|
7
|
+
/**
|
|
8
|
+
* The upload channel, carried on the client shape under a key only this
|
|
9
|
+
* package can name.
|
|
10
|
+
*
|
|
11
|
+
* @remarks
|
|
12
|
+
* A symbol rather than a `WeakMap` keyed on the shape: a property survives a
|
|
13
|
+
* consumer's `{ ...client, request: wrapped }` spread, a map entry does not.
|
|
14
|
+
* It is not on `GitHubClientShape`, so the public surface does not grow, and a
|
|
15
|
+
* module-private `Symbol` (not `Symbol.for`) cannot be forged from outside.
|
|
16
|
+
*/
|
|
17
|
+
const UPLOAD_CHANNEL = Symbol("@effected/github/uploadChannel");
|
|
18
|
+
/**
|
|
19
|
+
* Mark a client shape built by this package with its upload channel.
|
|
20
|
+
*
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
const withUploadChannel = (shape, channel) => Object.assign(shape, { [UPLOAD_CHANNEL]: channel });
|
|
24
|
+
/**
|
|
25
|
+
* The upload channel of a client this package built, or `undefined` for a
|
|
26
|
+
* shape built elsewhere (a hand-written double, `GitHubClient.makeTest`, a
|
|
27
|
+
* fixture). A channel whose `baseUrl` is `undefined` means "built here, for
|
|
28
|
+
* github.com", which is not the same as "not built here".
|
|
29
|
+
*
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
const uploadChannelOf = (client) => client[UPLOAD_CHANNEL];
|
|
33
|
+
/** `url` with every trailing slash removed — a loop, not a regex. */
|
|
34
|
+
const trimTrailingSlashes = (url) => {
|
|
35
|
+
let end = url.length;
|
|
36
|
+
while (end > 0 && url.charCodeAt(end - 1) === 47) end--;
|
|
37
|
+
return url.slice(0, end);
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* The release-asset upload base for a configured REST base URL.
|
|
41
|
+
*
|
|
42
|
+
* @remarks
|
|
43
|
+
* - absent, or `https://api.github.com` → `https://uploads.github.com`;
|
|
44
|
+
* - a GHES root `https://<host>/api/v3` → `https://<host>/api/uploads`;
|
|
45
|
+
* - anything else (a proxy, a test server) → the configured base itself, so
|
|
46
|
+
* the token still only goes to the host the caller configured.
|
|
47
|
+
*
|
|
48
|
+
* @internal
|
|
49
|
+
*/
|
|
50
|
+
const uploadsBaseUrlFor = (baseUrl) => {
|
|
51
|
+
if (baseUrl === void 0) return DOTCOM_UPLOADS_BASE_URL;
|
|
52
|
+
const trimmed = trimTrailingSlashes(baseUrl);
|
|
53
|
+
if (trimmed === "https://api.github.com") return DOTCOM_UPLOADS_BASE_URL;
|
|
54
|
+
if (trimmed.endsWith(GHES_REST_SUFFIX)) return `${trimmed.slice(0, -7)}/api/uploads`;
|
|
55
|
+
return trimmed;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* The origin of `url`, or `undefined` when it does not parse as an absolute URL.
|
|
59
|
+
*
|
|
60
|
+
* @internal
|
|
61
|
+
*/
|
|
62
|
+
const originOf = (url) => {
|
|
63
|
+
try {
|
|
64
|
+
return new URL(url).origin;
|
|
65
|
+
} catch {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export { DOTCOM_API_BASE_URL, DOTCOM_UPLOADS_BASE_URL, originOf, uploadChannelOf, uploadsBaseUrlFor, withUploadChannel };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effected/github",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Typed GitHub REST and GraphQL services over the octokit core request surface, with app auth and resource helpers.",
|
|
6
6
|
"keywords": [
|
|
@@ -38,10 +38,11 @@
|
|
|
38
38
|
"./package.json": "./package.json"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@effected/
|
|
42
|
-
"@effected/
|
|
43
|
-
"@effected/
|
|
44
|
-
"@effected/
|
|
41
|
+
"@effected/crypto": "^0.3.0",
|
|
42
|
+
"@effected/github-references": "^0.12.0",
|
|
43
|
+
"@effected/jwt": "^0.7.0",
|
|
44
|
+
"@effected/oidc": "^0.2.0",
|
|
45
|
+
"@effected/semver": "^0.19.0",
|
|
45
46
|
"@octokit/core": "^7.0.6",
|
|
46
47
|
"@octokit/plugin-paginate-rest": "^16.0.0",
|
|
47
48
|
"@octokit/types": "^18.0.0",
|