@effected/github-actions 0.6.1 → 0.8.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/ActionEnvironment.js +24 -1
- package/DetachedProcess.js +64 -1
- package/README.md +1 -1
- package/Secret.js +79 -10
- package/index.d.ts +183 -12
- package/package.json +9 -9
package/ActionEnvironment.js
CHANGED
|
@@ -286,6 +286,26 @@ var ActionEnvironment = class ActionEnvironment extends Context.Service()("@effe
|
|
|
286
286
|
* `undefined` means *not served*, so a suite cannot arrange `undefined` as
|
|
287
287
|
* the payload itself. A webhook payload is always a JSON object; the case
|
|
288
288
|
* does not arise, and reserving the sentinel keeps the argument optional.
|
|
289
|
+
*
|
|
290
|
+
* When a suite wants the **real read path** rather than a served value —
|
|
291
|
+
* exercising the actual `GITHUB_EVENT_PATH` read-and-parse — this member is
|
|
292
|
+
* the seam: it leaves `FileSystem` in `R` precisely so the suite chooses
|
|
293
|
+
* the filesystem. Compose it with `@effected/memfs`'s seeded in-memory
|
|
294
|
+
* volume rather than a hand-rolled stub: the volume honors the whole
|
|
295
|
+
* `FileSystem` contract, and an unseeded path fails typed `NotFound`
|
|
296
|
+
* instead of whatever a hand stub happens to answer.
|
|
297
|
+
*
|
|
298
|
+
* @example
|
|
299
|
+
* ```ts
|
|
300
|
+
* import { ActionEnvironment } from "@effected/github-actions";
|
|
301
|
+
* import { MemoryFileSystem } from "@effected/memfs";
|
|
302
|
+
* import { Layer } from "effect";
|
|
303
|
+
*
|
|
304
|
+
* const layer = Layer.effect(
|
|
305
|
+
* ActionEnvironment,
|
|
306
|
+
* ActionEnvironment.makeTest({ GITHUB_EVENT_PATH: "/event.json" }),
|
|
307
|
+
* ).pipe(Layer.provide(MemoryFileSystem.layerWith({ "/event.json": JSON.stringify({ action: "opened" }) })));
|
|
308
|
+
* ```
|
|
289
309
|
*/
|
|
290
310
|
static makeTest = (overrides = {}, payload) => Effect.map(make({
|
|
291
311
|
...TEST_DEFAULTS,
|
|
@@ -302,7 +322,10 @@ var ActionEnvironment = class ActionEnvironment extends Context.Service()("@effe
|
|
|
302
322
|
* The stub is why the payload has to be served here rather than through the
|
|
303
323
|
* filesystem: `make` captures the filesystem at construction, so seeding
|
|
304
324
|
* `GITHUB_EVENT_PATH` through `overrides` would send the read to a noop
|
|
305
|
-
* filesystem and fail. Pass the payload as the second argument instead
|
|
325
|
+
* filesystem and fail. Pass the payload as the second argument instead —
|
|
326
|
+
* or, for a suite that wants the real read path over a full filesystem,
|
|
327
|
+
* compose {@link ActionEnvironment.makeTest} with `@effected/memfs`'s
|
|
328
|
+
* seeded volume; the recipe is on that member.
|
|
306
329
|
*
|
|
307
330
|
* @example
|
|
308
331
|
* ```ts
|
package/DetachedProcess.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
|
+
import { HttpClient } from "effect/unstable/http";
|
|
2
3
|
import { ChildProcessSpawner } from "effect/unstable/process";
|
|
3
4
|
import { spawn } from "node:child_process";
|
|
4
5
|
import { closeSync, openSync } from "node:fs";
|
|
@@ -79,6 +80,10 @@ var DetachedProcessError = class extends Schema.TaggedError()("DetachedProcessEr
|
|
|
79
80
|
const ProcessId = Schema.Number.pipe(Schema.check(Schema.makeFilter((value) => Number.isInteger(value) && value > 0 ? void 0 : "Expected a positive integer process id")), Schema.brand("ProcessId"));
|
|
80
81
|
/** Whether a thrown value is a Node errno error with the given code. */
|
|
81
82
|
const isErrno = (cause, code) => typeof cause === "object" && cause !== null && cause.code === code;
|
|
83
|
+
/** See {@link DetachedProcess.makeTestOps}: an unstubbed member dies naming itself. */
|
|
84
|
+
const unimplemented = (member) => {
|
|
85
|
+
throw new Error(`DetachedProcess.makeTestOps: ${member}() was called but not stubbed — pass a \`${member}\` override.`);
|
|
86
|
+
};
|
|
82
87
|
/**
|
|
83
88
|
* A long-lived child that outlives the phase that started it.
|
|
84
89
|
*
|
|
@@ -111,7 +116,7 @@ const isErrno = (cause, code) => typeof cause === "object" && cause !== null &&
|
|
|
111
116
|
*
|
|
112
117
|
* @public
|
|
113
118
|
*/
|
|
114
|
-
var DetachedProcess = class {
|
|
119
|
+
var DetachedProcess = class DetachedProcess {
|
|
115
120
|
constructor() {}
|
|
116
121
|
/**
|
|
117
122
|
* Start a detached child with its output routed to a log file.
|
|
@@ -187,6 +192,34 @@ var DetachedProcess = class {
|
|
|
187
192
|
return yield* poll(attempts);
|
|
188
193
|
});
|
|
189
194
|
/**
|
|
195
|
+
* A readiness probe over HTTP: `true` when a `GET` answers 2xx.
|
|
196
|
+
*
|
|
197
|
+
* @remarks
|
|
198
|
+
* The probe {@link DetachedProcess.awaitReady} almost always wants: an HTTP
|
|
199
|
+
* `GET` at a port the child is still binding, where connection-refused *is*
|
|
200
|
+
* "not up yet". The consumers that hand-rolled the polling wrote exactly
|
|
201
|
+
* this probe around it, and each had to discover the same subtlety — the
|
|
202
|
+
* probe's error channel must stay empty, because `awaitReady` deliberately
|
|
203
|
+
* propagates probe failures, and a refused connection is not a failure of
|
|
204
|
+
* the probe.
|
|
205
|
+
*
|
|
206
|
+
* So this probe never fails: a refused connection, any other transport
|
|
207
|
+
* error and a non-2xx answer all collapse to `false`, leaving
|
|
208
|
+
* `awaitReady`'s own exhaustion as the only failure left. That is a
|
|
209
|
+
* deliberate opt-out of the propagate-probe-failures posture, and it is
|
|
210
|
+
* only sound because every failure an HTTP readiness check can see means
|
|
211
|
+
* the same thing here. The cost is the documented one: a *misconfigured*
|
|
212
|
+
* probe — a typo'd port, a wrong path — is indistinguishable from a child
|
|
213
|
+
* that never came up, and reports as `notReady` only after the full budget.
|
|
214
|
+
* A probe whose failures are meaningfully distinct should stay a
|
|
215
|
+
* hand-written effect with those failures in `E`.
|
|
216
|
+
*
|
|
217
|
+
* The request goes through core's `HttpClient` — `Action.run` provides it
|
|
218
|
+
* ambiently — rather than a bare `fetch`, per the package rule that
|
|
219
|
+
* everything that *can* go through a core contract does.
|
|
220
|
+
*/
|
|
221
|
+
static httpProbe = (url) => HttpClient.get(url).pipe(Effect.map((response) => response.status >= 200 && response.status < 300), Effect.catch(() => Effect.succeed(false)));
|
|
222
|
+
/**
|
|
190
223
|
* Signal a pid that came back across the phase boundary.
|
|
191
224
|
*
|
|
192
225
|
* @remarks
|
|
@@ -225,6 +258,36 @@ var DetachedProcess = class {
|
|
|
225
258
|
}
|
|
226
259
|
});
|
|
227
260
|
});
|
|
261
|
+
/**
|
|
262
|
+
* The real operations: the production default for a
|
|
263
|
+
* {@link DetachedProcessOps} parameter.
|
|
264
|
+
*
|
|
265
|
+
* @remarks
|
|
266
|
+
* The members ARE the statics — same references, not wrappers — so taking
|
|
267
|
+
* the seam costs a consumer nothing over calling the statics directly.
|
|
268
|
+
*/
|
|
269
|
+
static ops = {
|
|
270
|
+
spawn: DetachedProcess.spawn,
|
|
271
|
+
awaitReady: DetachedProcess.awaitReady,
|
|
272
|
+
reap: DetachedProcess.reap
|
|
273
|
+
};
|
|
274
|
+
/**
|
|
275
|
+
* A test double. Unstubbed members die rather than silently succeeding.
|
|
276
|
+
*
|
|
277
|
+
* @remarks
|
|
278
|
+
* The package's `makeTest` convention, on the module's one non-service.
|
|
279
|
+
* Every member a test does not stub dies loudly **naming the member**,
|
|
280
|
+
* because silence is worse here than anywhere else in the package: a spawn
|
|
281
|
+
* that silently "succeeds" fakes a child that does not exist, and a reap
|
|
282
|
+
* that silently succeeds asserts nothing about the one call whose
|
|
283
|
+
* production form signals a real pid.
|
|
284
|
+
*/
|
|
285
|
+
static makeTestOps = (overrides = {}) => ({
|
|
286
|
+
spawn: () => Effect.sync(() => unimplemented("spawn")),
|
|
287
|
+
awaitReady: () => Effect.sync(() => unimplemented("awaitReady")),
|
|
288
|
+
reap: () => Effect.sync(() => unimplemented("reap")),
|
|
289
|
+
...overrides
|
|
290
|
+
});
|
|
228
291
|
};
|
|
229
292
|
|
|
230
293
|
//#endregion
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
The GitHub Actions runtime for [Effect](https://effect.website) v4: the services an action needs to talk to the runner it is executing inside. `Action.run` composes the runtime, runs your program, renders a failure as an `::error::` annotation and sets the exit code. `ActionInput` reads workflow inputs as typed `Config` values — string, boolean, integer, redacted secret, multiline list, `key=value` pairs, or a schema-decoded JSON blob — with one absence rule shared by every accessor: unset and `""` are both missing data. `ActionOutputs`, `ActionState`, `ActionLogger` and a fiber-local `ActionEnvironment` round out the runner surface, alongside `ActionCache`, `Artifact`, a metadata-carrying `BlobStore` (S3-compatible or the runner's own cache), `OidcTokenIssuer` and `ToolInstaller` for the heavier protocols. A reporting suite — `GitHubMarkdown`, `ManagedDocument`, `CheckState` and `CheckDocument` — covers the other direction: rendering what a run did onto a pull request comment, a check run or the job summary. No `@actions/*` dependency anywhere — the cache, artifact and tool-cache protocols are implemented directly against their HTTP APIs.
|
|
9
9
|
|
|
10
10
|
> **Pre-release.** This package is part of the `@effected/*` kit, in pre-`1.0.0`
|
|
11
|
-
> development against a single pinned Effect v4
|
|
11
|
+
> development against a single pinned Effect v4 prerelease. Packages graduate to
|
|
12
12
|
> `1.0.0` once Effect `4.0.0` ships. To hold your own `effect` versions at
|
|
13
13
|
> exactly the ones the kit is built and tested against, install
|
|
14
14
|
> [`@effected/pnpm-plugin-effect`](https://www.npmjs.com/package/@effected/pnpm-plugin-effect).
|
package/Secret.js
CHANGED
|
@@ -14,10 +14,18 @@ import { Config, Effect, Redacted } from "effect";
|
|
|
14
14
|
* it makes declassification **explicit, auditable, and impossible to do
|
|
15
15
|
* quietly**.
|
|
16
16
|
*
|
|
17
|
-
* The invariant: **masking and declassification
|
|
18
|
-
* member
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* The invariant: **masking is the floor, and declassification implies it.**
|
|
18
|
+
* Every member that touches plaintext routes through
|
|
19
|
+
* `ActionOutputs.setSecret`, registering the value with the runner's log
|
|
20
|
+
* filter (`::add-mask::`) *before* any plaintext is returned — and the one
|
|
21
|
+
* member that returns nothing, {@link Secret.mask}, registers and stops there
|
|
22
|
+
* — so under the normal runner layer a secret cannot reach a child's
|
|
23
|
+
* environment or a state file without the runner already knowing to redact it
|
|
24
|
+
* from logs. Two scoped exceptions: {@link Secret.adopt} is a `Config` that
|
|
25
|
+
* re-wraps an inherited plaintext without involving `ActionOutputs` at all —
|
|
26
|
+
* the parent masked that value before the handoff — and under
|
|
27
|
+
* `ActionOutputs.layerDetached` the registration is a documented no-op,
|
|
28
|
+
* because a mask emitted inside a detached worker would itself be the leak.
|
|
21
29
|
*
|
|
22
30
|
* `Redacted.value` appears nowhere else in this package, and a test asserts
|
|
23
31
|
* that structurally — a reintroduction elsewhere fails the suite rather than
|
|
@@ -97,12 +105,9 @@ var Secret = class Secret {
|
|
|
97
105
|
* dividing line is where the value goes, not what it is for: a value
|
|
98
106
|
* crossing into a **child's** environment is {@link Secret.forChildEnv},
|
|
99
107
|
* one written to `GITHUB_STATE` or `GITHUB_OUTPUT` is
|
|
100
|
-
* {@link Secret.forRunnerFile},
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* once through `ActionEnvironment`), so if a consumer chooses that bridge,
|
|
104
|
-
* the mutation — and its restore discipline — lives in consumer code as
|
|
105
|
-
* the consumer's own tradeoff, not as a pattern this member recommends.
|
|
108
|
+
* {@link Secret.forRunnerFile}, one bound for the ambient environment is
|
|
109
|
+
* {@link Secret.forProcessEnv}, and one that stays in this process — held
|
|
110
|
+
* by this code, passed as an argument — is this member.
|
|
106
111
|
*
|
|
107
112
|
* It masks even though the value is never *written* anywhere, and that is
|
|
108
113
|
* deliberate rather than superstitious: a signing key that leaks does so
|
|
@@ -126,6 +131,70 @@ var Secret = class Secret {
|
|
|
126
131
|
*/
|
|
127
132
|
static forSigning = (secret) => Secret.forRunnerFile(secret);
|
|
128
133
|
/**
|
|
134
|
+
* Declassify one secret for the caller to bridge into `process.env`.
|
|
135
|
+
*
|
|
136
|
+
* @remarks
|
|
137
|
+
* The third-party-SDK case, under its own auditable name: an SDK that reads
|
|
138
|
+
* only the ambient environment cannot take the plaintext as an argument, so
|
|
139
|
+
* the value has to cross into `process.env` before the SDK looks. The
|
|
140
|
+
* mechanism is identical to {@link Secret.forRunnerFile} — mask first, then
|
|
141
|
+
* return plaintext — and the name is what earns the member: these names are
|
|
142
|
+
* the audit vocabulary, and a search for this one finds every place a
|
|
143
|
+
* secret enters the ambient environment without wading through signing
|
|
144
|
+
* keys and runner files.
|
|
145
|
+
*
|
|
146
|
+
* The ruling on who mutates is unchanged by the name existing: **this
|
|
147
|
+
* package never mutates `process.env`.** Reads are seeded once, at layer
|
|
148
|
+
* construction, through `ActionEnvironment`, and nothing here writes back
|
|
149
|
+
* — a write from inside the kit would be invisible to that seeding and to
|
|
150
|
+
* every consumer's assumptions about when the environment is stable. This
|
|
151
|
+
* member declassifies and masks; the assignment to `process.env`, and the
|
|
152
|
+
* restore discipline that unwinds it when the bridged scope ends, live in
|
|
153
|
+
* the **caller**, as the caller's own explicit tradeoff.
|
|
154
|
+
*
|
|
155
|
+
* The detached-worker inversion applies exactly as it does to
|
|
156
|
+
* {@link Secret.forSigning}: the mask only works where the runner parses
|
|
157
|
+
* stdout, so a worker that needs a bridged environment gets it from the
|
|
158
|
+
* parent — masked before the spawn via {@link Secret.forChildEnv} — rather
|
|
159
|
+
* than declassifying inside itself.
|
|
160
|
+
*/
|
|
161
|
+
static forProcessEnv = (secret) => Secret.forRunnerFile(secret);
|
|
162
|
+
/**
|
|
163
|
+
* Register a secret with the runner's log filter — and return nothing.
|
|
164
|
+
*
|
|
165
|
+
* @remarks
|
|
166
|
+
* Masking without declassification: the value is registered with
|
|
167
|
+
* `::add-mask::` through the same route as every other member, and the
|
|
168
|
+
* success channel is `void`, so a caller cannot come away holding the raw
|
|
169
|
+
* value at all. This is the register-only shape consumers previously spelled
|
|
170
|
+
* as a {@link Secret.forSigning} whose result was discarded — a spelling
|
|
171
|
+
* that lied to the audit vocabulary (a grep for signing found masking),
|
|
172
|
+
* contradicted forSigning's own once-at-construction guidance, and needed a
|
|
173
|
+
* comment to apologize for itself. The names are the vocabulary: a grep for
|
|
174
|
+
* this member finds every register-only site, and a grep for `forSigning`
|
|
175
|
+
* again finds only signing.
|
|
176
|
+
*
|
|
177
|
+
* The canonical caller masks every supplied credential input
|
|
178
|
+
* unconditionally, before the logic that decides which of them will
|
|
179
|
+
* actually be used — a secret the workflow supplied deserves redaction from
|
|
180
|
+
* the log whether or not resolution reaches for it.
|
|
181
|
+
*
|
|
182
|
+
* One value per call is the blessed shape; there is deliberately no
|
|
183
|
+
* set-taking form. {@link Secret.forChildEnv} takes a set because its
|
|
184
|
+
* mask-everything-before-returning-anything ordering is load-bearing; a
|
|
185
|
+
* plain mask returns nothing to order against, and a loop is fine.
|
|
186
|
+
*
|
|
187
|
+
* **In a detached worker, the mask this member emits is a leak.** The
|
|
188
|
+
* `::add-mask::` command only masks when the runner parses this process's
|
|
189
|
+
* stdout; a detached worker's stdout is a log file no runner parses, so
|
|
190
|
+
* there the command masks nothing *and* spells the plaintext into the log
|
|
191
|
+
* — a signing key shipped exactly that way for one round. A worker must
|
|
192
|
+
* compose `ActionOutputs.layerDetached`, under which the mask is a
|
|
193
|
+
* documented no-op; the masking itself is the **parent's** job, done
|
|
194
|
+
* before the spawn via {@link Secret.forChildEnv} under the real layer.
|
|
195
|
+
*/
|
|
196
|
+
static mask = (secret) => Effect.asVoid(Secret.forRunnerFile(secret));
|
|
197
|
+
/**
|
|
129
198
|
* The far side of a handoff: re-wrap a plaintext environment variable.
|
|
130
199
|
*
|
|
131
200
|
* @remarks
|
package/index.d.ts
CHANGED
|
@@ -194,6 +194,26 @@ declare class ActionEnvironment extends ActionEnvironment_base {
|
|
|
194
194
|
* `undefined` means *not served*, so a suite cannot arrange `undefined` as
|
|
195
195
|
* the payload itself. A webhook payload is always a JSON object; the case
|
|
196
196
|
* does not arise, and reserving the sentinel keeps the argument optional.
|
|
197
|
+
*
|
|
198
|
+
* When a suite wants the **real read path** rather than a served value —
|
|
199
|
+
* exercising the actual `GITHUB_EVENT_PATH` read-and-parse — this member is
|
|
200
|
+
* the seam: it leaves `FileSystem` in `R` precisely so the suite chooses
|
|
201
|
+
* the filesystem. Compose it with `@effected/memfs`'s seeded in-memory
|
|
202
|
+
* volume rather than a hand-rolled stub: the volume honors the whole
|
|
203
|
+
* `FileSystem` contract, and an unseeded path fails typed `NotFound`
|
|
204
|
+
* instead of whatever a hand stub happens to answer.
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```ts
|
|
208
|
+
* import { ActionEnvironment } from "@effected/github-actions";
|
|
209
|
+
* import { MemoryFileSystem } from "@effected/memfs";
|
|
210
|
+
* import { Layer } from "effect";
|
|
211
|
+
*
|
|
212
|
+
* const layer = Layer.effect(
|
|
213
|
+
* ActionEnvironment,
|
|
214
|
+
* ActionEnvironment.makeTest({ GITHUB_EVENT_PATH: "/event.json" }),
|
|
215
|
+
* ).pipe(Layer.provide(MemoryFileSystem.layerWith({ "/event.json": JSON.stringify({ action: "opened" }) })));
|
|
216
|
+
* ```
|
|
197
217
|
*/
|
|
198
218
|
static readonly makeTest: (overrides?: Readonly<Record<string, string>>, payload?: unknown) => Effect.Effect<ActionEnvironmentShape, never, FileSystem.FileSystem>;
|
|
199
219
|
/**
|
|
@@ -204,7 +224,10 @@ declare class ActionEnvironment extends ActionEnvironment_base {
|
|
|
204
224
|
* The stub is why the payload has to be served here rather than through the
|
|
205
225
|
* filesystem: `make` captures the filesystem at construction, so seeding
|
|
206
226
|
* `GITHUB_EVENT_PATH` through `overrides` would send the read to a noop
|
|
207
|
-
* filesystem and fail. Pass the payload as the second argument instead
|
|
227
|
+
* filesystem and fail. Pass the payload as the second argument instead —
|
|
228
|
+
* or, for a suite that wants the real read path over a full filesystem,
|
|
229
|
+
* compose {@link ActionEnvironment.makeTest} with `@effected/memfs`'s
|
|
230
|
+
* seeded volume; the recipe is on that member.
|
|
208
231
|
*
|
|
209
232
|
* @example
|
|
210
233
|
* ```ts
|
|
@@ -2897,6 +2920,36 @@ interface ReadinessOptions {
|
|
|
2897
2920
|
/** How many times to retry after the first probe. Defaults to 40. */
|
|
2898
2921
|
readonly attempts?: number | undefined;
|
|
2899
2922
|
}
|
|
2923
|
+
/**
|
|
2924
|
+
* The module's operations as a value: the seam a consumer's tests inject.
|
|
2925
|
+
*
|
|
2926
|
+
* @remarks
|
|
2927
|
+
* `DetachedProcess` is statics on a class, deliberately — there is no scope to
|
|
2928
|
+
* hang a service off, because the whole point is a child that outlives the
|
|
2929
|
+
* process holding any handle to it. The cost of that choice is that the
|
|
2930
|
+
* package's `makeTest`/`layerTest` convention has nothing to attach to:
|
|
2931
|
+
* statics cannot be swapped by a layer, and a test must never spawn a real
|
|
2932
|
+
* child, poll a real port, or — above all — signal a real pid. This interface
|
|
2933
|
+
* restores the seam as **parameter injection**: production code takes a
|
|
2934
|
+
* `DetachedProcessOps`, defaulting to {@link DetachedProcess.ops}, and a test
|
|
2935
|
+
* passes {@link DetachedProcess.makeTestOps} with only the members it means to
|
|
2936
|
+
* observe.
|
|
2937
|
+
*
|
|
2938
|
+
* {@link DetachedProcess.httpProbe} is deliberately not a member. It is a
|
|
2939
|
+
* probe *constructor* with no process side effect of its own; a test stubs
|
|
2940
|
+
* `awaitReady` — the operation that runs the probe — or provides a stub
|
|
2941
|
+
* `HttpClient`.
|
|
2942
|
+
*
|
|
2943
|
+
* @public
|
|
2944
|
+
*/
|
|
2945
|
+
interface DetachedProcessOps {
|
|
2946
|
+
/** {@link DetachedProcess.spawn}. */
|
|
2947
|
+
readonly spawn: (options: DetachedSpawnOptions) => Effect.Effect<ChildProcessSpawner.ProcessId, DetachedProcessError>;
|
|
2948
|
+
/** {@link DetachedProcess.awaitReady}. */
|
|
2949
|
+
readonly awaitReady: <E, R>(probe: Effect.Effect<boolean, E, R>, options?: ReadinessOptions) => Effect.Effect<void, E | DetachedProcessError, R>;
|
|
2950
|
+
/** {@link DetachedProcess.reap}. */
|
|
2951
|
+
readonly reap: (pid: number, signal?: NodeJS.Signals) => Effect.Effect<boolean, DetachedProcessError>;
|
|
2952
|
+
}
|
|
2900
2953
|
/**
|
|
2901
2954
|
* A long-lived child that outlives the phase that started it.
|
|
2902
2955
|
*
|
|
@@ -2955,6 +3008,34 @@ declare class DetachedProcess {
|
|
|
2955
3008
|
* timeout six seconds later with nothing to show for it.
|
|
2956
3009
|
*/
|
|
2957
3010
|
static readonly awaitReady: <E, R>(probe: Effect.Effect<boolean, E, R>, options?: ReadinessOptions | undefined) => Effect.Effect<void, E | DetachedProcessError, R>;
|
|
3011
|
+
/**
|
|
3012
|
+
* A readiness probe over HTTP: `true` when a `GET` answers 2xx.
|
|
3013
|
+
*
|
|
3014
|
+
* @remarks
|
|
3015
|
+
* The probe {@link DetachedProcess.awaitReady} almost always wants: an HTTP
|
|
3016
|
+
* `GET` at a port the child is still binding, where connection-refused *is*
|
|
3017
|
+
* "not up yet". The consumers that hand-rolled the polling wrote exactly
|
|
3018
|
+
* this probe around it, and each had to discover the same subtlety — the
|
|
3019
|
+
* probe's error channel must stay empty, because `awaitReady` deliberately
|
|
3020
|
+
* propagates probe failures, and a refused connection is not a failure of
|
|
3021
|
+
* the probe.
|
|
3022
|
+
*
|
|
3023
|
+
* So this probe never fails: a refused connection, any other transport
|
|
3024
|
+
* error and a non-2xx answer all collapse to `false`, leaving
|
|
3025
|
+
* `awaitReady`'s own exhaustion as the only failure left. That is a
|
|
3026
|
+
* deliberate opt-out of the propagate-probe-failures posture, and it is
|
|
3027
|
+
* only sound because every failure an HTTP readiness check can see means
|
|
3028
|
+
* the same thing here. The cost is the documented one: a *misconfigured*
|
|
3029
|
+
* probe — a typo'd port, a wrong path — is indistinguishable from a child
|
|
3030
|
+
* that never came up, and reports as `notReady` only after the full budget.
|
|
3031
|
+
* A probe whose failures are meaningfully distinct should stay a
|
|
3032
|
+
* hand-written effect with those failures in `E`.
|
|
3033
|
+
*
|
|
3034
|
+
* The request goes through core's `HttpClient` — `Action.run` provides it
|
|
3035
|
+
* ambiently — rather than a bare `fetch`, per the package rule that
|
|
3036
|
+
* everything that *can* go through a core contract does.
|
|
3037
|
+
*/
|
|
3038
|
+
static readonly httpProbe: (url: string | URL) => Effect.Effect<boolean, never, HttpClient.HttpClient>;
|
|
2958
3039
|
/**
|
|
2959
3040
|
* Signal a pid that came back across the phase boundary.
|
|
2960
3041
|
*
|
|
@@ -2976,6 +3057,27 @@ declare class DetachedProcess {
|
|
|
2976
3057
|
* finding its child already dead is the normal ending, not a failure.
|
|
2977
3058
|
*/
|
|
2978
3059
|
static readonly reap: (pid: number, signal?: NodeJS.Signals | undefined) => Effect.Effect<boolean, DetachedProcessError, never>;
|
|
3060
|
+
/**
|
|
3061
|
+
* The real operations: the production default for a
|
|
3062
|
+
* {@link DetachedProcessOps} parameter.
|
|
3063
|
+
*
|
|
3064
|
+
* @remarks
|
|
3065
|
+
* The members ARE the statics — same references, not wrappers — so taking
|
|
3066
|
+
* the seam costs a consumer nothing over calling the statics directly.
|
|
3067
|
+
*/
|
|
3068
|
+
static readonly ops: DetachedProcessOps;
|
|
3069
|
+
/**
|
|
3070
|
+
* A test double. Unstubbed members die rather than silently succeeding.
|
|
3071
|
+
*
|
|
3072
|
+
* @remarks
|
|
3073
|
+
* The package's `makeTest` convention, on the module's one non-service.
|
|
3074
|
+
* Every member a test does not stub dies loudly **naming the member**,
|
|
3075
|
+
* because silence is worse here than anywhere else in the package: a spawn
|
|
3076
|
+
* that silently "succeeds" fakes a child that does not exist, and a reap
|
|
3077
|
+
* that silently succeeds asserts nothing about the one call whose
|
|
3078
|
+
* production form signals a real pid.
|
|
3079
|
+
*/
|
|
3080
|
+
static readonly makeTestOps: (overrides?: Partial<DetachedProcessOps>) => DetachedProcessOps;
|
|
2979
3081
|
}
|
|
2980
3082
|
//#endregion
|
|
2981
3083
|
//#region src/DryRun.d.ts
|
|
@@ -4134,10 +4236,18 @@ declare class PackageManagerInstaller extends PackageManagerInstaller_base {
|
|
|
4134
4236
|
* it makes declassification **explicit, auditable, and impossible to do
|
|
4135
4237
|
* quietly**.
|
|
4136
4238
|
*
|
|
4137
|
-
* The invariant: **masking and declassification
|
|
4138
|
-
* member
|
|
4139
|
-
*
|
|
4140
|
-
*
|
|
4239
|
+
* The invariant: **masking is the floor, and declassification implies it.**
|
|
4240
|
+
* Every member that touches plaintext routes through
|
|
4241
|
+
* `ActionOutputs.setSecret`, registering the value with the runner's log
|
|
4242
|
+
* filter (`::add-mask::`) *before* any plaintext is returned — and the one
|
|
4243
|
+
* member that returns nothing, {@link Secret.mask}, registers and stops there
|
|
4244
|
+
* — so under the normal runner layer a secret cannot reach a child's
|
|
4245
|
+
* environment or a state file without the runner already knowing to redact it
|
|
4246
|
+
* from logs. Two scoped exceptions: {@link Secret.adopt} is a `Config` that
|
|
4247
|
+
* re-wraps an inherited plaintext without involving `ActionOutputs` at all —
|
|
4248
|
+
* the parent masked that value before the handoff — and under
|
|
4249
|
+
* `ActionOutputs.layerDetached` the registration is a documented no-op,
|
|
4250
|
+
* because a mask emitted inside a detached worker would itself be the leak.
|
|
4141
4251
|
*
|
|
4142
4252
|
* `Redacted.value` appears nowhere else in this package, and a test asserts
|
|
4143
4253
|
* that structurally — a reintroduction elsewhere fails the suite rather than
|
|
@@ -4203,12 +4313,9 @@ declare class Secret {
|
|
|
4203
4313
|
* dividing line is where the value goes, not what it is for: a value
|
|
4204
4314
|
* crossing into a **child's** environment is {@link Secret.forChildEnv},
|
|
4205
4315
|
* one written to `GITHUB_STATE` or `GITHUB_OUTPUT` is
|
|
4206
|
-
* {@link Secret.forRunnerFile},
|
|
4207
|
-
*
|
|
4208
|
-
*
|
|
4209
|
-
* once through `ActionEnvironment`), so if a consumer chooses that bridge,
|
|
4210
|
-
* the mutation — and its restore discipline — lives in consumer code as
|
|
4211
|
-
* the consumer's own tradeoff, not as a pattern this member recommends.
|
|
4316
|
+
* {@link Secret.forRunnerFile}, one bound for the ambient environment is
|
|
4317
|
+
* {@link Secret.forProcessEnv}, and one that stays in this process — held
|
|
4318
|
+
* by this code, passed as an argument — is this member.
|
|
4212
4319
|
*
|
|
4213
4320
|
* It masks even though the value is never *written* anywhere, and that is
|
|
4214
4321
|
* deliberate rather than superstitious: a signing key that leaks does so
|
|
@@ -4231,6 +4338,70 @@ declare class Secret {
|
|
|
4231
4338
|
* {@link Secret.forChildEnv} under the real layer.
|
|
4232
4339
|
*/
|
|
4233
4340
|
static readonly forSigning: (secret: Redacted.Redacted<string>) => Effect.Effect<string, never, ActionOutputs>;
|
|
4341
|
+
/**
|
|
4342
|
+
* Declassify one secret for the caller to bridge into `process.env`.
|
|
4343
|
+
*
|
|
4344
|
+
* @remarks
|
|
4345
|
+
* The third-party-SDK case, under its own auditable name: an SDK that reads
|
|
4346
|
+
* only the ambient environment cannot take the plaintext as an argument, so
|
|
4347
|
+
* the value has to cross into `process.env` before the SDK looks. The
|
|
4348
|
+
* mechanism is identical to {@link Secret.forRunnerFile} — mask first, then
|
|
4349
|
+
* return plaintext — and the name is what earns the member: these names are
|
|
4350
|
+
* the audit vocabulary, and a search for this one finds every place a
|
|
4351
|
+
* secret enters the ambient environment without wading through signing
|
|
4352
|
+
* keys and runner files.
|
|
4353
|
+
*
|
|
4354
|
+
* The ruling on who mutates is unchanged by the name existing: **this
|
|
4355
|
+
* package never mutates `process.env`.** Reads are seeded once, at layer
|
|
4356
|
+
* construction, through `ActionEnvironment`, and nothing here writes back
|
|
4357
|
+
* — a write from inside the kit would be invisible to that seeding and to
|
|
4358
|
+
* every consumer's assumptions about when the environment is stable. This
|
|
4359
|
+
* member declassifies and masks; the assignment to `process.env`, and the
|
|
4360
|
+
* restore discipline that unwinds it when the bridged scope ends, live in
|
|
4361
|
+
* the **caller**, as the caller's own explicit tradeoff.
|
|
4362
|
+
*
|
|
4363
|
+
* The detached-worker inversion applies exactly as it does to
|
|
4364
|
+
* {@link Secret.forSigning}: the mask only works where the runner parses
|
|
4365
|
+
* stdout, so a worker that needs a bridged environment gets it from the
|
|
4366
|
+
* parent — masked before the spawn via {@link Secret.forChildEnv} — rather
|
|
4367
|
+
* than declassifying inside itself.
|
|
4368
|
+
*/
|
|
4369
|
+
static readonly forProcessEnv: (secret: Redacted.Redacted<string>) => Effect.Effect<string, never, ActionOutputs>;
|
|
4370
|
+
/**
|
|
4371
|
+
* Register a secret with the runner's log filter — and return nothing.
|
|
4372
|
+
*
|
|
4373
|
+
* @remarks
|
|
4374
|
+
* Masking without declassification: the value is registered with
|
|
4375
|
+
* `::add-mask::` through the same route as every other member, and the
|
|
4376
|
+
* success channel is `void`, so a caller cannot come away holding the raw
|
|
4377
|
+
* value at all. This is the register-only shape consumers previously spelled
|
|
4378
|
+
* as a {@link Secret.forSigning} whose result was discarded — a spelling
|
|
4379
|
+
* that lied to the audit vocabulary (a grep for signing found masking),
|
|
4380
|
+
* contradicted forSigning's own once-at-construction guidance, and needed a
|
|
4381
|
+
* comment to apologize for itself. The names are the vocabulary: a grep for
|
|
4382
|
+
* this member finds every register-only site, and a grep for `forSigning`
|
|
4383
|
+
* again finds only signing.
|
|
4384
|
+
*
|
|
4385
|
+
* The canonical caller masks every supplied credential input
|
|
4386
|
+
* unconditionally, before the logic that decides which of them will
|
|
4387
|
+
* actually be used — a secret the workflow supplied deserves redaction from
|
|
4388
|
+
* the log whether or not resolution reaches for it.
|
|
4389
|
+
*
|
|
4390
|
+
* One value per call is the blessed shape; there is deliberately no
|
|
4391
|
+
* set-taking form. {@link Secret.forChildEnv} takes a set because its
|
|
4392
|
+
* mask-everything-before-returning-anything ordering is load-bearing; a
|
|
4393
|
+
* plain mask returns nothing to order against, and a loop is fine.
|
|
4394
|
+
*
|
|
4395
|
+
* **In a detached worker, the mask this member emits is a leak.** The
|
|
4396
|
+
* `::add-mask::` command only masks when the runner parses this process's
|
|
4397
|
+
* stdout; a detached worker's stdout is a log file no runner parses, so
|
|
4398
|
+
* there the command masks nothing *and* spells the plaintext into the log
|
|
4399
|
+
* — a signing key shipped exactly that way for one round. A worker must
|
|
4400
|
+
* compose `ActionOutputs.layerDetached`, under which the mask is a
|
|
4401
|
+
* documented no-op; the masking itself is the **parent's** job, done
|
|
4402
|
+
* before the spawn via {@link Secret.forChildEnv} under the real layer.
|
|
4403
|
+
*/
|
|
4404
|
+
static readonly mask: (secret: Redacted.Redacted<string>) => Effect.Effect<void, never, ActionOutputs>;
|
|
4234
4405
|
/**
|
|
4235
4406
|
* The far side of a handoff: re-wrap a plaintext environment variable.
|
|
4236
4407
|
*
|
|
@@ -4242,5 +4413,5 @@ declare class Secret {
|
|
|
4242
4413
|
static readonly adopt: (name: string) => Config.Config<Redacted.Redacted<string>>;
|
|
4243
4414
|
}
|
|
4244
4415
|
//#endregion
|
|
4245
|
-
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, ActionsIdentityToken, ActionsProvenance, AmbientPackageManager, type AnnotationProperties, Artifact, ArtifactError, type ArtifactItem, type ArtifactRef, type ArtifactShape, type Blob, BlobEnvelope, BlobEnvelopeError, BlobStore, BlobStoreError, type BlobStoreShape, BlobTransferError, CacheKey, CacheKeyError, CachedPackageManager, CheckDocument, CheckDocumentError, type CheckDocumentOptions, type CheckDocumentShape, type CheckDocumentSink, CheckReport, type CheckRunConclusion, type CheckRunProjection, CheckState, ChildEnv, type ClientLayerOptions, type DataBlobTransfer, DetachedProcess, DetachedProcessError, type DetachedSpawnOptions, type DownloadOptions, type DownloadResult, DryRun, type DryRunShape, type ExtractOptions, type FileBlobTransfer, GitHubCacheBlobStore, GitHubContext, type GitHubHeadingDepth, type GitHubListOptions, GitHubMarkdown, type GitHubRowSchema, type GitHubSchemaTable, type GitHubSchemaTableColumn, type GitHubSchemaTableColumns, type GitHubSchemaTableFormatRequiredKeys, type GitHubSchemaTableFormattedColumn, type GitHubSchemaTableOptions, GitHubToken, GitHubTokenError, InstalledPackageManager, ManagedDocument, ManagedDocumentError, type ManagedDocumentSource, OidcClaims, OidcTokenError, OidcTokenIssuer, type OidcTokenIssuerShape, type PackageManagerInstallOptions, PackageManagerInstaller, PackageManagerInstallerError, type PackageManagerInstallerShape, type PathPrependEnv, type PathPrependOptions, ProcessId, type ProvisionFileOptions, type ProvisionOptions, type ProvisionedFile, type ReadOptions, type ReadinessOptions, RunnerContext, type S3Config, Secret, type ToolDownloadOptions, ToolInstaller, ToolInstallerError, type ToolInstallerShape, type UploadOptions, type UploadResult, type WithBufferOptions, type WithStepOptions, WorkflowCommand, describeCause, projectCheckState };
|
|
4416
|
+
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, ActionsIdentityToken, ActionsProvenance, AmbientPackageManager, type AnnotationProperties, Artifact, ArtifactError, type ArtifactItem, type ArtifactRef, type ArtifactShape, type Blob, BlobEnvelope, BlobEnvelopeError, BlobStore, BlobStoreError, type BlobStoreShape, BlobTransferError, CacheKey, CacheKeyError, CachedPackageManager, CheckDocument, CheckDocumentError, type CheckDocumentOptions, type CheckDocumentShape, type CheckDocumentSink, CheckReport, type CheckRunConclusion, type CheckRunProjection, CheckState, ChildEnv, type ClientLayerOptions, type DataBlobTransfer, DetachedProcess, DetachedProcessError, type DetachedProcessOps, type DetachedSpawnOptions, type DownloadOptions, type DownloadResult, DryRun, type DryRunShape, type ExtractOptions, type FileBlobTransfer, GitHubCacheBlobStore, GitHubContext, type GitHubHeadingDepth, type GitHubListOptions, GitHubMarkdown, type GitHubRowSchema, type GitHubSchemaTable, type GitHubSchemaTableColumn, type GitHubSchemaTableColumns, type GitHubSchemaTableFormatRequiredKeys, type GitHubSchemaTableFormattedColumn, type GitHubSchemaTableOptions, GitHubToken, GitHubTokenError, InstalledPackageManager, ManagedDocument, ManagedDocumentError, type ManagedDocumentSource, OidcClaims, OidcTokenError, OidcTokenIssuer, type OidcTokenIssuerShape, type PackageManagerInstallOptions, PackageManagerInstaller, PackageManagerInstallerError, type PackageManagerInstallerShape, type PathPrependEnv, type PathPrependOptions, ProcessId, type ProvisionFileOptions, type ProvisionOptions, type ProvisionedFile, type ReadOptions, type ReadinessOptions, RunnerContext, type S3Config, Secret, type ToolDownloadOptions, ToolInstaller, ToolInstallerError, type ToolInstallerShape, type UploadOptions, type UploadResult, type WithBufferOptions, type WithStepOptions, WorkflowCommand, describeCause, projectCheckState };
|
|
4246
4417
|
//# sourceMappingURL=index.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effected/github-actions",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The GitHub Actions runtime: env, inputs, outputs, state, logging, cache, artifacts, blob storage, OIDC and the token bridge.",
|
|
6
6
|
"keywords": [
|
|
@@ -39,16 +39,16 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@azure/storage-blob": "^12.33.0",
|
|
42
|
-
"@effected/github": "^0.
|
|
43
|
-
"@effected/glob": "^0.
|
|
44
|
-
"@effected/markdown": "^0.
|
|
45
|
-
"@effected/npm": "^0.
|
|
46
|
-
"@effected/sbom": "^0.
|
|
47
|
-
"@effected/templates": "^0.
|
|
42
|
+
"@effected/github": "^0.5.0",
|
|
43
|
+
"@effected/glob": "^0.4.0",
|
|
44
|
+
"@effected/markdown": "^0.6.0",
|
|
45
|
+
"@effected/npm": "^0.10.0",
|
|
46
|
+
"@effected/sbom": "^0.4.0",
|
|
47
|
+
"@effected/templates": "^0.3.0"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"@effect/platform-node": "4.0.0-
|
|
51
|
-
"effect": "4.0.0-
|
|
50
|
+
"@effect/platform-node": "4.0.0-rc.109",
|
|
51
|
+
"effect": "4.0.0-rc.109"
|
|
52
52
|
},
|
|
53
53
|
"engines": {
|
|
54
54
|
"node": ">=24.11.0"
|