@effected/github 0.15.1 → 0.17.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 +180 -0
- package/CheckRun.js +87 -19
- package/GitHubApp.js +253 -35
- package/GitHubClient.js +52 -3
- package/GitHubContent.js +10 -2
- package/GitHubError.js +50 -20
- package/InstallationTokenStore.js +51 -0
- package/README.md +1 -1
- package/Repo.js +16 -1
- package/WorkflowDispatch.js +91 -23
- package/index.d.ts +683 -28
- package/index.js +5 -3
- package/internal/crypto.js +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ Hand-rolled octokit wrappers tend to converge on the same shape: a `rest<T>(oper
|
|
|
25
25
|
|
|
26
26
|
The error model gets the same treatment. `GitHubError.kind` is a literal union (`notFound`, `alreadyExists`, `rejected`, `unauthorized`, `rateLimited`, `transport`, `decode`) produced by one classification step, `GitHubError.fromOctokit`, instead of one hand-written mapper per resource. `kind: "alreadyExists"` is what makes `GitBranch.upsert` and `GitTag.upsert` a single round trip in the common case, rather than a create-then-catch-then-check-then-reset dance repeated at every call site that needs it.
|
|
27
27
|
|
|
28
|
-
This package also owns the octokit runtime so nothing downstream has to. `@octokit/rest` and `@octokit/auth-app` are deliberately absent — the rest wrapper is a request-logging plugin plus 1.4 MB of duplicate endpoint types, and `auth-app` drags in `createOAuthUserAuth`, roughly 492 KB of OAuth machinery this package never calls. GitHub App JWTs are signed by
|
|
28
|
+
This package also owns the octokit runtime so nothing downstream has to. `@octokit/rest` and `@octokit/auth-app` are deliberately absent — the rest wrapper is a request-logging plugin plus 1.4 MB of duplicate endpoint types, and `auth-app` drags in `createOAuthUserAuth`, roughly 492 KB of OAuth machinery this package never calls. GitHub App JWTs are signed by the kit's own `@effected/jwt` instead: WebCrypto RS256 with no runtime dependencies, so App auth runs on Cloudflare Workers as well as Node. A consumer that only authenticates with a token it already holds never links the JWT signer at all: the App-authenticated layer lives in its own module (`GitHubApp`), and nothing here is gathered into a namespace object that would defeat that split.
|
|
29
29
|
|
|
30
30
|
## Install
|
|
31
31
|
|
package/Repo.js
CHANGED
|
@@ -16,6 +16,15 @@ input: Schema.String }) {
|
|
|
16
16
|
/**
|
|
17
17
|
* Which repository an operation acts on.
|
|
18
18
|
*
|
|
19
|
+
* @remarks
|
|
20
|
+
* **`RepoRef.make` validates at construction and throws** on an empty `owner`
|
|
21
|
+
* or `repo`, so inside an `Effect` a bad value dies rather than failing. That
|
|
22
|
+
* is right for a coordinate the program itself spells out, and wrong for one
|
|
23
|
+
* built from data: for a slug read from a database row, a config file or a
|
|
24
|
+
* webhook, use {@link RepoRef.parse}, which fails with a typed
|
|
25
|
+
* {@link InvalidRepoRefError} instead (or {@link RepoRef.parseResult}
|
|
26
|
+
* outside an `Effect`).
|
|
27
|
+
*
|
|
19
28
|
* @public
|
|
20
29
|
*/
|
|
21
30
|
var RepoRef = class RepoRef extends Schema.Class("RepoRef")({
|
|
@@ -41,7 +50,13 @@ var RepoRef = class RepoRef extends Schema.Class("RepoRef")({
|
|
|
41
50
|
repo
|
|
42
51
|
}));
|
|
43
52
|
}
|
|
44
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* Parse `"owner/repo"`, failing with a typed {@link InvalidRepoRefError}.
|
|
55
|
+
*
|
|
56
|
+
* @remarks
|
|
57
|
+
* The typed path for building a `Repo` from stored data, such as a stored
|
|
58
|
+
* `full_name`: a malformed value fails here, where `make` would die.
|
|
59
|
+
*/
|
|
45
60
|
static parse = Effect.fn("RepoRef.parse")((slug) => Effect.fromResult(RepoRef.parseResult(slug)));
|
|
46
61
|
/** `"owner/repo"`. */
|
|
47
62
|
get slug() {
|
package/WorkflowDispatch.js
CHANGED
|
@@ -26,6 +26,24 @@ var WorkflowRunStatus = class extends Schema.Class("WorkflowRunStatus")({
|
|
|
26
26
|
return this.status === "completed";
|
|
27
27
|
}
|
|
28
28
|
};
|
|
29
|
+
/**
|
|
30
|
+
* The run a `workflow_dispatch` created, as GitHub reports it when asked.
|
|
31
|
+
*
|
|
32
|
+
* @remarks
|
|
33
|
+
* Returned by {@link WorkflowDispatchShape.dispatchWithRun}. `runId` is usable
|
|
34
|
+
* directly with {@link WorkflowDispatchShape.runStatus} and
|
|
35
|
+
* {@link WorkflowDispatchShape.cancelRun}.
|
|
36
|
+
*
|
|
37
|
+
* @public
|
|
38
|
+
*/
|
|
39
|
+
var DispatchedRun = class extends Schema.Class("DispatchedRun")({
|
|
40
|
+
/** The created run's numeric id. */
|
|
41
|
+
runId: Schema.Int,
|
|
42
|
+
/** The run's API URL. */
|
|
43
|
+
runUrl: Schema.String,
|
|
44
|
+
/** The run's web URL. */
|
|
45
|
+
htmlUrl: Schema.String
|
|
46
|
+
}) {};
|
|
29
47
|
const DEFAULT_INTERVAL = Duration.seconds(10);
|
|
30
48
|
const DEFAULT_TIMEOUT = Duration.minutes(5);
|
|
31
49
|
/**
|
|
@@ -60,7 +78,9 @@ var WorkflowDispatch = class WorkflowDispatch extends Context.Service()("@effect
|
|
|
60
78
|
/** An in-memory double; unstubbed members die naming themselves. */
|
|
61
79
|
static makeTest = (overrides = {}) => ({
|
|
62
80
|
dispatch: overrides.dispatch ?? (() => unstubbed("dispatch")),
|
|
81
|
+
dispatchWithRun: overrides.dispatchWithRun ?? (() => unstubbed("dispatchWithRun")),
|
|
63
82
|
runStatus: overrides.runStatus ?? (() => unstubbed("runStatus")),
|
|
83
|
+
cancelRun: overrides.cancelRun ?? (() => unstubbed("cancelRun")),
|
|
64
84
|
list: overrides.list ?? Effect.sync(() => unstubbed("list")),
|
|
65
85
|
dispatchAndWait: overrides.dispatchAndWait ?? (() => unstubbed("dispatchAndWait"))
|
|
66
86
|
});
|
|
@@ -76,6 +96,14 @@ const statusOf = (raw) => WorkflowRunStatus.make({
|
|
|
76
96
|
...raw.conclusion != null ? { conclusion: raw.conclusion } : {},
|
|
77
97
|
url: raw.html_url
|
|
78
98
|
});
|
|
99
|
+
/** GitHub's `workflow-dispatch-response`; the id is `number | bigint` in the generated types. */
|
|
100
|
+
const DispatchResponse = Schema.Struct({
|
|
101
|
+
workflow_run_id: Schema.Union([Schema.Number, Schema.BigInt]),
|
|
102
|
+
run_url: Schema.String,
|
|
103
|
+
html_url: Schema.String
|
|
104
|
+
});
|
|
105
|
+
const decodeDispatchResponse = Schema.decodeUnknownEffect(DispatchResponse);
|
|
106
|
+
const decodeDispatchedRun = Schema.decodeUnknownEffect(DispatchedRun);
|
|
79
107
|
const make = (client) => {
|
|
80
108
|
const dispatch = Effect.fn("WorkflowDispatch.dispatch")(function* (workflow, ref, inputs) {
|
|
81
109
|
const { owner, repo } = yield* Repo;
|
|
@@ -93,38 +121,78 @@ const make = (client) => {
|
|
|
93
121
|
...inputs !== void 0 ? { inputs } : {}
|
|
94
122
|
});
|
|
95
123
|
});
|
|
124
|
+
const dispatchWithRun = Effect.fn("WorkflowDispatch.dispatchWithRun")(function* (workflow, ref, inputs) {
|
|
125
|
+
const { owner, repo } = yield* Repo;
|
|
126
|
+
yield* Effect.annotateCurrentSpan({
|
|
127
|
+
owner,
|
|
128
|
+
repo,
|
|
129
|
+
workflow,
|
|
130
|
+
ref
|
|
131
|
+
});
|
|
132
|
+
const data = yield* client.request("POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches", {
|
|
133
|
+
owner,
|
|
134
|
+
repo,
|
|
135
|
+
workflow_id: workflow,
|
|
136
|
+
ref,
|
|
137
|
+
...inputs !== void 0 ? { inputs } : {},
|
|
138
|
+
return_run_details: true
|
|
139
|
+
});
|
|
140
|
+
if (data === "" || data === null) return Option.none();
|
|
141
|
+
const run = yield* decodeDispatchResponse(data).pipe(Effect.flatMap((raw) => decodeDispatchedRun({
|
|
142
|
+
runId: numericId(raw.workflow_run_id),
|
|
143
|
+
runUrl: raw.run_url,
|
|
144
|
+
htmlUrl: raw.html_url
|
|
145
|
+
})), Effect.catchTag("SchemaError", (error) => Effect.fail(GitHubError.decode("WorkflowDispatch.dispatchWithRun", "GitHub returned unexpected run details", error))));
|
|
146
|
+
return Option.some(run);
|
|
147
|
+
});
|
|
148
|
+
const runStatus = Effect.fn("WorkflowDispatch.runStatus")(function* (runId) {
|
|
149
|
+
const { owner, repo } = yield* Repo;
|
|
150
|
+
yield* Effect.annotateCurrentSpan({
|
|
151
|
+
owner,
|
|
152
|
+
repo,
|
|
153
|
+
runId
|
|
154
|
+
});
|
|
155
|
+
const raw = yield* client.request("GET /repos/{owner}/{repo}/actions/runs/{run_id}", {
|
|
156
|
+
owner,
|
|
157
|
+
repo,
|
|
158
|
+
run_id: runId
|
|
159
|
+
});
|
|
160
|
+
return statusOf(raw);
|
|
161
|
+
});
|
|
162
|
+
const list = Effect.fn("WorkflowDispatch.list")(function* () {
|
|
163
|
+
const { owner, repo } = yield* Repo;
|
|
164
|
+
yield* Effect.annotateCurrentSpan({
|
|
165
|
+
owner,
|
|
166
|
+
repo
|
|
167
|
+
});
|
|
168
|
+
return (yield* client.paginate("GET /repos/{owner}/{repo}/actions/workflows", {
|
|
169
|
+
owner,
|
|
170
|
+
repo
|
|
171
|
+
})).map((workflow) => ({
|
|
172
|
+
id: workflow.id,
|
|
173
|
+
name: workflow.name,
|
|
174
|
+
path: workflow.path,
|
|
175
|
+
state: workflow.state
|
|
176
|
+
}));
|
|
177
|
+
});
|
|
96
178
|
return {
|
|
97
179
|
dispatch,
|
|
98
|
-
|
|
180
|
+
dispatchWithRun,
|
|
181
|
+
runStatus,
|
|
182
|
+
cancelRun: Effect.fn("WorkflowDispatch.cancelRun")(function* (runId) {
|
|
99
183
|
const { owner, repo } = yield* Repo;
|
|
100
184
|
yield* Effect.annotateCurrentSpan({
|
|
101
185
|
owner,
|
|
102
186
|
repo,
|
|
103
187
|
runId
|
|
104
188
|
});
|
|
105
|
-
|
|
189
|
+
return yield* client.request("POST /repos/{owner}/{repo}/actions/runs/{run_id}/cancel", {
|
|
106
190
|
owner,
|
|
107
191
|
repo,
|
|
108
192
|
run_id: runId
|
|
109
|
-
});
|
|
110
|
-
return statusOf(raw);
|
|
193
|
+
}).pipe(Effect.as("cancelled"), Effect.catchIf((error) => error.status === 409, () => Effect.succeed("alreadyCompleted")));
|
|
111
194
|
}),
|
|
112
|
-
list:
|
|
113
|
-
const { owner, repo } = yield* Repo;
|
|
114
|
-
yield* Effect.annotateCurrentSpan({
|
|
115
|
-
owner,
|
|
116
|
-
repo
|
|
117
|
-
});
|
|
118
|
-
return (yield* client.paginate("GET /repos/{owner}/{repo}/actions/workflows", {
|
|
119
|
-
owner,
|
|
120
|
-
repo
|
|
121
|
-
})).map((workflow) => ({
|
|
122
|
-
id: workflow.id,
|
|
123
|
-
name: workflow.name,
|
|
124
|
-
path: workflow.path,
|
|
125
|
-
state: workflow.state
|
|
126
|
-
}));
|
|
127
|
-
})(),
|
|
195
|
+
list: list(),
|
|
128
196
|
dispatchAndWait: Effect.fn("WorkflowDispatch.dispatchAndWait")(function* (workflow, ref, options) {
|
|
129
197
|
const { owner, repo } = yield* Repo;
|
|
130
198
|
const interval = options?.poll?.interval ?? DEFAULT_INTERVAL;
|
|
@@ -138,8 +206,8 @@ const make = (client) => {
|
|
|
138
206
|
attempts
|
|
139
207
|
});
|
|
140
208
|
const dispatchedAt = new Date(yield* Clock.currentTimeMillis).toISOString();
|
|
141
|
-
yield*
|
|
142
|
-
const findRun = Effect.gen(function* () {
|
|
209
|
+
const dispatched = yield* dispatchWithRun(workflow, ref, options?.inputs);
|
|
210
|
+
const findRun = Option.isSome(dispatched) ? Effect.map(runStatus(dispatched.value.runId), Option.some) : Effect.gen(function* () {
|
|
143
211
|
const match = (yield* client.paginate("GET /repos/{owner}/{repo}/actions/runs", {
|
|
144
212
|
owner,
|
|
145
213
|
repo,
|
|
@@ -163,4 +231,4 @@ const make = (client) => {
|
|
|
163
231
|
};
|
|
164
232
|
|
|
165
233
|
//#endregion
|
|
166
|
-
export { WorkflowDispatch, WorkflowRunStatus };
|
|
234
|
+
export { DispatchedRun, WorkflowDispatch, WorkflowRunStatus };
|