@effected/github-actions 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/Action.js +138 -0
- package/ActionCache.js +317 -0
- package/ActionEnvironment.js +255 -0
- package/ActionInput.js +346 -0
- package/ActionLogger.js +205 -0
- package/ActionOutputs.js +135 -0
- package/ActionState.js +117 -0
- package/Artifact.js +423 -0
- package/BlobEnvelope.js +135 -0
- package/BlobStore.githubCache.js +183 -0
- package/BlobStore.js +203 -0
- package/BlobTransfer.js +21 -0
- package/CacheKey.js +234 -0
- package/DetachedProcess.js +231 -0
- package/DryRun.js +73 -0
- package/GitHubToken.js +226 -0
- package/LICENSE +21 -0
- package/OidcTokenIssuer.js +246 -0
- package/Secret.js +106 -0
- package/ToolInstaller.js +293 -0
- package/WorkflowCommand.js +100 -0
- package/index.d.ts +2467 -0
- package/index.js +22 -0
- package/internal/actionsResults.js +98 -0
- package/internal/sigv4.js +117 -0
- package/internal/twirp.js +97 -0
- package/package.json +52 -0
- package/tsdoc-metadata.json +11 -0
package/index.d.ts
ADDED
|
@@ -0,0 +1,2467 @@
|
|
|
1
|
+
import { NodeServices } from "@effect/platform-node";
|
|
2
|
+
import { Cause, Config, ConfigProvider, Context, Duration, Effect, FileSystem, Layer, Logger, Option, Path, Redacted, Result, Schema } from "effect";
|
|
3
|
+
import { HttpClient } from "effect/unstable/http";
|
|
4
|
+
import { ChildProcessSpawner } from "effect/unstable/process";
|
|
5
|
+
import { BotIdentity, GitHubApp, GitHubClient, InstallationToken, PermissionLevel, RetryPolicy } from "@effected/github";
|
|
6
|
+
//#region src/ActionEnvironment.d.ts
|
|
7
|
+
declare const ActionEnvironmentError_base: Schema.Class<ActionEnvironmentError, Schema.TaggedStruct<"ActionEnvironmentError", {
|
|
8
|
+
readonly reason: Schema.Literals<readonly ["missing", "malformed"]>;
|
|
9
|
+
/** The environment variable involved. */
|
|
10
|
+
readonly name: Schema.String;
|
|
11
|
+
/** What was wrong, when the reason is `malformed`. */
|
|
12
|
+
readonly detail: Schema.optionalKey<Schema.String>;
|
|
13
|
+
}>, import("effect/Cause").YieldableError>;
|
|
14
|
+
/**
|
|
15
|
+
* Raised when the runner environment does not say what an action needs.
|
|
16
|
+
*
|
|
17
|
+
* @remarks
|
|
18
|
+
* Two reasons, and the distinction is actionable: `missing` means the variable
|
|
19
|
+
* is absent or empty (the workflow did not grant something, or the action is
|
|
20
|
+
* running outside Actions); `malformed` means it is present but unusable (a
|
|
21
|
+
* non-numeric run id, an unparseable event payload). The first is usually a
|
|
22
|
+
* workflow fix, the second usually a bug report.
|
|
23
|
+
*
|
|
24
|
+
* @public
|
|
25
|
+
*/
|
|
26
|
+
declare class ActionEnvironmentError extends ActionEnvironmentError_base {
|
|
27
|
+
get message(): string;
|
|
28
|
+
}
|
|
29
|
+
declare const GitHubContext_base: Schema.Class<GitHubContext, Schema.Struct<{
|
|
30
|
+
/** `owner/repo`. */
|
|
31
|
+
readonly repository: Schema.String;
|
|
32
|
+
readonly repositoryOwner: Schema.String;
|
|
33
|
+
/** The full ref, e.g. `refs/heads/main`. */
|
|
34
|
+
readonly ref: Schema.String;
|
|
35
|
+
/** The short ref, e.g. `main`. */
|
|
36
|
+
readonly refName: Schema.String;
|
|
37
|
+
readonly sha: Schema.String;
|
|
38
|
+
readonly workflow: Schema.String;
|
|
39
|
+
readonly job: Schema.String;
|
|
40
|
+
readonly runId: Schema.Number;
|
|
41
|
+
readonly runAttempt: Schema.Number;
|
|
42
|
+
readonly eventName: Schema.String;
|
|
43
|
+
readonly actor: Schema.String;
|
|
44
|
+
readonly serverUrl: Schema.String;
|
|
45
|
+
readonly apiUrl: Schema.String;
|
|
46
|
+
readonly graphqlUrl: Schema.String;
|
|
47
|
+
readonly workspace: Schema.String;
|
|
48
|
+
}>, {}>;
|
|
49
|
+
/**
|
|
50
|
+
* The workflow's GitHub context, projected from the `GITHUB_*` variables.
|
|
51
|
+
*
|
|
52
|
+
* @public
|
|
53
|
+
*/
|
|
54
|
+
declare class GitHubContext extends GitHubContext_base {}
|
|
55
|
+
declare const RunnerContext_base: Schema.Class<RunnerContext, Schema.Struct<{
|
|
56
|
+
readonly os: Schema.String;
|
|
57
|
+
readonly arch: Schema.String;
|
|
58
|
+
readonly name: Schema.String;
|
|
59
|
+
/** A scratch directory emptied between jobs. */
|
|
60
|
+
readonly temp: Schema.String;
|
|
61
|
+
/** Where `ToolInstaller` caches toolchains. */
|
|
62
|
+
readonly toolCache: Schema.String;
|
|
63
|
+
}>, {}>;
|
|
64
|
+
/**
|
|
65
|
+
* The runner's own context, projected from the `RUNNER_*` variables.
|
|
66
|
+
*
|
|
67
|
+
* @public
|
|
68
|
+
*/
|
|
69
|
+
declare class RunnerContext extends RunnerContext_base {}
|
|
70
|
+
/**
|
|
71
|
+
* The {@link ActionEnvironment} service shape.
|
|
72
|
+
*
|
|
73
|
+
* @public
|
|
74
|
+
*/
|
|
75
|
+
interface ActionEnvironmentShape {
|
|
76
|
+
/** A required variable. Absent or empty fails typed. */
|
|
77
|
+
readonly get: (name: string) => Effect.Effect<string, ActionEnvironmentError>;
|
|
78
|
+
/** An optional variable. Empty reads as absent, as the runner treats it. */
|
|
79
|
+
readonly getOptional: (name: string) => Effect.Effect<Option.Option<string>>;
|
|
80
|
+
/** The `GITHUB_*` context. */
|
|
81
|
+
readonly github: Effect.Effect<GitHubContext, ActionEnvironmentError>;
|
|
82
|
+
/** The `RUNNER_*` context. */
|
|
83
|
+
readonly runner: Effect.Effect<RunnerContext, ActionEnvironmentError>;
|
|
84
|
+
/** Whether the runner is in step-debug mode. */
|
|
85
|
+
readonly isDebug: Effect.Effect<boolean>;
|
|
86
|
+
/**
|
|
87
|
+
* The webhook event payload.
|
|
88
|
+
*
|
|
89
|
+
* @remarks
|
|
90
|
+
* `R` is `never`: the layer resolves `FileSystem` once at construction, so
|
|
91
|
+
* a caller never re-injects one to keep its own requirements clean.
|
|
92
|
+
*/
|
|
93
|
+
readonly payload: Effect.Effect<unknown, ActionEnvironmentError>;
|
|
94
|
+
/**
|
|
95
|
+
* Run an effect with environment overrides applied.
|
|
96
|
+
*
|
|
97
|
+
* @remarks
|
|
98
|
+
* Fiber-local and parallel-safe — `process.env` is never mutated, so
|
|
99
|
+
* concurrent callers cannot corrupt each other and there is nothing to
|
|
100
|
+
* restore. Overrides **merge** with any enclosing `withEnv`.
|
|
101
|
+
*
|
|
102
|
+
* The trade, stated: a variable exported mid-run by
|
|
103
|
+
* `ActionOutputs.exportVariable` is not observed here, because the snapshot
|
|
104
|
+
* is taken once. That matches GitHub's own model, where `exportVariable`
|
|
105
|
+
* targets *subsequent* steps rather than the running one.
|
|
106
|
+
*/
|
|
107
|
+
readonly withEnv: <A, E, R>(overrides: Readonly<Record<string, string>>, effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
|
|
108
|
+
}
|
|
109
|
+
declare const ActionEnvironment_base: Context.ServiceClass<ActionEnvironment, "@effected/github-actions/ActionEnvironment", ActionEnvironmentShape>;
|
|
110
|
+
/**
|
|
111
|
+
* The runner environment an action is executing inside.
|
|
112
|
+
*
|
|
113
|
+
* @remarks
|
|
114
|
+
* The single reader of `process.env` in this package. Everything else — inputs,
|
|
115
|
+
* outputs, state, the token bridge — goes through a service rather than
|
|
116
|
+
* reaching for a global, which is what makes them all testable without
|
|
117
|
+
* mutating the test process.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```ts
|
|
121
|
+
* import { ActionEnvironment } from "@effected/github-actions";
|
|
122
|
+
* import { Effect } from "effect";
|
|
123
|
+
*
|
|
124
|
+
* const program = Effect.gen(function* () {
|
|
125
|
+
* const env = yield* ActionEnvironment;
|
|
126
|
+
* const { repository, runId } = yield* env.github;
|
|
127
|
+
* return `${repository}#${runId}`;
|
|
128
|
+
* });
|
|
129
|
+
* ```
|
|
130
|
+
*
|
|
131
|
+
* @public
|
|
132
|
+
*/
|
|
133
|
+
declare class ActionEnvironment extends ActionEnvironment_base {
|
|
134
|
+
/** Seeded from `process.env`, once, at layer construction. */
|
|
135
|
+
static readonly layer: Layer.Layer<ActionEnvironment, never, FileSystem.FileSystem>;
|
|
136
|
+
/**
|
|
137
|
+
* Seeded from an explicit record.
|
|
138
|
+
*
|
|
139
|
+
* @remarks
|
|
140
|
+
* A parameterized layer factory mints a fresh reference per call and layers
|
|
141
|
+
* memoize by reference — bind it to a `const` rather than calling it at each
|
|
142
|
+
* composition site.
|
|
143
|
+
*/
|
|
144
|
+
static readonly layerFrom: (env: Readonly<Record<string, string>>) => Layer.Layer<ActionEnvironment, never, FileSystem.FileSystem>;
|
|
145
|
+
/**
|
|
146
|
+
* A test double seeded with a complete `GITHUB_*` / `RUNNER_*` block.
|
|
147
|
+
*
|
|
148
|
+
* @remarks
|
|
149
|
+
* **A recorded exception to the die-on-unstubbed rule.** Every other service
|
|
150
|
+
* in this package dies when a suite calls something it did not stub, because
|
|
151
|
+
* a plausible default hides a real gap. Here the opposite is true: the twelve
|
|
152
|
+
* context variables have one obviously-correct shape, and requiring each
|
|
153
|
+
* suite to restate them produced a byte-identical block duplicated six times
|
|
154
|
+
* across the consumers. Overrides merge on top.
|
|
155
|
+
*/
|
|
156
|
+
static readonly makeTest: (overrides?: Readonly<Record<string, string>>) => Effect.Effect<ActionEnvironmentShape, never, FileSystem.FileSystem>;
|
|
157
|
+
/** {@link ActionEnvironment.makeTest} behind a layer, with `FileSystem` stubbed out. */
|
|
158
|
+
static readonly layerTest: (overrides?: Readonly<Record<string, string>>) => Layer.Layer<ActionEnvironment>;
|
|
159
|
+
}
|
|
160
|
+
//#endregion
|
|
161
|
+
//#region src/WorkflowCommand.d.ts
|
|
162
|
+
/**
|
|
163
|
+
* Where an annotation points in the repository.
|
|
164
|
+
*
|
|
165
|
+
* @remarks
|
|
166
|
+
* The field names here are the readable ones; GitHub's wire protocol uses
|
|
167
|
+
* abbreviations (`line`, `col`) that this module maps on the way out, so a
|
|
168
|
+
* caller never has to remember which of the six is abbreviated.
|
|
169
|
+
*
|
|
170
|
+
* @public
|
|
171
|
+
*/
|
|
172
|
+
interface AnnotationProperties {
|
|
173
|
+
/** A short title shown above the annotation. */
|
|
174
|
+
readonly title?: string;
|
|
175
|
+
/** Repository-relative path of the annotated file. */
|
|
176
|
+
readonly file?: string;
|
|
177
|
+
/** First annotated line, 1-based. */
|
|
178
|
+
readonly startLine?: number;
|
|
179
|
+
/** Last annotated line, 1-based. */
|
|
180
|
+
readonly endLine?: number;
|
|
181
|
+
/** First annotated column, 1-based. */
|
|
182
|
+
readonly startColumn?: number;
|
|
183
|
+
/** Last annotated column, 1-based. */
|
|
184
|
+
readonly endColumn?: number;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* The GitHub Actions workflow-command wire protocol: `::name key=value::message`.
|
|
188
|
+
*
|
|
189
|
+
* @remarks
|
|
190
|
+
* **Pure.** This module renders strings and nothing else — it performs no IO
|
|
191
|
+
* and holds no service — which is what makes the escaping rules testable
|
|
192
|
+
* without a runner, and what lets a non-Actions consumer reuse the protocol.
|
|
193
|
+
* Writing a rendered command to stdout is {@link ActionOutputs}'s job.
|
|
194
|
+
*
|
|
195
|
+
* The escaping is the whole point of the module. A message carrying a raw
|
|
196
|
+
* newline does not merely render oddly: the runner reads the text after it as
|
|
197
|
+
* a **new command**, so an unescaped log line is a command-injection vector.
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```ts
|
|
201
|
+
* import { WorkflowCommand } from "@effected/github-actions";
|
|
202
|
+
*
|
|
203
|
+
* WorkflowCommand.error("build failed", { file: "src/main.ts", startLine: 12 });
|
|
204
|
+
* // "::error file=src/main.ts,line=12::build failed"
|
|
205
|
+
* ```
|
|
206
|
+
*
|
|
207
|
+
* @public
|
|
208
|
+
*/
|
|
209
|
+
declare class WorkflowCommand {
|
|
210
|
+
private constructor();
|
|
211
|
+
/**
|
|
212
|
+
* Render an arbitrary command. The primitive every other member uses.
|
|
213
|
+
*
|
|
214
|
+
* @remarks
|
|
215
|
+
* The property type is written out structurally rather than as the module's
|
|
216
|
+
* `CommandProperties` alias: an internal named type on a `@public` signature
|
|
217
|
+
* fails the API Extractor gate, and neither an `@internal` tag nor a second
|
|
218
|
+
* alias helps — only inlining does.
|
|
219
|
+
*/
|
|
220
|
+
static render(name: string, properties: Readonly<Record<string, string | number | boolean | undefined>>, message: string): string;
|
|
221
|
+
/** `::debug::` — shown only when `ACTIONS_STEP_DEBUG` is enabled. */
|
|
222
|
+
static debug(message: string): string;
|
|
223
|
+
/** `::notice::` with optional source annotation. */
|
|
224
|
+
static notice(message: string, properties?: AnnotationProperties): string;
|
|
225
|
+
/** `::warning::` with optional source annotation. */
|
|
226
|
+
static warning(message: string, properties?: AnnotationProperties): string;
|
|
227
|
+
/** `::error::` with optional source annotation. */
|
|
228
|
+
static error(message: string, properties?: AnnotationProperties): string;
|
|
229
|
+
/** `::group::` — opens a collapsible section in the runner log. */
|
|
230
|
+
static group(name: string): string;
|
|
231
|
+
/** `::endgroup::` — closes the innermost open group. */
|
|
232
|
+
static endGroup(): string;
|
|
233
|
+
/** `::add-mask::` — registers a value for redaction in the runner log. */
|
|
234
|
+
static addMask(value: string): string;
|
|
235
|
+
/** Map readable annotation fields onto GitHub's abbreviated wire names. */
|
|
236
|
+
private static annotation;
|
|
237
|
+
}
|
|
238
|
+
//#endregion
|
|
239
|
+
//#region src/ActionLogger.d.ts
|
|
240
|
+
/**
|
|
241
|
+
* The {@link ActionLogger} service shape.
|
|
242
|
+
*
|
|
243
|
+
* @public
|
|
244
|
+
*/
|
|
245
|
+
interface ActionLoggerShape {
|
|
246
|
+
/** Run an effect inside a collapsible log group. */
|
|
247
|
+
readonly group: <A, E, R>(name: string, effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
|
|
248
|
+
/**
|
|
249
|
+
* Run an effect with its verbose output captured and replayed on exit.
|
|
250
|
+
*
|
|
251
|
+
* @remarks
|
|
252
|
+
* Warnings and errors are **not** buffered — they go out as they happen, so a
|
|
253
|
+
* long step still reports trouble while it is running. Everything at `Info`
|
|
254
|
+
* and below is held and flushed on every exit path, including a defect or an
|
|
255
|
+
* interruption, so a clean run still prints its transcript.
|
|
256
|
+
*
|
|
257
|
+
* Buffering is skipped entirely when the runner has step debugging enabled or
|
|
258
|
+
* the ambient minimum log level is already `Debug` or lower — someone asking
|
|
259
|
+
* for verbose output wants it live.
|
|
260
|
+
*/
|
|
261
|
+
readonly withBuffer: <A, E, R>(label: string, effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
|
|
262
|
+
/**
|
|
263
|
+
* Emit a `::notice::` annotation.
|
|
264
|
+
*
|
|
265
|
+
* @remarks
|
|
266
|
+
* A dedicated member rather than a log level, because Effect has no level
|
|
267
|
+
* between `Info` and `Warn` to map onto notices.
|
|
268
|
+
*/
|
|
269
|
+
readonly notice: (message: string, properties?: AnnotationProperties) => Effect.Effect<void>;
|
|
270
|
+
/**
|
|
271
|
+
* Attach source annotations to every `Effect.log*` inside an effect.
|
|
272
|
+
*
|
|
273
|
+
* @remarks
|
|
274
|
+
* The point is that a caller never spells an annotation key: the fields go in
|
|
275
|
+
* as {@link AnnotationProperties} and come out as `file=`/`line=` on the
|
|
276
|
+
* rendered command.
|
|
277
|
+
*/
|
|
278
|
+
readonly annotated: <A, E, R>(properties: AnnotationProperties, effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
|
|
279
|
+
}
|
|
280
|
+
declare const ActionLogger_base: Context.ServiceClass<ActionLogger, "@effected/github-actions/ActionLogger", ActionLoggerShape>;
|
|
281
|
+
/**
|
|
282
|
+
* Groups, buffered step transcripts, notices — and the `Logger` that renders
|
|
283
|
+
* every `Effect.log*` in the kit as a workflow command.
|
|
284
|
+
*
|
|
285
|
+
* @remarks
|
|
286
|
+
* The `Logger` is the seam that lets every other `@effected` package stay
|
|
287
|
+
* telemetry-agnostic: libraries call `Effect.logWarning`, and exactly one
|
|
288
|
+
* logger — installed at the edge by an action — turns those into `::warning::`
|
|
289
|
+
* annotations. No library needs to know it is running inside Actions.
|
|
290
|
+
*
|
|
291
|
+
* @example
|
|
292
|
+
* ```ts
|
|
293
|
+
* import { ActionLogger } from "@effected/github-actions";
|
|
294
|
+
* import { Effect } from "effect";
|
|
295
|
+
*
|
|
296
|
+
* const program = Effect.gen(function* () {
|
|
297
|
+
* const logger = yield* ActionLogger;
|
|
298
|
+
* yield* logger.group("install", logger.withBuffer("pnpm", Effect.logInfo("resolving")));
|
|
299
|
+
* });
|
|
300
|
+
* ```
|
|
301
|
+
*
|
|
302
|
+
* @public
|
|
303
|
+
*/
|
|
304
|
+
declare class ActionLogger extends ActionLogger_base {
|
|
305
|
+
static readonly layer: Layer.Layer<ActionLogger, never, ActionEnvironment>;
|
|
306
|
+
/**
|
|
307
|
+
* The `Logger` that maps Effect log levels onto workflow commands.
|
|
308
|
+
*
|
|
309
|
+
* @remarks
|
|
310
|
+
* `Error` and `Fatal` render `::error::`, `Warn` renders `::warning::`,
|
|
311
|
+
* `Debug` and `Trace` render `::debug::`, and `Info` renders as plain text.
|
|
312
|
+
* Annotations set by `annotated` travel with the entry.
|
|
313
|
+
*
|
|
314
|
+
* It writes through core `Console`, which is what makes its output
|
|
315
|
+
* observable in a test without a runner.
|
|
316
|
+
*/
|
|
317
|
+
static readonly logger: Logger.Logger<unknown, void>;
|
|
318
|
+
/** {@link ActionLogger.logger} installed as the only logger. */
|
|
319
|
+
static readonly layerLogger: Layer.Layer<never>;
|
|
320
|
+
/**
|
|
321
|
+
* A silent double.
|
|
322
|
+
*
|
|
323
|
+
* @remarks
|
|
324
|
+
* **A recorded exception to the die-on-unstubbed rule**, alongside
|
|
325
|
+
* {@link ActionEnvironment.makeTest}. A logger that dies when a suite logs
|
|
326
|
+
* would make every double unusable, and silence is the honest default for a
|
|
327
|
+
* service whose whole job is output. Group and buffer wrappers pass their
|
|
328
|
+
* effect through unchanged.
|
|
329
|
+
*/
|
|
330
|
+
static readonly makeTest: (overrides?: Partial<ActionLoggerShape>) => ActionLoggerShape;
|
|
331
|
+
/** {@link ActionLogger.makeTest} behind `Layer.succeed`. */
|
|
332
|
+
static readonly layerTest: (overrides?: Partial<ActionLoggerShape>) => Layer.Layer<ActionLogger>;
|
|
333
|
+
/**
|
|
334
|
+
* A silent service **and** a silent `Effect.log*`.
|
|
335
|
+
*
|
|
336
|
+
* @remarks
|
|
337
|
+
* This one layer replaces the bare `Effect.provide(Logger.layer([]))` that
|
|
338
|
+
* appeared thirteen times in one consuming action and twenty-three in
|
|
339
|
+
* another, purely to keep test output quiet. Bound to a constant rather than
|
|
340
|
+
* exposed as a factory, so composing it twice is free.
|
|
341
|
+
*/
|
|
342
|
+
static readonly layerSilent: Layer.Layer<ActionLogger>;
|
|
343
|
+
}
|
|
344
|
+
//#endregion
|
|
345
|
+
//#region src/ActionOutputs.d.ts
|
|
346
|
+
declare const ActionOutputError_base: Schema.Class<ActionOutputError, Schema.TaggedStruct<"ActionOutputError", {
|
|
347
|
+
/**
|
|
348
|
+
* `unavailable` — the runner file variable is not set, which usually means
|
|
349
|
+
* the code is running outside a GitHub runner. `writeFailed` — the file
|
|
350
|
+
* exists but could not be appended to. `invalidName` — the name would
|
|
351
|
+
* corrupt the file's block structure. `encodeFailed` — a value did not
|
|
352
|
+
* satisfy its schema.
|
|
353
|
+
*/
|
|
354
|
+
readonly reason: Schema.Literals<readonly ["unavailable", "writeFailed", "invalidName", "encodeFailed"]>;
|
|
355
|
+
/** Which runner file was involved, by environment variable name. */
|
|
356
|
+
readonly file: Schema.optionalKey<Schema.String>;
|
|
357
|
+
/** The output or variable name, when one is involved. */
|
|
358
|
+
readonly name: Schema.optionalKey<Schema.String>;
|
|
359
|
+
/** The underlying failure, preserved structurally. */
|
|
360
|
+
readonly cause: Schema.optionalKey<Schema.Defect>;
|
|
361
|
+
}>, import("effect/Cause").YieldableError>;
|
|
362
|
+
/**
|
|
363
|
+
* Raised when an action cannot publish an output.
|
|
364
|
+
*
|
|
365
|
+
* @public
|
|
366
|
+
*/
|
|
367
|
+
declare class ActionOutputError extends ActionOutputError_base {
|
|
368
|
+
get message(): string;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* The {@link ActionOutputs} service shape.
|
|
372
|
+
*
|
|
373
|
+
* @public
|
|
374
|
+
*/
|
|
375
|
+
interface ActionOutputsShape {
|
|
376
|
+
/** Publish a step output. */
|
|
377
|
+
readonly set: (name: string, value: string) => Effect.Effect<void, ActionOutputError>;
|
|
378
|
+
/** Publish a step output as JSON, encoded through a schema. */
|
|
379
|
+
readonly setJson: <A, I>(name: string, value: A, schema: Schema.Codec<A, I>) => Effect.Effect<void, ActionOutputError>;
|
|
380
|
+
/** Append to the job summary. */
|
|
381
|
+
readonly summary: (content: string) => Effect.Effect<void, ActionOutputError>;
|
|
382
|
+
/** Export an environment variable to **subsequent** steps. */
|
|
383
|
+
readonly exportVariable: (name: string, value: string) => Effect.Effect<void, ActionOutputError>;
|
|
384
|
+
/** Prepend a directory to `PATH` for subsequent steps. */
|
|
385
|
+
readonly addPath: (path: string) => Effect.Effect<void, ActionOutputError>;
|
|
386
|
+
/** Emit an error annotation. Does not itself set the exit code. */
|
|
387
|
+
readonly setFailed: (message: string) => Effect.Effect<void>;
|
|
388
|
+
/** Register a value for redaction in the runner log. */
|
|
389
|
+
readonly setSecret: (value: string) => Effect.Effect<void>;
|
|
390
|
+
}
|
|
391
|
+
declare const ActionOutputs_base: Context.ServiceClass<ActionOutputs, "@effected/github-actions/ActionOutputs", ActionOutputsShape>;
|
|
392
|
+
/**
|
|
393
|
+
* Everything an action publishes: step outputs, exported variables, `PATH`
|
|
394
|
+
* additions, the job summary, log masking and failure annotations.
|
|
395
|
+
*
|
|
396
|
+
* @remarks
|
|
397
|
+
* Outputs and variables go to the runner's **files** (`GITHUB_OUTPUT` and
|
|
398
|
+
* friends); masking and annotations go to stdout as workflow commands, through
|
|
399
|
+
* core `Console` so a test can observe them without a runner.
|
|
400
|
+
*
|
|
401
|
+
* `setFailed` emits the annotation but does **not** set the process exit code
|
|
402
|
+
* — that is `Action.run`'s job, so that an action which reports a failure and
|
|
403
|
+
* then recovers is not silently doomed by a side effect it cannot undo.
|
|
404
|
+
*
|
|
405
|
+
* @public
|
|
406
|
+
*/
|
|
407
|
+
declare class ActionOutputs extends ActionOutputs_base {
|
|
408
|
+
static readonly layer: Layer.Layer<ActionOutputs, never, ActionEnvironment | FileSystem.FileSystem>;
|
|
409
|
+
/** A test double. Unstubbed members die rather than silently succeeding. */
|
|
410
|
+
static readonly makeTest: (overrides?: Partial<ActionOutputsShape>) => ActionOutputsShape;
|
|
411
|
+
/** {@link ActionOutputs.makeTest} behind `Layer.succeed`. */
|
|
412
|
+
static readonly layerTest: (overrides?: Partial<ActionOutputsShape>) => Layer.Layer<ActionOutputs>;
|
|
413
|
+
}
|
|
414
|
+
//#endregion
|
|
415
|
+
//#region src/ActionState.d.ts
|
|
416
|
+
declare const ActionStateError_base: Schema.Class<ActionStateError, Schema.TaggedStruct<"ActionStateError", {
|
|
417
|
+
/**
|
|
418
|
+
* `missing` — no value was saved under this key in an earlier phase.
|
|
419
|
+
* `malformed` — a value is there but is not JSON, or does not satisfy the
|
|
420
|
+
* schema it was read with. `writeFailed` — the state file could not be
|
|
421
|
+
* appended to.
|
|
422
|
+
*/
|
|
423
|
+
readonly reason: Schema.Literals<readonly ["missing", "malformed", "writeFailed"]>;
|
|
424
|
+
readonly key: Schema.String;
|
|
425
|
+
readonly cause: Schema.optionalKey<Schema.Defect>;
|
|
426
|
+
}>, import("effect/Cause").YieldableError>;
|
|
427
|
+
/**
|
|
428
|
+
* Raised when state cannot cross the phase boundary.
|
|
429
|
+
*
|
|
430
|
+
* @public
|
|
431
|
+
*/
|
|
432
|
+
declare class ActionStateError extends ActionStateError_base {
|
|
433
|
+
get message(): string;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* The {@link ActionState} service shape.
|
|
437
|
+
*
|
|
438
|
+
* @public
|
|
439
|
+
*/
|
|
440
|
+
interface ActionStateShape {
|
|
441
|
+
/** Persist a value for a later phase. */
|
|
442
|
+
readonly save: <A, I>(key: string, value: A, schema: Schema.Codec<A, I>) => Effect.Effect<void, ActionStateError>;
|
|
443
|
+
/** Read a value saved by an earlier phase. */
|
|
444
|
+
readonly get: <A, I>(key: string, schema: Schema.Codec<A, I>) => Effect.Effect<A, ActionStateError>;
|
|
445
|
+
/** Read a value that may not have been saved. */
|
|
446
|
+
readonly getOptional: <A, I>(key: string, schema: Schema.Codec<A, I>) => Effect.Effect<Option.Option<A>, ActionStateError>;
|
|
447
|
+
/**
|
|
448
|
+
* Persist a secret, masking it in the runner log first.
|
|
449
|
+
*
|
|
450
|
+
* @remarks
|
|
451
|
+
* `GITHUB_STATE` is plaintext by GitHub's protocol — a `Redacted` cannot
|
|
452
|
+
* survive that boundary by design — so masking is the only available
|
|
453
|
+
* defense, and coupling it to the write is what makes it unforgettable.
|
|
454
|
+
*/
|
|
455
|
+
readonly saveSecret: (key: string, secret: string) => Effect.Effect<void, ActionStateError>;
|
|
456
|
+
}
|
|
457
|
+
declare const ActionState_base: Context.ServiceClass<ActionState, "@effected/github-actions/ActionState", ActionStateShape>;
|
|
458
|
+
/**
|
|
459
|
+
* State that survives the `pre` → `main` → `post` phase boundary.
|
|
460
|
+
*
|
|
461
|
+
* @remarks
|
|
462
|
+
* Each phase of an action is a **separate process**. GitHub's protocol is a
|
|
463
|
+
* write-only file (`GITHUB_STATE`) whose entries the runner republishes to the
|
|
464
|
+
* next phase as `STATE_<key>` environment variables — so saving and reading go
|
|
465
|
+
* through different mechanisms, which is why this is a service rather than a
|
|
466
|
+
* pair of helpers.
|
|
467
|
+
*
|
|
468
|
+
* @public
|
|
469
|
+
*/
|
|
470
|
+
declare class ActionState extends ActionState_base {
|
|
471
|
+
static readonly layer: Layer.Layer<ActionState, never, ActionEnvironment | FileSystem.FileSystem | ActionOutputs>;
|
|
472
|
+
/** A test double. Unstubbed members die rather than answering wrongly. */
|
|
473
|
+
static readonly makeTest: (overrides?: Partial<ActionStateShape>) => ActionStateShape;
|
|
474
|
+
/** {@link ActionState.makeTest} behind `Layer.succeed`. */
|
|
475
|
+
static readonly layerTest: (overrides?: Partial<ActionStateShape>) => Layer.Layer<ActionState>;
|
|
476
|
+
}
|
|
477
|
+
//#endregion
|
|
478
|
+
//#region src/Action.d.ts
|
|
479
|
+
/**
|
|
480
|
+
* Everything {@link ActionRuntime.layer} provides.
|
|
481
|
+
*
|
|
482
|
+
* @remarks
|
|
483
|
+
* Spelled out rather than inferred so a caller can name it — a program written
|
|
484
|
+
* against `ActionServices` is a program `Action.run` can run, and the compiler
|
|
485
|
+
* says so before the runner does.
|
|
486
|
+
*
|
|
487
|
+
* @public
|
|
488
|
+
*/
|
|
489
|
+
type ActionServices = ActionEnvironment | ActionLogger | ActionOutputs | ActionState | NodeServices.NodeServices | HttpClient.HttpClient;
|
|
490
|
+
/**
|
|
491
|
+
* The default runtime an action executes inside.
|
|
492
|
+
*
|
|
493
|
+
* @remarks
|
|
494
|
+
* The one place in the kit where `@effect/platform-node` is composed rather
|
|
495
|
+
* than left to the consumer, and the reason is that there is no second answer:
|
|
496
|
+
* a GitHub Action is a Node process on a GitHub-provided runner.
|
|
497
|
+
*
|
|
498
|
+
* **The cache, artifact and blob services are deliberately NOT in here.** They
|
|
499
|
+
* are the only modules that import `@azure/storage-blob`, and folding them into
|
|
500
|
+
* the default runtime would put a blob-storage client in the bundle of every
|
|
501
|
+
* action that merely sets an output. Their requirements are all satisfied by
|
|
502
|
+
* this layer, so taking one costs a caller exactly one line:
|
|
503
|
+
* `Action.run(program, { layer: ActionCache.layer })`.
|
|
504
|
+
*
|
|
505
|
+
* @public
|
|
506
|
+
*/
|
|
507
|
+
declare class ActionRuntime {
|
|
508
|
+
private constructor();
|
|
509
|
+
/**
|
|
510
|
+
* The composed default: the runner services, the platform, an HTTP client
|
|
511
|
+
* and the workflow-command `Logger`.
|
|
512
|
+
*
|
|
513
|
+
* @remarks
|
|
514
|
+
* A bound constant rather than a factory. A layer-returning function mints a
|
|
515
|
+
* fresh layer per call, and layers memoize by reference — a factory here
|
|
516
|
+
* would rebuild the environment snapshot for every composition site.
|
|
517
|
+
*/
|
|
518
|
+
static readonly layer: Layer.Layer<ActionServices>;
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* How to run an action.
|
|
522
|
+
*
|
|
523
|
+
* @public
|
|
524
|
+
*/
|
|
525
|
+
interface ActionRunOptions<R> {
|
|
526
|
+
/**
|
|
527
|
+
* Services the program needs beyond {@link ActionServices}.
|
|
528
|
+
*
|
|
529
|
+
* @remarks
|
|
530
|
+
* It may require anything {@link ActionRuntime.layer} provides — which is
|
|
531
|
+
* what makes `{ layer: ActionCache.layer }` compile with no further wiring.
|
|
532
|
+
*/
|
|
533
|
+
readonly layer?: Layer.Layer<R, never, ActionServices> | undefined;
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* A readable one-line summary of why an action failed.
|
|
537
|
+
*
|
|
538
|
+
* @remarks
|
|
539
|
+
* `[Tag] message` because a workflow log is read by a human scanning for the
|
|
540
|
+
* first red line, and every error in this kit carries both. The `_tag` is what
|
|
541
|
+
* makes two failures with the same wording distinguishable, and the `message`
|
|
542
|
+
* getter is what makes the line worth reading.
|
|
543
|
+
*
|
|
544
|
+
* An interruption has neither, and `Cause.pretty` is the honest answer for it.
|
|
545
|
+
*
|
|
546
|
+
* @public
|
|
547
|
+
*/
|
|
548
|
+
declare const describeCause: (cause: Cause.Cause<unknown>) => string;
|
|
549
|
+
/**
|
|
550
|
+
* The entry point an action's `main`, `pre` and `post` scripts call.
|
|
551
|
+
*
|
|
552
|
+
* @remarks
|
|
553
|
+
* Everything an action entry point does that is not the action: compose the
|
|
554
|
+
* runtime, run the program, render a failure as a `::error::` annotation, and
|
|
555
|
+
* **set the exit code** — which is the piece
|
|
556
|
+
* `ActionOutputs.setFailed` deliberately leaves alone, so an action that
|
|
557
|
+
* reports a failure and then recovers is not doomed by a side effect it cannot
|
|
558
|
+
* undo.
|
|
559
|
+
*
|
|
560
|
+
* Two things it deliberately does **not** do:
|
|
561
|
+
*
|
|
562
|
+
* - **It does not wrap the program in a log buffer.** The package this replaces
|
|
563
|
+
* did, and an unhandled defect inside the buffer swallowed the entire
|
|
564
|
+
* transcript — the run failed and printed nothing. `ActionLogger.withBuffer`
|
|
565
|
+
* is opt-in and flushes on every exit path including a defect, so the
|
|
566
|
+
* buffering is where the caller can see it.
|
|
567
|
+
* - **It does not throw.** The returned promise always resolves; the failure is
|
|
568
|
+
* in `process.exitCode`, which is what the runner reads. An action entry
|
|
569
|
+
* point that rejected would produce an unhandled rejection *and* a failed
|
|
570
|
+
* step, and only the first would be legible.
|
|
571
|
+
*
|
|
572
|
+
* The rendering depth is deliberate too: one `::error::` line carrying
|
|
573
|
+
* `[Tag]: message`, and the fiddly diagnostics — the full `Cause.pretty` render
|
|
574
|
+
* with its span trace and stack — behind `::debug::`, which the runner shows
|
|
575
|
+
* only when someone turns step debugging on. The package this replaces spliced
|
|
576
|
+
* a JS stack into the visible error, which in a bundled action points at one
|
|
577
|
+
* line of `dist/main.js`.
|
|
578
|
+
*
|
|
579
|
+
* @example
|
|
580
|
+
* ```ts
|
|
581
|
+
* import { Action, ActionCache } from "@effected/github-actions";
|
|
582
|
+
*
|
|
583
|
+
* await Action.run(program, { layer: ActionCache.layer });
|
|
584
|
+
* ```
|
|
585
|
+
*
|
|
586
|
+
* @public
|
|
587
|
+
*/
|
|
588
|
+
declare class Action {
|
|
589
|
+
private constructor();
|
|
590
|
+
/** {@link describeCause}, so an action can render its own failures the same way. */
|
|
591
|
+
static readonly describeCause: (cause: Cause.Cause<unknown>) => string;
|
|
592
|
+
/**
|
|
593
|
+
* Run an action program to completion.
|
|
594
|
+
*
|
|
595
|
+
* @remarks
|
|
596
|
+
* Resolves whether the program succeeded or not; read `process.exitCode` for
|
|
597
|
+
* the verdict.
|
|
598
|
+
*/
|
|
599
|
+
static readonly run: <E, R = never>(program: Effect.Effect<void, E, ActionServices | R>, options?: ActionRunOptions<R>) => Promise<void>;
|
|
600
|
+
}
|
|
601
|
+
//#endregion
|
|
602
|
+
//#region src/BlobTransfer.d.ts
|
|
603
|
+
declare const BlobTransferError_base: Schema.Class<BlobTransferError, Schema.TaggedStruct<"BlobTransferError", {
|
|
604
|
+
/** Which direction failed. */
|
|
605
|
+
readonly reason: Schema.Literals<readonly ["uploadFailed", "downloadFailed"]>;
|
|
606
|
+
/** The underlying failure, preserved structurally. */
|
|
607
|
+
readonly cause: Schema.optionalKey<Schema.Defect>;
|
|
608
|
+
}>, import("effect/Cause").YieldableError>;
|
|
609
|
+
/**
|
|
610
|
+
* Raised when bytes could not be moved to or from a signed blob url.
|
|
611
|
+
*
|
|
612
|
+
* @public
|
|
613
|
+
*/
|
|
614
|
+
declare class BlobTransferError extends BlobTransferError_base {
|
|
615
|
+
get message(): string;
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Moving whole files to and from the signed blob url a results-backend RPC
|
|
619
|
+
* hands back.
|
|
620
|
+
*
|
|
621
|
+
* @remarks
|
|
622
|
+
* The seam exists because the *protocol* is what this package owns and the
|
|
623
|
+
* *transport* is not: the Actions results backend answers a Twirp RPC with a
|
|
624
|
+
* pre-signed Azure url, and everything interesting — the RPC sequence, the
|
|
625
|
+
* conflict handling, the retry policy, the version hash — happens on this side
|
|
626
|
+
* of it. Taking the transport as an argument means all of that is exercised by
|
|
627
|
+
* a test rather than described by one, and the Azure client stays confined to
|
|
628
|
+
* the three modules permitted to import it.
|
|
629
|
+
*
|
|
630
|
+
* A url is a plain string on purpose: it is a **credential** (the signature is
|
|
631
|
+
* in the query), and it is never logged, annotated on a span, or carried on an
|
|
632
|
+
* error.
|
|
633
|
+
*
|
|
634
|
+
* @public
|
|
635
|
+
*/
|
|
636
|
+
interface FileBlobTransfer {
|
|
637
|
+
/** Upload a file's contents to the url. */
|
|
638
|
+
readonly uploadFile: (url: string, file: string) => Effect.Effect<void, BlobTransferError>;
|
|
639
|
+
/** Download the url's contents into a file. */
|
|
640
|
+
readonly downloadToFile: (url: string, file: string) => Effect.Effect<void, BlobTransferError>;
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Moving in-memory bytes to and from a signed blob url.
|
|
644
|
+
*
|
|
645
|
+
* @remarks
|
|
646
|
+
* The {@link FileBlobTransfer} counterpart for payloads that never touch the
|
|
647
|
+
* filesystem. Split rather than merged because the cache and artifact protocols
|
|
648
|
+
* only ever move files and the blob store only ever moves buffers — a single
|
|
649
|
+
* four-member interface would make every implementation stub two members it can
|
|
650
|
+
* never be asked for.
|
|
651
|
+
*
|
|
652
|
+
* @public
|
|
653
|
+
*/
|
|
654
|
+
interface DataBlobTransfer {
|
|
655
|
+
/** Upload bytes to the url. */
|
|
656
|
+
readonly uploadData: (url: string, data: Uint8Array) => Effect.Effect<void, BlobTransferError>;
|
|
657
|
+
/** Download the url's contents. */
|
|
658
|
+
readonly downloadToBuffer: (url: string) => Effect.Effect<Uint8Array, BlobTransferError>;
|
|
659
|
+
}
|
|
660
|
+
//#endregion
|
|
661
|
+
//#region src/CacheKey.d.ts
|
|
662
|
+
declare const CacheKeyError_base: Schema.Class<CacheKeyError, Schema.TaggedStruct<"CacheKeyError", {
|
|
663
|
+
/**
|
|
664
|
+
* `readFailed` — a file or directory that was going to be hashed could not be
|
|
665
|
+
* read. `badPattern` — a glob pattern would not compile. Key *derivation*
|
|
666
|
+
* itself is pure and total once the bytes are in hand, so these are the only
|
|
667
|
+
* two things that can go wrong.
|
|
668
|
+
*/
|
|
669
|
+
readonly reason: Schema.Literals<readonly ["readFailed", "badPattern"]>;
|
|
670
|
+
/** The path being read when it went wrong. */
|
|
671
|
+
readonly path: Schema.optionalKey<Schema.String>;
|
|
672
|
+
/** The pattern that would not compile. */
|
|
673
|
+
readonly pattern: Schema.optionalKey<Schema.String>;
|
|
674
|
+
/** The underlying failure, preserved structurally. */
|
|
675
|
+
readonly cause: Schema.optionalKey<Schema.Defect>;
|
|
676
|
+
}>, import("effect/Cause").YieldableError>;
|
|
677
|
+
/**
|
|
678
|
+
* Raised when a cache key cannot be derived from the filesystem.
|
|
679
|
+
*
|
|
680
|
+
* @public
|
|
681
|
+
*/
|
|
682
|
+
declare class CacheKeyError extends CacheKeyError_base {
|
|
683
|
+
get message(): string;
|
|
684
|
+
}
|
|
685
|
+
declare const CacheKey_base: Schema.Class<CacheKey, Schema.Struct<{
|
|
686
|
+
/** The components, most general first. */
|
|
687
|
+
readonly segments: Schema.NonEmptyArray<Schema.String>;
|
|
688
|
+
}>, {}>;
|
|
689
|
+
/**
|
|
690
|
+
* A GitHub Actions cache key and the restore-key ladder that goes with it.
|
|
691
|
+
*
|
|
692
|
+
* @remarks
|
|
693
|
+
* The ladder is the part every consumer re-derives and gets subtly wrong. A
|
|
694
|
+
* restore key is a **prefix match** on GitHub's side, so `Linux-pnpm` would
|
|
695
|
+
* also match `Linux-pnpm-store-…` from an unrelated cache; every rung here
|
|
696
|
+
* therefore ends in the separator. Deriving the rungs from the segments makes
|
|
697
|
+
* the primary key and its fallbacks impossible to drift apart, which is the
|
|
698
|
+
* failure that produces a cache that never hits and never says why.
|
|
699
|
+
*
|
|
700
|
+
* @example
|
|
701
|
+
* ```ts
|
|
702
|
+
* import { CacheKey } from "@effected/github-actions";
|
|
703
|
+
*
|
|
704
|
+
* const key = CacheKey.of("Linux", "pnpm-store", "abc123");
|
|
705
|
+
* key.key; // "Linux-pnpm-store-abc123"
|
|
706
|
+
* key.restoreKeys; // ["Linux-pnpm-store-", "Linux-"]
|
|
707
|
+
* ```
|
|
708
|
+
*
|
|
709
|
+
* @public
|
|
710
|
+
*/
|
|
711
|
+
declare class CacheKey extends CacheKey_base {
|
|
712
|
+
/** The primary key: every segment, joined. */
|
|
713
|
+
get key(): string;
|
|
714
|
+
/**
|
|
715
|
+
* The restore keys, most specific first.
|
|
716
|
+
*
|
|
717
|
+
* @remarks
|
|
718
|
+
* Each rung drops the last segment and keeps the trailing separator, so a
|
|
719
|
+
* three-segment key falls back to two prefixes and a one-segment key falls
|
|
720
|
+
* back to nothing — there is no rung that would match every cache in the
|
|
721
|
+
* repository.
|
|
722
|
+
*/
|
|
723
|
+
get restoreKeys(): ReadonlyArray<string>;
|
|
724
|
+
/** Build a key from its segments. */
|
|
725
|
+
static of(...segments: readonly [string, ...ReadonlyArray<string>]): CacheKey;
|
|
726
|
+
/**
|
|
727
|
+
* A branch-aware key.
|
|
728
|
+
*
|
|
729
|
+
* @remarks
|
|
730
|
+
* Ordering the segments `os → scope → branch → hash` is what makes the
|
|
731
|
+
* derived ladder do the right thing: the first rung falls back to any earlier
|
|
732
|
+
* cache **on this branch**, and only the next one reaches across branches. The
|
|
733
|
+
* reverse order would make the first fallback jump branches, which is how a
|
|
734
|
+
* feature branch ends up warming its cache from `main` and never noticing its
|
|
735
|
+
* own.
|
|
736
|
+
*
|
|
737
|
+
* Every part is an explicit argument rather than read from the environment,
|
|
738
|
+
* so a key is reproducible outside a runner and a test needs no ambient
|
|
739
|
+
* state.
|
|
740
|
+
*/
|
|
741
|
+
static forBranch(options: {
|
|
742
|
+
/** What is being cached, e.g. `"pnpm-store"`. */
|
|
743
|
+
readonly scope: string;
|
|
744
|
+
/** The short ref, e.g. `GITHUB_REF_NAME`. */
|
|
745
|
+
readonly branch: string;
|
|
746
|
+
/** The runner OS, e.g. `RUNNER_OS`. Included first when given. */
|
|
747
|
+
readonly os?: string;
|
|
748
|
+
/** The content hash, e.g. from {@link CacheKey.hashFiles}. Included last when given. */
|
|
749
|
+
readonly hash?: string;
|
|
750
|
+
}): CacheKey;
|
|
751
|
+
/**
|
|
752
|
+
* Hash a set of files into a single digest.
|
|
753
|
+
*
|
|
754
|
+
* @remarks
|
|
755
|
+
* **Byte-compatible with `@actions/glob`'s `hashFiles`**, and the three
|
|
756
|
+
* details that make it so are all easy to get wrong: the paths are sorted, each
|
|
757
|
+
* file is hashed on its own, and the per-file digest is fed into the
|
|
758
|
+
* accumulator as **binary** rather than hex. A hex-fed accumulator produces a
|
|
759
|
+
* perfectly plausible digest that simply never matches a cache entry written
|
|
760
|
+
* by any other action.
|
|
761
|
+
*
|
|
762
|
+
* Sorting and de-duplication happen here rather than at the call site, so a
|
|
763
|
+
* caller cannot accidentally make its key depend on the order it discovered
|
|
764
|
+
* files in.
|
|
765
|
+
*
|
|
766
|
+
* `Option.none()` for an empty set, because "nothing matched" is not a
|
|
767
|
+
* digest — it is the signal that the pattern is wrong, and a caller that
|
|
768
|
+
* folds it into a key silently caches against a constant.
|
|
769
|
+
*/
|
|
770
|
+
static readonly hashFiles: (files: readonly string[]) => Effect.Effect<Option.Option<string>, CacheKeyError, FileSystem.FileSystem>;
|
|
771
|
+
/**
|
|
772
|
+
* Every file under `workspace` that the pattern set matches.
|
|
773
|
+
*
|
|
774
|
+
* @remarks
|
|
775
|
+
* Discovery and matching are **two different jobs**, and separating them is
|
|
776
|
+
* what makes this testable and correct. The walk is core
|
|
777
|
+
* `FileSystem.readDirectory(recursive)`; the matching is `@effected/glob`'s
|
|
778
|
+
* full minimatch dialect — the same dialect `@actions/glob` uses, so a workflow
|
|
779
|
+
* author's `!**\/node_modules/**` behaves here exactly as it does in every
|
|
780
|
+
* other cache step in the same workflow. `node:fs.globSync` would have welded
|
|
781
|
+
* the two together behind one non-stubbable call *and* changed the dialect,
|
|
782
|
+
* and a dialect divergence surfaces as a silent cache-key difference — the
|
|
783
|
+
* worst failure mode a cache key has.
|
|
784
|
+
*
|
|
785
|
+
* Candidates are matched by their path **relative to the workspace**, which
|
|
786
|
+
* is what makes "never hash a file outside the workspace" structural rather
|
|
787
|
+
* than a rule someone has to remember. Directories are excluded: a directory
|
|
788
|
+
* called `notes.txt` matches `**\/*.txt` and is not a file, and hashing it
|
|
789
|
+
* would fail rather than being ignored.
|
|
790
|
+
*
|
|
791
|
+
* The answer is sorted, so a caller cannot make its key depend on the order
|
|
792
|
+
* the filesystem happened to report.
|
|
793
|
+
*/
|
|
794
|
+
static readonly matchingFiles: (options: {
|
|
795
|
+
/** The directory to walk. Nothing outside it is ever considered. */
|
|
796
|
+
readonly workspace: string;
|
|
797
|
+
/** Include patterns, with a leading `!` marking an exclusion. */
|
|
798
|
+
readonly patterns: ReadonlyArray<string>;
|
|
799
|
+
}) => Effect.Effect<readonly string[], CacheKeyError, FileSystem.FileSystem | Path.Path>;
|
|
800
|
+
/**
|
|
801
|
+
* Hash every file under `workspace` that the pattern set matches.
|
|
802
|
+
*
|
|
803
|
+
* @remarks
|
|
804
|
+
* {@link CacheKey.matchingFiles} into {@link CacheKey.hashFiles}, which is
|
|
805
|
+
* the pairing every consumer writes by hand — and the pairing whose two
|
|
806
|
+
* halves have to agree about ordering and about what counts as a file for the
|
|
807
|
+
* digest to match anything anyone else computed.
|
|
808
|
+
*
|
|
809
|
+
* `Option.none()` when nothing matched, for the same reason `hashFiles` gives:
|
|
810
|
+
* "no files" is not a digest, it is the signal that the patterns are wrong,
|
|
811
|
+
* and folding it into a key silently caches against a constant.
|
|
812
|
+
*
|
|
813
|
+
* @example
|
|
814
|
+
* ```ts
|
|
815
|
+
* import { CacheKey } from "@effected/github-actions";
|
|
816
|
+
* import { Effect, Option } from "effect";
|
|
817
|
+
*
|
|
818
|
+
* const key = Effect.gen(function* () {
|
|
819
|
+
* const hash = yield* CacheKey.hashMatching({
|
|
820
|
+
* workspace: "/home/runner/work/repo/repo",
|
|
821
|
+
* patterns: ["**\/pnpm-lock.yaml", "!**\/node_modules/**"],
|
|
822
|
+
* });
|
|
823
|
+
* return CacheKey.of("Linux", "pnpm-store", Option.getOrElse(hash, () => "empty"));
|
|
824
|
+
* });
|
|
825
|
+
* ```
|
|
826
|
+
*/
|
|
827
|
+
static readonly hashMatching: (options: {
|
|
828
|
+
readonly workspace: string;
|
|
829
|
+
readonly patterns: ReadonlyArray<string>;
|
|
830
|
+
}) => Effect.Effect<Option.Option<string>, CacheKeyError, FileSystem.FileSystem | Path.Path>;
|
|
831
|
+
}
|
|
832
|
+
//#endregion
|
|
833
|
+
//#region src/ActionCache.d.ts
|
|
834
|
+
declare const ActionCacheError_base: Schema.Class<ActionCacheError, Schema.TaggedStruct<"ActionCacheError", {
|
|
835
|
+
/**
|
|
836
|
+
* `misconfigured` — the results backend is not reachable from here (see
|
|
837
|
+
* {@link ActionCache}). `unreachable` — it could not be contacted, or
|
|
838
|
+
* answered with something that is not a Twirp body. `refused` — it answered,
|
|
839
|
+
* unhappily. `archiveFailed` — `tar` would not pack or unpack the paths.
|
|
840
|
+
* `transferFailed` — the archive itself did not move.
|
|
841
|
+
*/
|
|
842
|
+
readonly reason: Schema.Literals<readonly ["misconfigured", "unreachable", "refused", "archiveFailed", "transferFailed"]>;
|
|
843
|
+
/** The cache key involved. A stable identifier, never a value. */
|
|
844
|
+
readonly key: Schema.optionalKey<Schema.String>;
|
|
845
|
+
/** The HTTP status, when the backend answered. */
|
|
846
|
+
readonly status: Schema.optionalKey<Schema.Number>;
|
|
847
|
+
/** What went wrong, when the reason alone does not say. */
|
|
848
|
+
readonly detail: Schema.optionalKey<Schema.String>;
|
|
849
|
+
/** `tar`'s own complaint, which is the only useful part of an archive failure. */
|
|
850
|
+
readonly stderr: Schema.optionalKey<Schema.String>;
|
|
851
|
+
/** The underlying failure, preserved structurally. */
|
|
852
|
+
readonly cause: Schema.optionalKey<Schema.Defect>;
|
|
853
|
+
}>, import("effect/Cause").YieldableError>;
|
|
854
|
+
/**
|
|
855
|
+
* Raised when the runner's cache cannot be read or written.
|
|
856
|
+
*
|
|
857
|
+
* @public
|
|
858
|
+
*/
|
|
859
|
+
declare class ActionCacheError extends ActionCacheError_base {
|
|
860
|
+
get message(): string;
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* The {@link ActionCache} service shape.
|
|
864
|
+
*
|
|
865
|
+
* @public
|
|
866
|
+
*/
|
|
867
|
+
interface ActionCacheShape {
|
|
868
|
+
/**
|
|
869
|
+
* Archive `paths` and store them under `key`.
|
|
870
|
+
*
|
|
871
|
+
* @remarks
|
|
872
|
+
* A key that already exists is a **success**: cache entries are immutable, so
|
|
873
|
+
* another job having written it first is the outcome the caller wanted.
|
|
874
|
+
*/
|
|
875
|
+
readonly save: (paths: ReadonlyArray<string>, key: string | CacheKey) => Effect.Effect<void, ActionCacheError>;
|
|
876
|
+
/**
|
|
877
|
+
* Restore `paths` from the first entry that matches, and answer with the key
|
|
878
|
+
* that matched.
|
|
879
|
+
*
|
|
880
|
+
* @remarks
|
|
881
|
+
* `Option.none()` is a miss, not a failure — a cold cache is the normal state
|
|
882
|
+
* of a new branch. Passing a {@link CacheKey} supplies its restore-key ladder
|
|
883
|
+
* automatically; passing a `string` and no ladder means "this key or nothing".
|
|
884
|
+
*/
|
|
885
|
+
readonly restore: (paths: ReadonlyArray<string>, key: string | CacheKey, restoreKeys?: ReadonlyArray<string>) => Effect.Effect<Option.Option<string>, ActionCacheError>;
|
|
886
|
+
}
|
|
887
|
+
declare const ActionCache_base: Context.ServiceClass<ActionCache, "@effected/github-actions/ActionCache", ActionCacheShape>;
|
|
888
|
+
/**
|
|
889
|
+
* The runner's own cache: archive a set of paths under a key, and get them back
|
|
890
|
+
* in a later job.
|
|
891
|
+
*
|
|
892
|
+
* @remarks
|
|
893
|
+
* Speaks the Actions cache **Twirp v2** protocol at `ACTIONS_RESULTS_URL`,
|
|
894
|
+
* which answers with a pre-signed Azure blob url for the archive itself. No
|
|
895
|
+
* `@actions/cache` dependency — that package alone brings a dependency tree
|
|
896
|
+
* larger than this one.
|
|
897
|
+
*
|
|
898
|
+
* **Only reachable from a `uses:` step.** The runner injects
|
|
899
|
+
* `ACTIONS_RESULTS_URL` and `ACTIONS_RUNTIME_TOKEN` into action execution
|
|
900
|
+
* contexts and not into `run:` shell steps, so the identical code fails as
|
|
901
|
+
* `misconfigured` when a workflow invokes it with `node ./main.js`. The failure
|
|
902
|
+
* names the absent variable, because nothing else distinguishes the two cases.
|
|
903
|
+
*
|
|
904
|
+
* @example
|
|
905
|
+
* ```ts
|
|
906
|
+
* import { ActionCache, CacheKey } from "@effected/github-actions";
|
|
907
|
+
* import { Effect, Option } from "effect";
|
|
908
|
+
*
|
|
909
|
+
* const program = Effect.gen(function* () {
|
|
910
|
+
* const cache = yield* ActionCache;
|
|
911
|
+
* const key = CacheKey.of("Linux", "pnpm-store", "abc123");
|
|
912
|
+
* const hit = yield* cache.restore(["~/.pnpm-store"], key);
|
|
913
|
+
* if (Option.isNone(hit)) {
|
|
914
|
+
* yield* cache.save(["~/.pnpm-store"], key);
|
|
915
|
+
* }
|
|
916
|
+
* });
|
|
917
|
+
* ```
|
|
918
|
+
*
|
|
919
|
+
* @public
|
|
920
|
+
*/
|
|
921
|
+
declare class ActionCache extends ActionCache_base {
|
|
922
|
+
/** The cache, over the real Azure client and the real `tar`. */
|
|
923
|
+
static readonly layer: Layer.Layer<ActionCache, never, ActionEnvironment | HttpClient.HttpClient | FileSystem.FileSystem | Path.Path | ChildProcessSpawner.ChildProcessSpawner>;
|
|
924
|
+
/**
|
|
925
|
+
* The cache, over a supplied transport.
|
|
926
|
+
*
|
|
927
|
+
* @remarks
|
|
928
|
+
* The protocol, the version derivation, the conflict handling and the archive
|
|
929
|
+
* are what this package owns; the pre-signed `PUT` is not. Supplying the
|
|
930
|
+
* transport is what lets a test exercise all of the first group without the
|
|
931
|
+
* second, and what an integration test uses to point the same protocol at a
|
|
932
|
+
* local blob endpoint.
|
|
933
|
+
*
|
|
934
|
+
* A parameterized layer factory mints a fresh layer per call and layers
|
|
935
|
+
* memoize by reference — bind it to a `const` rather than calling it at each
|
|
936
|
+
* composition site.
|
|
937
|
+
*/
|
|
938
|
+
static readonly layerWith: (transfer: FileBlobTransfer) => Layer.Layer<ActionCache, never, ActionEnvironment | HttpClient.HttpClient | FileSystem.FileSystem | Path.Path | ChildProcessSpawner.ChildProcessSpawner>;
|
|
939
|
+
/** A test double. Unstubbed members die rather than reporting a miss. */
|
|
940
|
+
static readonly makeTest: (overrides?: Partial<ActionCacheShape>) => ActionCacheShape;
|
|
941
|
+
/** {@link ActionCache.makeTest} behind `Layer.succeed`. */
|
|
942
|
+
static readonly layerTest: (overrides?: Partial<ActionCacheShape>) => Layer.Layer<ActionCache>;
|
|
943
|
+
}
|
|
944
|
+
//#endregion
|
|
945
|
+
//#region src/ActionInput.d.ts
|
|
946
|
+
/**
|
|
947
|
+
* Action inputs, read as `Config` values.
|
|
948
|
+
*
|
|
949
|
+
* @remarks
|
|
950
|
+
* Inputs are **never** read from `process.env` directly. Every accessor names
|
|
951
|
+
* the input the way the workflow author wrote it and lets this module derive
|
|
952
|
+
* the runner's variable name — see `inputVariable` for why that matters. (A
|
|
953
|
+
* `{@link}` cannot reach it: it is internal, so it is not exported from the
|
|
954
|
+
* entrypoint and the reference would not resolve.)
|
|
955
|
+
*
|
|
956
|
+
* `lines`, `list` and `pairs` exist because the `@actions/core`-faithful
|
|
957
|
+
* newline split was not enough for real consumers: two of them reinvented
|
|
958
|
+
* richer parsing independently, one parsing JSON arrays and bullet lists, the
|
|
959
|
+
* other stripping comments and reading `key=value` pairs. Those are now one
|
|
960
|
+
* implementation with one set of tests instead of two divergent ones with
|
|
961
|
+
* none.
|
|
962
|
+
*
|
|
963
|
+
* These are grouped statics over one concept reaching nothing but `Config` —
|
|
964
|
+
* not a namespace object over engines.
|
|
965
|
+
*
|
|
966
|
+
* **The absence contract, shared by every accessor:** the runner writes `""`
|
|
967
|
+
* for an input the workflow left out, and the provider reads an empty string
|
|
968
|
+
* as absent — so *unset* and *empty* are the same case, **missing data**, and
|
|
969
|
+
* the read fails rather than yielding an empty value. `Config.withDefault`
|
|
970
|
+
* falls back for exactly that class, which makes
|
|
971
|
+
* `.pipe(Config.withDefault(…))` the idiom for every optional input. What a
|
|
972
|
+
* default never swallows is a *present but malformed* value — see
|
|
973
|
+
* {@link ActionInput.boolean}.
|
|
974
|
+
*
|
|
975
|
+
* @example
|
|
976
|
+
* ```ts
|
|
977
|
+
* import { ActionInput } from "@effected/github-actions";
|
|
978
|
+
* import { Effect } from "effect";
|
|
979
|
+
*
|
|
980
|
+
* const program = Effect.gen(function* () {
|
|
981
|
+
* const dryRun = yield* ActionInput.boolean("dry-run");
|
|
982
|
+
* const globs = yield* ActionInput.list("paths");
|
|
983
|
+
* });
|
|
984
|
+
* ```
|
|
985
|
+
*
|
|
986
|
+
* @public
|
|
987
|
+
*/
|
|
988
|
+
declare class ActionInput {
|
|
989
|
+
private constructor();
|
|
990
|
+
/**
|
|
991
|
+
* A required string input.
|
|
992
|
+
*
|
|
993
|
+
* @remarks
|
|
994
|
+
* Fails as *missing data* when the input is absent **or set to the empty
|
|
995
|
+
* string** — the runner writes `""` for an unsupplied input, and both the
|
|
996
|
+
* runtime's provider and {@link ActionInput.provider} read that as absent.
|
|
997
|
+
* Every accessor on this class shares the rule, so an **optional** input
|
|
998
|
+
* needs `Config.withDefault` (or `Config.option`) at the call site or the
|
|
999
|
+
* read fails outright.
|
|
1000
|
+
*/
|
|
1001
|
+
static string(name: string): Config.Config<string>;
|
|
1002
|
+
/**
|
|
1003
|
+
* A boolean input, per the runner's documented YAML 1.2 core schema.
|
|
1004
|
+
*
|
|
1005
|
+
* @remarks
|
|
1006
|
+
* Absent or `""` is **missing data** — wrap with `Config.withDefault` for
|
|
1007
|
+
* an optional flag. A **present but malformed** value (`yes`, `on`, `1`) is
|
|
1008
|
+
* a different class: it fails carrying its `actual`, deliberately, so a
|
|
1009
|
+
* default does NOT swallow it — the shipped defect this guards against was
|
|
1010
|
+
* a malformed `dry-run` quietly reading as `false`.
|
|
1011
|
+
*/
|
|
1012
|
+
static boolean(name: string): Config.Config<boolean>;
|
|
1013
|
+
/**
|
|
1014
|
+
* An integer input.
|
|
1015
|
+
*
|
|
1016
|
+
* @remarks
|
|
1017
|
+
* Unset and `""` both fail as **missing data**; for an optional input,
|
|
1018
|
+
* `.pipe(Config.withDefault(n))`.
|
|
1019
|
+
*/
|
|
1020
|
+
static integer(name: string): Config.Config<number>;
|
|
1021
|
+
/**
|
|
1022
|
+
* A secret input, kept redacted.
|
|
1023
|
+
*
|
|
1024
|
+
* @remarks
|
|
1025
|
+
* Unset and `""` both fail as **missing data**; `Config.option` is the
|
|
1026
|
+
* usual shape for an optional secret, since there is rarely a meaningful
|
|
1027
|
+
* default to redact.
|
|
1028
|
+
*/
|
|
1029
|
+
static redacted(name: string): Config.Config<Redacted.Redacted<string>>;
|
|
1030
|
+
/**
|
|
1031
|
+
* A multiline input, split on newlines.
|
|
1032
|
+
*
|
|
1033
|
+
* @remarks
|
|
1034
|
+
* The `@actions/core`-faithful shape: blank lines are dropped and each entry
|
|
1035
|
+
* is trimmed, because a YAML block scalar carries the workflow's own
|
|
1036
|
+
* indentation.
|
|
1037
|
+
*
|
|
1038
|
+
* Absent or `""` fails as missing data (see {@link ActionInput.string}) —
|
|
1039
|
+
* `Config.withDefault([])` is load-bearing for an optional multiline input.
|
|
1040
|
+
*/
|
|
1041
|
+
static lines(name: string): Config.Config<ReadonlyArray<string>>;
|
|
1042
|
+
/**
|
|
1043
|
+
* A list input, accepting the shapes workflow authors actually write.
|
|
1044
|
+
*
|
|
1045
|
+
* @remarks
|
|
1046
|
+
* A JSON array (`["a","b"]`), a bullet list (`- a` or `* a`), or comma- and
|
|
1047
|
+
* newline-separated values, with full-line `#` comments dropped — any
|
|
1048
|
+
* combination of these in the same input. Each shape was reinvented
|
|
1049
|
+
* independently by a consumer (one a bullet-list-and-JSON parser, the other
|
|
1050
|
+
* comment-stripping); accepting the union means a workflow author's first
|
|
1051
|
+
* guess works and nothing either prior consumer relied on regresses.
|
|
1052
|
+
*
|
|
1053
|
+
* Parsing order, applied per newline/comma-separated item after trimming:
|
|
1054
|
+
*
|
|
1055
|
+
* 1. **Comment lines are dropped first.** A trimmed item whose first
|
|
1056
|
+
* character is `#` is a full-line comment and is discarded entirely,
|
|
1057
|
+
* before any bullet marker is considered. This means `- #tag` is a
|
|
1058
|
+
* value, not a comment: the check runs on the untouched, still-bulleted
|
|
1059
|
+
* item, and `-` is not `#`.
|
|
1060
|
+
* 2. **Only then is a leading bullet marker stripped** — `- ` or `* `
|
|
1061
|
+
* (dash-or-asterisk followed by a space), mirroring the indentation a
|
|
1062
|
+
* YAML block scalar carries in. So `- #tag` becomes the value `#tag`.
|
|
1063
|
+
* 3. Blank results (an empty line, or a comment line) are dropped from the
|
|
1064
|
+
* final list.
|
|
1065
|
+
*
|
|
1066
|
+
* A `#` that is not the first character of a trimmed item — a trailing or
|
|
1067
|
+
* mid-value `#` — is left alone; unlike {@link ActionInput.pairs}, `list`
|
|
1068
|
+
* has no trailing-comment rule, only a whole-line one.
|
|
1069
|
+
*
|
|
1070
|
+
* Absent or `""` fails as **missing data** (see {@link ActionInput.string}),
|
|
1071
|
+
* so `Config.withDefault([])` is load-bearing at every optional-input call
|
|
1072
|
+
* site — omitting an optional multi-value input otherwise fails the read
|
|
1073
|
+
* outright. A *whitespace-only* value (a block scalar holding only a
|
|
1074
|
+
* newline) is present, and parses to `[]`.
|
|
1075
|
+
*/
|
|
1076
|
+
static list(name: string): Config.Config<ReadonlyArray<string>>;
|
|
1077
|
+
/**
|
|
1078
|
+
* A `key=value` input, one pair per line, with `#` comments stripped.
|
|
1079
|
+
*
|
|
1080
|
+
* @remarks
|
|
1081
|
+
* Only the **first** `=` splits, so a value may contain one.
|
|
1082
|
+
*
|
|
1083
|
+
* Absent or `""` fails as missing data (see {@link ActionInput.string}) —
|
|
1084
|
+
* `Config.withDefault({})` is the idiom for an optional pairs input.
|
|
1085
|
+
*/
|
|
1086
|
+
static pairs(name: string): Config.Config<Record<string, string>>;
|
|
1087
|
+
/**
|
|
1088
|
+
* A JSON-valued input, decoded through a schema.
|
|
1089
|
+
*
|
|
1090
|
+
* @remarks
|
|
1091
|
+
* Absent or `""` fails as **missing data** before any JSON parsing runs
|
|
1092
|
+
* (see {@link ActionInput.string}) — `Config.withDefault` or
|
|
1093
|
+
* `Config.option` for an optional input. A present value that is not valid
|
|
1094
|
+
* JSON, or does not satisfy the schema, fails carrying its `actual` and is
|
|
1095
|
+
* never swallowed by a default.
|
|
1096
|
+
*/
|
|
1097
|
+
static schema<A, I>(name: string, schema: Schema.Codec<A, I>): Config.Config<A>;
|
|
1098
|
+
/**
|
|
1099
|
+
* A `ConfigProvider` over a record of runner variables.
|
|
1100
|
+
*
|
|
1101
|
+
* @remarks
|
|
1102
|
+
* Subsumes the source package's free-standing `ActionsConfigProvider`, whose
|
|
1103
|
+
* three behaviors are preserved exactly: the path is joined with `_`, spaces
|
|
1104
|
+
* become underscores and the whole is uppercased; and an **empty string
|
|
1105
|
+
* reads as absent**, because the runner sets unsupplied inputs to `""` and
|
|
1106
|
+
* treating that as present would make every optional input look supplied.
|
|
1107
|
+
*
|
|
1108
|
+
* Taking the environment as an argument rather than reading `process.env`
|
|
1109
|
+
* is what makes inputs testable without mutating the test process.
|
|
1110
|
+
*/
|
|
1111
|
+
static provider(env?: Readonly<Record<string, string | undefined>>): ConfigProvider.ConfigProvider;
|
|
1112
|
+
/**
|
|
1113
|
+
* A layer installing {@link ActionInput.provider}.
|
|
1114
|
+
*
|
|
1115
|
+
* @remarks
|
|
1116
|
+
* `ConfigProvider.ConfigProvider` is a `Context.Reference` in v4, so this is
|
|
1117
|
+
* a reference override rather than a service — there is no
|
|
1118
|
+
* `Effect.withConfigProvider`.
|
|
1119
|
+
*
|
|
1120
|
+
* A parameterized layer factory mints a fresh reference per call; bind it to
|
|
1121
|
+
* a `const` rather than calling it at each composition site.
|
|
1122
|
+
*/
|
|
1123
|
+
static layer(env?: Readonly<Record<string, string | undefined>>): Layer.Layer<never>;
|
|
1124
|
+
/**
|
|
1125
|
+
* Inputs-first resolution layered over an existing provider.
|
|
1126
|
+
*
|
|
1127
|
+
* @remarks
|
|
1128
|
+
* A bare `Config.string("dry-run")` resolves a **flat** name — one string
|
|
1129
|
+
* segment — by first trying the variable the runner would have published for
|
|
1130
|
+
* an input of that name (the `INPUT_` derivation this module owns), and only
|
|
1131
|
+
* then trying the name unchanged through `ambient`. Anything the runner
|
|
1132
|
+
* could not have set as an input — a nested path, a numeric segment — is
|
|
1133
|
+
* passed to `ambient` untouched, so no path semantics are invented that the
|
|
1134
|
+
* runner does not have.
|
|
1135
|
+
*
|
|
1136
|
+
* This exists because a live action shipped a **false green**: every bare
|
|
1137
|
+
* `Config` read fell back to its `withDefault` because the runner exposes
|
|
1138
|
+
* inputs as `INPUT_<MANGLED>` variables and a plain-named lookup finds
|
|
1139
|
+
* nothing. Under this provider a bare read resolves through the same
|
|
1140
|
+
* derivation `ActionInput.string` uses, so side-stepping the typed accessors
|
|
1141
|
+
* degrades to the right answer instead of to the default.
|
|
1142
|
+
*
|
|
1143
|
+
* The documented trade: a workflow input named like an environment variable
|
|
1144
|
+
* **shadows it** for bare reads — and because the derivation uppercases,
|
|
1145
|
+
* any casing of the name sees the input. An unsupplied input does not
|
|
1146
|
+
* shadow: the runner writes `""` for it, and the ambient provider's
|
|
1147
|
+
* empty-is-absent rule (which the attempt resolves through) drops it. The
|
|
1148
|
+
* `ActionInput` accessors themselves are unaffected — they read
|
|
1149
|
+
* `INPUT_<MANGLED>` names, whose re-mangled form (`INPUT_INPUT_…`) never
|
|
1150
|
+
* matches, so they fall through to the ambient lookup they always used.
|
|
1151
|
+
*/
|
|
1152
|
+
static providerOver(ambient: ConfigProvider.ConfigProvider): ConfigProvider.ConfigProvider;
|
|
1153
|
+
/**
|
|
1154
|
+
* The provider the default action runtime installs: {@link ActionInput.providerOver}
|
|
1155
|
+
* composed over the ambient environment lookup.
|
|
1156
|
+
*
|
|
1157
|
+
* @remarks
|
|
1158
|
+
* Installed by `ActionRuntime.layer`, and therefore by `Action.run` — a bare
|
|
1159
|
+
* `Config` read inside an action resolves through the runner's `INPUT_`
|
|
1160
|
+
* derivation instead of silently missing. A caller-supplied
|
|
1161
|
+
* `ConfigProvider` layer still wins by normal layer precedence.
|
|
1162
|
+
*
|
|
1163
|
+
* The ambient half is whatever provider is **explicitly installed** when the
|
|
1164
|
+
* layer builds — which is how a test injects a deterministic environment by
|
|
1165
|
+
* providing `ConfigProvider.layer(ConfigProvider.fromEnv({ env }))` beneath
|
|
1166
|
+
* the runtime. When none is installed, a **fresh** `ConfigProvider.fromEnv()`
|
|
1167
|
+
* is built rather than reading the reference's default: v4 caches that
|
|
1168
|
+
* default once per process, and a snapshot taken before the runner's
|
|
1169
|
+
* variables are visible would resurrect exactly the missed-input class this
|
|
1170
|
+
* layer exists to kill. An action's environment is fixed before the process
|
|
1171
|
+
* starts, so production cannot tell the difference; a test mutating
|
|
1172
|
+
* `process.env` around `Action.run` can.
|
|
1173
|
+
*
|
|
1174
|
+
* A bound constant rather than a factory: layers memoize by reference.
|
|
1175
|
+
*/
|
|
1176
|
+
static readonly layerDefault: Layer.Layer<never>;
|
|
1177
|
+
}
|
|
1178
|
+
//#endregion
|
|
1179
|
+
//#region src/Artifact.d.ts
|
|
1180
|
+
declare const ArtifactError_base: Schema.Class<ArtifactError, Schema.TaggedStruct<"ArtifactError", {
|
|
1181
|
+
/**
|
|
1182
|
+
* `misconfigured` — the results backend is not reachable from here (see
|
|
1183
|
+
* {@link Artifact}). `unreachable` — it could not be contacted, or answered
|
|
1184
|
+
* with something that is not a Twirp body. `refused` — it answered,
|
|
1185
|
+
* unhappily; re-uploading a name that already exists in the run is the
|
|
1186
|
+
* common case. `notFound` — no artifact by that name or id exists in this
|
|
1187
|
+
* run. `archiveFailed` — `zip` would not pack or unpack the files.
|
|
1188
|
+
* `transferFailed` — the archive itself did not move. `invalidOptions` — the
|
|
1189
|
+
* call cannot be made as asked.
|
|
1190
|
+
*/
|
|
1191
|
+
readonly reason: Schema.Literals<readonly ["misconfigured", "unreachable", "refused", "notFound", "archiveFailed", "transferFailed", "invalidOptions"]>;
|
|
1192
|
+
/** The artifact's name or id. A stable identifier, never a value. */
|
|
1193
|
+
readonly artifact: Schema.optionalKey<Schema.String>;
|
|
1194
|
+
/** The HTTP status, when the backend answered. */
|
|
1195
|
+
readonly status: Schema.optionalKey<Schema.Number>;
|
|
1196
|
+
/** What went wrong, when the reason alone does not say. */
|
|
1197
|
+
readonly detail: Schema.optionalKey<Schema.String>;
|
|
1198
|
+
/** `zip`'s own complaint, which is the only useful part of an archive failure. */
|
|
1199
|
+
readonly stderr: Schema.optionalKey<Schema.String>;
|
|
1200
|
+
/** The underlying failure, preserved structurally. */
|
|
1201
|
+
readonly cause: Schema.optionalKey<Schema.Defect>;
|
|
1202
|
+
}>, import("effect/Cause").YieldableError>;
|
|
1203
|
+
/**
|
|
1204
|
+
* Raised when an artifact cannot be uploaded, listed, downloaded or deleted.
|
|
1205
|
+
*
|
|
1206
|
+
* @public
|
|
1207
|
+
*/
|
|
1208
|
+
declare class ArtifactError extends ArtifactError_base {
|
|
1209
|
+
get message(): string;
|
|
1210
|
+
}
|
|
1211
|
+
/**
|
|
1212
|
+
* One artifact, as the backend describes it.
|
|
1213
|
+
*
|
|
1214
|
+
* @public
|
|
1215
|
+
*/
|
|
1216
|
+
interface ArtifactItem {
|
|
1217
|
+
/** The database id, which is what `download` takes. */
|
|
1218
|
+
readonly id: number;
|
|
1219
|
+
readonly name: string;
|
|
1220
|
+
/** The size of the stored zip, in bytes. */
|
|
1221
|
+
readonly size: number;
|
|
1222
|
+
/** When it was created, ISO-8601, when the backend says. */
|
|
1223
|
+
readonly createdAt?: string | undefined;
|
|
1224
|
+
}
|
|
1225
|
+
/**
|
|
1226
|
+
* How to pack an upload.
|
|
1227
|
+
*
|
|
1228
|
+
* @public
|
|
1229
|
+
*/
|
|
1230
|
+
interface UploadOptions {
|
|
1231
|
+
/**
|
|
1232
|
+
* How long to keep it, in days.
|
|
1233
|
+
*
|
|
1234
|
+
* @remarks
|
|
1235
|
+
* Bounded by the repository's own retention setting; omitted means the
|
|
1236
|
+
* repository default. Zero or negative fails as `invalidOptions` rather than
|
|
1237
|
+
* being silently corrected, because "delete it immediately" and "keep it for
|
|
1238
|
+
* the default period" are too far apart to guess between.
|
|
1239
|
+
*/
|
|
1240
|
+
readonly retentionDays?: number | undefined;
|
|
1241
|
+
/**
|
|
1242
|
+
* The zlib level, 0–9, defaulting to 6 as `@actions/artifact` does.
|
|
1243
|
+
*
|
|
1244
|
+
* @remarks
|
|
1245
|
+
* Out-of-range values are clamped. No effect on Windows, where
|
|
1246
|
+
* `Compress-Archive` has no numeric equivalent.
|
|
1247
|
+
*/
|
|
1248
|
+
readonly compressionLevel?: number | undefined;
|
|
1249
|
+
}
|
|
1250
|
+
/**
|
|
1251
|
+
* What an upload produced.
|
|
1252
|
+
*
|
|
1253
|
+
* @public
|
|
1254
|
+
*/
|
|
1255
|
+
interface UploadResult {
|
|
1256
|
+
readonly id: number;
|
|
1257
|
+
/** The size of the uploaded zip, in bytes. */
|
|
1258
|
+
readonly size: number;
|
|
1259
|
+
}
|
|
1260
|
+
/**
|
|
1261
|
+
* Where a download should land.
|
|
1262
|
+
*
|
|
1263
|
+
* @public
|
|
1264
|
+
*/
|
|
1265
|
+
interface DownloadOptions {
|
|
1266
|
+
/** The destination directory. A fresh temporary directory when omitted. */
|
|
1267
|
+
readonly path?: string | undefined;
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* What a download produced.
|
|
1271
|
+
*
|
|
1272
|
+
* @public
|
|
1273
|
+
*/
|
|
1274
|
+
interface DownloadResult {
|
|
1275
|
+
/** The directory the artifact's contents were extracted into. */
|
|
1276
|
+
readonly downloadPath: string;
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* A reference to an artifact that is no longer there.
|
|
1280
|
+
*
|
|
1281
|
+
* @public
|
|
1282
|
+
*/
|
|
1283
|
+
interface ArtifactRef {
|
|
1284
|
+
readonly id: number;
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* The {@link Artifact} service shape.
|
|
1288
|
+
*
|
|
1289
|
+
* @public
|
|
1290
|
+
*/
|
|
1291
|
+
interface ArtifactShape {
|
|
1292
|
+
/**
|
|
1293
|
+
* Zip `files` — paths under `rootDirectory` — and upload them under `name`.
|
|
1294
|
+
*
|
|
1295
|
+
* @remarks
|
|
1296
|
+
* Entries are stored relative to `rootDirectory`, so what a later download
|
|
1297
|
+
* extracts is the layout the caller meant rather than its absolute path on
|
|
1298
|
+
* whichever runner produced it. A name already used in this run is refused by
|
|
1299
|
+
* the backend: artifacts are immutable within a run.
|
|
1300
|
+
*/
|
|
1301
|
+
readonly upload: (name: string, files: ReadonlyArray<string>, rootDirectory: string, options?: UploadOptions) => Effect.Effect<UploadResult, ArtifactError>;
|
|
1302
|
+
/** Every artifact in the current run. */
|
|
1303
|
+
readonly list: () => Effect.Effect<ReadonlyArray<ArtifactItem>, ArtifactError>;
|
|
1304
|
+
/** One artifact by name, or nothing — absent is not a failure. */
|
|
1305
|
+
readonly get: (name: string) => Effect.Effect<Option.Option<ArtifactItem>, ArtifactError>;
|
|
1306
|
+
/** Download an artifact by id and unzip it. Answers with where it landed. */
|
|
1307
|
+
readonly download: (artifactId: number, options?: DownloadOptions) => Effect.Effect<DownloadResult, ArtifactError>;
|
|
1308
|
+
/** Delete an artifact by name, answering with the id that is now gone. */
|
|
1309
|
+
readonly delete: (name: string) => Effect.Effect<ArtifactRef, ArtifactError>;
|
|
1310
|
+
}
|
|
1311
|
+
declare const Artifact_base: Context.ServiceClass<Artifact, "@effected/github-actions/Artifact", ArtifactShape>;
|
|
1312
|
+
/**
|
|
1313
|
+
* Upload, list, download and delete GitHub Actions artifacts.
|
|
1314
|
+
*
|
|
1315
|
+
* @remarks
|
|
1316
|
+
* Speaks the artifact **Twirp v2** protocol at `ACTIONS_RESULTS_URL`, which
|
|
1317
|
+
* answers with a pre-signed Azure blob url for the zip. No `@actions/artifact`
|
|
1318
|
+
* dependency.
|
|
1319
|
+
*
|
|
1320
|
+
* **Only reachable from a `uses:` step**, like {@link ActionCache} — the two
|
|
1321
|
+
* variables it needs are injected into action execution contexts and not into
|
|
1322
|
+
* `run:` shell steps. The `Actions.Results` scope inside the runtime token is a
|
|
1323
|
+
* third thing that can be absent, and it is reported as `misconfigured` for the
|
|
1324
|
+
* same reason.
|
|
1325
|
+
*
|
|
1326
|
+
* **This surface is provisional.** Unlike every other service here it was
|
|
1327
|
+
* ported without a call site to shape it against, so it keeps the source's
|
|
1328
|
+
* shape rather than a narrower one invented from nothing. The first consumer to
|
|
1329
|
+
* adopt it is the one whose feedback reshapes it — the members lost their
|
|
1330
|
+
* `Artifact` prefix (`artifact.uploadArtifact(...)` stutters) and the
|
|
1331
|
+
* cross-run `findBy` lookup is **not** ported, because every path through it in
|
|
1332
|
+
* the source is a typed "not implemented" failure. Adding it later is additive;
|
|
1333
|
+
* shipping a parameter that has never had behavior is not.
|
|
1334
|
+
*
|
|
1335
|
+
* @example
|
|
1336
|
+
* ```ts
|
|
1337
|
+
* import { Artifact } from "@effected/github-actions";
|
|
1338
|
+
* import { Effect } from "effect";
|
|
1339
|
+
*
|
|
1340
|
+
* const program = Effect.gen(function* () {
|
|
1341
|
+
* const artifacts = yield* Artifact;
|
|
1342
|
+
* return yield* artifacts.upload("logs", ["/tmp/build/log.txt"], "/tmp/build");
|
|
1343
|
+
* });
|
|
1344
|
+
* ```
|
|
1345
|
+
*
|
|
1346
|
+
* @public
|
|
1347
|
+
*/
|
|
1348
|
+
declare class Artifact extends Artifact_base {
|
|
1349
|
+
/** The service, over the real Azure client and the real `zip`. */
|
|
1350
|
+
static readonly layer: Layer.Layer<Artifact, never, ActionEnvironment | HttpClient.HttpClient | FileSystem.FileSystem | Path.Path | ChildProcessSpawner.ChildProcessSpawner>;
|
|
1351
|
+
/**
|
|
1352
|
+
* The service, over a supplied transport.
|
|
1353
|
+
*
|
|
1354
|
+
* @remarks
|
|
1355
|
+
* The protocol, the zip, the digest and the conflict handling are what this
|
|
1356
|
+
* package owns; the pre-signed `PUT` is not. Supplying the transport is what
|
|
1357
|
+
* lets a test exercise all of the first group without the second.
|
|
1358
|
+
*
|
|
1359
|
+
* A parameterized layer factory mints a fresh layer per call and layers
|
|
1360
|
+
* memoize by reference — bind it to a `const` rather than calling it at each
|
|
1361
|
+
* composition site.
|
|
1362
|
+
*/
|
|
1363
|
+
static readonly layerWith: (transfer: FileBlobTransfer) => Layer.Layer<Artifact, never, ActionEnvironment | HttpClient.HttpClient | FileSystem.FileSystem | Path.Path | ChildProcessSpawner.ChildProcessSpawner>;
|
|
1364
|
+
/** A test double. Unstubbed members die rather than reporting an empty run. */
|
|
1365
|
+
static readonly makeTest: (overrides?: Partial<ArtifactShape>) => ArtifactShape;
|
|
1366
|
+
/** {@link Artifact.makeTest} behind `Layer.succeed`. */
|
|
1367
|
+
static readonly layerTest: (overrides?: Partial<ArtifactShape>) => Layer.Layer<Artifact>;
|
|
1368
|
+
}
|
|
1369
|
+
//#endregion
|
|
1370
|
+
//#region src/BlobEnvelope.d.ts
|
|
1371
|
+
declare const BlobEnvelopeError_base: Schema.Class<BlobEnvelopeError, Schema.TaggedStruct<"BlobEnvelopeError", {
|
|
1372
|
+
/**
|
|
1373
|
+
* `notAnEnvelope` — the magic prefix is absent, so these are raw bytes
|
|
1374
|
+
* written before this format existed (or by something else entirely).
|
|
1375
|
+
* `unsupportedVersion` — an envelope from a newer revision.
|
|
1376
|
+
* `truncated` — the frame ends mid-header or mid-metadata.
|
|
1377
|
+
* `metadataDecodeFailed` — the metadata is well-framed but does not satisfy
|
|
1378
|
+
* the caller's schema. `metadataEncodeFailed` — the value being stored does
|
|
1379
|
+
* not satisfy it.
|
|
1380
|
+
*/
|
|
1381
|
+
readonly reason: Schema.Literals<readonly ["notAnEnvelope", "unsupportedVersion", "truncated", "metadataDecodeFailed", "metadataEncodeFailed"]>;
|
|
1382
|
+
/** The envelope version found, when one was readable. */
|
|
1383
|
+
readonly version: Schema.optionalKey<Schema.Number>;
|
|
1384
|
+
readonly cause: Schema.optionalKey<Schema.Defect>;
|
|
1385
|
+
}>, import("effect/Cause").YieldableError>;
|
|
1386
|
+
/**
|
|
1387
|
+
* Raised when bytes cannot be read as an envelope.
|
|
1388
|
+
*
|
|
1389
|
+
* @public
|
|
1390
|
+
*/
|
|
1391
|
+
declare class BlobEnvelopeError extends BlobEnvelopeError_base {
|
|
1392
|
+
get message(): string;
|
|
1393
|
+
}
|
|
1394
|
+
/**
|
|
1395
|
+
* The schema-versioned frame that gives a stored blob a **metadata channel**.
|
|
1396
|
+
*
|
|
1397
|
+
* @remarks
|
|
1398
|
+
* Layout:
|
|
1399
|
+
*
|
|
1400
|
+
* ```text
|
|
1401
|
+
* [4B magic "EFBS"][1B version][4B metadata length, big-endian][metadata JSON][body]
|
|
1402
|
+
* ```
|
|
1403
|
+
*
|
|
1404
|
+
* Three properties earn the format, and each replaces something a consumer was
|
|
1405
|
+
* hand-rolling:
|
|
1406
|
+
*
|
|
1407
|
+
* - **The magic prefix makes a legacy blob legible.** Raw, unframed bytes
|
|
1408
|
+
* decode as `notAnEnvelope` rather than as garbage metadata, so a store
|
|
1409
|
+
* holding pre-envelope entries produces a clean miss instead of a corrupt
|
|
1410
|
+
* read.
|
|
1411
|
+
* - **The version lives in the blob, not in the key.** A consumer previously
|
|
1412
|
+
* namespaced keys (`v2/...`) to represent a format change, because there was
|
|
1413
|
+
* no in-band version. Here a revision is detected on read and reported
|
|
1414
|
+
* typed, so **keys stay stable** and old entries age out naturally.
|
|
1415
|
+
* - **The metadata is the caller's own schema.** This module owns *framing*;
|
|
1416
|
+
* the caller owns *meaning*. Fields like a cache tag or a duration stop
|
|
1417
|
+
* being bytes at fixed offsets in a consumer's private codec.
|
|
1418
|
+
*
|
|
1419
|
+
* **Pure**: `Result`-returning, no IO, no service — the framing is testable
|
|
1420
|
+
* from a byte array, which is the point.
|
|
1421
|
+
*
|
|
1422
|
+
* @public
|
|
1423
|
+
*/
|
|
1424
|
+
declare class BlobEnvelope {
|
|
1425
|
+
private constructor();
|
|
1426
|
+
/** The version this build writes. */
|
|
1427
|
+
static readonly version: number;
|
|
1428
|
+
/** Frame metadata and a body into a single blob. */
|
|
1429
|
+
static encodeResult<A, I>(metadata: A, body: Uint8Array, schema: Schema.Codec<A, I>): Result.Result<Uint8Array, BlobEnvelopeError>;
|
|
1430
|
+
/** Read a framed blob back into its metadata and body. */
|
|
1431
|
+
static decodeResult<A, I>(bytes: Uint8Array, schema: Schema.Codec<A, I>): Result.Result<{
|
|
1432
|
+
readonly metadata: A;
|
|
1433
|
+
readonly body: Uint8Array;
|
|
1434
|
+
}, BlobEnvelopeError>;
|
|
1435
|
+
}
|
|
1436
|
+
//#endregion
|
|
1437
|
+
//#region src/BlobStore.d.ts
|
|
1438
|
+
declare const BlobStoreError_base: Schema.Class<BlobStoreError, Schema.TaggedStruct<"BlobStoreError", {
|
|
1439
|
+
/**
|
|
1440
|
+
* `unreachable` — the store could not be contacted. `refused` — it answered,
|
|
1441
|
+
* unhappily; `status` says how. `misconfigured` — the layer was built with
|
|
1442
|
+
* settings the store cannot use.
|
|
1443
|
+
*/
|
|
1444
|
+
readonly reason: Schema.Literals<readonly ["unreachable", "refused", "misconfigured"]>;
|
|
1445
|
+
/** The key involved. A stable identifier, never a value. */
|
|
1446
|
+
readonly key: Schema.optionalKey<Schema.String>;
|
|
1447
|
+
/** The HTTP status, when the store answered. */
|
|
1448
|
+
readonly status: Schema.optionalKey<Schema.Number>;
|
|
1449
|
+
/** What is wrong, when the reason alone does not say. */
|
|
1450
|
+
readonly detail: Schema.optionalKey<Schema.String>;
|
|
1451
|
+
/** The underlying failure, preserved structurally. */
|
|
1452
|
+
readonly cause: Schema.optionalKey<Schema.Defect>;
|
|
1453
|
+
}>, import("effect/Cause").YieldableError>;
|
|
1454
|
+
/**
|
|
1455
|
+
* Raised when a blob cannot be stored or retrieved.
|
|
1456
|
+
*
|
|
1457
|
+
* @public
|
|
1458
|
+
*/
|
|
1459
|
+
declare class BlobStoreError extends BlobStoreError_base {
|
|
1460
|
+
get message(): string;
|
|
1461
|
+
}
|
|
1462
|
+
/**
|
|
1463
|
+
* A stored value: the caller's metadata beside the bytes it describes.
|
|
1464
|
+
*
|
|
1465
|
+
* @public
|
|
1466
|
+
*/
|
|
1467
|
+
interface Blob<A> {
|
|
1468
|
+
/** The caller's own metadata, decoded through the caller's own schema. */
|
|
1469
|
+
readonly metadata: A;
|
|
1470
|
+
/** The payload, verbatim. */
|
|
1471
|
+
readonly body: Uint8Array;
|
|
1472
|
+
}
|
|
1473
|
+
/**
|
|
1474
|
+
* The {@link BlobStore} service shape.
|
|
1475
|
+
*
|
|
1476
|
+
* @public
|
|
1477
|
+
*/
|
|
1478
|
+
interface BlobStoreShape {
|
|
1479
|
+
/** Read a blob, or nothing if the key is absent. */
|
|
1480
|
+
readonly get: <A, I>(key: string, schema: Schema.Codec<A, I>) => Effect.Effect<Option.Option<Blob<A>>, BlobStoreError | BlobEnvelopeError>;
|
|
1481
|
+
/** Write a blob. */
|
|
1482
|
+
readonly put: <A, I>(key: string, blob: Blob<A>, schema: Schema.Codec<A, I>) => Effect.Effect<void, BlobStoreError | BlobEnvelopeError>;
|
|
1483
|
+
/** Whether a key is present, without transferring the body. */
|
|
1484
|
+
readonly has: (key: string) => Effect.Effect<boolean, BlobStoreError>;
|
|
1485
|
+
}
|
|
1486
|
+
/**
|
|
1487
|
+
* How to reach an S3-compatible object store.
|
|
1488
|
+
*
|
|
1489
|
+
* @public
|
|
1490
|
+
*/
|
|
1491
|
+
interface S3Config {
|
|
1492
|
+
readonly bucket: string;
|
|
1493
|
+
readonly region: string;
|
|
1494
|
+
/**
|
|
1495
|
+
* A custom endpoint, e.g. `https://<account>.r2.cloudflarestorage.com`.
|
|
1496
|
+
*
|
|
1497
|
+
* @remarks
|
|
1498
|
+
* Omitting it targets AWS itself. Addressing is always **path-style**
|
|
1499
|
+
* (`<endpoint>/<bucket>/<key>`), which is what makes the same layer work
|
|
1500
|
+
* against R2, MinIO and Spaces — virtual-host addressing needs per-bucket
|
|
1501
|
+
* DNS that none of them provide the same way.
|
|
1502
|
+
*/
|
|
1503
|
+
readonly endpoint?: string | undefined;
|
|
1504
|
+
readonly accessKeyId: string;
|
|
1505
|
+
readonly secretAccessKey: Redacted.Redacted<string>;
|
|
1506
|
+
/** For temporary credentials. */
|
|
1507
|
+
readonly sessionToken?: Redacted.Redacted<string> | undefined;
|
|
1508
|
+
/** Prepended to every key, so one bucket can hold several caches. */
|
|
1509
|
+
readonly prefix?: string | undefined;
|
|
1510
|
+
}
|
|
1511
|
+
declare const BlobStore_base: Context.ServiceClass<BlobStore, "@effected/github-actions/BlobStore", BlobStoreShape>;
|
|
1512
|
+
/**
|
|
1513
|
+
* Durable blob storage with a metadata channel.
|
|
1514
|
+
*
|
|
1515
|
+
* @remarks
|
|
1516
|
+
* `get`/`put`/`has` over `Uint8Array` **plus the caller's own metadata**, framed
|
|
1517
|
+
* by {@link BlobEnvelope}. The metadata channel is the reason this exists: the
|
|
1518
|
+
* consumer it was designed against hand-rolled a binary frame
|
|
1519
|
+
* (`[4B tagLen][4B durationMs][tag][body]`) and namespaced its keys with a `v2`
|
|
1520
|
+
* prefix to represent a format change. Both are framing concerns that had
|
|
1521
|
+
* leaked into a consumer, and both are absorbed here.
|
|
1522
|
+
*
|
|
1523
|
+
* Deliberately no `list` and no `delete`: eviction belongs to the backend, both
|
|
1524
|
+
* backends already have one, and no consumer asked for either.
|
|
1525
|
+
*
|
|
1526
|
+
* @example
|
|
1527
|
+
* ```ts
|
|
1528
|
+
* import { BlobStore } from "@effected/github-actions";
|
|
1529
|
+
* import { Effect, Schema } from "effect";
|
|
1530
|
+
*
|
|
1531
|
+
* class Meta extends Schema.Class<Meta>("Meta")({ tag: Schema.String, durationMs: Schema.Number }) {}
|
|
1532
|
+
*
|
|
1533
|
+
* const program = Effect.gen(function* () {
|
|
1534
|
+
* const store = yield* BlobStore;
|
|
1535
|
+
* yield* store.put("build/1", { metadata: Meta.make({ tag: "x", durationMs: 12 }), body: bytes }, Meta);
|
|
1536
|
+
* return yield* store.get("build/1", Meta);
|
|
1537
|
+
* });
|
|
1538
|
+
* ```
|
|
1539
|
+
*
|
|
1540
|
+
* @public
|
|
1541
|
+
*/
|
|
1542
|
+
declare class BlobStore extends BlobStore_base {
|
|
1543
|
+
/**
|
|
1544
|
+
* An S3-compatible backend, signed with SigV4.
|
|
1545
|
+
*
|
|
1546
|
+
* @remarks
|
|
1547
|
+
* No `@aws-sdk/*` dependency: signing is a specified algorithm over strings
|
|
1548
|
+
* and HMACs, and this package already has `node:crypto`. The SDK's weight is
|
|
1549
|
+
* in credential management, retries and a service catalogue none of which
|
|
1550
|
+
* this needs.
|
|
1551
|
+
*
|
|
1552
|
+
* A parameterized layer factory mints a fresh layer per call and layers
|
|
1553
|
+
* memoize by reference — bind it to a `const` rather than calling it at each
|
|
1554
|
+
* composition site.
|
|
1555
|
+
*/
|
|
1556
|
+
static readonly layerS3: (config: S3Config) => Layer.Layer<BlobStore, never, HttpClient.HttpClient | ActionOutputs>;
|
|
1557
|
+
/** A test double. Unstubbed members die rather than reporting a miss. */
|
|
1558
|
+
static readonly makeTest: (overrides?: Partial<BlobStoreShape>) => BlobStoreShape;
|
|
1559
|
+
/** {@link BlobStore.makeTest} behind `Layer.succeed`. */
|
|
1560
|
+
static readonly layerTest: (overrides?: Partial<BlobStoreShape>) => Layer.Layer<BlobStore>;
|
|
1561
|
+
/**
|
|
1562
|
+
* An in-memory backend.
|
|
1563
|
+
*
|
|
1564
|
+
* @remarks
|
|
1565
|
+
* Not a stub: it runs the real {@link BlobEnvelope} framing, so a round trip
|
|
1566
|
+
* through it exercises encode and decode exactly as a network backend would.
|
|
1567
|
+
* A test that wants to prove its metadata survives storage should use this
|
|
1568
|
+
* rather than a double whose `get` returns whatever its `put` was handed.
|
|
1569
|
+
*/
|
|
1570
|
+
static readonly layerMemory: Layer.Layer<BlobStore>;
|
|
1571
|
+
}
|
|
1572
|
+
//#endregion
|
|
1573
|
+
//#region src/BlobStore.githubCache.d.ts
|
|
1574
|
+
/**
|
|
1575
|
+
* The {@link BlobStore} backend that stores blobs in the runner's own Actions
|
|
1576
|
+
* cache.
|
|
1577
|
+
*
|
|
1578
|
+
* @remarks
|
|
1579
|
+
* The Actions cache is reachable over a Twirp v2 protocol at
|
|
1580
|
+
* `ACTIONS_RESULTS_URL`, which answers a `CreateCacheEntry` /
|
|
1581
|
+
* `GetCacheEntryDownloadURL` RPC with a **pre-signed Azure blob url** — which is
|
|
1582
|
+
* why this backend lives in its own module rather than beside the S3 one, and
|
|
1583
|
+
* why the spec's "Azure is confined to the cache and the artifact service" is
|
|
1584
|
+
* three modules rather than two.
|
|
1585
|
+
*
|
|
1586
|
+
* **Only reachable from a `uses:` step.** The runner injects
|
|
1587
|
+
* `ACTIONS_RESULTS_URL` and `ACTIONS_RUNTIME_TOKEN` into action execution
|
|
1588
|
+
* contexts and not into `run:` shell steps, so a program that works when
|
|
1589
|
+
* invoked from a bundled action fails as `misconfigured` when the same code is
|
|
1590
|
+
* run by `node ./main.js` in a workflow step. The failure names the variable
|
|
1591
|
+
* for exactly that reason.
|
|
1592
|
+
*
|
|
1593
|
+
* The layer statics live here rather than on {@link BlobStore} because a static
|
|
1594
|
+
* belongs to the module that owns the dependency it needs: putting
|
|
1595
|
+
* `BlobStore.layerGitHubCache` on the service class would make `@azure/storage-blob`
|
|
1596
|
+
* reachable from every module that reads a blob.
|
|
1597
|
+
*
|
|
1598
|
+
* @example
|
|
1599
|
+
* ```ts
|
|
1600
|
+
* import { GitHubCacheBlobStore } from "@effected/github-actions";
|
|
1601
|
+
*
|
|
1602
|
+
* const layer = GitHubCacheBlobStore.layer;
|
|
1603
|
+
* ```
|
|
1604
|
+
*
|
|
1605
|
+
* @public
|
|
1606
|
+
*/
|
|
1607
|
+
declare class GitHubCacheBlobStore {
|
|
1608
|
+
private constructor();
|
|
1609
|
+
/** The backend, over the real Azure client. */
|
|
1610
|
+
static readonly layer: Layer.Layer<BlobStore, never, HttpClient.HttpClient | ActionEnvironment>;
|
|
1611
|
+
/**
|
|
1612
|
+
* The backend, over a supplied transport.
|
|
1613
|
+
*
|
|
1614
|
+
* @remarks
|
|
1615
|
+
* The Twirp protocol — the RPC sequence, the conflict handling, the retry
|
|
1616
|
+
* policy, the framing — is what this package owns and what a test needs to
|
|
1617
|
+
* exercise; the Azure client is a pre-signed `PUT` that owns none of it. This
|
|
1618
|
+
* is also the seam an integration test uses to point the same protocol at a
|
|
1619
|
+
* local blob endpoint.
|
|
1620
|
+
*
|
|
1621
|
+
* A parameterized layer factory mints a fresh layer per call and layers
|
|
1622
|
+
* memoize by reference — bind it to a `const` rather than calling it at each
|
|
1623
|
+
* composition site.
|
|
1624
|
+
*/
|
|
1625
|
+
static readonly layerWith: (transfer: DataBlobTransfer) => Layer.Layer<BlobStore, never, HttpClient.HttpClient | ActionEnvironment>;
|
|
1626
|
+
}
|
|
1627
|
+
//#endregion
|
|
1628
|
+
//#region src/DetachedProcess.d.ts
|
|
1629
|
+
declare const DetachedProcessError_base: Schema.Class<DetachedProcessError, Schema.TaggedStruct<"DetachedProcessError", {
|
|
1630
|
+
/**
|
|
1631
|
+
* `logUnavailable` — the log file could not be opened, so there is nowhere
|
|
1632
|
+
* for the child's output to go. `spawnFailed` — the child did not start.
|
|
1633
|
+
* `invalidPid` — a non-positive pid was handed to {@link DetachedProcess.reap};
|
|
1634
|
+
* see there for why this is the most important value in the module.
|
|
1635
|
+
* `signalFailed` — the signal was refused, e.g. the process belongs to
|
|
1636
|
+
* another user. `notReady` — the readiness probe never held.
|
|
1637
|
+
*/
|
|
1638
|
+
readonly reason: Schema.Literals<readonly ["logUnavailable", "spawnFailed", "invalidPid", "signalFailed", "notReady"]>;
|
|
1639
|
+
/** The pid involved, when one is. */
|
|
1640
|
+
readonly pid: Schema.optionalKey<Schema.Number>;
|
|
1641
|
+
/** The path involved, when one is. */
|
|
1642
|
+
readonly path: Schema.optionalKey<Schema.String>;
|
|
1643
|
+
/** The underlying failure, preserved structurally. */
|
|
1644
|
+
readonly cause: Schema.optionalKey<Schema.Defect>;
|
|
1645
|
+
}>, import("effect/Cause").YieldableError>;
|
|
1646
|
+
/**
|
|
1647
|
+
* Raised when a detached child cannot be started, waited for, or reaped.
|
|
1648
|
+
*
|
|
1649
|
+
* @public
|
|
1650
|
+
*/
|
|
1651
|
+
declare class DetachedProcessError extends DetachedProcessError_base {
|
|
1652
|
+
get message(): string;
|
|
1653
|
+
}
|
|
1654
|
+
/**
|
|
1655
|
+
* The schema a pid crosses the phase boundary through.
|
|
1656
|
+
*
|
|
1657
|
+
* @remarks
|
|
1658
|
+
* A pid is persisted in `GITHUB_STATE` by `main` and read back by `post`, which
|
|
1659
|
+
* means it makes the round trip as **text**. This is the boundary check on the
|
|
1660
|
+
* way back in: a truncated file, an absent key or a `Number("")` all decode to
|
|
1661
|
+
* `0`, and `0` is not a process — it is the current process's whole group.
|
|
1662
|
+
* Refusing it here means the bad value never reaches {@link DetachedProcess.reap}
|
|
1663
|
+
* in the first place, and reap's own guard is the second of two defenses rather
|
|
1664
|
+
* than the only one.
|
|
1665
|
+
*
|
|
1666
|
+
* **The brand is core's**, from `ChildProcessSpawner` — the subprocess
|
|
1667
|
+
* vocabulary belongs to core and this package does not get to re-declare it.
|
|
1668
|
+
* What core does not have is a *validating* way to reconstruct one: its
|
|
1669
|
+
* constructor is `Brand.nominal`, which applies no runtime check, so
|
|
1670
|
+
* `ChildProcessSpawner.ProcessId(0)` succeeds. That is exactly right for a pid
|
|
1671
|
+
* the spawner just produced, and exactly wrong for a pid that has been through a
|
|
1672
|
+
* text file. This schema is that missing check, and it decodes to core's type so
|
|
1673
|
+
* the two are interchangeable.
|
|
1674
|
+
*
|
|
1675
|
+
* @example
|
|
1676
|
+
* ```ts
|
|
1677
|
+
* import { ActionState, DetachedProcess, ProcessId } from "@effected/github-actions";
|
|
1678
|
+
* import { Effect } from "effect";
|
|
1679
|
+
*
|
|
1680
|
+
* const post = Effect.gen(function* () {
|
|
1681
|
+
* const pid = yield* (yield* ActionState).get("server-pid", ProcessId);
|
|
1682
|
+
* return yield* DetachedProcess.reap(pid);
|
|
1683
|
+
* });
|
|
1684
|
+
* ```
|
|
1685
|
+
*
|
|
1686
|
+
* @public
|
|
1687
|
+
*/
|
|
1688
|
+
declare const ProcessId: Schema.brand<Schema.Number, "ProcessId">;
|
|
1689
|
+
/**
|
|
1690
|
+
* How a detached child is started.
|
|
1691
|
+
*
|
|
1692
|
+
* @public
|
|
1693
|
+
*/
|
|
1694
|
+
interface DetachedSpawnOptions {
|
|
1695
|
+
/** The executable. */
|
|
1696
|
+
readonly command: string;
|
|
1697
|
+
/** Its arguments. */
|
|
1698
|
+
readonly args?: ReadonlyArray<string> | undefined;
|
|
1699
|
+
/**
|
|
1700
|
+
* Where the child's stdout and stderr go, opened for append.
|
|
1701
|
+
*
|
|
1702
|
+
* @remarks
|
|
1703
|
+
* A file **descriptor**, not a pipe. An in-process pipe would keep the parent
|
|
1704
|
+
* attached to the child and defeat the detachment, which is the upstream
|
|
1705
|
+
* limitation this module exists to work around — core's `CommandOptions.stdout`
|
|
1706
|
+
* maps to a pipe, and so do its additional fds. If core grows fd routing this
|
|
1707
|
+
* becomes a thin adapter over it.
|
|
1708
|
+
*/
|
|
1709
|
+
readonly logFile: string;
|
|
1710
|
+
/** The working directory. */
|
|
1711
|
+
readonly cwd?: string | undefined;
|
|
1712
|
+
/**
|
|
1713
|
+
* Environment additions.
|
|
1714
|
+
*
|
|
1715
|
+
* @remarks
|
|
1716
|
+
* **Merged over the parent's environment, not substituted for it.** A bare
|
|
1717
|
+
* environment costs the child `PATH`, which usually means it cannot find its
|
|
1718
|
+
* own runtime and dies before writing a word to the log — a failure that
|
|
1719
|
+
* looks like a spawn bug and is not. Secrets belong here only via
|
|
1720
|
+
* `Secret.forChildEnv`, which masks them on the way out.
|
|
1721
|
+
*/
|
|
1722
|
+
readonly env?: Readonly<Record<string, string>> | undefined;
|
|
1723
|
+
}
|
|
1724
|
+
/**
|
|
1725
|
+
* How long to wait for a child to become ready.
|
|
1726
|
+
*
|
|
1727
|
+
* @public
|
|
1728
|
+
*/
|
|
1729
|
+
interface ReadinessOptions {
|
|
1730
|
+
/** Time between probes. Defaults to 150 milliseconds. */
|
|
1731
|
+
readonly interval?: Duration.Input | undefined;
|
|
1732
|
+
/** How many times to retry after the first probe. Defaults to 40. */
|
|
1733
|
+
readonly attempts?: number | undefined;
|
|
1734
|
+
}
|
|
1735
|
+
/**
|
|
1736
|
+
* A long-lived child that outlives the phase that started it.
|
|
1737
|
+
*
|
|
1738
|
+
* @remarks
|
|
1739
|
+
* The shape an action needs is three separate things, because the child
|
|
1740
|
+
* survives the process that spawned it: `main` starts it and persists the pid,
|
|
1741
|
+
* `main` or `post` waits on a domain predicate rather than on time, and `post`
|
|
1742
|
+
* — a **different process**, holding no handle to the child — signals the pid
|
|
1743
|
+
* it read back out of `GITHUB_STATE`.
|
|
1744
|
+
*
|
|
1745
|
+
* That last step is why this lives here rather than in a command runner. There
|
|
1746
|
+
* is no `ChildProcess` to `.kill()`; there is only a number that has been
|
|
1747
|
+
* through a text file. See {@link DetachedProcess.reap}.
|
|
1748
|
+
*
|
|
1749
|
+
* @example
|
|
1750
|
+
* ```ts
|
|
1751
|
+
* import { DetachedProcess } from "@effected/github-actions";
|
|
1752
|
+
* import { Effect } from "effect";
|
|
1753
|
+
*
|
|
1754
|
+
* const program = Effect.gen(function* () {
|
|
1755
|
+
* const pid = yield* DetachedProcess.spawn({
|
|
1756
|
+
* command: "node",
|
|
1757
|
+
* args: ["server.js"],
|
|
1758
|
+
* logFile: "/tmp/server.log",
|
|
1759
|
+
* });
|
|
1760
|
+
* yield* DetachedProcess.awaitReady(healthCheck);
|
|
1761
|
+
* return pid;
|
|
1762
|
+
* });
|
|
1763
|
+
* ```
|
|
1764
|
+
*
|
|
1765
|
+
* @public
|
|
1766
|
+
*/
|
|
1767
|
+
declare class DetachedProcess {
|
|
1768
|
+
private constructor();
|
|
1769
|
+
/**
|
|
1770
|
+
* Start a detached child with its output routed to a log file.
|
|
1771
|
+
*
|
|
1772
|
+
* @remarks
|
|
1773
|
+
* The parent closes its own copy of the descriptor immediately — the child
|
|
1774
|
+
* holds a duplicate, so the log keeps filling after the action's process
|
|
1775
|
+
* exits, which is the entire point.
|
|
1776
|
+
*/
|
|
1777
|
+
static readonly spawn: (options: DetachedSpawnOptions) => Effect.Effect<ChildProcessSpawner.ProcessId, DetachedProcessError, never>;
|
|
1778
|
+
/**
|
|
1779
|
+
* Poll a predicate until it holds.
|
|
1780
|
+
*
|
|
1781
|
+
* @remarks
|
|
1782
|
+
* A domain predicate rather than a sleep: "the server answers" is the thing
|
|
1783
|
+
* being waited for, and any fixed delay is simultaneously too long on a fast
|
|
1784
|
+
* runner and too short on a slow one. Two consumers hand-rolled this, one of
|
|
1785
|
+
* them around an `Effect.suspend` footgun it had to explain in a comment.
|
|
1786
|
+
*
|
|
1787
|
+
* A probe *failure* is propagated rather than treated as "not ready" — a
|
|
1788
|
+
* probe that cannot run is a different situation from a child that is not up
|
|
1789
|
+
* yet, and silently retrying the first is how a misconfigured probe becomes a
|
|
1790
|
+
* timeout six seconds later with nothing to show for it.
|
|
1791
|
+
*/
|
|
1792
|
+
static readonly awaitReady: <E, R>(probe: Effect.Effect<boolean, E, R>, options?: ReadinessOptions | undefined) => Effect.Effect<void, E | DetachedProcessError, R>;
|
|
1793
|
+
/**
|
|
1794
|
+
* Signal a pid that came back across the phase boundary.
|
|
1795
|
+
*
|
|
1796
|
+
* @remarks
|
|
1797
|
+
* **The guard is the reason this function exists.** `process.kill(0, …)`
|
|
1798
|
+
* signals the caller's entire process group and `process.kill(-1, …)` signals
|
|
1799
|
+
* every process the user owns — so on a GitHub runner, an unguarded reap of a
|
|
1800
|
+
* pid that decoded to `0` takes down the job that is running it. The value is
|
|
1801
|
+
* exactly the one most likely to be wrong: it is what an absent state key, a
|
|
1802
|
+
* truncated state file or a `Number("")` all produce.
|
|
1803
|
+
*
|
|
1804
|
+
* A non-positive pid therefore fails **typed and before any signal is sent**.
|
|
1805
|
+
* The parameter is a plain `number` rather than a {@link ProcessId} on purpose:
|
|
1806
|
+
* the value arrives as text from another process, so the type system stopped
|
|
1807
|
+
* applying the moment it crossed that boundary, and a guard that only a
|
|
1808
|
+
* well-typed caller can trip is not a guard.
|
|
1809
|
+
*
|
|
1810
|
+
* Returns `false` for a process that is already gone, because a `post` phase
|
|
1811
|
+
* finding its child already dead is the normal ending, not a failure.
|
|
1812
|
+
*/
|
|
1813
|
+
static readonly reap: (pid: number, signal?: NodeJS.Signals | undefined) => Effect.Effect<boolean, DetachedProcessError, never>;
|
|
1814
|
+
}
|
|
1815
|
+
//#endregion
|
|
1816
|
+
//#region src/DryRun.d.ts
|
|
1817
|
+
/**
|
|
1818
|
+
* The {@link DryRun} service shape.
|
|
1819
|
+
*
|
|
1820
|
+
* @public
|
|
1821
|
+
*/
|
|
1822
|
+
interface DryRunShape {
|
|
1823
|
+
/** Whether this run is a rehearsal. */
|
|
1824
|
+
readonly isDryRun: Effect.Effect<boolean>;
|
|
1825
|
+
/**
|
|
1826
|
+
* Run a mutation, or skip it and take the fallback.
|
|
1827
|
+
*
|
|
1828
|
+
* @remarks
|
|
1829
|
+
* The `label` is what a dry run reports instead of doing the work, so it
|
|
1830
|
+
* should name the mutation from the workflow author's point of view
|
|
1831
|
+
* (`"publish @acme/thing@1.2.3"`), not the function that would have run it.
|
|
1832
|
+
*
|
|
1833
|
+
* The fallback is required rather than optional on purpose: a mutation whose
|
|
1834
|
+
* result the caller uses must say what a rehearsal produces instead, and the
|
|
1835
|
+
* type is the place to force that. For a `void` mutation it is simply
|
|
1836
|
+
* `undefined`.
|
|
1837
|
+
*/
|
|
1838
|
+
readonly guard: <A, E, R>(label: string, effect: Effect.Effect<A, E, R>, fallback: A) => Effect.Effect<A, E, R>;
|
|
1839
|
+
}
|
|
1840
|
+
declare const DryRun_base: Context.ServiceClass<DryRun, "@effected/github-actions/DryRun", DryRunShape>;
|
|
1841
|
+
/**
|
|
1842
|
+
* The rehearsal guard: every mutation an action performs goes through it, so a
|
|
1843
|
+
* workflow can be run end to end without changing anything.
|
|
1844
|
+
*
|
|
1845
|
+
* @remarks
|
|
1846
|
+
* A service rather than a boolean threaded through call sites, because the
|
|
1847
|
+
* decision has to be available wherever a mutation is, and because a test that
|
|
1848
|
+
* wants to prove "this path mutates nothing" provides one layer instead of
|
|
1849
|
+
* auditing every branch.
|
|
1850
|
+
*
|
|
1851
|
+
* @example
|
|
1852
|
+
* ```ts
|
|
1853
|
+
* import { DryRun } from "@effected/github-actions";
|
|
1854
|
+
* import { Effect } from "effect";
|
|
1855
|
+
*
|
|
1856
|
+
* const program = Effect.gen(function* () {
|
|
1857
|
+
* const dryRun = yield* DryRun;
|
|
1858
|
+
* yield* dryRun.guard("publish @acme/thing@1.2.3", publish, undefined);
|
|
1859
|
+
* });
|
|
1860
|
+
* ```
|
|
1861
|
+
*
|
|
1862
|
+
* @public
|
|
1863
|
+
*/
|
|
1864
|
+
declare class DryRun extends DryRun_base {
|
|
1865
|
+
/**
|
|
1866
|
+
* Driven by the `dry-run` action input, defaulting to a real run.
|
|
1867
|
+
*
|
|
1868
|
+
* @remarks
|
|
1869
|
+
* Fails with a `ConfigError` when the input is present but is not a YAML 1.2
|
|
1870
|
+
* core-schema boolean — a workflow that writes `dry-run: yes` should stop,
|
|
1871
|
+
* not quietly perform the mutations it meant to rehearse.
|
|
1872
|
+
*/
|
|
1873
|
+
static readonly layer: Layer.Layer<DryRun, Config.ConfigError>;
|
|
1874
|
+
/**
|
|
1875
|
+
* Driven by a named input.
|
|
1876
|
+
*
|
|
1877
|
+
* @remarks
|
|
1878
|
+
* A parameterized layer factory mints a fresh layer per call and layers
|
|
1879
|
+
* memoize by reference — bind it to a `const` rather than calling it at each
|
|
1880
|
+
* composition site.
|
|
1881
|
+
*/
|
|
1882
|
+
static readonly layerFromInput: (name: string) => Layer.Layer<DryRun, Config.ConfigError>;
|
|
1883
|
+
/** Driven by an explicit decision the caller has already made. */
|
|
1884
|
+
static readonly layerFrom: (enabled: boolean) => Layer.Layer<DryRun>;
|
|
1885
|
+
/**
|
|
1886
|
+
* A double that rehearses.
|
|
1887
|
+
*
|
|
1888
|
+
* @remarks
|
|
1889
|
+
* **A recorded exception to the die-on-unstubbed rule.** The default is not a
|
|
1890
|
+
* fabrication but the safe direction: a test that forgot to say which mode it
|
|
1891
|
+
* wants gets the mode that mutates nothing. The members are the real
|
|
1892
|
+
* implementation, so the double cannot drift from it.
|
|
1893
|
+
*/
|
|
1894
|
+
static readonly makeTest: (overrides?: Partial<DryRunShape>) => DryRunShape;
|
|
1895
|
+
/** {@link DryRun.makeTest} behind `Layer.succeed`. */
|
|
1896
|
+
static readonly layerTest: (overrides?: Partial<DryRunShape>) => Layer.Layer<DryRun>;
|
|
1897
|
+
}
|
|
1898
|
+
//#endregion
|
|
1899
|
+
//#region src/GitHubToken.d.ts
|
|
1900
|
+
declare const GitHubTokenError_base: Schema.Class<GitHubTokenError, Schema.TaggedStruct<"GitHubTokenError", {
|
|
1901
|
+
/**
|
|
1902
|
+
* One reason, deliberately: everything else that can go wrong here already
|
|
1903
|
+
* has an owner — persistence is an `ActionStateError`, minting is a
|
|
1904
|
+
* `GitHubAppError`, and scope verification is a `TokenPermissionError`.
|
|
1905
|
+
*/
|
|
1906
|
+
readonly reason: Schema.Literals<readonly ["expired"]>;
|
|
1907
|
+
/** When GitHub stopped accepting it, ISO-8601. */
|
|
1908
|
+
readonly expiresAt: Schema.String;
|
|
1909
|
+
}>, import("effect/Cause").YieldableError>;
|
|
1910
|
+
/**
|
|
1911
|
+
* Raised when the token an earlier phase persisted cannot be used.
|
|
1912
|
+
*
|
|
1913
|
+
* @public
|
|
1914
|
+
*/
|
|
1915
|
+
declare class GitHubTokenError extends GitHubTokenError_base {
|
|
1916
|
+
get message(): string;
|
|
1917
|
+
}
|
|
1918
|
+
/**
|
|
1919
|
+
* What to mint a token for.
|
|
1920
|
+
*
|
|
1921
|
+
* @public
|
|
1922
|
+
*/
|
|
1923
|
+
interface ProvisionOptions {
|
|
1924
|
+
/** The app id or client id. */
|
|
1925
|
+
readonly appId: string;
|
|
1926
|
+
/** The app's private key, in PEM. */
|
|
1927
|
+
readonly privateKey: Redacted.Redacted<string>;
|
|
1928
|
+
/** The installation. Discovered from `owner`, or from the app's only installation, when omitted. */
|
|
1929
|
+
readonly installationId?: number | undefined;
|
|
1930
|
+
/** The account whose installation to use, when `installationId` is omitted. */
|
|
1931
|
+
readonly owner?: string | undefined;
|
|
1932
|
+
/**
|
|
1933
|
+
* The permissions the action needs.
|
|
1934
|
+
*
|
|
1935
|
+
* @remarks
|
|
1936
|
+
* Verified against what GitHub actually granted, which can be narrower than
|
|
1937
|
+
* what was asked for. Checked **before** the token is persisted, so a
|
|
1938
|
+
* workflow with a misconfigured installation fails at `pre` with a message
|
|
1939
|
+
* naming the missing permission rather than in the middle of `main` with a
|
|
1940
|
+
* `403` on one request.
|
|
1941
|
+
*/
|
|
1942
|
+
readonly required?: Readonly<Record<string, PermissionLevel>> | undefined;
|
|
1943
|
+
/** The `GITHUB_STATE` key. One default, so `pre` and `post` agree without saying so. */
|
|
1944
|
+
readonly stateKey?: string | undefined;
|
|
1945
|
+
}
|
|
1946
|
+
/** Where to read a persisted token from. @public */
|
|
1947
|
+
interface ReadOptions {
|
|
1948
|
+
/** The `GITHUB_STATE` key. */
|
|
1949
|
+
readonly stateKey?: string | undefined;
|
|
1950
|
+
/**
|
|
1951
|
+
* How long before the stated expiry to treat the token as spent.
|
|
1952
|
+
*
|
|
1953
|
+
* @remarks
|
|
1954
|
+
* A minute by default. The window exists because the check and the request
|
|
1955
|
+
* it guards are not the same instant.
|
|
1956
|
+
*/
|
|
1957
|
+
readonly skew?: Duration.Duration | undefined;
|
|
1958
|
+
}
|
|
1959
|
+
/** How to build a client from a persisted token. @public */
|
|
1960
|
+
interface ClientLayerOptions extends ReadOptions {
|
|
1961
|
+
/** Retry behavior. Defaults to the client's own policy; `"off"` disables it. */
|
|
1962
|
+
readonly retry?: RetryPolicy | "off" | undefined;
|
|
1963
|
+
/** A GitHub Enterprise API root. */
|
|
1964
|
+
readonly baseUrl?: string | undefined;
|
|
1965
|
+
/** Appended to octokit's own user agent. */
|
|
1966
|
+
readonly userAgent?: string | undefined;
|
|
1967
|
+
/** A replacement for the global `fetch`. */
|
|
1968
|
+
readonly fetch?: typeof globalThis.fetch | undefined;
|
|
1969
|
+
}
|
|
1970
|
+
/**
|
|
1971
|
+
* The GitHub App token lifecycle, shaped like a workflow.
|
|
1972
|
+
*
|
|
1973
|
+
* @remarks
|
|
1974
|
+
* One of the two seams between this package and
|
|
1975
|
+
* [`@effected/github`](https://npmjs.com) — mint in `pre`, use in `main`,
|
|
1976
|
+
* revoke in `post`. The three phases are **three separate processes**, so
|
|
1977
|
+
* nothing survives between them except what `GITHUB_STATE` carries, which is
|
|
1978
|
+
* why this is a persistence problem rather than a `Scope`.
|
|
1979
|
+
*
|
|
1980
|
+
* That is also why `GitHubApp.scopedToken` is the wrong primitive here and
|
|
1981
|
+
* `GitHubApp.clientLayer` is a different thing entirely: both revoke when their
|
|
1982
|
+
* scope closes, and the scope closes at the end of `pre` — before `main` has
|
|
1983
|
+
* run a single request.
|
|
1984
|
+
*
|
|
1985
|
+
* **The token lives about an hour, and no later phase can re-mint one.** The
|
|
1986
|
+
* credentials that could are the app's private key, and persisting *that*
|
|
1987
|
+
* through `GITHUB_STATE` — a plaintext file by GitHub's protocol — would trade
|
|
1988
|
+
* a one-hour token for a permanent one. So the v1 contract is stated rather
|
|
1989
|
+
* than worked around: {@link GitHubToken.read} fails typed when the persisted
|
|
1990
|
+
* token is spent, and a phase that can outlive the hour calls
|
|
1991
|
+
* {@link GitHubToken.provision} itself.
|
|
1992
|
+
*
|
|
1993
|
+
* Grouped statics on one module reaching one dependency — the carve-out to the
|
|
1994
|
+
* no-namespace-objects rule, not the hazard it names. Every member's `R` says
|
|
1995
|
+
* exactly which services it needs, and the members below say which parts of
|
|
1996
|
+
* each they touch, so a partial double is built from the documentation rather
|
|
1997
|
+
* than from a stack trace:
|
|
1998
|
+
*
|
|
1999
|
+
* | Member | `ActionState` | `ActionOutputs` | `GitHubApp` |
|
|
2000
|
+
* | --- | --- | --- | --- |
|
|
2001
|
+
* | `provision` | `save` | `setSecret` | `token`, `identity`, `revoke` |
|
|
2002
|
+
* | `read` | `get` | — | — |
|
|
2003
|
+
* | `botIdentity` | `get` | — | — |
|
|
2004
|
+
* | `clientLayer` | `get` | — | — |
|
|
2005
|
+
* | `dispose` | `getOptional` | — | `revoke` |
|
|
2006
|
+
*
|
|
2007
|
+
* @example
|
|
2008
|
+
* ```ts
|
|
2009
|
+
* import { GitHubToken } from "@effected/github-actions";
|
|
2010
|
+
* import { Effect, Redacted } from "effect";
|
|
2011
|
+
*
|
|
2012
|
+
* const pre = Effect.gen(function* () {
|
|
2013
|
+
* return yield* GitHubToken.provision({
|
|
2014
|
+
* appId: "123456",
|
|
2015
|
+
* privateKey: Redacted.make(pem),
|
|
2016
|
+
* owner: "acme",
|
|
2017
|
+
* required: { contents: "write", pull_requests: "write" },
|
|
2018
|
+
* });
|
|
2019
|
+
* });
|
|
2020
|
+
* ```
|
|
2021
|
+
*
|
|
2022
|
+
* @public
|
|
2023
|
+
*/
|
|
2024
|
+
declare class GitHubToken {
|
|
2025
|
+
private constructor();
|
|
2026
|
+
/**
|
|
2027
|
+
* Mint a token, verify its scopes, mask it and persist it for later phases.
|
|
2028
|
+
*
|
|
2029
|
+
* @remarks
|
|
2030
|
+
* An `acquireUseRelease`, and the release arm is the load-bearing part: if
|
|
2031
|
+
* scope verification or persistence fails, the minted token is **revoked**
|
|
2032
|
+
* rather than left live until GitHub expires it. A workflow that retries a
|
|
2033
|
+
* failing `pre` would otherwise leave an hour's worth of unreferenced write
|
|
2034
|
+
* tokens behind, each of which is a credential nobody is tracking.
|
|
2035
|
+
*
|
|
2036
|
+
* The masking happens **before** the persistence, through
|
|
2037
|
+
* {@link Secret.forRunnerFile}, so the ordering is structural rather than
|
|
2038
|
+
* remembered: `GITHUB_STATE` is plaintext by protocol, and the runner's log
|
|
2039
|
+
* filter is the only defense available.
|
|
2040
|
+
*/
|
|
2041
|
+
static readonly provision: (options: ProvisionOptions) => Effect.Effect<InstallationToken, ActionStateError | import("@effected/github").GitHubAppError | import("@effected/github").TokenPermissionError, ActionOutputs | ActionState | GitHubApp>;
|
|
2042
|
+
/**
|
|
2043
|
+
* The token an earlier phase persisted.
|
|
2044
|
+
*
|
|
2045
|
+
* @remarks
|
|
2046
|
+
* Fails typed when it is spent. The package this replaces persisted the
|
|
2047
|
+
* expiry and read it nowhere, so a `main` phase that outlived the hour simply
|
|
2048
|
+
* started answering `401` with no explanation — which is the single hardest
|
|
2049
|
+
* failure in this whole lifecycle to diagnose from a workflow log.
|
|
2050
|
+
*/
|
|
2051
|
+
static readonly read: (options?: ReadOptions | undefined) => Effect.Effect<InstallationToken, ActionStateError | GitHubTokenError, ActionState>;
|
|
2052
|
+
/**
|
|
2053
|
+
* The committer identity a commit made with the persisted token should carry.
|
|
2054
|
+
*
|
|
2055
|
+
* @remarks
|
|
2056
|
+
* Pure once the token is in hand — the identity fields travel with it — so
|
|
2057
|
+
* this costs one state read and no request.
|
|
2058
|
+
*/
|
|
2059
|
+
static readonly botIdentity: (options?: ReadOptions | undefined) => Effect.Effect<BotIdentity, ActionStateError | GitHubTokenError, ActionState>;
|
|
2060
|
+
/**
|
|
2061
|
+
* A client built from the persisted token.
|
|
2062
|
+
*
|
|
2063
|
+
* @remarks
|
|
2064
|
+
* Built with `GitHubClient.layerFromToken` rather than through `GitHubApp`,
|
|
2065
|
+
* and the difference matters: the App path links a JWT signer and needs the
|
|
2066
|
+
* private key, neither of which a later phase has or should have. This path
|
|
2067
|
+
* needs only the token the `pre` phase already minted.
|
|
2068
|
+
*
|
|
2069
|
+
* A parameterized layer factory mints a fresh layer per call and layers
|
|
2070
|
+
* memoize by reference — bind it to a `const` rather than calling it at each
|
|
2071
|
+
* composition site.
|
|
2072
|
+
*/
|
|
2073
|
+
static readonly clientLayer: (options?: ClientLayerOptions) => Layer.Layer<GitHubClient, ActionStateError | GitHubTokenError, ActionState>;
|
|
2074
|
+
/**
|
|
2075
|
+
* Revoke the persisted token, if there is one.
|
|
2076
|
+
*
|
|
2077
|
+
* @remarks
|
|
2078
|
+
* `getOptional`, so a `post` phase that runs after a `pre` that never got as
|
|
2079
|
+
* far as provisioning is a **no-op rather than a failure** — which is the
|
|
2080
|
+
* common shape of a workflow that failed early, and the last thing it needs
|
|
2081
|
+
* is a second failure on the way out.
|
|
2082
|
+
*
|
|
2083
|
+
* An already-expired token is not revoked either: GitHub has already stopped
|
|
2084
|
+
* accepting it, so the request would fail and the only thing it could
|
|
2085
|
+
* accomplish is turning a successful run into a failed one.
|
|
2086
|
+
*/
|
|
2087
|
+
static readonly dispose: (options?: ReadOptions | undefined) => Effect.Effect<void, ActionStateError | import("@effected/github").GitHubAppError, ActionState | GitHubApp>;
|
|
2088
|
+
}
|
|
2089
|
+
//#endregion
|
|
2090
|
+
//#region src/OidcTokenIssuer.d.ts
|
|
2091
|
+
declare const OidcTokenError_base: Schema.Class<OidcTokenError, Schema.TaggedStruct<"OidcTokenError", {
|
|
2092
|
+
/**
|
|
2093
|
+
* `unavailable` — the runner did not publish the token-service variables,
|
|
2094
|
+
* which almost always means the workflow is missing `permissions: id-token:
|
|
2095
|
+
* write`. `requestFailed` — the token service could not be reached or refused
|
|
2096
|
+
* the request. `malformedResponse` — it answered with something that is not a
|
|
2097
|
+
* token envelope. `malformedToken` — the token is not a decodable JWT.
|
|
2098
|
+
* `missingClaims` — it decoded, but without the claims a provenance statement
|
|
2099
|
+
* needs.
|
|
2100
|
+
*/
|
|
2101
|
+
readonly reason: Schema.Literals<readonly ["unavailable", "requestFailed", "malformedResponse", "malformedToken", "missingClaims"]>;
|
|
2102
|
+
/** What was wrong, in one line. */
|
|
2103
|
+
readonly detail: Schema.optionalKey<Schema.String>;
|
|
2104
|
+
/** The HTTP status, when the token service answered. */
|
|
2105
|
+
readonly status: Schema.optionalKey<Schema.Number>;
|
|
2106
|
+
/** The underlying failure, preserved structurally. */
|
|
2107
|
+
readonly cause: Schema.optionalKey<Schema.Defect>;
|
|
2108
|
+
}>, import("effect/Cause").YieldableError>;
|
|
2109
|
+
/**
|
|
2110
|
+
* Raised when an OIDC token cannot be issued or read.
|
|
2111
|
+
*
|
|
2112
|
+
* @public
|
|
2113
|
+
*/
|
|
2114
|
+
declare class OidcTokenError extends OidcTokenError_base {
|
|
2115
|
+
get message(): string;
|
|
2116
|
+
}
|
|
2117
|
+
declare const OidcClaims_base: Schema.Class<OidcClaims, Schema.Struct<{
|
|
2118
|
+
/** The issuer, e.g. `https://token.actions.githubusercontent.com`. */
|
|
2119
|
+
readonly iss: Schema.String;
|
|
2120
|
+
/** The full ref the workflow ran on. */
|
|
2121
|
+
readonly ref: Schema.String;
|
|
2122
|
+
/** The commit. */
|
|
2123
|
+
readonly sha: Schema.String;
|
|
2124
|
+
/** `owner/repo`. */
|
|
2125
|
+
readonly repository: Schema.String;
|
|
2126
|
+
/** The event that triggered the run. */
|
|
2127
|
+
readonly event_name: Schema.String;
|
|
2128
|
+
/** The reusable-workflow ref, which is what a verifier pins against. */
|
|
2129
|
+
readonly job_workflow_ref: Schema.String;
|
|
2130
|
+
/** The calling workflow's ref. */
|
|
2131
|
+
readonly workflow_ref: Schema.String;
|
|
2132
|
+
/** The numeric repository id, as a string. */
|
|
2133
|
+
readonly repository_id: Schema.String;
|
|
2134
|
+
/** The numeric owner id, as a string. */
|
|
2135
|
+
readonly repository_owner_id: Schema.String;
|
|
2136
|
+
/** `github-hosted` or `self-hosted`. */
|
|
2137
|
+
readonly runner_environment: Schema.String;
|
|
2138
|
+
/** The run id, as a string. */
|
|
2139
|
+
readonly run_id: Schema.String;
|
|
2140
|
+
/** The run attempt, as a string. */
|
|
2141
|
+
readonly run_attempt: Schema.String;
|
|
2142
|
+
}>, {}>;
|
|
2143
|
+
/**
|
|
2144
|
+
* The claims a GitHub Actions OIDC token carries about the workflow that ran.
|
|
2145
|
+
*
|
|
2146
|
+
* @remarks
|
|
2147
|
+
* The field names are GitHub's own JWT claim names, kept verbatim rather than
|
|
2148
|
+
* translated to the kit's casing. They are an **external vocabulary** — the
|
|
2149
|
+
* same strings that appear in a SLSA provenance predicate and in every
|
|
2150
|
+
* published example of an Actions OIDC policy — and renaming them would put a
|
|
2151
|
+
* translation layer between a consumer and the specification they are reading.
|
|
2152
|
+
*
|
|
2153
|
+
* @public
|
|
2154
|
+
*/
|
|
2155
|
+
declare class OidcClaims extends OidcClaims_base {}
|
|
2156
|
+
/**
|
|
2157
|
+
* The {@link OidcTokenIssuer} service shape.
|
|
2158
|
+
*
|
|
2159
|
+
* @public
|
|
2160
|
+
*/
|
|
2161
|
+
interface OidcTokenIssuerShape {
|
|
2162
|
+
/**
|
|
2163
|
+
* Request an ID token, optionally bound to an audience.
|
|
2164
|
+
*
|
|
2165
|
+
* @remarks
|
|
2166
|
+
* Omitting the audience sends no `audience` parameter at all, matching
|
|
2167
|
+
* `@actions/core.getIDToken` — the runner's default audience is not the empty
|
|
2168
|
+
* string.
|
|
2169
|
+
*/
|
|
2170
|
+
readonly token: (audience?: string) => Effect.Effect<Redacted.Redacted<string>, OidcTokenError>;
|
|
2171
|
+
/**
|
|
2172
|
+
* The token's claims, decoded.
|
|
2173
|
+
*
|
|
2174
|
+
* @remarks
|
|
2175
|
+
* **The signature is deliberately not verified**, for three reasons that are
|
|
2176
|
+
* recorded so nobody "fixes" it:
|
|
2177
|
+
*
|
|
2178
|
+
* 1. The token comes from the runner's own token-service endpoint over TLS.
|
|
2179
|
+
* The transport is the trust boundary, and the process asking for the
|
|
2180
|
+
* token is the process that received it.
|
|
2181
|
+
* 2. The claims populate a provenance predicate, not a trust decision.
|
|
2182
|
+
* Nothing branches on them for authorization; they are recorded as
|
|
2183
|
+
* attested facts about the workflow that ran.
|
|
2184
|
+
* 3. Verifying would need a JWKS fetch, which turns a decode into a network
|
|
2185
|
+
* call — untestable without a fixture server, and dependent on GitHub's
|
|
2186
|
+
* key endpoint being reachable at attestation time.
|
|
2187
|
+
*
|
|
2188
|
+
* A consumer that needs a *verified* token needs a different operation with a
|
|
2189
|
+
* different name and a different error channel, not an option on this one.
|
|
2190
|
+
*
|
|
2191
|
+
* Claims are on the surface rather than left to the call site because that is
|
|
2192
|
+
* what makes the provenance path reachable in a test: a double built with
|
|
2193
|
+
* {@link OidcTokenIssuer.layerFor} answers with real, decodable claims, where
|
|
2194
|
+
* the synthetic non-JWT its predecessor returned made every consumer's
|
|
2195
|
+
* `decodeJwtClaims` yield nothing and silently skipped the path under test.
|
|
2196
|
+
*/
|
|
2197
|
+
readonly claims: (audience?: string) => Effect.Effect<OidcClaims, OidcTokenError>;
|
|
2198
|
+
}
|
|
2199
|
+
declare const OidcTokenIssuer_base: Context.ServiceClass<OidcTokenIssuer, "@effected/github-actions/OidcTokenIssuer", OidcTokenIssuerShape>;
|
|
2200
|
+
/**
|
|
2201
|
+
* The runner's OIDC token service.
|
|
2202
|
+
*
|
|
2203
|
+
* @remarks
|
|
2204
|
+
* Lives here rather than with attestation because it reads
|
|
2205
|
+
* `ACTIONS_ID_TOKEN_REQUEST_TOKEN` and `ACTIONS_ID_TOKEN_REQUEST_URL`, which
|
|
2206
|
+
* exist only when a workflow declares `permissions: id-token: write` — that is
|
|
2207
|
+
* a fact about the runner, not about signing.
|
|
2208
|
+
*
|
|
2209
|
+
* @example
|
|
2210
|
+
* ```ts
|
|
2211
|
+
* import { OidcTokenIssuer } from "@effected/github-actions";
|
|
2212
|
+
* import { Effect } from "effect";
|
|
2213
|
+
*
|
|
2214
|
+
* const program = Effect.gen(function* () {
|
|
2215
|
+
* const issuer = yield* OidcTokenIssuer;
|
|
2216
|
+
* const claims = yield* issuer.claims("sigstore");
|
|
2217
|
+
* return claims.job_workflow_ref;
|
|
2218
|
+
* });
|
|
2219
|
+
* ```
|
|
2220
|
+
*
|
|
2221
|
+
* @public
|
|
2222
|
+
*/
|
|
2223
|
+
declare class OidcTokenIssuer extends OidcTokenIssuer_base {
|
|
2224
|
+
static readonly layer: Layer.Layer<OidcTokenIssuer, never, ActionEnvironment | HttpClient.HttpClient>;
|
|
2225
|
+
/**
|
|
2226
|
+
* An **unsigned** JWT carrying these claims.
|
|
2227
|
+
*
|
|
2228
|
+
* @remarks
|
|
2229
|
+
* For building test doubles, and nothing else: the signature segment is a
|
|
2230
|
+
* placeholder, so this token would fail any verifier. It exists because the
|
|
2231
|
+
* alternative — a double returning a synthetic non-JWT — is what made the
|
|
2232
|
+
* provenance path structurally unreachable in the source package, drawing
|
|
2233
|
+
* four separate apologetic comments from one consumer.
|
|
2234
|
+
*/
|
|
2235
|
+
static readonly unsignedTokenFor: (claims: OidcClaims) => Redacted.Redacted<string>;
|
|
2236
|
+
/** A test double. Unstubbed members die rather than answering with a non-token. */
|
|
2237
|
+
static readonly makeTest: (overrides?: Partial<OidcTokenIssuerShape>) => OidcTokenIssuerShape;
|
|
2238
|
+
/** {@link OidcTokenIssuer.makeTest} behind `Layer.succeed`. */
|
|
2239
|
+
static readonly layerTest: (overrides?: Partial<OidcTokenIssuerShape>) => Layer.Layer<OidcTokenIssuer>;
|
|
2240
|
+
/**
|
|
2241
|
+
* A double that answers with these claims, consistently on both members.
|
|
2242
|
+
*
|
|
2243
|
+
* @remarks
|
|
2244
|
+
* `token()` returns a real decodable JWT built from the same claims
|
|
2245
|
+
* `claims()` returns, so a consumer that decodes the token itself and a
|
|
2246
|
+
* consumer that asks the service both see the same thing. A double whose two
|
|
2247
|
+
* members can disagree is how a test proves a path works while production
|
|
2248
|
+
* takes the other one.
|
|
2249
|
+
*/
|
|
2250
|
+
static readonly layerFor: (claims: OidcClaims) => Layer.Layer<OidcTokenIssuer>;
|
|
2251
|
+
}
|
|
2252
|
+
//#endregion
|
|
2253
|
+
//#region src/Secret.d.ts
|
|
2254
|
+
/**
|
|
2255
|
+
* The declassification seam: the **only** place in this package where a
|
|
2256
|
+
* `Redacted` value becomes a plain string.
|
|
2257
|
+
*
|
|
2258
|
+
* @remarks
|
|
2259
|
+
* Some boundaries cannot carry a `Redacted`. A detached child process reads
|
|
2260
|
+
* its configuration from environment variables; `GITHUB_STATE` is a plaintext
|
|
2261
|
+
* file by GitHub's protocol. `Redacted` refusing to serialize is the whole
|
|
2262
|
+
* value of the type, so this module does not try to make it serializable —
|
|
2263
|
+
* it makes declassification **explicit, auditable, and impossible to do
|
|
2264
|
+
* quietly**.
|
|
2265
|
+
*
|
|
2266
|
+
* The invariant: **masking and declassification are the same call.** Every
|
|
2267
|
+
* member registers the value with the runner's log filter (`::add-mask::`)
|
|
2268
|
+
* *before* returning plaintext, so a secret cannot reach a child's environment
|
|
2269
|
+
* or a state file without the runner already knowing to redact it from logs.
|
|
2270
|
+
*
|
|
2271
|
+
* `Redacted.value` appears nowhere else in this package, and a test asserts
|
|
2272
|
+
* that structurally — a reintroduction elsewhere fails the suite rather than
|
|
2273
|
+
* review.
|
|
2274
|
+
*
|
|
2275
|
+
* What this deliberately does **not** do is encrypt the handoff. The child
|
|
2276
|
+
* runs on the same runner, as the same user, as the parent; a key would have
|
|
2277
|
+
* to travel the same channel as the secret it protects.
|
|
2278
|
+
*
|
|
2279
|
+
* @example
|
|
2280
|
+
* ```ts
|
|
2281
|
+
* import { Secret } from "@effected/github-actions";
|
|
2282
|
+
* import { Effect } from "effect";
|
|
2283
|
+
*
|
|
2284
|
+
* const program = Effect.gen(function* () {
|
|
2285
|
+
* const env = yield* Secret.forChildEnv({ MY_TOKEN: theToken });
|
|
2286
|
+
* // every value in `env` is already masked in the runner log
|
|
2287
|
+
* return env;
|
|
2288
|
+
* });
|
|
2289
|
+
* ```
|
|
2290
|
+
*
|
|
2291
|
+
* @public
|
|
2292
|
+
*/
|
|
2293
|
+
declare class Secret {
|
|
2294
|
+
private constructor();
|
|
2295
|
+
/**
|
|
2296
|
+
* Declassify a set of secrets for a child process's environment.
|
|
2297
|
+
*
|
|
2298
|
+
* @remarks
|
|
2299
|
+
* Every value is masked before any plaintext is returned — including when
|
|
2300
|
+
* one of them fails, because masking happens for the whole set first.
|
|
2301
|
+
*/
|
|
2302
|
+
static readonly forChildEnv: (entries: Readonly<Record<string, Redacted.Redacted<string>>>) => Effect.Effect<Record<string, string>, never, ActionOutputs>;
|
|
2303
|
+
/**
|
|
2304
|
+
* Declassify one secret for a runner file (`GITHUB_STATE`, `GITHUB_OUTPUT`).
|
|
2305
|
+
*
|
|
2306
|
+
* @remarks
|
|
2307
|
+
* Masks first. The runner file is plaintext regardless; the mask is what
|
|
2308
|
+
* keeps the value out of the visible log.
|
|
2309
|
+
*/
|
|
2310
|
+
static readonly forRunnerFile: (secret: Redacted.Redacted<string>) => Effect.Effect<string, never, ActionOutputs>;
|
|
2311
|
+
/**
|
|
2312
|
+
* Declassify one secret for an in-process use that needs the raw bytes.
|
|
2313
|
+
*
|
|
2314
|
+
* @remarks
|
|
2315
|
+
* Request signing is the case this exists for: an HMAC cannot be computed
|
|
2316
|
+
* over a `Redacted`, so a signing key has to become a string somewhere, and
|
|
2317
|
+
* the point of this module is that "somewhere" is here and nowhere else.
|
|
2318
|
+
*
|
|
2319
|
+
* It masks even though the value is never *written* anywhere, and that is
|
|
2320
|
+
* deliberate rather than superstitious: a signing key that leaks does so
|
|
2321
|
+
* through something nobody audited — a debug log of outgoing headers, a
|
|
2322
|
+
* serialized error, a stack trace carrying the closure. Registering it with
|
|
2323
|
+
* the runner's filter costs one call and makes every one of those redacted.
|
|
2324
|
+
*
|
|
2325
|
+
* Call it **once, at layer construction**, not per operation: masking is
|
|
2326
|
+
* idempotent but a workflow command per request is noise in the log.
|
|
2327
|
+
*/
|
|
2328
|
+
static readonly forSigning: (secret: Redacted.Redacted<string>) => Effect.Effect<string, never, ActionOutputs>;
|
|
2329
|
+
/**
|
|
2330
|
+
* The far side of a handoff: re-wrap a plaintext environment variable.
|
|
2331
|
+
*
|
|
2332
|
+
* @remarks
|
|
2333
|
+
* A `Config`, so a missing or empty handoff is an honest `ConfigError`
|
|
2334
|
+
* naming the variable rather than an empty `Redacted` that fails much later
|
|
2335
|
+
* as an authentication error.
|
|
2336
|
+
*/
|
|
2337
|
+
static readonly adopt: (name: string) => Config.Config<Redacted.Redacted<string>>;
|
|
2338
|
+
}
|
|
2339
|
+
//#endregion
|
|
2340
|
+
//#region src/ToolInstaller.d.ts
|
|
2341
|
+
declare const ToolInstallerError_base: Schema.Class<ToolInstallerError, Schema.TaggedStruct<"ToolInstallerError", {
|
|
2342
|
+
/**
|
|
2343
|
+
* `downloadFailed` — the archive could not be fetched. `extractFailed` — the
|
|
2344
|
+
* extraction tool refused the archive or is not installed. `cacheFailed` —
|
|
2345
|
+
* the tool could not be written into the runner's tool cache.
|
|
2346
|
+
*/
|
|
2347
|
+
readonly reason: Schema.Literals<readonly ["downloadFailed", "extractFailed", "cacheFailed"]>;
|
|
2348
|
+
/** The HTTP status, when there was one. Drives the retry decision. */
|
|
2349
|
+
readonly status: Schema.optionalKey<Schema.Number>;
|
|
2350
|
+
/** What was being worked on — a url, an archive path, or a tool name. */
|
|
2351
|
+
readonly subject: Schema.optionalKey<Schema.String>;
|
|
2352
|
+
/** The extraction tool's own complaint, which is the only useful part of a tar failure. */
|
|
2353
|
+
readonly stderr: Schema.optionalKey<Schema.String>;
|
|
2354
|
+
/** The underlying failure, preserved structurally. */
|
|
2355
|
+
readonly cause: Schema.optionalKey<Schema.Defect>;
|
|
2356
|
+
}>, import("effect/Cause").YieldableError>;
|
|
2357
|
+
/**
|
|
2358
|
+
* Raised when a tool cannot be downloaded, extracted or cached.
|
|
2359
|
+
*
|
|
2360
|
+
* @public
|
|
2361
|
+
*/
|
|
2362
|
+
declare class ToolInstallerError extends ToolInstallerError_base {
|
|
2363
|
+
get message(): string;
|
|
2364
|
+
/**
|
|
2365
|
+
* Whether retrying could plausibly help.
|
|
2366
|
+
*
|
|
2367
|
+
* @remarks
|
|
2368
|
+
* A derived getter rather than a field, so it cannot be set inconsistently
|
|
2369
|
+
* with the status it is derived from. `408`, `429` and any `5xx` are the
|
|
2370
|
+
* server saying "later"; a `404` is it saying "never", and retrying that
|
|
2371
|
+
* three times just makes a broken url take longer to fail.
|
|
2372
|
+
*/
|
|
2373
|
+
get retryable(): boolean;
|
|
2374
|
+
}
|
|
2375
|
+
/**
|
|
2376
|
+
* Where extraction should put its output.
|
|
2377
|
+
*
|
|
2378
|
+
* @public
|
|
2379
|
+
*/
|
|
2380
|
+
interface ExtractOptions {
|
|
2381
|
+
/** The destination. A fresh temporary directory when omitted. */
|
|
2382
|
+
readonly destination?: string | undefined;
|
|
2383
|
+
/** Flags for `tar`. Defaults to `["xzf"]`, i.e. a gzipped tarball. */
|
|
2384
|
+
readonly flags?: ReadonlyArray<string> | undefined;
|
|
2385
|
+
}
|
|
2386
|
+
/**
|
|
2387
|
+
* The {@link ToolInstaller} service shape.
|
|
2388
|
+
*
|
|
2389
|
+
* @public
|
|
2390
|
+
*/
|
|
2391
|
+
interface ToolInstallerShape {
|
|
2392
|
+
/**
|
|
2393
|
+
* A cached tool, if this runner already has it.
|
|
2394
|
+
*
|
|
2395
|
+
* @remarks
|
|
2396
|
+
* Absent is not an error, and neither is an unreadable cache: both mean
|
|
2397
|
+
* "install it", which is the only thing a caller can do about either.
|
|
2398
|
+
*/
|
|
2399
|
+
readonly find: (tool: string, version: string) => Effect.Effect<Option.Option<string>>;
|
|
2400
|
+
/** Download a url to a temporary file, retrying what is worth retrying. */
|
|
2401
|
+
readonly download: (url: string) => Effect.Effect<string, ToolInstallerError>;
|
|
2402
|
+
/** Extract a tarball. Returns the directory its contents landed in. */
|
|
2403
|
+
readonly extractTar: (archive: string, options?: ExtractOptions) => Effect.Effect<string, ToolInstallerError>;
|
|
2404
|
+
/** Extract a zip. Returns the directory its contents landed in. */
|
|
2405
|
+
readonly extractZip: (archive: string, options?: ExtractOptions) => Effect.Effect<string, ToolInstallerError>;
|
|
2406
|
+
/** Install a directory into the tool cache. Returns its cached path. */
|
|
2407
|
+
readonly cacheDir: (source: string, tool: string, version: string) => Effect.Effect<string, ToolInstallerError>;
|
|
2408
|
+
/** Install a single file into the tool cache under `name`. Returns the cached directory. */
|
|
2409
|
+
readonly cacheFile: (source: string, name: string, tool: string, version: string) => Effect.Effect<string, ToolInstallerError>;
|
|
2410
|
+
}
|
|
2411
|
+
declare const ToolInstaller_base: Context.ServiceClass<ToolInstaller, "@effected/github-actions/ToolInstaller", ToolInstallerShape>;
|
|
2412
|
+
/**
|
|
2413
|
+
* Download, extract and cache a toolchain in the runner's tool cache.
|
|
2414
|
+
*
|
|
2415
|
+
* @remarks
|
|
2416
|
+
* The primitives, not a workflow: a consumer composes `find` → `download` →
|
|
2417
|
+
* `extractTar` → `cacheDir` in whatever order its tool needs, because the
|
|
2418
|
+
* orders genuinely differ (a single binary skips extraction, a tarball with a
|
|
2419
|
+
* nested root directory needs a path fixup between extract and cache).
|
|
2420
|
+
*
|
|
2421
|
+
* No `@actions/tool-cache` dependency — the cache is a directory layout, and
|
|
2422
|
+
* this reproduces the layout rather than importing a package to compute it.
|
|
2423
|
+
*
|
|
2424
|
+
* @example
|
|
2425
|
+
* ```ts
|
|
2426
|
+
* import { ToolInstaller } from "@effected/github-actions";
|
|
2427
|
+
* import { Effect, Option } from "effect";
|
|
2428
|
+
*
|
|
2429
|
+
* const install = Effect.gen(function* () {
|
|
2430
|
+
* const installer = yield* ToolInstaller;
|
|
2431
|
+
* const cached = yield* installer.find("node", "22.11.0");
|
|
2432
|
+
* if (Option.isSome(cached)) return cached.value;
|
|
2433
|
+
* const archive = yield* installer.download("https://nodejs.org/dist/v22.11.0/node.tar.gz");
|
|
2434
|
+
* const extracted = yield* installer.extractTar(archive);
|
|
2435
|
+
* return yield* installer.cacheDir(extracted, "node", "22.11.0");
|
|
2436
|
+
* });
|
|
2437
|
+
* ```
|
|
2438
|
+
*
|
|
2439
|
+
* @public
|
|
2440
|
+
*/
|
|
2441
|
+
declare class ToolInstaller extends ToolInstaller_base {
|
|
2442
|
+
static readonly layer: Layer.Layer<ToolInstaller, never, ActionEnvironment | FileSystem.FileSystem | Path.Path | HttpClient.HttpClient | ChildProcessSpawner.ChildProcessSpawner>;
|
|
2443
|
+
/**
|
|
2444
|
+
* Where a tool lives in the cache.
|
|
2445
|
+
*
|
|
2446
|
+
* @remarks
|
|
2447
|
+
* Pure, exported and tested on its own, because the layout is a **contract
|
|
2448
|
+
* with the runner**: `<root>/<tool>/<version>/<arch>` is what the hosted
|
|
2449
|
+
* image populates and what `@actions/tool-cache` reads, so a tool cached at
|
|
2450
|
+
* any other path is invisible to every other step in the workflow. The `arch`
|
|
2451
|
+
* segment uses Node's `process.arch` spelling (`x64`) rather than the
|
|
2452
|
+
* runner's `RUNNER_ARCH` (`X64`) for the same reason.
|
|
2453
|
+
*/
|
|
2454
|
+
static readonly cachePath: (options: {
|
|
2455
|
+
readonly root: string;
|
|
2456
|
+
readonly tool: string;
|
|
2457
|
+
readonly version: string;
|
|
2458
|
+
readonly arch: string;
|
|
2459
|
+
}) => string;
|
|
2460
|
+
/** A test double. Unstubbed members die rather than reporting a tool that is not there. */
|
|
2461
|
+
static readonly makeTest: (overrides?: Partial<ToolInstallerShape>) => ToolInstallerShape;
|
|
2462
|
+
/** {@link ToolInstaller.makeTest} behind `Layer.succeed`. */
|
|
2463
|
+
static readonly layerTest: (overrides?: Partial<ToolInstallerShape>) => Layer.Layer<ToolInstaller>;
|
|
2464
|
+
}
|
|
2465
|
+
//#endregion
|
|
2466
|
+
export { Action, ActionCache, ActionCacheError, type ActionCacheShape, ActionEnvironment, ActionEnvironmentError, type ActionEnvironmentShape, ActionInput, ActionLogger, type ActionLoggerShape, ActionOutputError, ActionOutputs, type ActionOutputsShape, type ActionRunOptions, ActionRuntime, type ActionServices, ActionState, ActionStateError, type ActionStateShape, type AnnotationProperties, Artifact, ArtifactError, type ArtifactItem, type ArtifactRef, type ArtifactShape, type Blob, BlobEnvelope, BlobEnvelopeError, BlobStore, BlobStoreError, type BlobStoreShape, BlobTransferError, CacheKey, CacheKeyError, type ClientLayerOptions, type DataBlobTransfer, DetachedProcess, DetachedProcessError, type DetachedSpawnOptions, type DownloadOptions, type DownloadResult, DryRun, type DryRunShape, type ExtractOptions, type FileBlobTransfer, GitHubCacheBlobStore, GitHubContext, GitHubToken, GitHubTokenError, OidcClaims, OidcTokenError, OidcTokenIssuer, type OidcTokenIssuerShape, ProcessId, type ProvisionOptions, type ReadOptions, type ReadinessOptions, RunnerContext, type S3Config, Secret, ToolInstaller, ToolInstallerError, type ToolInstallerShape, type UploadOptions, type UploadResult, WorkflowCommand, describeCause };
|
|
2467
|
+
//# sourceMappingURL=index.d.ts.map
|