@effected/github-actions 0.4.1 → 0.5.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 +36 -3
- package/ActionInput.js +79 -3
- package/ActionLogger.js +20 -17
- package/GitHubMarkdown.js +48 -5
- package/README.md +11 -2
- package/index.d.ts +191 -7
- package/package.json +1 -1
package/ActionEnvironment.js
CHANGED
|
@@ -275,13 +275,46 @@ var ActionEnvironment = class ActionEnvironment extends Context.Service()("@effe
|
|
|
275
275
|
* context variables have one obviously-correct shape, and requiring each
|
|
276
276
|
* suite to restate them produced a byte-identical block duplicated six times
|
|
277
277
|
* across the consumers. Overrides merge on top.
|
|
278
|
+
*
|
|
279
|
+
* The optional second argument serves {@link ActionEnvironmentShape.payload}
|
|
280
|
+
* **directly**, replacing the `GITHUB_EVENT_PATH` read rather than seeding a
|
|
281
|
+
* path and a file behind it. That is the only route to a payload through the
|
|
282
|
+
* standard double: `TEST_DEFAULTS` deliberately omits `GITHUB_EVENT_PATH`, so
|
|
283
|
+
* an unserved payload fails typed and names the variable — the die-on-
|
|
284
|
+
* unstubbed posture the rest of this package keeps.
|
|
285
|
+
*
|
|
286
|
+
* `undefined` means *not served*, so a suite cannot arrange `undefined` as
|
|
287
|
+
* the payload itself. A webhook payload is always a JSON object; the case
|
|
288
|
+
* does not arise, and reserving the sentinel keeps the argument optional.
|
|
278
289
|
*/
|
|
279
|
-
static makeTest = (overrides = {}) => make({
|
|
290
|
+
static makeTest = (overrides = {}, payload) => Effect.map(make({
|
|
280
291
|
...TEST_DEFAULTS,
|
|
281
292
|
...overrides
|
|
293
|
+
}), (shape) => payload === void 0 ? shape : {
|
|
294
|
+
...shape,
|
|
295
|
+
payload: Effect.succeed(payload)
|
|
282
296
|
});
|
|
283
|
-
/**
|
|
284
|
-
|
|
297
|
+
/**
|
|
298
|
+
* {@link ActionEnvironment.makeTest} behind a layer, with `FileSystem` stubbed
|
|
299
|
+
* out.
|
|
300
|
+
*
|
|
301
|
+
* @remarks
|
|
302
|
+
* The stub is why the payload has to be served here rather than through the
|
|
303
|
+
* filesystem: `make` captures the filesystem at construction, so seeding
|
|
304
|
+
* `GITHUB_EVENT_PATH` through `overrides` would send the read to a noop
|
|
305
|
+
* filesystem and fail. Pass the payload as the second argument instead.
|
|
306
|
+
*
|
|
307
|
+
* @example
|
|
308
|
+
* ```ts
|
|
309
|
+
* import { ActionEnvironment } from "@effected/github-actions";
|
|
310
|
+
*
|
|
311
|
+
* const layer = ActionEnvironment.layerTest(
|
|
312
|
+
* { GITHUB_EVENT_NAME: "pull_request" },
|
|
313
|
+
* { pull_request: { number: 42 } },
|
|
314
|
+
* );
|
|
315
|
+
* ```
|
|
316
|
+
*/
|
|
317
|
+
static layerTest = (overrides = {}, payload) => Layer.effect(ActionEnvironment, ActionEnvironment.makeTest(overrides, payload)).pipe(Layer.provide(FileSystem.layerNoop({})));
|
|
285
318
|
};
|
|
286
319
|
|
|
287
320
|
//#endregion
|
package/ActionInput.js
CHANGED
|
@@ -115,6 +115,43 @@ var ActionInput = class ActionInput {
|
|
|
115
115
|
* Every accessor on this class shares the rule, so an **optional** input
|
|
116
116
|
* needs `Config.withDefault` (or `Config.option`) at the call site or the
|
|
117
117
|
* read fails outright.
|
|
118
|
+
*
|
|
119
|
+
* **An input whose documented meaning is "set it empty to disable this"
|
|
120
|
+
* cannot be read with `Config.withDefault`.** Empty is classified as missing
|
|
121
|
+
* *before* the default is consulted, so the default substitutes and the
|
|
122
|
+
* documented behavior can never happen. A live action shipped exactly that
|
|
123
|
+
* for its whole lifetime: `action.yml` promised `release-prefix: ""` would
|
|
124
|
+
* disable retry, and it never could. `Config.option` is the shape that
|
|
125
|
+
* distinguishes the states:
|
|
126
|
+
*
|
|
127
|
+
* ```ts
|
|
128
|
+
* Config.option(ActionInput.string("release-prefix"))
|
|
129
|
+
* // WITH a non-empty default in action.yml:
|
|
130
|
+
* // unsupplied -> Some("release:") (the manifest default, via the runner)
|
|
131
|
+
* // explicit "" -> None
|
|
132
|
+
* // explicit value -> Some(value)
|
|
133
|
+
* // WITHOUT a default in action.yml:
|
|
134
|
+
* // unsupplied -> None (the runner still writes the variable, empty)
|
|
135
|
+
* ```
|
|
136
|
+
*
|
|
137
|
+
* Both rows matter: "the runner writes `""` for an unsupplied input" is the
|
|
138
|
+
* no-default case, and it is the manifest default — when there is one — that
|
|
139
|
+
* makes the unsupplied and explicitly-empty cases distinguishable at all.
|
|
140
|
+
*
|
|
141
|
+
* Two consequences follow from how the runner actually behaves, both
|
|
142
|
+
* confirmed against `actions/runner` and on a live runner:
|
|
143
|
+
*
|
|
144
|
+
* - **The runner publishes a variable for every declared input**, so *absent*
|
|
145
|
+
* is a state a runner never produces. The `Some` branch above depends on
|
|
146
|
+
* `action.yml` declaring a **non-empty default** — a fact in a different
|
|
147
|
+
* file, invisible at this call site. Deleting that default does not restore
|
|
148
|
+
* a fallback; an input declaring no default arrives set-and-empty when
|
|
149
|
+
* unsupplied, silently flipping every unsupplied run to `None`. Pin the
|
|
150
|
+
* default with a test if a read depends on it.
|
|
151
|
+
* - **A bare test environment inverts the unsupplied case.** Omitting the key
|
|
152
|
+
* in a test means genuinely absent, which no runner emits, so a test named
|
|
153
|
+
* "unsupplied" asserts the opposite of production and passes. Arrange the
|
|
154
|
+
* manifest default explicitly instead of omitting the key.
|
|
118
155
|
*/
|
|
119
156
|
static string(name) {
|
|
120
157
|
return Config.string(inputVariable(name));
|
|
@@ -299,6 +336,22 @@ var ActionInput = class ActionInput {
|
|
|
299
336
|
*
|
|
300
337
|
* Taking the environment as an argument rather than reading `process.env`
|
|
301
338
|
* is what makes inputs testable without mutating the test process.
|
|
339
|
+
*
|
|
340
|
+
* **A bare `ConfigProvider.fromEnv` cannot serve input-name keys.** Composed
|
|
341
|
+
* beneath this provider or {@link ActionInput.providerOver}, it uppercases
|
|
342
|
+
* the config path, so `{ "target-branch": "trunk" }` is looked up as
|
|
343
|
+
* `TARGET-BRANCH`, never matches, and the read falls through to whatever
|
|
344
|
+
* default the call site supplies. It **fails green**: nothing errors, the
|
|
345
|
+
* suite passes against the default, and the test's name claims it proved the
|
|
346
|
+
* input was read. A consuming action's integration harness did exactly this
|
|
347
|
+
* and stayed green across several commits while a supplied `target-branch`
|
|
348
|
+
* silently did nothing.
|
|
349
|
+
*
|
|
350
|
+
* Runner-variable keys (`INPUT_TARGET-BRANCH`, `PLAIN_VAR`) are unaffected —
|
|
351
|
+
* they are already in the spelling `fromEnv` produces, and they resolve. The
|
|
352
|
+
* hazard is specific to keying a `fromEnv` record by **input name**. Passing
|
|
353
|
+
* the record to *this* function instead removes the distinction: it
|
|
354
|
+
* dual-accepts both spellings, so neither can miss.
|
|
302
355
|
*/
|
|
303
356
|
static provider(env = process.env) {
|
|
304
357
|
/** Unset, and the `""` the runner writes for an unsupplied input, are both absent. */
|
|
@@ -362,6 +415,22 @@ var ActionInput = class ActionInput {
|
|
|
362
415
|
* `ActionInput` accessors themselves are unaffected — they read
|
|
363
416
|
* `INPUT_<MANGLED>` names, whose re-mangled form (`INPUT_INPUT_…`) never
|
|
364
417
|
* matches, so they fall through to the ambient lookup they always used.
|
|
418
|
+
*
|
|
419
|
+
* **A bare `ConfigProvider.fromEnv({ env })` here serves only runner-variable
|
|
420
|
+
* keys.** `INPUT_TARGET-BRANCH` and `PLAIN_VAR` resolve; an input-name key
|
|
421
|
+
* like `target-branch` is uppercased to `TARGET-BRANCH`, never matches, and
|
|
422
|
+
* fails green — see {@link ActionInput.provider} for the mechanism. Use
|
|
423
|
+
* `ActionInput.provider(env)`, which dual-accepts both spellings.
|
|
424
|
+
*
|
|
425
|
+
* **The single-segment retry above makes the obvious test
|
|
426
|
+
* non-discriminating**, which is worth knowing before writing one. Because a
|
|
427
|
+
* bare `Config.string("release-branch")` resolves through the same `INPUT_`
|
|
428
|
+
* derivation as `ActionInput.string("release-branch")`, a test meant to prove
|
|
429
|
+
* the accessor reads the mangled key passes just as well with the accessor
|
|
430
|
+
* swapped for a bare `Config`. Only a bare `fromEnv` underneath tells them
|
|
431
|
+
* apart — and that is the one shape you should never reach for. Test the
|
|
432
|
+
* derivation through `ActionInput.variable` instead of trying to catch it by
|
|
433
|
+
* substitution.
|
|
365
434
|
*/
|
|
366
435
|
static providerOver(ambient) {
|
|
367
436
|
return ConfigProvider.orElse(ConfigProvider.make((path) => {
|
|
@@ -380,9 +449,16 @@ var ActionInput = class ActionInput {
|
|
|
380
449
|
* `ConfigProvider` layer still wins by normal layer precedence.
|
|
381
450
|
*
|
|
382
451
|
* The ambient half is whatever provider is **explicitly installed** when the
|
|
383
|
-
* layer builds — which is how a test injects a deterministic environment by
|
|
384
|
-
* providing `ConfigProvider.layer(
|
|
385
|
-
*
|
|
452
|
+
* layer builds — which is how a test injects a deterministic environment, by
|
|
453
|
+
* providing `ConfigProvider.layer(ActionInput.provider(env))` beneath the
|
|
454
|
+
* runtime. Prefer `ActionInput.provider` over a bare
|
|
455
|
+
* `ConfigProvider.fromEnv({ env })`: `fromEnv` serves runner-variable keys
|
|
456
|
+
* fine, but uppercases the config path, so a record keyed by **input name**
|
|
457
|
+
* never matches and every such read silently takes its default while the
|
|
458
|
+
* suite stays green ({@link ActionInput.provider} has the full account).
|
|
459
|
+
* `ActionInput.provider` dual-accepts both spellings, so neither can miss.
|
|
460
|
+
*
|
|
461
|
+
* When none is installed, a **fresh** `ConfigProvider.fromEnv()`
|
|
386
462
|
* is built rather than reading the reference's default: v4 caches that
|
|
387
463
|
* default once per process, and a snapshot taken before the runner's
|
|
388
464
|
* variables are visible would resurrect exactly the missed-input class this
|
package/ActionLogger.js
CHANGED
|
@@ -109,25 +109,27 @@ const flush = (state) => Effect.suspend(() => {
|
|
|
109
109
|
const make = Effect.gen(function* () {
|
|
110
110
|
const env = yield* ActionEnvironment;
|
|
111
111
|
const flushActive = Effect.flatMap(ActiveBuffer, (state) => state === null ? Effect.void : flush(state));
|
|
112
|
+
const withBuffer = (label, effect, options) => Effect.gen(function* () {
|
|
113
|
+
const minimum = yield* References.MinimumLogLevel;
|
|
114
|
+
if ((yield* env.isDebug) || LogLevel.isLessThanOrEqualTo(minimum, "Debug")) return yield* effect;
|
|
115
|
+
const state = {
|
|
116
|
+
label,
|
|
117
|
+
entries: []
|
|
118
|
+
};
|
|
119
|
+
const buffering = Logger.make((options) => {
|
|
120
|
+
if (LogLevel.isGreaterThanOrEqualTo(options.logLevel, "Warn")) {
|
|
121
|
+
const annotations = options.fiber.getRef(References.CurrentLogAnnotations);
|
|
122
|
+
options.fiber.getRef(Console.Console).log(renderEntry(options.logLevel, options.message, annotations));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
state.entries.push(formatMessage(options.message));
|
|
126
|
+
});
|
|
127
|
+
return yield* effect.pipe(Effect.provideService(References.MinimumLogLevel, "All"), Effect.provideService(Logger.CurrentLoggers, /* @__PURE__ */ new Set([buffering])), Effect.provideService(ActiveBuffer, state), Effect.onExit((exit) => Exit.isSuccess(exit) && options?.onSuccess === "discard" ? discard(state) : flush(state)));
|
|
128
|
+
});
|
|
112
129
|
return {
|
|
113
130
|
group: (name, effect) => Effect.acquireUseRelease(Console.log(WorkflowCommand.group(name)), () => Effect.tapCause(effect, () => flushActive), () => Console.log(WorkflowCommand.endGroup())),
|
|
114
|
-
withBuffer
|
|
115
|
-
|
|
116
|
-
if ((yield* env.isDebug) || LogLevel.isLessThanOrEqualTo(minimum, "Debug")) return yield* effect;
|
|
117
|
-
const state = {
|
|
118
|
-
label,
|
|
119
|
-
entries: []
|
|
120
|
-
};
|
|
121
|
-
const buffering = Logger.make((options) => {
|
|
122
|
-
if (LogLevel.isGreaterThanOrEqualTo(options.logLevel, "Warn")) {
|
|
123
|
-
const annotations = options.fiber.getRef(References.CurrentLogAnnotations);
|
|
124
|
-
options.fiber.getRef(Console.Console).log(renderEntry(options.logLevel, options.message, annotations));
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
state.entries.push(formatMessage(options.message));
|
|
128
|
-
});
|
|
129
|
-
return yield* effect.pipe(Effect.provideService(References.MinimumLogLevel, "All"), Effect.provideService(Logger.CurrentLoggers, /* @__PURE__ */ new Set([buffering])), Effect.provideService(ActiveBuffer, state), Effect.onExit((exit) => Exit.isSuccess(exit) && options?.onSuccess === "discard" ? discard(state) : flush(state)));
|
|
130
|
-
}),
|
|
131
|
+
withBuffer,
|
|
132
|
+
withStep: (name, effect, options) => withBuffer(name, Effect.tapCause(effect, () => Console.log(`❌ ${name}`)), { onSuccess: "discard" }).pipe(Effect.tap(() => Effect.logInfo(options?.summary ?? `✅ ${name}`))),
|
|
131
133
|
notice: (message, properties = {}) => Console.log(WorkflowCommand.notice(message, properties)),
|
|
132
134
|
annotated: (properties, effect) => Effect.annotateLogs(effect, annotationRecord(properties))
|
|
133
135
|
};
|
|
@@ -187,6 +189,7 @@ var ActionLogger = class ActionLogger extends Context.Service()("@effected/githu
|
|
|
187
189
|
static makeTest = (overrides = {}) => ({
|
|
188
190
|
group: (_name, effect) => effect,
|
|
189
191
|
withBuffer: (_label, effect) => effect,
|
|
192
|
+
withStep: (_name, effect) => effect,
|
|
190
193
|
notice: () => Effect.void,
|
|
191
194
|
annotated: (_properties, effect) => effect,
|
|
192
195
|
...overrides
|
package/GitHubMarkdown.js
CHANGED
|
@@ -13,11 +13,25 @@ const passthrough = (value) => Html.make({ value });
|
|
|
13
13
|
*
|
|
14
14
|
* @remarks
|
|
15
15
|
* `Markdown.stringifyResult` is total over every tree this module can
|
|
16
|
-
* construct — its only failure is the hardening depth cap
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
16
|
+
* construct — its only failure is the hardening nesting-depth guard (cap
|
|
17
|
+
* 256) — so the impossible arm is a defect, **not** a silent fallback to
|
|
18
|
+
* unescaped string joining. The predecessor's fallback arm is the live
|
|
19
|
+
* table-corruption defect this module exists to delete.
|
|
20
|
+
*
|
|
21
|
+
* What makes the guard unreachable is not that these trees are small; it is
|
|
22
|
+
* that **their depth does not depend on input**. A fragment handed to a
|
|
23
|
+
* member always becomes a **leaf**, never a subtree: the composition members
|
|
24
|
+
* (`table`, `list`, `link`) wrap it in a single `passthrough` node, and the
|
|
25
|
+
* content members (`code`, `codeBlock`) carry it as a node's `value` string.
|
|
26
|
+
* Either way nothing re-parses a fragment into child nodes, so a composition
|
|
27
|
+
* nests strings rather than nodes and the deepest tree the writer can build
|
|
28
|
+
* is a table's five levels, whatever it is handed. Pinned by
|
|
29
|
+
* `__test__/GitHubMarkdown.test.ts`, which nests the writer's own output a
|
|
30
|
+
* thousand deep — four times the cap — and still renders.
|
|
31
|
+
*
|
|
32
|
+
* Keep that property when adding a member: a member that accepts a *node*, or
|
|
33
|
+
* that re-parses a fragment into one, makes depth input-dependent and this
|
|
34
|
+
* whole argument — and the no-wrapping guidance on the class — collapses.
|
|
21
35
|
*/
|
|
22
36
|
const render = (root) => Result.getOrThrow(Markdown.stringifyResult(root)).trimEnd();
|
|
23
37
|
const block = (node) => render(Root.make({ children: [node] }));
|
|
@@ -41,6 +55,22 @@ const block = (node) => render(Root.make({ children: [node] }));
|
|
|
41
55
|
* structural test pins that, on the same terms as the Azure confinement — so
|
|
42
56
|
* an action that never renders markdown never links the engine.
|
|
43
57
|
*
|
|
58
|
+
* **Renders cannot fail, so do not `Effect.try`-wrap them.** The serializer's
|
|
59
|
+
* single failure mode is a nesting-depth guard, and no input can reach it: a
|
|
60
|
+
* fragment handed to a member becomes a leaf — a passthrough node or a node's
|
|
61
|
+
* `value` string — never a parsed subtree, so a composition nests strings
|
|
62
|
+
* rather than nodes and the tree's depth is fixed by the construct, not by
|
|
63
|
+
* what it is handed. A test nests the writer's own output a thousand deep and
|
|
64
|
+
* still renders. Wrapping is boilerplate that also widens the catch to
|
|
65
|
+
* anything else thrown inside the builder — a reporting surface degrading for
|
|
66
|
+
* reasons it did not intend.
|
|
67
|
+
*
|
|
68
|
+
* Two exceptions, both in {@link GitHubMarkdown.tableFor} and neither in the
|
|
69
|
+
* serializer. Cells encode through the row schema, so a value smuggled past
|
|
70
|
+
* the types throws its `SchemaError`; and a column's own `format` function is
|
|
71
|
+
* called directly, so its totality is the caller's to guarantee. Well-typed
|
|
72
|
+
* rows through the default codec are total.
|
|
73
|
+
*
|
|
44
74
|
* @example
|
|
45
75
|
* ```ts
|
|
46
76
|
* import { GitHubMarkdown } from "@effected/github-actions";
|
|
@@ -95,6 +125,19 @@ var GitHubMarkdown = class GitHubMarkdown {
|
|
|
95
125
|
* past the types throws the codec's `SchemaError` as a defect — same
|
|
96
126
|
* posture as the serializer arm above, **not** a silent fallback.
|
|
97
127
|
*
|
|
128
|
+
* This is the **only** member where a throw is reachable at runtime, and
|
|
129
|
+
* neither route is the serializer — see the no-wrapping remark on
|
|
130
|
+
* {@link GitHubMarkdown}. There are two:
|
|
131
|
+
*
|
|
132
|
+
* - the **codec**, for a value smuggled past the types. A caller holding
|
|
133
|
+
* only well-typed rows (the normal case, and what the types express)
|
|
134
|
+
* needs no wrapping; a caller feeding rows decoded from somewhere
|
|
135
|
+
* untyped should decode through the schema rather than guard the render.
|
|
136
|
+
* - a column's own **`format`** function, which is called directly and
|
|
137
|
+
* bypasses the codec entirely. Nothing here can make user code total, so
|
|
138
|
+
* a `format` that can throw makes its render able to throw — keep
|
|
139
|
+
* formatters total, or guard at the formatter rather than the render.
|
|
140
|
+
*
|
|
98
141
|
* Only string-keyed fields become columns; symbol keys are not
|
|
99
142
|
* enumerable table columns.
|
|
100
143
|
*
|
package/README.md
CHANGED
|
@@ -202,7 +202,16 @@ The debounce is trailing with a max-wait, so a burst of reports coalesces into o
|
|
|
202
202
|
|
|
203
203
|
## Testing
|
|
204
204
|
|
|
205
|
-
Every service ships `makeTest(overrides?)` and `layerTest(overrides?)`, with unstubbed members dying loudly and naming themselves — three recorded exceptions state why they default instead: `ActionEnvironment.makeTest` seeds the twelve `GITHUB_*`/`RUNNER_*` variables, `ActionLogger.makeTest` defaults to silent, and `DryRun.makeTest` defaults to rehearsing, the safe direction:
|
|
205
|
+
Every service ships `makeTest(overrides?)` and `layerTest(overrides?)`, with unstubbed members dying loudly and naming themselves — three recorded exceptions state why they default instead: `ActionEnvironment.makeTest` seeds the twelve `GITHUB_*`/`RUNNER_*` variables, `ActionLogger.makeTest` defaults to silent, and `DryRun.makeTest` defaults to rehearsing, the safe direction. `ActionEnvironment.makeTest`/`layerTest` also take the webhook event payload as an optional second argument, serving it directly instead of routing it through a `GITHUB_EVENT_PATH` filesystem read the stubbed filesystem could never satisfy:
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
import { ActionEnvironment } from "@effected/github-actions";
|
|
209
|
+
|
|
210
|
+
const layer = ActionEnvironment.layerTest(
|
|
211
|
+
{ GITHUB_EVENT_NAME: "pull_request" },
|
|
212
|
+
{ pull_request: { number: 42 } },
|
|
213
|
+
);
|
|
214
|
+
```
|
|
206
215
|
|
|
207
216
|
```ts
|
|
208
217
|
import { ActionOutputs } from "@effected/github-actions";
|
|
@@ -221,7 +230,7 @@ const TestOutputs = ActionOutputs.layerTest({
|
|
|
221
230
|
- `ActionInput` — typed input accessors (`string`, `boolean`, `integer`, `redacted`, `lines`, `list`, `pairs`, `schema`) sharing one absence rule, plus the `INPUT_`-aware `ConfigProvider` `Action.run` installs by default.
|
|
222
231
|
- `ActionOutputs` — step outputs, JSON outputs, the job summary, exported variables, `PATH` additions, failure annotations and log masking.
|
|
223
232
|
- `ActionState` / `ActionEnvironment` — persisted `pre`/`main`/`post` state, and the fiber-local `GITHUB_*`/`RUNNER_*` context read once from `process.env`.
|
|
224
|
-
- `ActionLogger` — collapsible groups, buffered step transcripts (`withBuffer({ onSuccess: "discard" })`
|
|
233
|
+
- `ActionLogger` — collapsible groups, buffered step transcripts (`withBuffer({ onSuccess: "discard" })` drops a green step's transcript entirely, while a failure, defect or interruption still flushes it), `withStep(name, effect, options?)` for the same buffering plus one summary line on success and a `❌ <name>` header above the transcript on failure, `::notice::` annotations, and the `Logger` that renders every `Effect.log*` as a workflow command.
|
|
225
234
|
- `DryRun` — the rehearsal guard every mutation goes through, driven by the `dry-run` input by default.
|
|
226
235
|
- `Secret` — the one declassification seam between a `Redacted` value and a plain string, masking before it returns plaintext.
|
|
227
236
|
- `GitHubToken` — the App-token lifecycle shaped like the `pre`/`main`/`post` workflow: mint, verify scopes, persist, read, revoke.
|
package/index.d.ts
CHANGED
|
@@ -183,10 +183,40 @@ declare class ActionEnvironment extends ActionEnvironment_base {
|
|
|
183
183
|
* context variables have one obviously-correct shape, and requiring each
|
|
184
184
|
* suite to restate them produced a byte-identical block duplicated six times
|
|
185
185
|
* across the consumers. Overrides merge on top.
|
|
186
|
+
*
|
|
187
|
+
* The optional second argument serves {@link ActionEnvironmentShape.payload}
|
|
188
|
+
* **directly**, replacing the `GITHUB_EVENT_PATH` read rather than seeding a
|
|
189
|
+
* path and a file behind it. That is the only route to a payload through the
|
|
190
|
+
* standard double: `TEST_DEFAULTS` deliberately omits `GITHUB_EVENT_PATH`, so
|
|
191
|
+
* an unserved payload fails typed and names the variable — the die-on-
|
|
192
|
+
* unstubbed posture the rest of this package keeps.
|
|
193
|
+
*
|
|
194
|
+
* `undefined` means *not served*, so a suite cannot arrange `undefined` as
|
|
195
|
+
* the payload itself. A webhook payload is always a JSON object; the case
|
|
196
|
+
* does not arise, and reserving the sentinel keeps the argument optional.
|
|
186
197
|
*/
|
|
187
|
-
static readonly makeTest: (overrides?: Readonly<Record<string, string
|
|
188
|
-
/**
|
|
189
|
-
|
|
198
|
+
static readonly makeTest: (overrides?: Readonly<Record<string, string>>, payload?: unknown) => Effect.Effect<ActionEnvironmentShape, never, FileSystem.FileSystem>;
|
|
199
|
+
/**
|
|
200
|
+
* {@link ActionEnvironment.makeTest} behind a layer, with `FileSystem` stubbed
|
|
201
|
+
* out.
|
|
202
|
+
*
|
|
203
|
+
* @remarks
|
|
204
|
+
* The stub is why the payload has to be served here rather than through the
|
|
205
|
+
* filesystem: `make` captures the filesystem at construction, so seeding
|
|
206
|
+
* `GITHUB_EVENT_PATH` through `overrides` would send the read to a noop
|
|
207
|
+
* filesystem and fail. Pass the payload as the second argument instead.
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* ```ts
|
|
211
|
+
* import { ActionEnvironment } from "@effected/github-actions";
|
|
212
|
+
*
|
|
213
|
+
* const layer = ActionEnvironment.layerTest(
|
|
214
|
+
* { GITHUB_EVENT_NAME: "pull_request" },
|
|
215
|
+
* { pull_request: { number: 42 } },
|
|
216
|
+
* );
|
|
217
|
+
* ```
|
|
218
|
+
*/
|
|
219
|
+
static readonly layerTest: (overrides?: Readonly<Record<string, string>>, payload?: unknown) => Layer.Layer<ActionEnvironment>;
|
|
190
220
|
}
|
|
191
221
|
//#endregion
|
|
192
222
|
//#region src/WorkflowCommand.d.ts
|
|
@@ -286,6 +316,21 @@ interface WithBufferOptions {
|
|
|
286
316
|
*/
|
|
287
317
|
readonly onSuccess?: "flush" | "discard";
|
|
288
318
|
}
|
|
319
|
+
/**
|
|
320
|
+
* Options for {@link ActionLoggerShape.withStep}.
|
|
321
|
+
*
|
|
322
|
+
* @public
|
|
323
|
+
*/
|
|
324
|
+
interface WithStepOptions {
|
|
325
|
+
/**
|
|
326
|
+
* The info line emitted when the step **succeeds**.
|
|
327
|
+
*
|
|
328
|
+
* @remarks
|
|
329
|
+
* Defaults to `✅ <name>`. This is the line the buffered transcript is traded
|
|
330
|
+
* for: a green step reports that it happened and nothing else.
|
|
331
|
+
*/
|
|
332
|
+
readonly summary?: string;
|
|
333
|
+
}
|
|
289
334
|
/**
|
|
290
335
|
* The {@link ActionLogger} service shape.
|
|
291
336
|
*
|
|
@@ -313,6 +358,40 @@ interface ActionLoggerShape {
|
|
|
313
358
|
* what a green step did.
|
|
314
359
|
*/
|
|
315
360
|
readonly withBuffer: <A, E, R>(label: string, effect: Effect.Effect<A, E, R>, options?: WithBufferOptions) => Effect.Effect<A, E, R>;
|
|
361
|
+
/**
|
|
362
|
+
* Run a named step: quiet when it succeeds, its full transcript when it does
|
|
363
|
+
* not.
|
|
364
|
+
*
|
|
365
|
+
* @remarks
|
|
366
|
+
* The composition a release log actually wants, and the one
|
|
367
|
+
* {@link ActionLoggerShape.withBuffer} alone does not reach. Buffering with
|
|
368
|
+
* `{ onSuccess: "discard" }` gets a green step to *zero* lines; this trades
|
|
369
|
+
* that transcript for exactly one:
|
|
370
|
+
*
|
|
371
|
+
* 1. verbose output is captured rather than printed live,
|
|
372
|
+
* 2. on success the transcript is dropped and a single info line — `summary`,
|
|
373
|
+
* default `✅ <name>` ({@link WithStepOptions}) — goes out in its place,
|
|
374
|
+
* 3. on failure a `❌ <name>` header lands **first**, then the transcript
|
|
375
|
+
* spills beneath it.
|
|
376
|
+
*
|
|
377
|
+
* The ordering in (3) is a property of the **buffered** path. With step
|
|
378
|
+
* debugging on, nothing is buffered — verbose output has already gone out
|
|
379
|
+
* live by the time the failure fires — so the header lands *after* the
|
|
380
|
+
* transcript it would otherwise introduce. That is the honest trade of
|
|
381
|
+
* asking for live output, not a defect, and both orderings are pinned by
|
|
382
|
+
* test.
|
|
383
|
+
*
|
|
384
|
+
* The header and the summary are the whole difference from `withBuffer`, and
|
|
385
|
+
* they are why this is a member rather than a documented recipe: the recipe
|
|
386
|
+
* was independently derived wrong three times by careful readers during one
|
|
387
|
+
* port, because `group` + `withBuffer` looks like complete parity until you
|
|
388
|
+
* notice nothing emits the success line.
|
|
389
|
+
*
|
|
390
|
+
* The summary survives step debugging. Buffering is skipped when the runner
|
|
391
|
+
* asks for verbose output, but the line naming what succeeded is still the
|
|
392
|
+
* cheapest thing in the log.
|
|
393
|
+
*/
|
|
394
|
+
readonly withStep: <A, E, R>(name: string, effect: Effect.Effect<A, E, R>, options?: WithStepOptions) => Effect.Effect<A, E, R>;
|
|
316
395
|
/**
|
|
317
396
|
* Emit a `::notice::` annotation.
|
|
318
397
|
*
|
|
@@ -1273,6 +1352,43 @@ declare class ActionInput {
|
|
|
1273
1352
|
* Every accessor on this class shares the rule, so an **optional** input
|
|
1274
1353
|
* needs `Config.withDefault` (or `Config.option`) at the call site or the
|
|
1275
1354
|
* read fails outright.
|
|
1355
|
+
*
|
|
1356
|
+
* **An input whose documented meaning is "set it empty to disable this"
|
|
1357
|
+
* cannot be read with `Config.withDefault`.** Empty is classified as missing
|
|
1358
|
+
* *before* the default is consulted, so the default substitutes and the
|
|
1359
|
+
* documented behavior can never happen. A live action shipped exactly that
|
|
1360
|
+
* for its whole lifetime: `action.yml` promised `release-prefix: ""` would
|
|
1361
|
+
* disable retry, and it never could. `Config.option` is the shape that
|
|
1362
|
+
* distinguishes the states:
|
|
1363
|
+
*
|
|
1364
|
+
* ```ts
|
|
1365
|
+
* Config.option(ActionInput.string("release-prefix"))
|
|
1366
|
+
* // WITH a non-empty default in action.yml:
|
|
1367
|
+
* // unsupplied -> Some("release:") (the manifest default, via the runner)
|
|
1368
|
+
* // explicit "" -> None
|
|
1369
|
+
* // explicit value -> Some(value)
|
|
1370
|
+
* // WITHOUT a default in action.yml:
|
|
1371
|
+
* // unsupplied -> None (the runner still writes the variable, empty)
|
|
1372
|
+
* ```
|
|
1373
|
+
*
|
|
1374
|
+
* Both rows matter: "the runner writes `""` for an unsupplied input" is the
|
|
1375
|
+
* no-default case, and it is the manifest default — when there is one — that
|
|
1376
|
+
* makes the unsupplied and explicitly-empty cases distinguishable at all.
|
|
1377
|
+
*
|
|
1378
|
+
* Two consequences follow from how the runner actually behaves, both
|
|
1379
|
+
* confirmed against `actions/runner` and on a live runner:
|
|
1380
|
+
*
|
|
1381
|
+
* - **The runner publishes a variable for every declared input**, so *absent*
|
|
1382
|
+
* is a state a runner never produces. The `Some` branch above depends on
|
|
1383
|
+
* `action.yml` declaring a **non-empty default** — a fact in a different
|
|
1384
|
+
* file, invisible at this call site. Deleting that default does not restore
|
|
1385
|
+
* a fallback; an input declaring no default arrives set-and-empty when
|
|
1386
|
+
* unsupplied, silently flipping every unsupplied run to `None`. Pin the
|
|
1387
|
+
* default with a test if a read depends on it.
|
|
1388
|
+
* - **A bare test environment inverts the unsupplied case.** Omitting the key
|
|
1389
|
+
* in a test means genuinely absent, which no runner emits, so a test named
|
|
1390
|
+
* "unsupplied" asserts the opposite of production and passes. Arrange the
|
|
1391
|
+
* manifest default explicitly instead of omitting the key.
|
|
1276
1392
|
*/
|
|
1277
1393
|
static string(name: string): Config.Config<string>;
|
|
1278
1394
|
/**
|
|
@@ -1406,6 +1522,22 @@ declare class ActionInput {
|
|
|
1406
1522
|
*
|
|
1407
1523
|
* Taking the environment as an argument rather than reading `process.env`
|
|
1408
1524
|
* is what makes inputs testable without mutating the test process.
|
|
1525
|
+
*
|
|
1526
|
+
* **A bare `ConfigProvider.fromEnv` cannot serve input-name keys.** Composed
|
|
1527
|
+
* beneath this provider or {@link ActionInput.providerOver}, it uppercases
|
|
1528
|
+
* the config path, so `{ "target-branch": "trunk" }` is looked up as
|
|
1529
|
+
* `TARGET-BRANCH`, never matches, and the read falls through to whatever
|
|
1530
|
+
* default the call site supplies. It **fails green**: nothing errors, the
|
|
1531
|
+
* suite passes against the default, and the test's name claims it proved the
|
|
1532
|
+
* input was read. A consuming action's integration harness did exactly this
|
|
1533
|
+
* and stayed green across several commits while a supplied `target-branch`
|
|
1534
|
+
* silently did nothing.
|
|
1535
|
+
*
|
|
1536
|
+
* Runner-variable keys (`INPUT_TARGET-BRANCH`, `PLAIN_VAR`) are unaffected —
|
|
1537
|
+
* they are already in the spelling `fromEnv` produces, and they resolve. The
|
|
1538
|
+
* hazard is specific to keying a `fromEnv` record by **input name**. Passing
|
|
1539
|
+
* the record to *this* function instead removes the distinction: it
|
|
1540
|
+
* dual-accepts both spellings, so neither can miss.
|
|
1409
1541
|
*/
|
|
1410
1542
|
static provider(env?: Readonly<Record<string, string | undefined>>): ConfigProvider.ConfigProvider;
|
|
1411
1543
|
/**
|
|
@@ -1447,6 +1579,22 @@ declare class ActionInput {
|
|
|
1447
1579
|
* `ActionInput` accessors themselves are unaffected — they read
|
|
1448
1580
|
* `INPUT_<MANGLED>` names, whose re-mangled form (`INPUT_INPUT_…`) never
|
|
1449
1581
|
* matches, so they fall through to the ambient lookup they always used.
|
|
1582
|
+
*
|
|
1583
|
+
* **A bare `ConfigProvider.fromEnv({ env })` here serves only runner-variable
|
|
1584
|
+
* keys.** `INPUT_TARGET-BRANCH` and `PLAIN_VAR` resolve; an input-name key
|
|
1585
|
+
* like `target-branch` is uppercased to `TARGET-BRANCH`, never matches, and
|
|
1586
|
+
* fails green — see {@link ActionInput.provider} for the mechanism. Use
|
|
1587
|
+
* `ActionInput.provider(env)`, which dual-accepts both spellings.
|
|
1588
|
+
*
|
|
1589
|
+
* **The single-segment retry above makes the obvious test
|
|
1590
|
+
* non-discriminating**, which is worth knowing before writing one. Because a
|
|
1591
|
+
* bare `Config.string("release-branch")` resolves through the same `INPUT_`
|
|
1592
|
+
* derivation as `ActionInput.string("release-branch")`, a test meant to prove
|
|
1593
|
+
* the accessor reads the mangled key passes just as well with the accessor
|
|
1594
|
+
* swapped for a bare `Config`. Only a bare `fromEnv` underneath tells them
|
|
1595
|
+
* apart — and that is the one shape you should never reach for. Test the
|
|
1596
|
+
* derivation through `ActionInput.variable` instead of trying to catch it by
|
|
1597
|
+
* substitution.
|
|
1450
1598
|
*/
|
|
1451
1599
|
static providerOver(ambient: ConfigProvider.ConfigProvider): ConfigProvider.ConfigProvider;
|
|
1452
1600
|
/**
|
|
@@ -1460,9 +1608,16 @@ declare class ActionInput {
|
|
|
1460
1608
|
* `ConfigProvider` layer still wins by normal layer precedence.
|
|
1461
1609
|
*
|
|
1462
1610
|
* The ambient half is whatever provider is **explicitly installed** when the
|
|
1463
|
-
* layer builds — which is how a test injects a deterministic environment by
|
|
1464
|
-
* providing `ConfigProvider.layer(
|
|
1465
|
-
*
|
|
1611
|
+
* layer builds — which is how a test injects a deterministic environment, by
|
|
1612
|
+
* providing `ConfigProvider.layer(ActionInput.provider(env))` beneath the
|
|
1613
|
+
* runtime. Prefer `ActionInput.provider` over a bare
|
|
1614
|
+
* `ConfigProvider.fromEnv({ env })`: `fromEnv` serves runner-variable keys
|
|
1615
|
+
* fine, but uppercases the config path, so a record keyed by **input name**
|
|
1616
|
+
* never matches and every such read silently takes its default while the
|
|
1617
|
+
* suite stays green ({@link ActionInput.provider} has the full account).
|
|
1618
|
+
* `ActionInput.provider` dual-accepts both spellings, so neither can miss.
|
|
1619
|
+
*
|
|
1620
|
+
* When none is installed, a **fresh** `ConfigProvider.fromEnv()`
|
|
1466
1621
|
* is built rather than reading the reference's default: v4 caches that
|
|
1467
1622
|
* default once per process, and a snapshot taken before the runner's
|
|
1468
1623
|
* variables are visible would resurrect exactly the missed-input class this
|
|
@@ -3023,6 +3178,22 @@ interface GitHubSchemaTable<Row> {
|
|
|
3023
3178
|
* structural test pins that, on the same terms as the Azure confinement — so
|
|
3024
3179
|
* an action that never renders markdown never links the engine.
|
|
3025
3180
|
*
|
|
3181
|
+
* **Renders cannot fail, so do not `Effect.try`-wrap them.** The serializer's
|
|
3182
|
+
* single failure mode is a nesting-depth guard, and no input can reach it: a
|
|
3183
|
+
* fragment handed to a member becomes a leaf — a passthrough node or a node's
|
|
3184
|
+
* `value` string — never a parsed subtree, so a composition nests strings
|
|
3185
|
+
* rather than nodes and the tree's depth is fixed by the construct, not by
|
|
3186
|
+
* what it is handed. A test nests the writer's own output a thousand deep and
|
|
3187
|
+
* still renders. Wrapping is boilerplate that also widens the catch to
|
|
3188
|
+
* anything else thrown inside the builder — a reporting surface degrading for
|
|
3189
|
+
* reasons it did not intend.
|
|
3190
|
+
*
|
|
3191
|
+
* Two exceptions, both in {@link GitHubMarkdown.tableFor} and neither in the
|
|
3192
|
+
* serializer. Cells encode through the row schema, so a value smuggled past
|
|
3193
|
+
* the types throws its `SchemaError`; and a column's own `format` function is
|
|
3194
|
+
* called directly, so its totality is the caller's to guarantee. Well-typed
|
|
3195
|
+
* rows through the default codec are total.
|
|
3196
|
+
*
|
|
3026
3197
|
* @example
|
|
3027
3198
|
* ```ts
|
|
3028
3199
|
* import { GitHubMarkdown } from "@effected/github-actions";
|
|
@@ -3073,6 +3244,19 @@ declare class GitHubMarkdown {
|
|
|
3073
3244
|
* past the types throws the codec's `SchemaError` as a defect — same
|
|
3074
3245
|
* posture as the serializer arm above, **not** a silent fallback.
|
|
3075
3246
|
*
|
|
3247
|
+
* This is the **only** member where a throw is reachable at runtime, and
|
|
3248
|
+
* neither route is the serializer — see the no-wrapping remark on
|
|
3249
|
+
* {@link GitHubMarkdown}. There are two:
|
|
3250
|
+
*
|
|
3251
|
+
* - the **codec**, for a value smuggled past the types. A caller holding
|
|
3252
|
+
* only well-typed rows (the normal case, and what the types express)
|
|
3253
|
+
* needs no wrapping; a caller feeding rows decoded from somewhere
|
|
3254
|
+
* untyped should decode through the schema rather than guard the render.
|
|
3255
|
+
* - a column's own **`format`** function, which is called directly and
|
|
3256
|
+
* bypasses the codec entirely. Nothing here can make user code total, so
|
|
3257
|
+
* a `format` that can throw makes its render able to throw — keep
|
|
3258
|
+
* formatters total, or guard at the formatter rather than the render.
|
|
3259
|
+
*
|
|
3076
3260
|
* Only string-keyed fields become columns; symbol keys are not
|
|
3077
3261
|
* enumerable table columns.
|
|
3078
3262
|
*
|
|
@@ -4058,5 +4242,5 @@ declare class Secret {
|
|
|
4058
4242
|
static readonly adopt: (name: string) => Config.Config<Redacted.Redacted<string>>;
|
|
4059
4243
|
}
|
|
4060
4244
|
//#endregion
|
|
4061
|
-
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, WorkflowCommand, describeCause, projectCheckState };
|
|
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 };
|
|
4062
4246
|
//# sourceMappingURL=index.d.ts.map
|
package/package.json
CHANGED