@effected/github 0.23.1 → 0.25.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/ActionsOidc.js +17 -4
- package/ArtifactMetadata.js +4 -5
- package/Attestation.js +9 -6
- package/AuthenticatedApp.js +8 -9
- package/CheckRun.js +12 -13
- package/CodeScanning.js +5 -6
- package/DeploymentEnvironment.js +6 -7
- package/GitBranch.js +11 -12
- package/GitCommit.js +7 -8
- package/GitHubApp.js +14 -13
- package/GitHubClient.js +72 -18
- package/GitHubCommit.js +7 -8
- package/GitHubContent.js +5 -6
- package/GitHubIssue.js +16 -46
- package/GitHubRelease.js +10 -11
- package/GitHubRepository.js +63 -24
- package/GitTag.js +9 -10
- package/InstallationTokenStore.js +5 -3
- package/PullRequest.js +112 -37
- package/PullRequestComment.js +7 -8
- package/README.md +8 -4
- package/RepositorySecret.js +9 -10
- package/RepositorySecurity.js +9 -10
- package/RepositoryVariable.js +9 -10
- package/Ruleset.js +8 -9
- package/WorkflowDispatch.js +13 -11
- package/index.d.ts +231 -78
- package/internal/octokit.js +16 -8
- package/internal/repositoryRestSettingKeys.js +44 -0
- package/internal/unstubbed.js +25 -0
- package/package.json +2 -2
package/ActionsOidc.js
CHANGED
|
@@ -191,10 +191,23 @@ const testClaims = (overrides = {}) => ({
|
|
|
191
191
|
*
|
|
192
192
|
* @public
|
|
193
193
|
*/
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
194
|
+
var ActionsOidc = class {
|
|
195
|
+
constructor() {}
|
|
196
|
+
/** The Actions OIDC issuer, `https://token.actions.githubusercontent.com`. */
|
|
197
|
+
static issuer = ISSUER;
|
|
198
|
+
/** Verify a runner-issued OIDC token: signature against GitHub's JWKS, iss, aud, exp/nbf, then typed claims. */
|
|
199
|
+
static verify = verify;
|
|
200
|
+
/**
|
|
201
|
+
* A valid claims payload for tests, overridable field by field.
|
|
202
|
+
*
|
|
203
|
+
* @remarks
|
|
204
|
+
* Wire-shaped (snake_case keys, string ids) and complete except for the
|
|
205
|
+
* time claims `iat` and `exp`, which depend on the clock: a signer stamps
|
|
206
|
+
* them, as `TestIssuer.sign` from `@effected/jwt/testing` does. `iss` is
|
|
207
|
+
* {@link ActionsOidc.issuer}; `aud` is a placeholder to override with the
|
|
208
|
+
* audience under test.
|
|
209
|
+
*/
|
|
210
|
+
static testClaims = testClaims;
|
|
198
211
|
};
|
|
199
212
|
|
|
200
213
|
export { ActionsOidc, ActionsOidcClaims, ActionsOidcClaimsFromWire };
|
package/ArtifactMetadata.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { unstubbed } from "./internal/unstubbed.js";
|
|
1
2
|
import { GitHubClient } from "./GitHubClient.js";
|
|
2
3
|
import { Repo } from "./Repo.js";
|
|
3
4
|
import { Context, Effect, Layer, Schema } from "effect";
|
|
@@ -37,14 +38,12 @@ var StorageRecordInput = class extends Schema.Class("StorageRecordInput")({
|
|
|
37
38
|
var ArtifactMetadata = class ArtifactMetadata extends Context.Service()("@effected/github/ArtifactMetadata") {
|
|
38
39
|
/** The live service, built over a `GitHubClient`. */
|
|
39
40
|
static layer = Layer.effect(this, Effect.map(GitHubClient, (client) => make(client)));
|
|
40
|
-
/** An in-memory double; unstubbed
|
|
41
|
-
static makeTest = (overrides = {}) => ({ createStorageRecord: overrides.createStorageRecord ?? (() =>
|
|
41
|
+
/** An in-memory double; an unstubbed member dies when run, naming itself. */
|
|
42
|
+
static makeTest = (overrides = {}) => ({ createStorageRecord: overrides.createStorageRecord ?? (() => dies("createStorageRecord")) });
|
|
42
43
|
/** {@link ArtifactMetadata.makeTest} behind a `Layer`. */
|
|
43
44
|
static layerTest = (overrides = {}) => Layer.succeed(ArtifactMetadata, ArtifactMetadata.makeTest(overrides));
|
|
44
45
|
};
|
|
45
|
-
const
|
|
46
|
-
throw new Error(`ArtifactMetadata.makeTest: ${member}() was called but not stubbed — pass an override.`);
|
|
47
|
-
};
|
|
46
|
+
const dies = unstubbed("ArtifactMetadata.makeTest");
|
|
48
47
|
const make = (client) => ({ createStorageRecord: Effect.fn("ArtifactMetadata.createStorageRecord")(function* (input) {
|
|
49
48
|
const { owner } = yield* Repo;
|
|
50
49
|
yield* Effect.annotateCurrentSpan({
|
package/Attestation.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { unstubbed } from "./internal/unstubbed.js";
|
|
1
2
|
import { GitHubClient } from "./GitHubClient.js";
|
|
2
3
|
import { Repo } from "./Repo.js";
|
|
3
4
|
import { Context, Effect, Layer, Schema } from "effect";
|
|
@@ -11,6 +12,10 @@ import { Context, Effect, Layer, Schema } from "effect";
|
|
|
11
12
|
* with a stated sunset. Pinning the version means the response is on a contract
|
|
12
13
|
* the generated types do not describe — which is why this module is the one that
|
|
13
14
|
* uses `requestDecoded` with owned schemas rather than the route table.
|
|
15
|
+
*
|
|
16
|
+
* Sent as a per-call header, which wins over the client's own
|
|
17
|
+
* `apiVersion`, so these reads stay on this contract even when a consumer
|
|
18
|
+
* pins the client to another version.
|
|
14
19
|
*/
|
|
15
20
|
const API_VERSION = "2026-03-10";
|
|
16
21
|
/**
|
|
@@ -54,17 +59,15 @@ const ListResponse = Schema.Struct({ attestations: Schema.optionalKey(Schema.Arr
|
|
|
54
59
|
var Attestation = class Attestation extends Context.Service()("@effected/github/Attestation") {
|
|
55
60
|
/** The live service, built over a `GitHubClient`. */
|
|
56
61
|
static layer = Layer.effect(this, Effect.map(GitHubClient, (client) => make(client)));
|
|
57
|
-
/** An in-memory double; unstubbed
|
|
62
|
+
/** An in-memory double; an unstubbed member dies when run, naming itself. */
|
|
58
63
|
static makeTest = (overrides = {}) => ({
|
|
59
|
-
upload: overrides.upload ?? (() =>
|
|
60
|
-
listForSubject: overrides.listForSubject ?? (() =>
|
|
64
|
+
upload: overrides.upload ?? (() => dies("upload")),
|
|
65
|
+
listForSubject: overrides.listForSubject ?? (() => dies("listForSubject"))
|
|
61
66
|
});
|
|
62
67
|
/** {@link Attestation.makeTest} behind a `Layer`. */
|
|
63
68
|
static layerTest = (overrides = {}) => Layer.succeed(Attestation, Attestation.makeTest(overrides));
|
|
64
69
|
};
|
|
65
|
-
const
|
|
66
|
-
throw new Error(`Attestation.makeTest: ${member}() was called but not stubbed — pass an override.`);
|
|
67
|
-
};
|
|
70
|
+
const dies = unstubbed("Attestation.makeTest");
|
|
68
71
|
const make = (client) => ({
|
|
69
72
|
upload: Effect.fn("Attestation.upload")(function* (bundle) {
|
|
70
73
|
const { owner, repo } = yield* Repo;
|
package/AuthenticatedApp.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { GitHubError } from "./GitHubError.js";
|
|
2
|
+
import { unstubbed } from "./internal/unstubbed.js";
|
|
2
3
|
import { GitHubClient } from "./GitHubClient.js";
|
|
3
4
|
import { numericId } from "./internal/ids.js";
|
|
4
5
|
import { Context, Effect, Layer, Schema, Stream } from "effect";
|
|
@@ -118,20 +119,18 @@ var DeliveryAttempt = class extends Schema.Class("DeliveryAttempt")({
|
|
|
118
119
|
var AuthenticatedApp = class AuthenticatedApp extends Context.Service()("@effected/github/AuthenticatedApp") {
|
|
119
120
|
/** The live service, built over an App-JWT `GitHubClient`. */
|
|
120
121
|
static layer = Layer.effect(this, Effect.map(GitHubClient, (client) => make(client)));
|
|
121
|
-
/** An in-memory double; unstubbed
|
|
122
|
+
/** An in-memory double; an unstubbed member dies when run, naming itself. */
|
|
122
123
|
static makeTest = (overrides = {}) => ({
|
|
123
|
-
get: overrides.get ?? (() =>
|
|
124
|
-
deliveries: overrides.deliveries ?? (() =>
|
|
125
|
-
delivery: overrides.delivery ?? (() =>
|
|
126
|
-
redeliver: overrides.redeliver ?? (() =>
|
|
127
|
-
uninstall: overrides.uninstall ?? (() =>
|
|
124
|
+
get: overrides.get ?? (() => dies("get")),
|
|
125
|
+
deliveries: overrides.deliveries ?? (() => Stream.fromEffect(dies("deliveries"))),
|
|
126
|
+
delivery: overrides.delivery ?? (() => dies("delivery")),
|
|
127
|
+
redeliver: overrides.redeliver ?? (() => dies("redeliver")),
|
|
128
|
+
uninstall: overrides.uninstall ?? (() => dies("uninstall"))
|
|
128
129
|
});
|
|
129
130
|
/** {@link AuthenticatedApp.makeTest} behind a `Layer`. */
|
|
130
131
|
static layerTest = (overrides = {}) => Layer.succeed(AuthenticatedApp, AuthenticatedApp.makeTest(overrides));
|
|
131
132
|
};
|
|
132
|
-
const
|
|
133
|
-
throw new Error(`AuthenticatedApp.makeTest: ${member}() was called but not stubbed — pass an override.`);
|
|
134
|
-
};
|
|
133
|
+
const dies = unstubbed("AuthenticatedApp.makeTest");
|
|
135
134
|
const decodeDelivery = Schema.decodeUnknownEffect(DeliveryAttempt);
|
|
136
135
|
const decodeApp = Schema.decodeUnknownEffect(AppInfo);
|
|
137
136
|
/** A nullable int64 id, narrowed like every other id. */
|
package/CheckRun.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { GitHubError } from "./GitHubError.js";
|
|
2
|
+
import { unstubbed } from "./internal/unstubbed.js";
|
|
2
3
|
import { GitHubClient } from "./GitHubClient.js";
|
|
3
4
|
import { Repo } from "./Repo.js";
|
|
4
5
|
import { numericId } from "./internal/ids.js";
|
|
@@ -239,24 +240,22 @@ var CheckRunRef = class extends Schema.Class("CheckRunRef")({
|
|
|
239
240
|
var CheckRun = class CheckRun extends Context.Service()("@effected/github/CheckRun") {
|
|
240
241
|
/** The live service, built over a `GitHubClient`. */
|
|
241
242
|
static layer = Layer.effect(this, Effect.map(GitHubClient, (client) => make(client)));
|
|
242
|
-
/** An in-memory double; unstubbed
|
|
243
|
+
/** An in-memory double; an unstubbed member dies when run, naming itself. */
|
|
243
244
|
static makeTest = (overrides = {}) => ({
|
|
244
|
-
create: overrides.create ?? (() =>
|
|
245
|
-
get: overrides.get ?? (() =>
|
|
246
|
-
update: overrides.update ?? (() =>
|
|
247
|
-
updateRef: overrides.updateRef ?? (() =>
|
|
248
|
-
list: overrides.list ?? (() =>
|
|
249
|
-
findByExternalId: overrides.findByExternalId ?? (() =>
|
|
250
|
-
complete: overrides.complete ?? (() =>
|
|
251
|
-
completeRef: overrides.completeRef ?? (() =>
|
|
252
|
-
withCheckRun: overrides.withCheckRun ?? (() =>
|
|
245
|
+
create: overrides.create ?? (() => dies("create")),
|
|
246
|
+
get: overrides.get ?? (() => dies("get")),
|
|
247
|
+
update: overrides.update ?? (() => dies("update")),
|
|
248
|
+
updateRef: overrides.updateRef ?? (() => dies("updateRef")),
|
|
249
|
+
list: overrides.list ?? (() => dies("list")),
|
|
250
|
+
findByExternalId: overrides.findByExternalId ?? (() => dies("findByExternalId")),
|
|
251
|
+
complete: overrides.complete ?? (() => dies("complete")),
|
|
252
|
+
completeRef: overrides.completeRef ?? (() => dies("completeRef")),
|
|
253
|
+
withCheckRun: overrides.withCheckRun ?? (() => dies("withCheckRun"))
|
|
253
254
|
});
|
|
254
255
|
/** {@link CheckRun.makeTest} behind a `Layer`. */
|
|
255
256
|
static layerTest = (overrides = {}) => Layer.succeed(CheckRun, CheckRun.makeTest(overrides));
|
|
256
257
|
};
|
|
257
|
-
const
|
|
258
|
-
throw new Error(`CheckRun.makeTest: ${member}() was called but not stubbed — pass an override.`);
|
|
259
|
-
};
|
|
258
|
+
const dies = unstubbed("CheckRun.makeTest");
|
|
260
259
|
const wireOutput = (output) => {
|
|
261
260
|
const capped = output.truncated();
|
|
262
261
|
return {
|
package/CodeScanning.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { unstubbed } from "./internal/unstubbed.js";
|
|
1
2
|
import { GitHubClient } from "./GitHubClient.js";
|
|
2
3
|
import { Repo } from "./Repo.js";
|
|
3
4
|
import { Context, Effect, Layer } from "effect";
|
|
@@ -25,17 +26,15 @@ var CodeScanning = class CodeScanning extends Context.Service()("@effected/githu
|
|
|
25
26
|
* with a clean typecheck.
|
|
26
27
|
*/
|
|
27
28
|
static layer = Layer.effect(this, Effect.map(GitHubClient, (client) => make(client)));
|
|
28
|
-
/** An in-memory double; unstubbed
|
|
29
|
+
/** An in-memory double; an unstubbed member dies when run, naming itself. */
|
|
29
30
|
static makeTest = (overrides = {}) => ({
|
|
30
|
-
configure: overrides.configure ?? (() =>
|
|
31
|
-
languages: overrides.languages ?? (() =>
|
|
31
|
+
configure: overrides.configure ?? (() => dies("configure")),
|
|
32
|
+
languages: overrides.languages ?? (() => dies("languages"))
|
|
32
33
|
});
|
|
33
34
|
/** {@link CodeScanning.makeTest} behind a `Layer`. */
|
|
34
35
|
static layerTest = (overrides = {}) => Layer.succeed(CodeScanning, CodeScanning.makeTest(overrides));
|
|
35
36
|
};
|
|
36
|
-
const
|
|
37
|
-
throw new Error(`CodeScanning.makeTest: ${member}() was called but not stubbed — pass an override.`);
|
|
38
|
-
};
|
|
37
|
+
const dies = unstubbed("CodeScanning.makeTest");
|
|
39
38
|
const SETUP_KEYS = [
|
|
40
39
|
"state",
|
|
41
40
|
"languages",
|
package/DeploymentEnvironment.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { unstubbed } from "./internal/unstubbed.js";
|
|
1
2
|
import { GitHubClient } from "./GitHubClient.js";
|
|
2
3
|
import { Repo } from "./Repo.js";
|
|
3
4
|
import { Context, Effect, Layer } from "effect";
|
|
@@ -34,18 +35,16 @@ var DeploymentEnvironment = class DeploymentEnvironment extends Context.Service(
|
|
|
34
35
|
* further down throws at import time with a clean typecheck.
|
|
35
36
|
*/
|
|
36
37
|
static layer = Layer.effect(this, Effect.map(GitHubClient, (client) => make(client)));
|
|
37
|
-
/** An in-memory double; unstubbed
|
|
38
|
+
/** An in-memory double; an unstubbed member dies when run, naming itself. */
|
|
38
39
|
static makeTest = (overrides = {}) => ({
|
|
39
|
-
upsert: overrides.upsert ?? (() =>
|
|
40
|
-
list: overrides.list ?? (() =>
|
|
41
|
-
delete: overrides.delete ?? (() =>
|
|
40
|
+
upsert: overrides.upsert ?? (() => dies("upsert")),
|
|
41
|
+
list: overrides.list ?? (() => dies("list")),
|
|
42
|
+
delete: overrides.delete ?? (() => dies("delete"))
|
|
42
43
|
});
|
|
43
44
|
/** {@link DeploymentEnvironment.makeTest} behind a `Layer`. */
|
|
44
45
|
static layerTest = (overrides = {}) => Layer.succeed(DeploymentEnvironment, DeploymentEnvironment.makeTest(overrides));
|
|
45
46
|
};
|
|
46
|
-
const
|
|
47
|
-
throw new Error(`DeploymentEnvironment.makeTest: ${member}() was called but not stubbed — pass an override.`);
|
|
48
|
-
};
|
|
47
|
+
const dies = unstubbed("DeploymentEnvironment.makeTest");
|
|
49
48
|
const make = (client) => {
|
|
50
49
|
return {
|
|
51
50
|
upsert: Effect.fn("DeploymentEnvironment.upsert")(function* (name, config = {}) {
|
package/GitBranch.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { GitHubError } from "./GitHubError.js";
|
|
2
2
|
import { GraphQLDocument } from "./GraphQL.js";
|
|
3
|
+
import { unstubbed } from "./internal/unstubbed.js";
|
|
3
4
|
import { GitHubClient } from "./GitHubClient.js";
|
|
4
5
|
import { Repo } from "./Repo.js";
|
|
5
6
|
import { Context, Effect, Layer, Option, Schema } from "effect";
|
|
@@ -50,23 +51,21 @@ var GitBranch = class GitBranch extends Context.Service()("@effected/github/GitB
|
|
|
50
51
|
* layer is built.
|
|
51
52
|
*/
|
|
52
53
|
static layer = Layer.effect(this, Effect.map(GitHubClient, (client) => make(client)));
|
|
53
|
-
/** An in-memory double; unstubbed
|
|
54
|
+
/** An in-memory double; an unstubbed member dies when run, naming itself. */
|
|
54
55
|
static makeTest = (overrides = {}) => ({
|
|
55
|
-
create: overrides.create ?? (() =>
|
|
56
|
-
upsert: overrides.upsert ?? (() =>
|
|
57
|
-
exists: overrides.exists ?? (() =>
|
|
58
|
-
sha: overrides.sha ?? (() =>
|
|
59
|
-
shaOption: overrides.shaOption ?? (() =>
|
|
60
|
-
reset: overrides.reset ?? (() =>
|
|
61
|
-
delete: overrides.delete ?? (() =>
|
|
62
|
-
createLinked: overrides.createLinked ?? (() =>
|
|
56
|
+
create: overrides.create ?? (() => dies("create")),
|
|
57
|
+
upsert: overrides.upsert ?? (() => dies("upsert")),
|
|
58
|
+
exists: overrides.exists ?? (() => dies("exists")),
|
|
59
|
+
sha: overrides.sha ?? (() => dies("sha")),
|
|
60
|
+
shaOption: overrides.shaOption ?? (() => dies("shaOption")),
|
|
61
|
+
reset: overrides.reset ?? (() => dies("reset")),
|
|
62
|
+
delete: overrides.delete ?? (() => dies("delete")),
|
|
63
|
+
createLinked: overrides.createLinked ?? (() => dies("createLinked"))
|
|
63
64
|
});
|
|
64
65
|
/** {@link GitBranch.makeTest} behind a `Layer`. */
|
|
65
66
|
static layerTest = (overrides = {}) => Layer.succeed(GitBranch, GitBranch.makeTest(overrides));
|
|
66
67
|
};
|
|
67
|
-
const
|
|
68
|
-
throw new Error(`GitBranch.makeTest: ${member}() was called but not stubbed — pass an override.`);
|
|
69
|
-
};
|
|
68
|
+
const dies = unstubbed("GitBranch.makeTest");
|
|
70
69
|
/**
|
|
71
70
|
* `main`, `refs/heads/main` and `heads/main` all name the same branch.
|
|
72
71
|
*
|
package/GitCommit.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { unstubbed } from "./internal/unstubbed.js";
|
|
1
2
|
import { GitHubClient } from "./GitHubClient.js";
|
|
2
3
|
import { Repo } from "./Repo.js";
|
|
3
4
|
import { Context, Effect, Layer, Schema } from "effect";
|
|
@@ -86,19 +87,17 @@ var CommitRef = class extends Schema.Class("CommitRef")({
|
|
|
86
87
|
var GitCommit = class GitCommit extends Context.Service()("@effected/github/GitCommit") {
|
|
87
88
|
/** The live service, built over a `GitHubClient`. */
|
|
88
89
|
static layer = Layer.effect(this, Effect.map(GitHubClient, (client) => make(client)));
|
|
89
|
-
/** An in-memory double; unstubbed
|
|
90
|
+
/** An in-memory double; an unstubbed member dies when run, naming itself. */
|
|
90
91
|
static makeTest = (overrides = {}) => ({
|
|
91
|
-
get: overrides.get ?? (() =>
|
|
92
|
-
createTree: overrides.createTree ?? (() =>
|
|
93
|
-
createCommit: overrides.createCommit ?? (() =>
|
|
94
|
-
commitFiles: overrides.commitFiles ?? (() =>
|
|
92
|
+
get: overrides.get ?? (() => dies("get")),
|
|
93
|
+
createTree: overrides.createTree ?? (() => dies("createTree")),
|
|
94
|
+
createCommit: overrides.createCommit ?? (() => dies("createCommit")),
|
|
95
|
+
commitFiles: overrides.commitFiles ?? (() => dies("commitFiles"))
|
|
95
96
|
});
|
|
96
97
|
/** {@link GitCommit.makeTest} behind a `Layer`. */
|
|
97
98
|
static layerTest = (overrides = {}) => Layer.succeed(GitCommit, GitCommit.makeTest(overrides));
|
|
98
99
|
};
|
|
99
|
-
const
|
|
100
|
-
throw new Error(`GitCommit.makeTest: ${member}() was called but not stubbed — pass an override.`);
|
|
101
|
-
};
|
|
100
|
+
const dies = unstubbed("GitCommit.makeTest");
|
|
102
101
|
/** The Git Database tree entry for one change. */
|
|
103
102
|
const treeEntry = (change) => change._tag === "FileContent" ? {
|
|
104
103
|
path: change.path,
|
package/GitHubApp.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { GitHubError } from "./GitHubError.js";
|
|
2
2
|
import { GitHubGraphQLError } from "./GraphQL.js";
|
|
3
|
-
import {
|
|
3
|
+
import { unstubbed } from "./internal/unstubbed.js";
|
|
4
|
+
import { GitHubClient, makeClientShape, resolveApiVersion } from "./GitHubClient.js";
|
|
4
5
|
import { numericId } from "./internal/ids.js";
|
|
5
6
|
import { InstallationTokenStore } from "./InstallationTokenStore.js";
|
|
6
7
|
import { Cause, Clock, Context, DateTime, Deferred, Duration, Effect, Layer, Option, Redacted, Ref, Schema, Semaphore, Stream } from "effect";
|
|
@@ -435,16 +436,16 @@ var GitHubApp = class GitHubApp extends Context.Service()("@effected/github/GitH
|
|
|
435
436
|
...options,
|
|
436
437
|
token: cached.token.token
|
|
437
438
|
})));
|
|
438
|
-
/** An in-memory double; unstubbed
|
|
439
|
+
/** An in-memory double; an unstubbed member dies when run, naming itself. */
|
|
439
440
|
static makeTest = (overrides = {}) => {
|
|
440
|
-
const token = overrides.token ?? (() =>
|
|
441
|
+
const token = overrides.token ?? (() => dies("token"));
|
|
441
442
|
return {
|
|
442
443
|
token,
|
|
443
|
-
scopedToken: overrides.scopedToken ?? (() =>
|
|
444
|
-
revoke: overrides.revoke ?? (() =>
|
|
445
|
-
identity: overrides.identity ?? (() =>
|
|
446
|
-
botUser: overrides.botUser ?? (() =>
|
|
447
|
-
installations: overrides.installations ?? (() =>
|
|
444
|
+
scopedToken: overrides.scopedToken ?? (() => dies("scopedToken")),
|
|
445
|
+
revoke: overrides.revoke ?? (() => dies("revoke")),
|
|
446
|
+
identity: overrides.identity ?? (() => dies("identity")),
|
|
447
|
+
botUser: overrides.botUser ?? (() => dies("botUser")),
|
|
448
|
+
installations: overrides.installations ?? (() => dies("installations")),
|
|
448
449
|
cachedToken: overrides.cachedToken ?? makeCachedToken(token)
|
|
449
450
|
};
|
|
450
451
|
};
|
|
@@ -515,9 +516,7 @@ const makeCachedToken = (token) => {
|
|
|
515
516
|
};
|
|
516
517
|
});
|
|
517
518
|
};
|
|
518
|
-
const
|
|
519
|
-
throw new Error(`GitHubApp.makeTest: ${member}() was called but not stubbed — pass an override.`);
|
|
520
|
-
};
|
|
519
|
+
const dies = unstubbed("GitHubApp.makeTest");
|
|
521
520
|
/** Mint an app JWT: iat 60 s in the past (clock drift), exp 9 minutes after now (GitHub caps at 10). */
|
|
522
521
|
const mintJwt = (credentials) => Effect.gen(function* () {
|
|
523
522
|
const key = yield* JwtKey.fromPkcs8Pem(credentials.privateKey, { alg: "RS256" });
|
|
@@ -545,7 +544,7 @@ const asBearer = (token, options) => makeClientShape({
|
|
|
545
544
|
});
|
|
546
545
|
const appFailure = (kind) => (error) => Effect.fail(GitHubAppError.of(kind, error.reason, error));
|
|
547
546
|
function makeApp(options) {
|
|
548
|
-
return Effect.
|
|
547
|
+
return Effect.map(resolveApiVersion(options.apiVersion), () => {
|
|
549
548
|
const installations = Effect.fn("GitHubApp.installations")(function* (credentials) {
|
|
550
549
|
return (yield* (yield* asApp(credentials, options)).paginate("GET /app/installations", {}).pipe(Effect.catch(appFailure("installation")))).map((entry) => {
|
|
551
550
|
const account = entry.account ?? void 0;
|
|
@@ -635,6 +634,7 @@ const normalizePermissions = (raw) => {
|
|
|
635
634
|
* release.
|
|
636
635
|
*/
|
|
637
636
|
const makeRotatingClient = (mint, release, options, operation) => Effect.gen(function* () {
|
|
637
|
+
const apiVersion = yield* resolveApiVersion(options.apiVersion);
|
|
638
638
|
const held = yield* Ref.make(Option.none());
|
|
639
639
|
const revokeHeld = Effect.flatMap(Ref.get(held), (current) => Option.isSome(current) ? Effect.ignore(release(current.value.credential.token)) : Effect.void);
|
|
640
640
|
const rotate = Effect.gen(function* () {
|
|
@@ -679,7 +679,8 @@ const makeRotatingClient = (mint, release, options, operation) => Effect.gen(fun
|
|
|
679
679
|
paginate: (route, params, pageOptions) => Effect.flatMap(current, (client) => client.paginate(route, params, pageOptions)),
|
|
680
680
|
paginateStream: (route, params, pageOptions) => Stream.unwrap(Effect.map(current, (client) => client.paginateStream(route, params, pageOptions))),
|
|
681
681
|
graphql: (document, variables) => Effect.flatMap(currentForGraphQL, (client) => client.graphql(document, variables)),
|
|
682
|
-
rateLimit: Effect.flatMap(Ref.get(held), (state) => Option.isSome(state) ? state.value.client.rateLimit : Effect.succeed(Option.none()))
|
|
682
|
+
rateLimit: Effect.flatMap(Ref.get(held), (state) => Option.isSome(state) ? state.value.client.rateLimit : Effect.succeed(Option.none())),
|
|
683
|
+
apiVersion
|
|
683
684
|
};
|
|
684
685
|
});
|
|
685
686
|
|
package/GitHubClient.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { GitHubError } from "./GitHubError.js";
|
|
2
2
|
import { GitHubGraphQLError } from "./GraphQL.js";
|
|
3
3
|
import { RetryPolicy } from "./Resilience.js";
|
|
4
|
-
import { makeTransport } from "./internal/octokit.js";
|
|
4
|
+
import { DEFAULT_API_VERSION, makeTransport } from "./internal/octokit.js";
|
|
5
5
|
import { fromArray, paginate } from "./internal/paginate.js";
|
|
6
|
+
import { unstubbed } from "./internal/unstubbed.js";
|
|
6
7
|
import { Clock, Config, Context, Effect, Layer, Option, Schema, Stream } from "effect";
|
|
7
8
|
|
|
8
9
|
// src/GitHubClient.ts
|
|
@@ -10,7 +11,7 @@ import { Clock, Config, Context, Effect, Layer, Option, Schema, Stream } from "e
|
|
|
10
11
|
const DEFAULT_PER_PAGE = 100;
|
|
11
12
|
/**
|
|
12
13
|
* A raw HTTP failure recorded for {@link GitHubClient.layerFixture}, made by
|
|
13
|
-
* {@link (GitHubFixtures:
|
|
14
|
+
* {@link (GitHubFixtures:class).failure}.
|
|
14
15
|
*
|
|
15
16
|
* @remarks
|
|
16
17
|
* Branded, so a fixture's data payload that happens to have a `status` and a
|
|
@@ -45,12 +46,42 @@ const isRawFailure = (value) => typeof value === "object" && value !== null && v
|
|
|
45
46
|
*
|
|
46
47
|
* @public
|
|
47
48
|
*/
|
|
48
|
-
|
|
49
|
+
var GitHubFixtures = class {
|
|
50
|
+
constructor() {}
|
|
51
|
+
/**
|
|
52
|
+
* A raw failure: what GitHub answered, classified at call time by the same
|
|
53
|
+
* classifier the live client uses.
|
|
54
|
+
*
|
|
55
|
+
* @remarks
|
|
56
|
+
* Record it wherever a fixture accepts a `GitHubError` (`request`, and so
|
|
57
|
+
* `requestDecoded`, and `paginate`). Each call fails with
|
|
58
|
+
* `GitHubError.fromResponse(route, response, now)`, `now` read from `Clock`,
|
|
59
|
+
* so a rate-limit reset header is measured against the `TestClock`. Prefer it
|
|
60
|
+
* to a hand-built `GitHubError` when the test is about GitHub's actual
|
|
61
|
+
* response: the classification is then the package's, not the test's.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* import { GitHubClient, GitHubFixtures } from "@effected/github";
|
|
66
|
+
*
|
|
67
|
+
* const layer = GitHubClient.layerFixture({
|
|
68
|
+
* request: {
|
|
69
|
+
* "POST /repos/{owner}/{repo}/releases": GitHubFixtures.failure({
|
|
70
|
+
* status: 422,
|
|
71
|
+
* body: {
|
|
72
|
+
* message: "Validation Failed",
|
|
73
|
+
* errors: [{ resource: "Release", code: "already_exists", field: "tag_name" }],
|
|
74
|
+
* },
|
|
75
|
+
* }),
|
|
76
|
+
* },
|
|
77
|
+
* });
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
static failure = (response) => new RawFailure(response);
|
|
81
|
+
};
|
|
49
82
|
const resolvePolicy = (retry) => retry === void 0 ? RetryPolicy.default : retry === "off" ? RetryPolicy.none : retry;
|
|
50
83
|
const perPageOf = (options) => options?.perPage ?? DEFAULT_PER_PAGE;
|
|
51
|
-
const
|
|
52
|
-
throw new Error(`GitHubClient.makeTest: ${member}() was called but not stubbed — pass an override, or use GitHubClient.layerFixture for recorded responses.`);
|
|
53
|
-
};
|
|
84
|
+
const dies = unstubbed("GitHubClient.makeTest", "pass an override, or use GitHubClient.layerFixture for recorded responses.");
|
|
54
85
|
/**
|
|
55
86
|
* The typed GitHub API client.
|
|
56
87
|
*
|
|
@@ -118,21 +149,25 @@ var GitHubClient = class GitHubClient extends Context.Service()("@effected/githu
|
|
|
118
149
|
* member **dies** naming itself.
|
|
119
150
|
*
|
|
120
151
|
* @remarks
|
|
121
|
-
* No member has an honest default. A fabricated response — an empty list, a
|
|
152
|
+
* No request member has an honest default. A fabricated response — an empty list, a
|
|
122
153
|
* made-up sha — would leak into the code under test as fact, so the double
|
|
123
154
|
* fails loudly instead, which also makes it proof that a test touches nothing
|
|
124
155
|
* but what it stubbed.
|
|
125
156
|
*
|
|
157
|
+
* `rateLimit` answers `Option.none()` and `apiVersion` the live default,
|
|
158
|
+
* because a real client legitimately answers both without a request.
|
|
159
|
+
*
|
|
126
160
|
* For recorded responses that page for real, use
|
|
127
161
|
* {@link GitHubClient.layerFixture}.
|
|
128
162
|
*/
|
|
129
163
|
static makeTest = (overrides = {}) => ({
|
|
130
|
-
request: overrides.request ?? (() =>
|
|
131
|
-
requestDecoded: overrides.requestDecoded ?? (() =>
|
|
132
|
-
paginate: overrides.paginate ?? (() =>
|
|
133
|
-
paginateStream: overrides.paginateStream ?? (() =>
|
|
134
|
-
graphql: overrides.graphql ?? (() =>
|
|
135
|
-
rateLimit: overrides.rateLimit ?? Effect.succeed(Option.none())
|
|
164
|
+
request: overrides.request ?? (() => dies("request")),
|
|
165
|
+
requestDecoded: overrides.requestDecoded ?? (() => dies("requestDecoded")),
|
|
166
|
+
paginate: overrides.paginate ?? (() => dies("paginate")),
|
|
167
|
+
paginateStream: overrides.paginateStream ?? (() => Stream.fromEffect(dies("paginateStream"))),
|
|
168
|
+
graphql: overrides.graphql ?? (() => dies("graphql")),
|
|
169
|
+
rateLimit: overrides.rateLimit ?? Effect.succeed(Option.none()),
|
|
170
|
+
apiVersion: overrides.apiVersion ?? "2026-03-10"
|
|
136
171
|
});
|
|
137
172
|
/** {@link GitHubClient.makeTest} behind a `Layer`. */
|
|
138
173
|
static layerTest = (overrides = {}) => Layer.succeed(GitHubClient, GitHubClient.makeTest(overrides));
|
|
@@ -150,13 +185,28 @@ var GitHubClient = class GitHubClient extends Context.Service()("@effected/githu
|
|
|
150
185
|
*
|
|
151
186
|
* **It records every call it serves, including one that fails.** A call
|
|
152
187
|
* answered by a recorded `GitHubError` or a
|
|
153
|
-
* {@link (GitHubFixtures:
|
|
188
|
+
* {@link (GitHubFixtures:class).failure} is recorded before it fails, so
|
|
154
189
|
* one fixture both records the calls and classifies a raw failure the way
|
|
155
190
|
* the live client would; there is no need to classify by hand inside a
|
|
156
191
|
* {@link GitHubClient.layerTest} handler.
|
|
157
192
|
*/
|
|
158
193
|
static layerFixture = (fixtures) => Layer.succeed(GitHubClient, makeFixture(fixtures));
|
|
159
194
|
};
|
|
195
|
+
/** A calendar api-version: `YYYY-MM-DD`. */
|
|
196
|
+
const API_VERSION_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
197
|
+
/**
|
|
198
|
+
* The api-version a client pins: the option when it is a calendar version,
|
|
199
|
+
* the default when it is absent, and a defect naming the option otherwise.
|
|
200
|
+
*
|
|
201
|
+
* @remarks
|
|
202
|
+
* A defect, not a typed failure: a malformed version is a wiring mistake no
|
|
203
|
+
* caller recovers from, and every layer that builds a client would otherwise
|
|
204
|
+
* widen its error channel for it. Exported for `GitHubApp`, which validates
|
|
205
|
+
* eagerly at its own layer construction. Not part of the public surface.
|
|
206
|
+
*
|
|
207
|
+
* @internal
|
|
208
|
+
*/
|
|
209
|
+
const resolveApiVersion = (option) => option === void 0 ? Effect.succeed(DEFAULT_API_VERSION) : API_VERSION_PATTERN.test(option) ? Effect.succeed(option) : Effect.die(/* @__PURE__ */ new Error(`GitHubClient: the apiVersion option must be a calendar version like "${DEFAULT_API_VERSION}" (YYYY-MM-DD), got ${JSON.stringify(option)}`));
|
|
160
210
|
/**
|
|
161
211
|
* Builds the live shape over a constructed transport.
|
|
162
212
|
*
|
|
@@ -168,12 +218,14 @@ var GitHubClient = class GitHubClient extends Context.Service()("@effected/githu
|
|
|
168
218
|
* @internal
|
|
169
219
|
*/
|
|
170
220
|
const makeClientShape = (options) => Effect.gen(function* () {
|
|
221
|
+
const apiVersion = yield* resolveApiVersion(options.apiVersion);
|
|
171
222
|
const transport = yield* makeTransport({
|
|
172
223
|
token: options.token,
|
|
173
224
|
retry: resolvePolicy(options.retry),
|
|
174
225
|
baseUrl: options.baseUrl,
|
|
175
226
|
userAgent: options.userAgent,
|
|
176
|
-
fetch: options.fetch
|
|
227
|
+
fetch: options.fetch,
|
|
228
|
+
apiVersion
|
|
177
229
|
});
|
|
178
230
|
const request = Effect.fn("GitHubClient.request")(function* (route, params) {
|
|
179
231
|
yield* Effect.annotateCurrentSpan({ route });
|
|
@@ -205,7 +257,8 @@ const makeClientShape = (options) => Effect.gen(function* () {
|
|
|
205
257
|
const raw = yield* transport.graphql(document.name, document.document, document.encodeVariables(variables));
|
|
206
258
|
return yield* document.decode(raw).pipe(Effect.catchTag("SchemaError", (error) => Effect.fail(GitHubGraphQLError.decode(document.name, "response did not match its schema", error))));
|
|
207
259
|
}),
|
|
208
|
-
rateLimit: transport.rateLimit
|
|
260
|
+
rateLimit: transport.rateLimit,
|
|
261
|
+
apiVersion
|
|
209
262
|
};
|
|
210
263
|
});
|
|
211
264
|
const makeFixture = (fixtures) => {
|
|
@@ -272,8 +325,9 @@ const makeFixture = (fixtures) => {
|
|
|
272
325
|
const raw = fixtures.graphql?.[document.name];
|
|
273
326
|
return raw === void 0 ? Effect.die(/* @__PURE__ */ new Error(`GitHubClient.layerFixture: no graphql fixture for ${document.name}`)) : document.decode(raw).pipe(Effect.catchTag("SchemaError", (error) => Effect.fail(GitHubGraphQLError.decode(document.name, "fixture did not match its schema", error))));
|
|
274
327
|
},
|
|
275
|
-
rateLimit: Effect.succeed(Option.fromUndefinedOr(fixtures.rateLimit))
|
|
328
|
+
rateLimit: Effect.succeed(Option.fromUndefinedOr(fixtures.rateLimit)),
|
|
329
|
+
apiVersion: fixtures.apiVersion ?? "2026-03-10"
|
|
276
330
|
};
|
|
277
331
|
};
|
|
278
332
|
|
|
279
|
-
export { GitHubClient, GitHubFixtures, RawFailure, makeClientShape };
|
|
333
|
+
export { GitHubClient, GitHubFixtures, RawFailure, makeClientShape, resolveApiVersion };
|
package/GitHubCommit.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { paginate } from "./internal/paginate.js";
|
|
2
|
+
import { unstubbed } from "./internal/unstubbed.js";
|
|
2
3
|
import { GitHubClient } from "./GitHubClient.js";
|
|
3
4
|
import { Repo } from "./Repo.js";
|
|
4
5
|
import { Context, Effect, Layer, Option, Schema, Stream } from "effect";
|
|
@@ -114,19 +115,17 @@ var CommitComparison = class extends Schema.Class("CommitComparison")({
|
|
|
114
115
|
var GitHubCommit = class GitHubCommit extends Context.Service()("@effected/github/GitHubCommit") {
|
|
115
116
|
/** The live service, built over a `GitHubClient`. */
|
|
116
117
|
static layer = Layer.effect(this, Effect.map(GitHubClient, (client) => make(client)));
|
|
117
|
-
/** An in-memory double; unstubbed
|
|
118
|
+
/** An in-memory double; an unstubbed member dies when run, naming itself. */
|
|
118
119
|
static makeTest = (overrides = {}) => ({
|
|
119
|
-
get: overrides.get ?? (() =>
|
|
120
|
-
list: overrides.list ?? (() =>
|
|
121
|
-
compare: overrides.compare ?? (() =>
|
|
122
|
-
changedFiles: overrides.changedFiles ?? (() =>
|
|
120
|
+
get: overrides.get ?? (() => dies("get")),
|
|
121
|
+
list: overrides.list ?? (() => dies("list")),
|
|
122
|
+
compare: overrides.compare ?? (() => dies("compare")),
|
|
123
|
+
changedFiles: overrides.changedFiles ?? (() => dies("changedFiles"))
|
|
123
124
|
});
|
|
124
125
|
/** {@link GitHubCommit.makeTest} behind a `Layer`. */
|
|
125
126
|
static layerTest = (overrides = {}) => Layer.succeed(GitHubCommit, GitHubCommit.makeTest(overrides));
|
|
126
127
|
};
|
|
127
|
-
const
|
|
128
|
-
throw new Error(`GitHubCommit.makeTest: ${member}() was called but not stubbed — pass an override.`);
|
|
129
|
-
};
|
|
128
|
+
const dies = unstubbed("GitHubCommit.makeTest");
|
|
130
129
|
const summarize = (raw) => CommitSummary.make({
|
|
131
130
|
sha: raw.sha,
|
|
132
131
|
message: raw.commit.message,
|
package/GitHubContent.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { GitHubError } from "./GitHubError.js";
|
|
2
|
+
import { unstubbed } from "./internal/unstubbed.js";
|
|
2
3
|
import { GitHubClient } from "./GitHubClient.js";
|
|
3
4
|
import { Repo } from "./Repo.js";
|
|
4
5
|
import { Context, Effect, Layer, Option, Result } from "effect";
|
|
@@ -35,17 +36,15 @@ const utf8 = new TextDecoder("utf-8", { ignoreBOM: true });
|
|
|
35
36
|
var GitHubContent = class GitHubContent extends Context.Service()("@effected/github/GitHubContent") {
|
|
36
37
|
/** The live service, built over a `GitHubClient`. */
|
|
37
38
|
static layer = Layer.effect(this, Effect.map(GitHubClient, (client) => make(client)));
|
|
38
|
-
/** An in-memory double; unstubbed
|
|
39
|
+
/** An in-memory double; an unstubbed member dies when run, naming itself. */
|
|
39
40
|
static makeTest = (overrides = {}) => ({
|
|
40
|
-
getFile: overrides.getFile ?? (() =>
|
|
41
|
-
getFileOption: overrides.getFileOption ?? (() =>
|
|
41
|
+
getFile: overrides.getFile ?? (() => dies("getFile")),
|
|
42
|
+
getFileOption: overrides.getFileOption ?? (() => dies("getFileOption"))
|
|
42
43
|
});
|
|
43
44
|
/** {@link GitHubContent.makeTest} behind a `Layer`. */
|
|
44
45
|
static layerTest = (overrides = {}) => Layer.succeed(GitHubContent, GitHubContent.makeTest(overrides));
|
|
45
46
|
};
|
|
46
|
-
const
|
|
47
|
-
throw new Error(`GitHubContent.makeTest: ${member}() was called but not stubbed — pass an override.`);
|
|
48
|
-
};
|
|
47
|
+
const dies = unstubbed("GitHubContent.makeTest");
|
|
49
48
|
const make = (client) => {
|
|
50
49
|
const getFile = Effect.fn("GitHubContent.getFile")(function* (path, options) {
|
|
51
50
|
const { owner, repo } = yield* Repo;
|