@effected/github-actions 0.19.0 → 0.20.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 +13 -12
- package/ActionCache.js +1 -2
- package/ActionEnvironment.js +21 -6
- package/ActionInput.js +37 -55
- package/ActionLogger.js +27 -10
- package/ActionOutputs.js +27 -8
- package/ActionState.js +31 -2
- package/ActionsIdentityToken.js +2 -1
- package/ActionsProvenance.js +2 -1
- package/Artifact.js +4 -8
- package/BlobEnvelope.js +41 -15
- package/BlobStore.githubCache.js +11 -4
- package/BlobStore.js +8 -7
- package/CacheKey.js +6 -7
- package/ChildEnv.js +2 -5
- package/DetachedProcess.js +8 -11
- package/GitHubMarkdown.js +8 -10
- package/GitHubToken.js +12 -14
- package/ManagedDocument.js +3 -4
- package/OidcTokenIssuer.js +11 -7
- package/PackageManagerInstaller.js +10 -3
- package/README.md +4 -4
- package/Secret.js +10 -17
- package/ToolInstaller.js +12 -6
- package/index.d.ts +334 -293
- package/index.js +1 -1
- package/internal/actionsResults.js +0 -12
- package/internal/archiveCommands.js +0 -66
- package/internal/cacheService.js +0 -16
- package/internal/digest.js +0 -13
- package/internal/jwt.js +0 -17
- package/internal/pnpmExe.js +0 -15
- package/internal/runnerFile.js +0 -16
- package/internal/sigv4.js +1 -4
- package/internal/unstubbed.js +0 -12
- package/package.json +12 -11
- package/WorkflowCommand.js +0 -100
package/Action.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { ActionEnvironment } from "./ActionEnvironment.js";
|
|
2
2
|
import { ActionInput } from "./ActionInput.js";
|
|
3
|
-
import { WorkflowCommand } from "./WorkflowCommand.js";
|
|
4
3
|
import { ActionLogger } from "./ActionLogger.js";
|
|
5
4
|
import { ActionOutputs } from "./ActionOutputs.js";
|
|
6
5
|
import { ActionState } from "./ActionState.js";
|
|
6
|
+
import { WorkflowCommand } from "@effected/github-commands";
|
|
7
7
|
import { NodeServices } from "@effect/platform-node";
|
|
8
8
|
import { Cause, Effect, Exit, Layer, LogLevel, Option, References, Result } from "effect";
|
|
9
9
|
import { FetchHttpClient } from "effect/http";
|
|
@@ -52,7 +52,7 @@ const withStepDebugLogLevel = (program) => Effect.gen(function* () {
|
|
|
52
52
|
* A readable one-line summary of why an action failed.
|
|
53
53
|
*
|
|
54
54
|
* @remarks
|
|
55
|
-
* `[Tag] message` because a workflow log is read by a human scanning for the
|
|
55
|
+
* `[Tag]: message` because a workflow log is read by a human scanning for the
|
|
56
56
|
* first red line, and every error in this kit carries both. The `_tag` is what
|
|
57
57
|
* makes two failures with the same wording distinguishable, and the `message`
|
|
58
58
|
* getter is what makes the line worth reading.
|
|
@@ -99,11 +99,11 @@ const describeError = (error) => {
|
|
|
99
99
|
*
|
|
100
100
|
* Two things it deliberately does **not** do:
|
|
101
101
|
*
|
|
102
|
-
* - **It does not wrap the program in a log buffer.**
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
102
|
+
* - **It does not wrap the program in a log buffer.** A buffer that swallowed
|
|
103
|
+
* the transcript on an unhandled defect would leave a run that failed and
|
|
104
|
+
* printed nothing. `ActionLogger.withBuffer` is opt-in and flushes on every
|
|
105
|
+
* exit path including a defect, so the buffering is where the caller can
|
|
106
|
+
* see it.
|
|
107
107
|
* - **It does not throw.** The returned promise always resolves; the failure is
|
|
108
108
|
* in `process.exitCode`, which is what the runner reads. An action entry
|
|
109
109
|
* point that rejected would produce an unhandled rejection *and* a failed
|
|
@@ -112,9 +112,9 @@ const describeError = (error) => {
|
|
|
112
112
|
* The rendering depth is deliberate too: one `::error::` line carrying
|
|
113
113
|
* `[Tag]: message`, and the fiddly diagnostics — the full `Cause.pretty` render
|
|
114
114
|
* with its span trace and stack — behind `::debug::`, which the runner shows
|
|
115
|
-
* only when someone turns step debugging on.
|
|
116
|
-
*
|
|
117
|
-
*
|
|
115
|
+
* only when someone turns step debugging on. No JS stack is spliced into the
|
|
116
|
+
* visible error, because in a bundled action it points at one line of
|
|
117
|
+
* `dist/main.js`.
|
|
118
118
|
*
|
|
119
119
|
* @example
|
|
120
120
|
* ```ts
|
|
@@ -133,8 +133,9 @@ var Action = class {
|
|
|
133
133
|
* Run an action program to completion.
|
|
134
134
|
*
|
|
135
135
|
* @remarks
|
|
136
|
-
*
|
|
137
|
-
* the verdict.
|
|
136
|
+
* Never rejects: the promise resolves whether the program succeeded or not,
|
|
137
|
+
* so read `process.exitCode` for the verdict. Requirements beyond
|
|
138
|
+
* {@link ActionServices} come from `options.layer`.
|
|
138
139
|
*/
|
|
139
140
|
static run = (program, options = {}) => {
|
|
140
141
|
const extra = options.layer;
|
package/ActionCache.js
CHANGED
|
@@ -169,8 +169,7 @@ const make = (transfer) => Effect.gen(function* () {
|
|
|
169
169
|
*
|
|
170
170
|
* Output and exit code come from the SAME `spawn` handle
|
|
171
171
|
* (`internal/spawn.ts` — the spawner's convenience members each spawn
|
|
172
|
-
* independently,
|
|
173
|
-
* `ToolInstaller.extractZip` on Windows).
|
|
172
|
+
* independently, so reading both from them would run `tar` twice).
|
|
174
173
|
*/
|
|
175
174
|
const tar = (args, key, tolerateWarnings) => Effect.gen(function* () {
|
|
176
175
|
const { output, code } = yield* spawnOnce(spawner, ChildProcess.make("tar", [...args])).pipe(Effect.mapError(archiveFailed(key)));
|
package/ActionEnvironment.js
CHANGED
|
@@ -14,6 +14,7 @@ import { Context, Effect, FileSystem, Layer, Option, Schema } from "effect";
|
|
|
14
14
|
* @public
|
|
15
15
|
*/
|
|
16
16
|
var ActionEnvironmentError = class extends Schema.TaggedError()("ActionEnvironmentError", {
|
|
17
|
+
/** `missing` — absent or empty; `malformed` — present but unusable. */
|
|
17
18
|
reason: Schema.Literals(["missing", "malformed"]),
|
|
18
19
|
/** The environment variable involved. */
|
|
19
20
|
name: Schema.String,
|
|
@@ -32,6 +33,7 @@ var ActionEnvironmentError = class extends Schema.TaggedError()("ActionEnvironme
|
|
|
32
33
|
var GitHubContext = class extends Schema.Class("GitHubContext")({
|
|
33
34
|
/** `owner/repo`. */
|
|
34
35
|
repository: Schema.String,
|
|
36
|
+
/** The repository owner's login. */
|
|
35
37
|
repositoryOwner: Schema.String,
|
|
36
38
|
/** The full ref, e.g. `refs/heads/main`. */
|
|
37
39
|
ref: Schema.String,
|
|
@@ -53,16 +55,27 @@ var GitHubContext = class extends Schema.Class("GitHubContext")({
|
|
|
53
55
|
* an encoded context stays plain JSON.
|
|
54
56
|
*/
|
|
55
57
|
headRef: Schema.OptionFromNullOr(Schema.String),
|
|
58
|
+
/** The commit SHA that triggered the workflow. */
|
|
56
59
|
sha: Schema.String,
|
|
60
|
+
/** The workflow's name. */
|
|
57
61
|
workflow: Schema.String,
|
|
62
|
+
/** The current job's id. */
|
|
58
63
|
job: Schema.String,
|
|
64
|
+
/** The run's unique number, decoded from `GITHUB_RUN_ID`. */
|
|
59
65
|
runId: Schema.Number,
|
|
66
|
+
/** The attempt number of this run, starting at 1. */
|
|
60
67
|
runAttempt: Schema.Number,
|
|
68
|
+
/** The name of the event that triggered the workflow, e.g. `push`. */
|
|
61
69
|
eventName: Schema.String,
|
|
70
|
+
/** The login of the user that triggered the run. */
|
|
62
71
|
actor: Schema.String,
|
|
72
|
+
/** The GitHub server URL, e.g. `https://github.com`. */
|
|
63
73
|
serverUrl: Schema.String,
|
|
74
|
+
/** The REST API URL, e.g. `https://api.github.com`. */
|
|
64
75
|
apiUrl: Schema.String,
|
|
76
|
+
/** The GraphQL API URL. */
|
|
65
77
|
graphqlUrl: Schema.String,
|
|
78
|
+
/** The default working directory on the runner for steps. */
|
|
66
79
|
workspace: Schema.String
|
|
67
80
|
}) {
|
|
68
81
|
/**
|
|
@@ -86,8 +99,11 @@ var GitHubContext = class extends Schema.Class("GitHubContext")({
|
|
|
86
99
|
* @public
|
|
87
100
|
*/
|
|
88
101
|
var RunnerContext = class extends Schema.Class("RunnerContext")({
|
|
102
|
+
/** The runner's operating system: `Linux`, `Windows` or `macOS`. */
|
|
89
103
|
os: Schema.String,
|
|
104
|
+
/** The runner's architecture, e.g. `X64` or `ARM64`. */
|
|
90
105
|
arch: Schema.String,
|
|
106
|
+
/** The runner's name. */
|
|
91
107
|
name: Schema.String,
|
|
92
108
|
/** A scratch directory emptied between jobs. */
|
|
93
109
|
temp: Schema.String,
|
|
@@ -101,8 +117,7 @@ var RunnerContext = class extends Schema.Class("RunnerContext")({
|
|
|
101
117
|
* A `Context.Reference` rather than a mutable global: overrides are
|
|
102
118
|
* **fiber-local**, so two concurrent fibers can override the same variable
|
|
103
119
|
* without seeing each other, and nothing needs restoring afterwards. This is
|
|
104
|
-
* what makes {@link ActionEnvironmentShape.withEnv} parallel-safe
|
|
105
|
-
* property the hand-rolled set/restore version it replaces could not have.
|
|
120
|
+
* what makes {@link ActionEnvironmentShape.withEnv} parallel-safe.
|
|
106
121
|
*
|
|
107
122
|
* @internal
|
|
108
123
|
*/
|
|
@@ -240,10 +255,10 @@ var ActionEnvironment = class ActionEnvironment extends Context.Service()("@effe
|
|
|
240
255
|
* @remarks
|
|
241
256
|
* **A recorded exception to the die-on-unstubbed rule.** Every other service
|
|
242
257
|
* in this package dies when a suite calls something it did not stub, because
|
|
243
|
-
* a plausible default hides a real gap. Here the opposite is true: the
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
258
|
+
* a plausible default hides a real gap. Here the opposite is true: the context
|
|
259
|
+
* variables have one obviously-correct shape, and requiring each suite to
|
|
260
|
+
* restate them would duplicate a byte-identical block in every suite.
|
|
261
|
+
* Overrides merge on top.
|
|
247
262
|
*
|
|
248
263
|
* The optional second argument serves {@link ActionEnvironmentShape.payload}
|
|
249
264
|
* **directly**, replacing the `GITHUB_EVENT_PATH` read rather than seeding a
|
package/ActionInput.js
CHANGED
|
@@ -9,10 +9,10 @@ import { Config, ConfigProvider, Context, Effect, Schema, SchemaIssue } from "ef
|
|
|
9
9
|
* and leaves every other character, **dashes included**, alone. So the input
|
|
10
10
|
* `sbom-config` arrives as `INPUT_SBOM-CONFIG`, not `INPUT_SBOM_CONFIG`.
|
|
11
11
|
*
|
|
12
|
-
* That distinction is not academic:
|
|
13
|
-
* directly
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* That distinction is not academic: reading `process.env["INPUT_SBOM_CONFIG"]`
|
|
13
|
+
* directly silently yields nothing. Every accessor in this module goes through
|
|
14
|
+
* this function so no caller ever spells the variable name, which makes that
|
|
15
|
+
* class of bug unrepresentable rather than merely documented.
|
|
16
16
|
*
|
|
17
17
|
* @internal
|
|
18
18
|
*/
|
|
@@ -24,11 +24,10 @@ const inputVariable = (name) => `INPUT_${name.replaceAll(" ", "_").toUpperCase()
|
|
|
24
24
|
* `Config.withDefault` and `Config.option` fall back only when the config
|
|
25
25
|
* resolves *absent* — no provider input at all. A failure raised from inside
|
|
26
26
|
* `Config.mapEffect` is a hard evaluation failure carrying the evidence that
|
|
27
|
-
* input WAS present, so a default never swallows it
|
|
28
|
-
* `
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* carries the offending value so the rendered error names it.
|
|
27
|
+
* input WAS present, so a default never swallows it. That is the guard against
|
|
28
|
+
* `dry-run: yes` quietly becoming `false` and the action performing the
|
|
29
|
+
* mutations the author meant to rehearse. `actual` carries the offending value
|
|
30
|
+
* so the rendered error names it.
|
|
32
31
|
*/
|
|
33
32
|
const configError = (message, actual) => new Config.ConfigError(new Schema.SchemaError(new SchemaIssue.InvalidValue({ message }, actual)));
|
|
34
33
|
/** YAML 1.2 core-schema booleans, which is what the runner documents. */
|
|
@@ -53,19 +52,12 @@ const stripComment = (line) => {
|
|
|
53
52
|
* @remarks
|
|
54
53
|
* Inputs are **never** read from `process.env` directly. Every accessor names
|
|
55
54
|
* the input the way the workflow author wrote it and lets this module derive
|
|
56
|
-
* the runner's variable name — see `
|
|
57
|
-
*
|
|
58
|
-
* entrypoint and the reference would not resolve.)
|
|
55
|
+
* the runner's variable name — see `ActionInput.variable` for why that
|
|
56
|
+
* matters.
|
|
59
57
|
*
|
|
60
|
-
* `lines
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* other stripping comments and reading `key=value` pairs. Those are now one
|
|
64
|
-
* implementation with one set of tests instead of two divergent ones with
|
|
65
|
-
* none.
|
|
66
|
-
*
|
|
67
|
-
* These are grouped statics over one concept reaching nothing but `Config` —
|
|
68
|
-
* not a namespace object over engines.
|
|
58
|
+
* `lines` is the `@actions/core`-faithful newline split; `list` and `pairs`
|
|
59
|
+
* read the richer shapes workflow authors write — JSON arrays, bullet lists,
|
|
60
|
+
* comments and `key=value` pairs.
|
|
69
61
|
*
|
|
70
62
|
* **The absence contract, shared by every accessor:** the runner writes `""`
|
|
71
63
|
* for an input the workflow left out, and the provider reads an empty string
|
|
@@ -120,10 +112,9 @@ var ActionInput = class ActionInput {
|
|
|
120
112
|
* **An input whose documented meaning is "set it empty to disable this"
|
|
121
113
|
* cannot be read with `Config.withDefault`.** Empty is classified as missing
|
|
122
114
|
* *before* the default is consulted, so the default substitutes and the
|
|
123
|
-
* documented behavior can never happen
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
* distinguishes the states:
|
|
115
|
+
* documented behavior can never happen: if `action.yml` promises
|
|
116
|
+
* `release-prefix: ""` disables retry, a `withDefault` read makes that
|
|
117
|
+
* impossible. `Config.option` is the shape that distinguishes the states:
|
|
127
118
|
*
|
|
128
119
|
* ```ts
|
|
129
120
|
* Config.option(ActionInput.string("release-prefix"))
|
|
@@ -139,8 +130,7 @@ var ActionInput = class ActionInput {
|
|
|
139
130
|
* no-default case, and it is the manifest default — when there is one — that
|
|
140
131
|
* makes the unsupplied and explicitly-empty cases distinguishable at all.
|
|
141
132
|
*
|
|
142
|
-
* Two consequences follow from how the runner actually behaves
|
|
143
|
-
* confirmed against `actions/runner` and on a live runner:
|
|
133
|
+
* Two consequences follow from how the runner actually behaves:
|
|
144
134
|
*
|
|
145
135
|
* - **The runner publishes a variable for every declared input**, so *absent*
|
|
146
136
|
* is a state a runner never produces. The `Some` branch above depends on
|
|
@@ -164,8 +154,8 @@ var ActionInput = class ActionInput {
|
|
|
164
154
|
* Absent or `""` is **missing data** — wrap with `Config.withDefault` for
|
|
165
155
|
* an optional flag. A **present but malformed** value (`yes`, `on`, `1`) is
|
|
166
156
|
* a different class: it fails carrying its `actual`, deliberately, so a
|
|
167
|
-
* default does NOT swallow it
|
|
168
|
-
*
|
|
157
|
+
* default does NOT swallow it, so a malformed `dry-run` never quietly reads
|
|
158
|
+
* as `false`.
|
|
169
159
|
*/
|
|
170
160
|
static boolean(name) {
|
|
171
161
|
return Config.String(inputVariable(name)).pipe(Config.mapEffect((raw) => {
|
|
@@ -247,10 +237,8 @@ var ActionInput = class ActionInput {
|
|
|
247
237
|
* @remarks
|
|
248
238
|
* A JSON array (`["a","b"]`), a bullet list (`- a` or `* a`), or comma- and
|
|
249
239
|
* newline-separated values, with full-line `#` comments dropped — any
|
|
250
|
-
* combination of these in the same input
|
|
251
|
-
*
|
|
252
|
-
* comment-stripping); accepting the union means a workflow author's first
|
|
253
|
-
* guess works and nothing either prior consumer relied on regresses.
|
|
240
|
+
* combination of these in the same input — so a workflow author's first
|
|
241
|
+
* guess works.
|
|
254
242
|
*
|
|
255
243
|
* Parsing order, applied per newline/comma-separated item after trimming:
|
|
256
244
|
*
|
|
@@ -297,9 +285,9 @@ var ActionInput = class ActionInput {
|
|
|
297
285
|
* Only the **first** `=` splits, so a value may contain one.
|
|
298
286
|
*
|
|
299
287
|
* An **empty key** (`=value`, or a bare `=`) is always rejected: `{ "": v }`
|
|
300
|
-
* cannot be what a workflow meant, and the damage
|
|
301
|
-
* an empty key
|
|
302
|
-
*
|
|
288
|
+
* cannot be what a workflow meant, and the damage would land far from the
|
|
289
|
+
* typo — an empty key becomes a filter that matches nothing, and the run
|
|
290
|
+
* reports zero results with no indication why. An empty **value** (`key=`)
|
|
303
291
|
* is accepted by default, because setting a property to the empty string is
|
|
304
292
|
* legitimate; pass `requireValue` to reject it. Every rejection names the
|
|
305
293
|
* offending line.
|
|
@@ -340,9 +328,8 @@ var ActionInput = class ActionInput {
|
|
|
340
328
|
*
|
|
341
329
|
* There is no `schemaOption`: `ActionInput.schema(name, S).pipe(Config.option)`
|
|
342
330
|
* yields `Config<Option<A>>` with missing → `None` and a malformed value
|
|
343
|
-
* still failing — core's
|
|
344
|
-
*
|
|
345
|
-
* missing-versus-malformed split above.
|
|
331
|
+
* still failing — core's `Config.option` lets validation errors propagate,
|
|
332
|
+
* which is exactly the missing-versus-malformed split above.
|
|
346
333
|
*/
|
|
347
334
|
static schema(name, schema) {
|
|
348
335
|
return Config.String(inputVariable(name)).pipe(Config.mapEffect((raw) => {
|
|
@@ -362,7 +349,7 @@ var ActionInput = class ActionInput {
|
|
|
362
349
|
* The record dual-accepts, and the split is the `INPUT_` prefix:
|
|
363
350
|
*
|
|
364
351
|
* - A key already spelled as a runner variable (`INPUT_BIOME-VERSION`,
|
|
365
|
-
* `PLAIN_VAR`) is read verbatim
|
|
352
|
+
* `PLAIN_VAR`) is read verbatim.
|
|
366
353
|
* - Any other key is treated as an **input name**, `with:`-block style —
|
|
367
354
|
* `{ "biome-version": "2.3.14" }` — and serves the variable this module
|
|
368
355
|
* derives for it (`ActionInput.variable`). This is the spelling to
|
|
@@ -379,11 +366,10 @@ var ActionInput = class ActionInput {
|
|
|
379
366
|
* installs ({@link ActionInput.providerOver} via `layerDefault`) — so a
|
|
380
367
|
* bare `Config.String("biome-version")` resolves under this provider
|
|
381
368
|
* exactly as it does inside `Action.run`. Nested and numeric paths pass
|
|
382
|
-
* through as the joined spelling only
|
|
369
|
+
* through as the joined spelling only.
|
|
383
370
|
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
* and the whole is uppercased; and an **empty string reads as absent**,
|
|
371
|
+
* Otherwise the path is joined with `_`, spaces become underscores and the
|
|
372
|
+
* whole is uppercased; and an **empty string reads as absent**,
|
|
387
373
|
* because the runner sets unsupplied inputs to `""` and treating that as
|
|
388
374
|
* present would make every optional input look supplied. That rule applies
|
|
389
375
|
* to input-name entries too: `{ "flag": "" }` is an unsupplied input.
|
|
@@ -401,9 +387,7 @@ var ActionInput = class ActionInput {
|
|
|
401
387
|
* `TARGET-BRANCH`, never matches, and the read falls through to whatever
|
|
402
388
|
* default the call site supplies. It **fails green**: nothing errors, the
|
|
403
389
|
* suite passes against the default, and the test's name claims it proved the
|
|
404
|
-
* input was read.
|
|
405
|
-
* and stayed green across several commits while a supplied `target-branch`
|
|
406
|
-
* silently did nothing.
|
|
390
|
+
* input was read.
|
|
407
391
|
*
|
|
408
392
|
* Runner-variable keys (`INPUT_TARGET-BRANCH`, `PLAIN_VAR`) are unaffected —
|
|
409
393
|
* they are already in the spelling `fromEnv` produces, and they resolve. The
|
|
@@ -437,9 +421,8 @@ var ActionInput = class ActionInput {
|
|
|
437
421
|
* A layer installing {@link ActionInput.provider}.
|
|
438
422
|
*
|
|
439
423
|
* @remarks
|
|
440
|
-
* `ConfigProvider.ConfigProvider` is a `Context.Reference
|
|
441
|
-
*
|
|
442
|
-
* `Effect.withConfigProvider`.
|
|
424
|
+
* `ConfigProvider.ConfigProvider` is a `Context.Reference`, so this is a
|
|
425
|
+
* reference override rather than a service.
|
|
443
426
|
*
|
|
444
427
|
* A parameterized layer factory mints a fresh reference per call; bind it to
|
|
445
428
|
* a `const` rather than calling it at each composition site.
|
|
@@ -459,10 +442,9 @@ var ActionInput = class ActionInput {
|
|
|
459
442
|
* passed to `ambient` untouched, so no path semantics are invented that the
|
|
460
443
|
* runner does not have.
|
|
461
444
|
*
|
|
462
|
-
* This exists because
|
|
463
|
-
*
|
|
464
|
-
*
|
|
465
|
-
* nothing. Under this provider a bare read resolves through the same
|
|
445
|
+
* This exists because the runner exposes inputs as `INPUT_<MANGLED>`
|
|
446
|
+
* variables, so a plain-named lookup finds nothing and a bare `Config` read
|
|
447
|
+
* would silently fall back to its `withDefault`. Under this provider a bare read resolves through the same
|
|
466
448
|
* derivation `ActionInput.string` uses, so side-stepping the typed accessors
|
|
467
449
|
* degrades to the right answer instead of to the default.
|
|
468
450
|
*
|
|
@@ -518,7 +500,7 @@ var ActionInput = class ActionInput {
|
|
|
518
500
|
* `ActionInput.provider` dual-accepts both spellings, so neither can miss.
|
|
519
501
|
*
|
|
520
502
|
* When none is installed, a **fresh** `ConfigProvider.fromEnv()`
|
|
521
|
-
* is built rather than reading the reference's default:
|
|
503
|
+
* is built rather than reading the reference's default: core caches that
|
|
522
504
|
* default once per process, and a snapshot taken before the runner's
|
|
523
505
|
* variables are visible would resurrect exactly the missed-input class this
|
|
524
506
|
* layer exists to kill. An action's environment is fixed before the process
|
package/ActionLogger.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ActionEnvironment } from "./ActionEnvironment.js";
|
|
2
|
-
import { WorkflowCommand } from "
|
|
2
|
+
import { CommandNeutralizer, WorkflowCommand } from "@effected/github-commands";
|
|
3
3
|
import { Console, Context, Effect, Exit, Inspectable, Layer, LogLevel, Logger, References } from "effect";
|
|
4
4
|
|
|
5
5
|
//#region src/ActionLogger.ts
|
|
@@ -67,7 +67,11 @@ const annotationRecord = (properties) => ({ ...properties });
|
|
|
67
67
|
* @remarks
|
|
68
68
|
* `Info` is deliberately plain text with no command prefix — it is ordinary
|
|
69
69
|
* step output, and prefixing it would make every informational line an
|
|
70
|
-
* annotation in the workflow summary.
|
|
70
|
+
* annotation in the workflow summary. It is neutralized, though: the text is
|
|
71
|
+
* whatever the program logged, and the runner reads every line of stdout, so a
|
|
72
|
+
* message carrying `::add-mask::` or a `##[` would be a command. The levels that
|
|
73
|
+
* render AS a command escape their data instead, and a line that starts `::` is
|
|
74
|
+
* read by the runner as that command alone.
|
|
71
75
|
*/
|
|
72
76
|
const renderEntry = (level, message, annotations) => {
|
|
73
77
|
const text = formatMessage(message);
|
|
@@ -75,7 +79,7 @@ const renderEntry = (level, message, annotations) => {
|
|
|
75
79
|
const properties = readAnnotations(annotations);
|
|
76
80
|
return LogLevel.isGreaterThanOrEqualTo(level, "Error") ? WorkflowCommand.error(text, properties) : WorkflowCommand.warning(text, properties);
|
|
77
81
|
}
|
|
78
|
-
return LogLevel.isGreaterThanOrEqualTo(level, "Info") ? text : WorkflowCommand.debug(text);
|
|
82
|
+
return LogLevel.isGreaterThanOrEqualTo(level, "Info") ? CommandNeutralizer.text(text) : WorkflowCommand.debug(text);
|
|
79
83
|
};
|
|
80
84
|
/** The line the runner should see for one log event, with the fiber's annotations. */
|
|
81
85
|
const renderLogger = Logger.make((options) => renderEntry(options.logLevel, options.message, options.fiber.getRef(References.CurrentLogAnnotations)));
|
|
@@ -106,7 +110,7 @@ const flush = (state) => Effect.suspend(() => {
|
|
|
106
110
|
`--- End buffered output for "${state.label}" ---`
|
|
107
111
|
];
|
|
108
112
|
state.entries.length = 0;
|
|
109
|
-
return Effect.forEach(body, (line) => Console.log(line), { discard: true });
|
|
113
|
+
return Effect.forEach(body, (line) => Console.log(CommandNeutralizer.text(line)), { discard: true });
|
|
110
114
|
});
|
|
111
115
|
const make = Effect.gen(function* () {
|
|
112
116
|
const env = yield* ActionEnvironment;
|
|
@@ -130,7 +134,7 @@ const make = Effect.gen(function* () {
|
|
|
130
134
|
return {
|
|
131
135
|
group: (name, effect) => Effect.acquireUseRelease(Console.log(WorkflowCommand.group(name)), () => Effect.tapCause(effect, () => flushActive), () => Console.log(WorkflowCommand.endGroup())),
|
|
132
136
|
withBuffer,
|
|
133
|
-
withStep: (name, effect, options) => withBuffer(name, Effect.tapCause(effect, () => Console.log(`❌ ${name}`)), { onSuccess: "discard" }).pipe(Effect.tap(() => Effect.logInfo(options?.summary ?? `✅ ${name}`))),
|
|
137
|
+
withStep: (name, effect, options) => withBuffer(name, Effect.tapCause(effect, () => Console.log(CommandNeutralizer.text(`❌ ${name}`))), { onSuccess: "discard" }).pipe(Effect.tap(() => Effect.logInfo(options?.summary ?? `✅ ${name}`))),
|
|
134
138
|
notice: (message, properties = {}) => Console.log(WorkflowCommand.notice(message, properties)),
|
|
135
139
|
annotated: (properties, effect) => Effect.annotateLogs(effect, annotationRecord(properties))
|
|
136
140
|
};
|
|
@@ -159,6 +163,14 @@ const make = Effect.gen(function* () {
|
|
|
159
163
|
* @public
|
|
160
164
|
*/
|
|
161
165
|
var ActionLogger = class ActionLogger extends Context.Service()("@effected/github-actions/ActionLogger") {
|
|
166
|
+
/**
|
|
167
|
+
* The service: groups, the buffered step renderer, notices and annotations.
|
|
168
|
+
*
|
|
169
|
+
* @remarks
|
|
170
|
+
* This installs no `Logger`. Without {@link ActionLogger.layerLogger} (or `Action.run`, which installs it) Effect's
|
|
171
|
+
* default logger writes `Effect.log*` text raw, so a message carrying `::add-mask::` or `##[` is a workflow command
|
|
172
|
+
* to the runner. Install both: the service for its structure, the logger for its neutralizing of every log line.
|
|
173
|
+
*/
|
|
162
174
|
static layer = Layer.effect(this, make);
|
|
163
175
|
/**
|
|
164
176
|
* The `Logger` that maps Effect log levels onto workflow commands.
|
|
@@ -172,7 +184,13 @@ var ActionLogger = class ActionLogger extends Context.Service()("@effected/githu
|
|
|
172
184
|
* observable in a test without a runner.
|
|
173
185
|
*/
|
|
174
186
|
static logger = commandLogger;
|
|
175
|
-
/**
|
|
187
|
+
/**
|
|
188
|
+
* {@link ActionLogger.logger} installed as the only logger.
|
|
189
|
+
*
|
|
190
|
+
* @remarks
|
|
191
|
+
* Bound to a constant so every composition site shares one layer;
|
|
192
|
+
* `ActionRuntime.layer` already includes it.
|
|
193
|
+
*/
|
|
176
194
|
static layerLogger = Logger.layer([ActionLogger.logger]);
|
|
177
195
|
/**
|
|
178
196
|
* A silent double.
|
|
@@ -198,10 +216,9 @@ var ActionLogger = class ActionLogger extends Context.Service()("@effected/githu
|
|
|
198
216
|
* A silent service **and** a silent `Effect.log*`.
|
|
199
217
|
*
|
|
200
218
|
* @remarks
|
|
201
|
-
* This one layer
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
* exposed as a factory, so composing it twice is free.
|
|
219
|
+
* This one layer stands in for a bare `Effect.provide(Logger.layer([]))`
|
|
220
|
+
* repeated in every suite just to keep test output quiet. Bound to a
|
|
221
|
+
* constant rather than exposed as a factory, so composing it twice is free.
|
|
205
222
|
*/
|
|
206
223
|
static layerSilent = Layer.mergeAll(ActionLogger.layerTest(), Logger.layer([]));
|
|
207
224
|
};
|
package/ActionOutputs.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ActionEnvironment } from "./ActionEnvironment.js";
|
|
2
|
-
import { WorkflowCommand } from "./WorkflowCommand.js";
|
|
3
2
|
import { heredocBlock, isUsableName } from "./internal/runnerFile.js";
|
|
4
3
|
import { unstubbed } from "./internal/unstubbed.js";
|
|
4
|
+
import { CommandNeutralizer, WorkflowCommand } from "@effected/github-commands";
|
|
5
5
|
import { Console, Context, Effect, FileSystem, Layer, Schema } from "effect";
|
|
6
6
|
|
|
7
7
|
//#region src/ActionOutputs.ts
|
|
@@ -180,9 +180,30 @@ const dies = unstubbed("ActionOutputs.makeTest");
|
|
|
180
180
|
* — that is `Action.run`'s job, so that an action which reports a failure and
|
|
181
181
|
* then recovers is not silently doomed by a side effect it cannot undo.
|
|
182
182
|
*
|
|
183
|
+
* Members that write a runner file fail with {@link ActionOutputError}.
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```ts
|
|
187
|
+
* import { ActionOutputs } from "@effected/github-actions";
|
|
188
|
+
* import { Effect, Schema } from "effect";
|
|
189
|
+
*
|
|
190
|
+
* const program = Effect.gen(function* () {
|
|
191
|
+
* const outputs = yield* ActionOutputs;
|
|
192
|
+
* yield* outputs.set("version", "1.2.3");
|
|
193
|
+
* yield* outputs.setJson("packages", ["a", "b"], Schema.Array(Schema.String));
|
|
194
|
+
* });
|
|
195
|
+
* ```
|
|
196
|
+
*
|
|
183
197
|
* @public
|
|
184
198
|
*/
|
|
185
199
|
var ActionOutputs = class ActionOutputs extends Context.Service()("@effected/github-actions/ActionOutputs") {
|
|
200
|
+
/**
|
|
201
|
+
* The live service, appending to the runner's `GITHUB_OUTPUT`, `GITHUB_ENV`,
|
|
202
|
+
* `GITHUB_PATH` and `GITHUB_STEP_SUMMARY` files.
|
|
203
|
+
*
|
|
204
|
+
* @remarks
|
|
205
|
+
* `ActionRuntime.layer` already provides every requirement.
|
|
206
|
+
*/
|
|
186
207
|
static layer = Layer.effect(this, make);
|
|
187
208
|
/**
|
|
188
209
|
* The outputs surface that is correct inside a **detached worker**.
|
|
@@ -192,10 +213,8 @@ var ActionOutputs = class ActionOutputs extends Context.Service()("@effected/git
|
|
|
192
213
|
* stdout is a **log file no runner parses**, so under the real layer
|
|
193
214
|
* `setSecret` emits `::add-mask::<plaintext>` into that file — a command
|
|
194
215
|
* that is simultaneously **inert** (nothing reads it, so nothing is masked)
|
|
195
|
-
* and a **secret leak** (the plaintext is now sitting verbatim in a log).
|
|
196
|
-
*
|
|
197
|
-
* session token written into a worker's log by the masking call itself.
|
|
198
|
-
* This layer is the worker-side fix; the parent-side rule is that every
|
|
216
|
+
* and a **secret leak** (the plaintext is now sitting verbatim in a log).
|
|
217
|
+
* This layer is the worker-side guard; the parent-side rule is that every
|
|
199
218
|
* secret a worker will hold is masked **before** the spawn, by
|
|
200
219
|
* `Secret.forChildEnv` under the real layer.
|
|
201
220
|
*
|
|
@@ -208,7 +227,7 @@ var ActionOutputs = class ActionOutputs extends Context.Service()("@effected/git
|
|
|
208
227
|
* spelling of it in the log IS the leak. Masking is the parent's job,
|
|
209
228
|
* before the worker exists.
|
|
210
229
|
* - **`set`, `setJson`, `exportVariable`, `addPath`, `summary` — fail
|
|
211
|
-
* typed**
|
|
230
|
+
* typed** with {@link DetachedOutputError}, naming the file. Each writes a runner
|
|
212
231
|
* file that configures the parent job's *later steps* — `GITHUB_OUTPUT`,
|
|
213
232
|
* `GITHUB_ENV`, `GITHUB_PATH` — or is collected when the step completes
|
|
214
233
|
* (`GITHUB_STEP_SUMMARY`). A detached worker has no later steps, may
|
|
@@ -237,7 +256,7 @@ var ActionOutputs = class ActionOutputs extends Context.Service()("@effected/git
|
|
|
237
256
|
name
|
|
238
257
|
})),
|
|
239
258
|
addPath: () => Effect.fail(new DetachedOutputError({ file: RUNNER_FILE.addPath })),
|
|
240
|
-
setFailed: (message) => Console.error(message),
|
|
259
|
+
setFailed: (message) => Console.error(CommandNeutralizer.text(message)),
|
|
241
260
|
setSecret: () => Effect.void
|
|
242
261
|
});
|
|
243
262
|
/**
|
|
@@ -277,7 +296,7 @@ var ActionOutputs = class ActionOutputs extends Context.Service()("@effected/git
|
|
|
277
296
|
* @remarks
|
|
278
297
|
* This is the double most tests of an action want — "what did it publish?"
|
|
279
298
|
* — shipped so that nobody hand-writes a `setJson` override that drops the
|
|
280
|
-
* schema encode (
|
|
299
|
+
* schema encode (a trap `makeTest` closes too). `setJson` encodes
|
|
281
300
|
* through its schema exactly as the real layer does, failing typed with
|
|
282
301
|
* {@link OutputEncodeError} and recording nothing on a drift, and records
|
|
283
302
|
* the **encoded JSON text** — what the runner would have read — not the
|
package/ActionState.js
CHANGED
|
@@ -6,7 +6,8 @@ import { Context, Effect, FileSystem, Layer, Option, Schema } from "effect";
|
|
|
6
6
|
|
|
7
7
|
//#region src/ActionState.ts
|
|
8
8
|
/**
|
|
9
|
-
* Raised when state cannot
|
|
9
|
+
* Raised when action state cannot be saved, read or decoded across the phase
|
|
10
|
+
* boundary.
|
|
10
11
|
*
|
|
11
12
|
* @public
|
|
12
13
|
*/
|
|
@@ -26,7 +27,9 @@ var ActionStateError = class extends Schema.TaggedError()("ActionStateError", {
|
|
|
26
27
|
"notPlainJson",
|
|
27
28
|
"writeFailed"
|
|
28
29
|
]),
|
|
30
|
+
/** The state key that was being saved or read. */
|
|
29
31
|
key: Schema.String,
|
|
32
|
+
/** The underlying failure, preserved structurally. */
|
|
30
33
|
cause: Schema.optionalKey(Schema.Defect())
|
|
31
34
|
}) {
|
|
32
35
|
get message() {
|
|
@@ -110,11 +113,37 @@ const dies = unstubbed("ActionState.makeTest");
|
|
|
110
113
|
* write-only file (`GITHUB_STATE`) whose entries the runner republishes to the
|
|
111
114
|
* next phase as `STATE_<key>` environment variables — so saving and reading go
|
|
112
115
|
* through different mechanisms, which is why this is a service rather than a
|
|
113
|
-
* pair of helpers.
|
|
116
|
+
* pair of helpers. Every member fails with {@link ActionStateError}.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```ts
|
|
120
|
+
* import { ActionState } from "@effected/github-actions";
|
|
121
|
+
* import { Effect, Schema } from "effect";
|
|
122
|
+
*
|
|
123
|
+
* // in `pre`
|
|
124
|
+
* const pre = Effect.gen(function* () {
|
|
125
|
+
* const state = yield* ActionState;
|
|
126
|
+
* yield* state.save("server-pid", 4242, Schema.Number);
|
|
127
|
+
* });
|
|
128
|
+
*
|
|
129
|
+
* // in `post`
|
|
130
|
+
* const post = Effect.gen(function* () {
|
|
131
|
+
* const state = yield* ActionState;
|
|
132
|
+
* const pid = yield* state.get("server-pid", Schema.Number);
|
|
133
|
+
* return pid;
|
|
134
|
+
* });
|
|
135
|
+
* ```
|
|
114
136
|
*
|
|
115
137
|
* @public
|
|
116
138
|
*/
|
|
117
139
|
var ActionState = class ActionState extends Context.Service()("@effected/github-actions/ActionState") {
|
|
140
|
+
/**
|
|
141
|
+
* The live service, writing to the runner's `GITHUB_STATE` file and reading
|
|
142
|
+
* the `STATE_<key>` variables it republishes.
|
|
143
|
+
*
|
|
144
|
+
* @remarks
|
|
145
|
+
* `ActionRuntime.layer` already provides every requirement.
|
|
146
|
+
*/
|
|
118
147
|
static layer = Layer.effect(this, make);
|
|
119
148
|
/** A test double. Unstubbed members die rather than answering wrongly. */
|
|
120
149
|
static makeTest = (overrides = {}) => ({
|
package/ActionsIdentityToken.js
CHANGED
|
@@ -4,7 +4,8 @@ import { IdentityToken, IdentityTokenError } from "@effected/sbom";
|
|
|
4
4
|
|
|
5
5
|
//#region src/ActionsIdentityToken.ts
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Serves `@effected/sbom`'s `IdentityToken` contract from the runner's own OIDC
|
|
8
|
+
* token service, so signing an SBOM inside a workflow needs no further wiring.
|
|
8
9
|
*
|
|
9
10
|
* @remarks
|
|
10
11
|
* `@effected/sbom` declares `IdentityToken` — one method, one audience,
|
package/ActionsProvenance.js
CHANGED
|
@@ -15,7 +15,8 @@ import { SlsaProvenance } from "@effected/sbom";
|
|
|
15
15
|
*/
|
|
16
16
|
const DEFAULT_SERVER_URL = "https://github.com";
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
18
|
+
* Builds the current run's SLSA provenance from the runner's OIDC claims: the
|
|
19
|
+
* Actions side of `@effected/sbom`'s `SlsaProvenance.forGitHubWorkflow`.
|
|
19
20
|
*
|
|
20
21
|
* @remarks
|
|
21
22
|
* `SlsaProvenance.forGitHubWorkflow` is total and does the real work, but it
|
package/Artifact.js
CHANGED
|
@@ -352,14 +352,10 @@ const dies = unstubbed("Artifact.makeTest");
|
|
|
352
352
|
* third thing that can be absent, and it is reported as `misconfigured` for the
|
|
353
353
|
* same reason.
|
|
354
354
|
*
|
|
355
|
-
* **This surface is provisional.**
|
|
356
|
-
*
|
|
357
|
-
*
|
|
358
|
-
*
|
|
359
|
-
* `Artifact` prefix (`artifact.uploadArtifact(...)` stutters) and the
|
|
360
|
-
* cross-run `findBy` lookup is **not** ported, because every path through it in
|
|
361
|
-
* the source is a typed "not implemented" failure. Adding it later is additive;
|
|
362
|
-
* shipping a parameter that has never had behavior is not.
|
|
355
|
+
* **This surface is provisional.** It has not yet been shaped by a real call
|
|
356
|
+
* site, so it may be reworked once a consumer adopts it. It covers the current
|
|
357
|
+
* run only: there is no cross-run lookup, and adding one later would be
|
|
358
|
+
* additive.
|
|
363
359
|
*
|
|
364
360
|
* @example
|
|
365
361
|
* ```ts
|