@computis/effect-github 0.0.0-stage → 0.1.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/CHANGELOG.md +7 -0
- package/LICENSE +23 -0
- package/README.md +79 -2
- package/dist/action-client.d.ts +38 -0
- package/dist/action-client.d.ts.map +1 -0
- package/dist/action-client.js +96 -0
- package/dist/action-client.js.map +1 -0
- package/dist/action-context.d.ts +51 -0
- package/dist/action-context.d.ts.map +1 -0
- package/dist/action-context.js +61 -0
- package/dist/action-context.js.map +1 -0
- package/dist/action-runner.d.ts +57 -0
- package/dist/action-runner.d.ts.map +1 -0
- package/dist/action-runner.js +100 -0
- package/dist/action-runner.js.map +1 -0
- package/dist/action-summary.d.ts +45 -0
- package/dist/action-summary.d.ts.map +1 -0
- package/dist/action-summary.js +92 -0
- package/dist/action-summary.js.map +1 -0
- package/dist/action.d.ts +16 -0
- package/dist/action.d.ts.map +1 -0
- package/dist/action.js +37 -0
- package/dist/action.js.map +1 -0
- package/dist/errors/action-api-error.d.ts +13 -0
- package/dist/errors/action-api-error.d.ts.map +1 -0
- package/dist/errors/action-api-error.js +17 -0
- package/dist/errors/action-api-error.js.map +1 -0
- package/dist/errors/action-context-error.d.ts +16 -0
- package/dist/errors/action-context-error.d.ts.map +1 -0
- package/dist/errors/action-context-error.js +16 -0
- package/dist/errors/action-context-error.js.map +1 -0
- package/dist/errors/action-failed.d.ts +10 -0
- package/dist/errors/action-failed.d.ts.map +1 -0
- package/dist/errors/action-failed.js +10 -0
- package/dist/errors/action-failed.js.map +1 -0
- package/dist/errors/action-failure.d.ts +15 -0
- package/dist/errors/action-failure.d.ts.map +1 -0
- package/dist/errors/action-failure.js +15 -0
- package/dist/errors/action-failure.js.map +1 -0
- package/dist/errors/action-input-error.d.ts +17 -0
- package/dist/errors/action-input-error.d.ts.map +1 -0
- package/dist/errors/action-input-error.js +22 -0
- package/dist/errors/action-input-error.js.map +1 -0
- package/dist/errors/action-oidc-error.d.ts +15 -0
- package/dist/errors/action-oidc-error.d.ts.map +1 -0
- package/dist/errors/action-oidc-error.js +18 -0
- package/dist/errors/action-oidc-error.js.map +1 -0
- package/dist/errors/action-summary-error.d.ts +15 -0
- package/dist/errors/action-summary-error.d.ts.map +1 -0
- package/dist/errors/action-summary-error.js +18 -0
- package/dist/errors/action-summary-error.js.map +1 -0
- package/dist/errors/input-validation-failure.d.ts +19 -0
- package/dist/errors/input-validation-failure.d.ts.map +1 -0
- package/dist/errors/input-validation-failure.js +19 -0
- package/dist/errors/input-validation-failure.js.map +1 -0
- package/dist/errors/platform-missing.d.ts +9 -0
- package/dist/errors/platform-missing.d.ts.map +1 -0
- package/dist/errors/platform-missing.js +9 -0
- package/dist/errors/platform-missing.js.map +1 -0
- package/dist/input.d.ts +14 -0
- package/dist/input.d.ts.map +1 -0
- package/dist/input.js +30 -0
- package/dist/input.js.map +1 -0
- package/dist/object.d.ts +2 -0
- package/dist/object.d.ts.map +1 -0
- package/dist/object.js +2 -0
- package/dist/object.js.map +1 -0
- package/dist/testing/action-client-test.d.ts +7 -0
- package/dist/testing/action-client-test.d.ts.map +1 -0
- package/dist/testing/action-client-test.js +10 -0
- package/dist/testing/action-client-test.js.map +1 -0
- package/dist/testing/action-context-test.d.ts +10 -0
- package/dist/testing/action-context-test.d.ts.map +1 -0
- package/dist/testing/action-context-test.js +27 -0
- package/dist/testing/action-context-test.js.map +1 -0
- package/dist/testing/action-runner-test.d.ts +26 -0
- package/dist/testing/action-runner-test.d.ts.map +1 -0
- package/dist/testing/action-runner-test.js +92 -0
- package/dist/testing/action-runner-test.js.map +1 -0
- package/dist/testing/action-summary-test.d.ts +11 -0
- package/dist/testing/action-summary-test.d.ts.map +1 -0
- package/dist/testing/action-summary-test.js +62 -0
- package/dist/testing/action-summary-test.js.map +1 -0
- package/dist/thrown-message.d.ts +2 -0
- package/dist/thrown-message.d.ts.map +1 -0
- package/dist/thrown-message.js +7 -0
- package/dist/thrown-message.js.map +1 -0
- package/package.json +64 -3
- package/src/action-client.ts +238 -0
- package/src/action-context.ts +121 -0
- package/src/action-runner.ts +221 -0
- package/src/action-summary.ts +143 -0
- package/src/action.ts +81 -0
- package/src/errors/action-api-error.ts +18 -0
- package/src/errors/action-context-error.ts +20 -0
- package/src/errors/action-failed.ts +10 -0
- package/src/errors/action-failure.ts +36 -0
- package/src/errors/action-input-error.ts +24 -0
- package/src/errors/action-oidc-error.ts +19 -0
- package/src/errors/action-summary-error.ts +22 -0
- package/src/errors/input-validation-failure.ts +23 -0
- package/src/errors/platform-missing.ts +9 -0
- package/src/input.ts +60 -0
- package/src/object.ts +4 -0
- package/src/testing/action-client-test.ts +15 -0
- package/src/testing/action-context-test.ts +36 -0
- package/src/testing/action-runner-test.ts +138 -0
- package/src/testing/action-summary-test.ts +82 -0
- package/src/thrown-message.ts +9 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { getOctokit } from "@actions/github";
|
|
2
|
+
import { GitHub } from "@actions/github/lib/utils";
|
|
3
|
+
import { whenDefined } from "./object.ts";
|
|
4
|
+
import { thrownMessage } from "./thrown-message.ts";
|
|
5
|
+
import { Config, Context, Effect, flow, Layer, Option, Redacted, Schema, Stream } from "effect";
|
|
6
|
+
import { ActionApiError } from "./errors/action-api-error.ts";
|
|
7
|
+
|
|
8
|
+
export type Octokit = ReturnType<typeof getOctokit>;
|
|
9
|
+
|
|
10
|
+
export type OctokitOptions = NonNullable<Parameters<typeof getOctokit>[1]>;
|
|
11
|
+
|
|
12
|
+
export type RequestParameters = Readonly<Record<string, Schema.Json>>;
|
|
13
|
+
|
|
14
|
+
export type RestResponse = Omit<Awaited<ReturnType<Octokit["request"]>>, "data"> & {
|
|
15
|
+
readonly data: Schema.Json;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export interface PaginateOptions {
|
|
19
|
+
readonly maxPages?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ActionClientApi {
|
|
23
|
+
readonly octokit: Octokit;
|
|
24
|
+
readonly call: <A>(
|
|
25
|
+
method: string,
|
|
26
|
+
run: (octokit: Octokit) => Promise<A>,
|
|
27
|
+
) => Effect.Effect<A, ActionApiError>;
|
|
28
|
+
readonly request: (
|
|
29
|
+
route: string,
|
|
30
|
+
parameters?: RequestParameters,
|
|
31
|
+
) => Effect.Effect<RestResponse, ActionApiError>;
|
|
32
|
+
readonly graphql: (
|
|
33
|
+
query: string,
|
|
34
|
+
variables?: RequestParameters,
|
|
35
|
+
) => Effect.Effect<Schema.Json, ActionApiError>;
|
|
36
|
+
readonly paginate: (
|
|
37
|
+
route: string,
|
|
38
|
+
parameters?: RequestParameters,
|
|
39
|
+
options?: PaginateOptions,
|
|
40
|
+
) => Effect.Effect<readonly Schema.Json[], ActionApiError>;
|
|
41
|
+
readonly requestDecoded: <S extends Schema.Top>(
|
|
42
|
+
schema: S,
|
|
43
|
+
route: string,
|
|
44
|
+
parameters?: RequestParameters,
|
|
45
|
+
) => Effect.Effect<S["Type"], ActionApiError, S["DecodingServices"]>;
|
|
46
|
+
readonly graphqlDecoded: <S extends Schema.Top>(
|
|
47
|
+
schema: S,
|
|
48
|
+
query: string,
|
|
49
|
+
variables?: RequestParameters,
|
|
50
|
+
) => Effect.Effect<S["Type"], ActionApiError, S["DecodingServices"]>;
|
|
51
|
+
readonly paginateDecoded: <S extends Schema.Top>(
|
|
52
|
+
schema: S,
|
|
53
|
+
route: string,
|
|
54
|
+
parameters?: RequestParameters,
|
|
55
|
+
options?: PaginateOptions,
|
|
56
|
+
) => Effect.Effect<readonly S["Type"][], ActionApiError, S["DecodingServices"]>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const defaultMaxPages = 100;
|
|
60
|
+
|
|
61
|
+
const decodeMaxPages = Schema.decodeUnknownEffect(
|
|
62
|
+
Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const decodeHttpStatus = Schema.decodeUnknownOption(Schema.Struct({ status: Schema.Number }));
|
|
66
|
+
|
|
67
|
+
const decodeResponse = <S extends Schema.Top>(schema: S, method: string) =>
|
|
68
|
+
flow(
|
|
69
|
+
Schema.decodeUnknownEffect(schema),
|
|
70
|
+
Effect.mapError(
|
|
71
|
+
(cause) =>
|
|
72
|
+
new ActionApiError({ cause, description: `unexpected response: ${cause.message}`, method }),
|
|
73
|
+
),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const requestFailed = (method: string) => (cause: unknown) =>
|
|
77
|
+
cause instanceof ActionApiError
|
|
78
|
+
? cause
|
|
79
|
+
: new ActionApiError({
|
|
80
|
+
cause,
|
|
81
|
+
description: thrownMessage(cause, "the request failed"),
|
|
82
|
+
method,
|
|
83
|
+
...whenDefined(Option.getOrUndefined(decodeHttpStatus(cause)), ({ status }) => ({
|
|
84
|
+
status,
|
|
85
|
+
})),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const make = (token: Redacted.Redacted, options: OctokitOptions = {}): ActionClientApi => {
|
|
89
|
+
const secret = Redacted.value(token);
|
|
90
|
+
const octokit = secret === "" ? new GitHub(options) : getOctokit(secret, options);
|
|
91
|
+
const call = Effect.fn("ActionClient.call")(function* call<A>(
|
|
92
|
+
method: string,
|
|
93
|
+
run: (client: Octokit) => Promise<A>,
|
|
94
|
+
) {
|
|
95
|
+
return yield* Effect.tryPromise({
|
|
96
|
+
catch: requestFailed(method),
|
|
97
|
+
try: async () => await run(octokit),
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
const request = Effect.fn("ActionClient.request")(function* request(
|
|
101
|
+
route: string,
|
|
102
|
+
parameters?: RequestParameters,
|
|
103
|
+
) {
|
|
104
|
+
const response = yield* call(route, async (client) => await client.request(route, parameters));
|
|
105
|
+
const data = yield* decodeResponse(Schema.Json, route)(response.data);
|
|
106
|
+
const restResponse: RestResponse = { ...response, data };
|
|
107
|
+
return restResponse;
|
|
108
|
+
});
|
|
109
|
+
const graphql = Effect.fn("ActionClient.graphql")(function* graphql(
|
|
110
|
+
query: string,
|
|
111
|
+
variables?: RequestParameters,
|
|
112
|
+
) {
|
|
113
|
+
const data = yield* call("graphql", async (client) => await client.graphql(query, variables));
|
|
114
|
+
return yield* decodeResponse(Schema.Json, "graphql")(data);
|
|
115
|
+
});
|
|
116
|
+
const paginate = Effect.fn("ActionClient.paginate")(function* paginate(
|
|
117
|
+
route: string,
|
|
118
|
+
parameters?: RequestParameters,
|
|
119
|
+
paginateOptions: PaginateOptions = {},
|
|
120
|
+
) {
|
|
121
|
+
const maxPages = yield* decodeMaxPages(paginateOptions.maxPages ?? defaultMaxPages).pipe(
|
|
122
|
+
Effect.mapError(
|
|
123
|
+
(cause) =>
|
|
124
|
+
new ActionApiError({
|
|
125
|
+
cause,
|
|
126
|
+
description: `maxPages must be a positive integer: ${cause.message}`,
|
|
127
|
+
method: route,
|
|
128
|
+
}),
|
|
129
|
+
),
|
|
130
|
+
);
|
|
131
|
+
const iterator = yield* Effect.try({
|
|
132
|
+
catch: requestFailed(route),
|
|
133
|
+
try: () => octokit.paginate.iterator(route, parameters),
|
|
134
|
+
});
|
|
135
|
+
const pages = yield* Stream.fromAsyncIterable(iterator, requestFailed(route)).pipe(
|
|
136
|
+
Stream.take(maxPages + 1),
|
|
137
|
+
Stream.runCollect,
|
|
138
|
+
);
|
|
139
|
+
if (pages.length > maxPages) {
|
|
140
|
+
return yield* new ActionApiError({
|
|
141
|
+
description: `more than ${maxPages} pages`,
|
|
142
|
+
method: route,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return yield* decodeResponse(
|
|
146
|
+
Schema.Array(Schema.Json),
|
|
147
|
+
route,
|
|
148
|
+
)(pages.flatMap((page) => page.data));
|
|
149
|
+
});
|
|
150
|
+
return {
|
|
151
|
+
call,
|
|
152
|
+
graphql,
|
|
153
|
+
graphqlDecoded: Effect.fn("ActionClient.graphqlDecoded")(function* graphqlDecoded<
|
|
154
|
+
S extends Schema.Top,
|
|
155
|
+
>(schema: S, query: string, variables?: RequestParameters) {
|
|
156
|
+
const data = yield* graphql(query, variables);
|
|
157
|
+
return yield* decodeResponse(schema, "graphql")(data);
|
|
158
|
+
}),
|
|
159
|
+
octokit,
|
|
160
|
+
paginate,
|
|
161
|
+
paginateDecoded: Effect.fn("ActionClient.paginateDecoded")(function* paginateDecoded<
|
|
162
|
+
S extends Schema.Top,
|
|
163
|
+
>(schema: S, route: string, parameters?: RequestParameters, paginateOptions?: PaginateOptions) {
|
|
164
|
+
const items = yield* paginate(route, parameters, paginateOptions);
|
|
165
|
+
return yield* decodeResponse(Schema.Array(schema), route)(items);
|
|
166
|
+
}),
|
|
167
|
+
request,
|
|
168
|
+
requestDecoded: Effect.fn("ActionClient.requestDecoded")(function* requestDecoded<
|
|
169
|
+
S extends Schema.Top,
|
|
170
|
+
>(schema: S, route: string, parameters?: RequestParameters) {
|
|
171
|
+
const { data } = yield* request(route, parameters);
|
|
172
|
+
return yield* decodeResponse(schema, route)(data);
|
|
173
|
+
}),
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export class ActionClient extends Context.Service<ActionClient, ActionClientApi>()(
|
|
178
|
+
"@computis/effect-github/ActionClient",
|
|
179
|
+
) {
|
|
180
|
+
static readonly layer = (token: Redacted.Redacted, options?: OctokitOptions) =>
|
|
181
|
+
Layer.sync(ActionClient, () => make(token, options));
|
|
182
|
+
|
|
183
|
+
static readonly layerConfig = Layer.effect(
|
|
184
|
+
ActionClient,
|
|
185
|
+
Effect.gen(function* makeActionClientFromConfig() {
|
|
186
|
+
const token = yield* Config.Redacted("INPUT_GITHUB_TOKEN").pipe(
|
|
187
|
+
Config.orElse(() => Config.Redacted("GITHUB_TOKEN")),
|
|
188
|
+
Config.withDefault(Redacted.make("")),
|
|
189
|
+
);
|
|
190
|
+
return make(token);
|
|
191
|
+
}),
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const client = Effect.service(ActionClient);
|
|
196
|
+
|
|
197
|
+
export const octokit: Effect.Effect<Octokit, never, ActionClient> = client.pipe(
|
|
198
|
+
Effect.map((service) => service.octokit),
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
export const request = (route: string, parameters?: RequestParameters) =>
|
|
202
|
+
client.pipe(Effect.flatMap((service) => service.request(route, parameters)));
|
|
203
|
+
|
|
204
|
+
export const graphql = (query: string, variables?: RequestParameters) =>
|
|
205
|
+
client.pipe(Effect.flatMap((service) => service.graphql(query, variables)));
|
|
206
|
+
|
|
207
|
+
export const paginate = (
|
|
208
|
+
route: string,
|
|
209
|
+
parameters?: RequestParameters,
|
|
210
|
+
options?: PaginateOptions,
|
|
211
|
+
) => client.pipe(Effect.flatMap((service) => service.paginate(route, parameters, options)));
|
|
212
|
+
|
|
213
|
+
export const { layer, layerConfig } = ActionClient;
|
|
214
|
+
|
|
215
|
+
export const call = <A>(method: string, run: (octokit: Octokit) => Promise<A>) =>
|
|
216
|
+
client.pipe(Effect.flatMap((service) => service.call(method, run)));
|
|
217
|
+
|
|
218
|
+
export const requestDecoded = <S extends Schema.Top>(
|
|
219
|
+
schema: S,
|
|
220
|
+
route: string,
|
|
221
|
+
parameters?: RequestParameters,
|
|
222
|
+
) => client.pipe(Effect.flatMap((service) => service.requestDecoded(schema, route, parameters)));
|
|
223
|
+
|
|
224
|
+
export const graphqlDecoded = <S extends Schema.Top>(
|
|
225
|
+
schema: S,
|
|
226
|
+
query: string,
|
|
227
|
+
variables?: RequestParameters,
|
|
228
|
+
) => client.pipe(Effect.flatMap((service) => service.graphqlDecoded(schema, query, variables)));
|
|
229
|
+
|
|
230
|
+
export const paginateDecoded = <S extends Schema.Top>(
|
|
231
|
+
schema: S,
|
|
232
|
+
route: string,
|
|
233
|
+
parameters?: RequestParameters,
|
|
234
|
+
options?: PaginateOptions,
|
|
235
|
+
) =>
|
|
236
|
+
client.pipe(
|
|
237
|
+
Effect.flatMap((service) => service.paginateDecoded(schema, route, parameters, options)),
|
|
238
|
+
);
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { context } from "@actions/github";
|
|
2
|
+
import { thrownMessage } from "./thrown-message.ts";
|
|
3
|
+
import { Context, Effect, Layer, Schema } from "effect";
|
|
4
|
+
import { ActionContextError, ActionContextErrorReason } from "./errors/action-context-error.ts";
|
|
5
|
+
|
|
6
|
+
export interface RepoContext {
|
|
7
|
+
readonly owner: string;
|
|
8
|
+
readonly repo: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface IssueContext extends RepoContext {
|
|
12
|
+
readonly number: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ActionContextApi {
|
|
16
|
+
readonly eventName: string;
|
|
17
|
+
readonly payload: unknown;
|
|
18
|
+
readonly workflow: string;
|
|
19
|
+
readonly action: string;
|
|
20
|
+
readonly actor: string;
|
|
21
|
+
readonly job: string;
|
|
22
|
+
readonly runId: number;
|
|
23
|
+
readonly runNumber: number;
|
|
24
|
+
readonly runAttempt: number;
|
|
25
|
+
readonly ref: string;
|
|
26
|
+
readonly sha: string;
|
|
27
|
+
readonly apiUrl: string;
|
|
28
|
+
readonly serverUrl: string;
|
|
29
|
+
readonly graphqlUrl: string;
|
|
30
|
+
readonly repo: Effect.Effect<RepoContext, ActionContextError>;
|
|
31
|
+
readonly issue: Effect.Effect<IssueContext, ActionContextError>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const readRepository = Effect.fn("ActionContext.readRepository")(function* readRepository<A>(
|
|
35
|
+
read: () => A,
|
|
36
|
+
) {
|
|
37
|
+
return yield* Effect.try({
|
|
38
|
+
catch: (cause) =>
|
|
39
|
+
new ActionContextError({
|
|
40
|
+
cause,
|
|
41
|
+
description: thrownMessage(cause, "GITHUB_REPOSITORY is not set"),
|
|
42
|
+
reason: ActionContextErrorReason.InvalidRepo,
|
|
43
|
+
}),
|
|
44
|
+
try: read,
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
export class ActionContext extends Context.Service<ActionContext, ActionContextApi>()(
|
|
49
|
+
"@computis/effect-github/ActionContext",
|
|
50
|
+
) {
|
|
51
|
+
static readonly layer = Layer.sync(ActionContext, () => ({
|
|
52
|
+
action: context.action,
|
|
53
|
+
actor: context.actor,
|
|
54
|
+
apiUrl: context.apiUrl,
|
|
55
|
+
eventName: context.eventName,
|
|
56
|
+
graphqlUrl: context.graphqlUrl,
|
|
57
|
+
issue: readRepository(() => context.issue),
|
|
58
|
+
job: context.job,
|
|
59
|
+
payload: context.payload,
|
|
60
|
+
ref: context.ref,
|
|
61
|
+
repo: readRepository(() => context.repo),
|
|
62
|
+
runAttempt: context.runAttempt,
|
|
63
|
+
runId: context.runId,
|
|
64
|
+
runNumber: context.runNumber,
|
|
65
|
+
serverUrl: context.serverUrl,
|
|
66
|
+
sha: context.sha,
|
|
67
|
+
workflow: context.workflow,
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const decodePayload = Effect.fn("ActionContext.decodePayload")(function* decodePayload<
|
|
72
|
+
S extends Schema.Top,
|
|
73
|
+
>(schema: S) {
|
|
74
|
+
const current = yield* ActionContext;
|
|
75
|
+
return yield* Schema.decodeUnknownEffect(schema)(current.payload).pipe(
|
|
76
|
+
Effect.mapError(
|
|
77
|
+
(cause) =>
|
|
78
|
+
new ActionContextError({
|
|
79
|
+
cause,
|
|
80
|
+
description: `The ${current.eventName} payload does not match: ${cause.message}`,
|
|
81
|
+
reason: ActionContextErrorReason.InvalidPayload,
|
|
82
|
+
}),
|
|
83
|
+
),
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const actionContext = Effect.service(ActionContext);
|
|
88
|
+
|
|
89
|
+
export const eventName = actionContext.pipe(Effect.map((current) => current.eventName));
|
|
90
|
+
|
|
91
|
+
export const payload = actionContext.pipe(Effect.map((current) => current.payload));
|
|
92
|
+
|
|
93
|
+
export const workflow = actionContext.pipe(Effect.map((current) => current.workflow));
|
|
94
|
+
|
|
95
|
+
export const action = actionContext.pipe(Effect.map((current) => current.action));
|
|
96
|
+
|
|
97
|
+
export const actor = actionContext.pipe(Effect.map((current) => current.actor));
|
|
98
|
+
|
|
99
|
+
export const job = actionContext.pipe(Effect.map((current) => current.job));
|
|
100
|
+
|
|
101
|
+
export const runId = actionContext.pipe(Effect.map((current) => current.runId));
|
|
102
|
+
|
|
103
|
+
export const runNumber = actionContext.pipe(Effect.map((current) => current.runNumber));
|
|
104
|
+
|
|
105
|
+
export const runAttempt = actionContext.pipe(Effect.map((current) => current.runAttempt));
|
|
106
|
+
|
|
107
|
+
export const ref = actionContext.pipe(Effect.map((current) => current.ref));
|
|
108
|
+
|
|
109
|
+
export const sha = actionContext.pipe(Effect.map((current) => current.sha));
|
|
110
|
+
|
|
111
|
+
export const apiUrl = actionContext.pipe(Effect.map((current) => current.apiUrl));
|
|
112
|
+
|
|
113
|
+
export const serverUrl = actionContext.pipe(Effect.map((current) => current.serverUrl));
|
|
114
|
+
|
|
115
|
+
export const graphqlUrl = actionContext.pipe(Effect.map((current) => current.graphqlUrl));
|
|
116
|
+
|
|
117
|
+
export const repo = actionContext.pipe(Effect.flatMap((current) => current.repo));
|
|
118
|
+
|
|
119
|
+
export const issue = actionContext.pipe(Effect.flatMap((current) => current.issue));
|
|
120
|
+
|
|
121
|
+
export const { layer } = ActionContext;
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import * as core from "@actions/core";
|
|
2
|
+
import { thrownMessage } from "./thrown-message.ts";
|
|
3
|
+
import { Context, Effect, Layer, Redacted } from "effect";
|
|
4
|
+
import type { Schema } from "effect";
|
|
5
|
+
import { ActionInputError, ActionInputErrorReason } from "./errors/action-input-error.ts";
|
|
6
|
+
import { ActionOIDCError, ActionOIDCErrorReason } from "./errors/action-oidc-error.ts";
|
|
7
|
+
|
|
8
|
+
export interface InputOptions {
|
|
9
|
+
readonly required?: boolean;
|
|
10
|
+
readonly trimWhitespace?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type AnnotationProperties = core.AnnotationProperties;
|
|
14
|
+
|
|
15
|
+
export interface ActionRunnerApi {
|
|
16
|
+
readonly getInput: (
|
|
17
|
+
name: string,
|
|
18
|
+
options?: InputOptions,
|
|
19
|
+
) => Effect.Effect<string, ActionInputError>;
|
|
20
|
+
readonly getMultilineInput: (
|
|
21
|
+
name: string,
|
|
22
|
+
options?: InputOptions,
|
|
23
|
+
) => Effect.Effect<readonly string[], ActionInputError>;
|
|
24
|
+
readonly getBooleanInput: (
|
|
25
|
+
name: string,
|
|
26
|
+
options?: InputOptions,
|
|
27
|
+
) => Effect.Effect<boolean, ActionInputError>;
|
|
28
|
+
readonly setOutput: (name: string, value: Schema.Json) => Effect.Effect<void>;
|
|
29
|
+
readonly debug: (message: string) => Effect.Effect<void>;
|
|
30
|
+
readonly info: (message: string) => Effect.Effect<void>;
|
|
31
|
+
readonly warning: (
|
|
32
|
+
message: string | Error,
|
|
33
|
+
properties?: AnnotationProperties,
|
|
34
|
+
) => Effect.Effect<void>;
|
|
35
|
+
readonly error: (
|
|
36
|
+
message: string | Error,
|
|
37
|
+
properties?: AnnotationProperties,
|
|
38
|
+
) => Effect.Effect<void>;
|
|
39
|
+
readonly notice: (
|
|
40
|
+
message: string | Error,
|
|
41
|
+
properties?: AnnotationProperties,
|
|
42
|
+
) => Effect.Effect<void>;
|
|
43
|
+
readonly startGroup: (name: string) => Effect.Effect<void>;
|
|
44
|
+
readonly endGroup: () => Effect.Effect<void>;
|
|
45
|
+
readonly group: <A, E, R>(
|
|
46
|
+
name: string,
|
|
47
|
+
fn: () => Effect.Effect<A, E, R>,
|
|
48
|
+
) => Effect.Effect<A, E, R>;
|
|
49
|
+
readonly exportVariable: (name: string, value: string) => Effect.Effect<void>;
|
|
50
|
+
readonly addPath: (path: string) => Effect.Effect<void>;
|
|
51
|
+
readonly setSecret: (secret: string) => Effect.Effect<void>;
|
|
52
|
+
readonly saveState: (name: string, value: Schema.Json) => Effect.Effect<void>;
|
|
53
|
+
readonly getState: (name: string) => Effect.Effect<string>;
|
|
54
|
+
readonly setFailed: (message: string | Error) => Effect.Effect<void>;
|
|
55
|
+
readonly getIDToken: (audience?: string) => Effect.Effect<Redacted.Redacted, ActionOIDCError>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const readInput = Effect.fn("ActionRunner.readInput")(function* readInput<A>(
|
|
59
|
+
name: string,
|
|
60
|
+
read: () => A,
|
|
61
|
+
) {
|
|
62
|
+
return yield* Effect.try({
|
|
63
|
+
catch: (cause) => new ActionInputError({ cause, name, reason: ActionInputErrorReason.Missing }),
|
|
64
|
+
try: read,
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export class ActionRunner extends Context.Service<ActionRunner, ActionRunnerApi>()(
|
|
69
|
+
"@computis/effect-github/ActionRunner",
|
|
70
|
+
) {
|
|
71
|
+
static readonly layer = Layer.succeed(ActionRunner, {
|
|
72
|
+
addPath: (path) =>
|
|
73
|
+
Effect.sync(() => {
|
|
74
|
+
core.addPath(path);
|
|
75
|
+
}),
|
|
76
|
+
debug: (message) =>
|
|
77
|
+
Effect.sync(() => {
|
|
78
|
+
core.debug(message);
|
|
79
|
+
}),
|
|
80
|
+
endGroup: () =>
|
|
81
|
+
Effect.sync(() => {
|
|
82
|
+
core.endGroup();
|
|
83
|
+
}),
|
|
84
|
+
error: (message, properties) =>
|
|
85
|
+
Effect.sync(() => {
|
|
86
|
+
core.error(message, properties);
|
|
87
|
+
}),
|
|
88
|
+
exportVariable: (name, value) =>
|
|
89
|
+
Effect.sync(() => {
|
|
90
|
+
core.exportVariable(name, value);
|
|
91
|
+
}),
|
|
92
|
+
getBooleanInput: Effect.fn("ActionRunner.getBooleanInput")(function* getBooleanInput(
|
|
93
|
+
name: string,
|
|
94
|
+
options?: InputOptions,
|
|
95
|
+
) {
|
|
96
|
+
return yield* Effect.try({
|
|
97
|
+
catch: (cause) =>
|
|
98
|
+
new ActionInputError({ cause, name, reason: ActionInputErrorReason.InvalidType }),
|
|
99
|
+
try: () => core.getBooleanInput(name, options),
|
|
100
|
+
});
|
|
101
|
+
}),
|
|
102
|
+
getIDToken: Effect.fn("ActionRunner.getIDToken")(function* getIDToken(audience?: string) {
|
|
103
|
+
return yield* Effect.tryPromise({
|
|
104
|
+
catch: (cause) =>
|
|
105
|
+
new ActionOIDCError({
|
|
106
|
+
cause,
|
|
107
|
+
description: thrownMessage(cause, "the token request was rejected"),
|
|
108
|
+
reason: ActionOIDCErrorReason.RequestFailed,
|
|
109
|
+
}),
|
|
110
|
+
try: async () => Redacted.make(await core.getIDToken(audience)),
|
|
111
|
+
});
|
|
112
|
+
}),
|
|
113
|
+
getInput: (name, options) => readInput(name, () => core.getInput(name, options)),
|
|
114
|
+
getMultilineInput: (name, options) =>
|
|
115
|
+
readInput(name, () => core.getMultilineInput(name, options)),
|
|
116
|
+
getState: (name) => Effect.sync(() => core.getState(name)),
|
|
117
|
+
group: <A, E, R>(name: string, fn: () => Effect.Effect<A, E, R>) =>
|
|
118
|
+
Effect.acquireUseRelease(
|
|
119
|
+
Effect.sync(() => {
|
|
120
|
+
core.startGroup(name);
|
|
121
|
+
}),
|
|
122
|
+
fn,
|
|
123
|
+
() =>
|
|
124
|
+
Effect.sync(() => {
|
|
125
|
+
core.endGroup();
|
|
126
|
+
}),
|
|
127
|
+
).pipe(Effect.withSpan("ActionRunner.group", { attributes: { group: name } })),
|
|
128
|
+
info: (message) =>
|
|
129
|
+
Effect.sync(() => {
|
|
130
|
+
core.info(message);
|
|
131
|
+
}),
|
|
132
|
+
notice: (message, properties) =>
|
|
133
|
+
Effect.sync(() => {
|
|
134
|
+
core.notice(message, properties);
|
|
135
|
+
}),
|
|
136
|
+
saveState: (name, value) =>
|
|
137
|
+
Effect.sync(() => {
|
|
138
|
+
core.saveState(name, value);
|
|
139
|
+
}),
|
|
140
|
+
setFailed: (message) =>
|
|
141
|
+
Effect.sync(() => {
|
|
142
|
+
core.setFailed(message);
|
|
143
|
+
}),
|
|
144
|
+
setOutput: (name, value) =>
|
|
145
|
+
Effect.sync(() => {
|
|
146
|
+
core.setOutput(name, value);
|
|
147
|
+
}),
|
|
148
|
+
setSecret: (secret) =>
|
|
149
|
+
Effect.sync(() => {
|
|
150
|
+
core.setSecret(secret);
|
|
151
|
+
}),
|
|
152
|
+
startGroup: (name) =>
|
|
153
|
+
Effect.sync(() => {
|
|
154
|
+
core.startGroup(name);
|
|
155
|
+
}),
|
|
156
|
+
warning: (message, properties) =>
|
|
157
|
+
Effect.sync(() => {
|
|
158
|
+
core.warning(message, properties);
|
|
159
|
+
}),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const runner = Effect.service(ActionRunner);
|
|
164
|
+
|
|
165
|
+
export const getInput = (name: string, options?: InputOptions) =>
|
|
166
|
+
runner.pipe(Effect.flatMap((service) => service.getInput(name, options)));
|
|
167
|
+
|
|
168
|
+
export const getMultilineInput = (name: string, options?: InputOptions) =>
|
|
169
|
+
runner.pipe(Effect.flatMap((service) => service.getMultilineInput(name, options)));
|
|
170
|
+
|
|
171
|
+
export const getBooleanInput = (name: string, options?: InputOptions) =>
|
|
172
|
+
runner.pipe(Effect.flatMap((service) => service.getBooleanInput(name, options)));
|
|
173
|
+
|
|
174
|
+
export const setOutput = (name: string, value: Schema.Json) =>
|
|
175
|
+
runner.pipe(Effect.flatMap((service) => service.setOutput(name, value)));
|
|
176
|
+
|
|
177
|
+
export const debug = (message: string) =>
|
|
178
|
+
runner.pipe(Effect.flatMap((service) => service.debug(message)));
|
|
179
|
+
|
|
180
|
+
export const info = (message: string) =>
|
|
181
|
+
runner.pipe(Effect.flatMap((service) => service.info(message)));
|
|
182
|
+
|
|
183
|
+
export const warning = (message: string | Error, properties?: AnnotationProperties) =>
|
|
184
|
+
runner.pipe(Effect.flatMap((service) => service.warning(message, properties)));
|
|
185
|
+
|
|
186
|
+
export const error = (message: string | Error, properties?: AnnotationProperties) =>
|
|
187
|
+
runner.pipe(Effect.flatMap((service) => service.error(message, properties)));
|
|
188
|
+
|
|
189
|
+
export const notice = (message: string | Error, properties?: AnnotationProperties) =>
|
|
190
|
+
runner.pipe(Effect.flatMap((service) => service.notice(message, properties)));
|
|
191
|
+
|
|
192
|
+
export const startGroup = (name: string) =>
|
|
193
|
+
runner.pipe(Effect.flatMap((service) => service.startGroup(name)));
|
|
194
|
+
|
|
195
|
+
export const endGroup = runner.pipe(Effect.flatMap((service) => service.endGroup()));
|
|
196
|
+
|
|
197
|
+
export const group = <A, E, R>(name: string, fn: () => Effect.Effect<A, E, R>) =>
|
|
198
|
+
runner.pipe(Effect.flatMap((service) => service.group(name, fn)));
|
|
199
|
+
|
|
200
|
+
export const exportVariable = (name: string, value: string) =>
|
|
201
|
+
runner.pipe(Effect.flatMap((service) => service.exportVariable(name, value)));
|
|
202
|
+
|
|
203
|
+
export const addPath = (path: string) =>
|
|
204
|
+
runner.pipe(Effect.flatMap((service) => service.addPath(path)));
|
|
205
|
+
|
|
206
|
+
export const setSecret = (secret: string) =>
|
|
207
|
+
runner.pipe(Effect.flatMap((service) => service.setSecret(secret)));
|
|
208
|
+
|
|
209
|
+
export const saveState = (name: string, value: Schema.Json) =>
|
|
210
|
+
runner.pipe(Effect.flatMap((service) => service.saveState(name, value)));
|
|
211
|
+
|
|
212
|
+
export const getState = (name: string) =>
|
|
213
|
+
runner.pipe(Effect.flatMap((service) => service.getState(name)));
|
|
214
|
+
|
|
215
|
+
export const setFailed = (message: string | Error) =>
|
|
216
|
+
runner.pipe(Effect.flatMap((service) => service.setFailed(message)));
|
|
217
|
+
|
|
218
|
+
export const getIDToken = (audience?: string) =>
|
|
219
|
+
runner.pipe(Effect.flatMap((service) => service.getIDToken(audience)));
|
|
220
|
+
|
|
221
|
+
export const { layer } = ActionRunner;
|