@effected/github-actions 0.2.0 → 0.4.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/index.d.ts CHANGED
@@ -4,6 +4,7 @@ import { HttpClient } from "effect/unstable/http";
4
4
  import { ChildProcessSpawner } from "effect/unstable/process";
5
5
  import { IdentityToken, SlsaProvenance } from "@effected/sbom";
6
6
  import { BotIdentity, GitHubApp, GitHubClient, InstallationToken, PermissionLevel, RetryPolicy } from "@effected/github";
7
+ import { PackageManagerPin } from "@effected/npm";
7
8
  //#region src/ActionEnvironment.d.ts
8
9
  declare const ActionEnvironmentError_base: Schema.Class<ActionEnvironmentError, Schema.TaggedStruct<"ActionEnvironmentError", {
9
10
  readonly reason: Schema.Literals<readonly ["missing", "malformed"]>;
@@ -35,6 +36,22 @@ declare const GitHubContext_base: Schema.Class<GitHubContext, Schema.Struct<{
35
36
  readonly ref: Schema.String;
36
37
  /** The short ref, e.g. `main`. */
37
38
  readonly refName: Schema.String;
39
+ /**
40
+ * The source branch of the pull request, when the event has one.
41
+ *
42
+ * @remarks
43
+ * `GITHUB_HEAD_REF` is only set for `pull_request` events — and on every
44
+ * other event the runner does not merely omit it, it may write the **empty
45
+ * string**, which a raw `process.env` read happily reports as present. That
46
+ * trap is encoded in the type: absent and `""` both decode to
47
+ * `Option.none()`, so a consumer cannot build a cache key segment out of an
48
+ * empty branch name by accident. For "the branch a human means", use
49
+ * {@link GitHubContext.branch}, which owns the fallback to `refName`.
50
+ *
51
+ * The encoded form is `string | null` rather than a serialized `Option`, so
52
+ * an encoded context stays plain JSON.
53
+ */
54
+ readonly headRef: Schema.OptionFromNullOr<Schema.String>;
38
55
  readonly sha: Schema.String;
39
56
  readonly workflow: Schema.String;
40
57
  readonly job: Schema.String;
@@ -52,7 +69,20 @@ declare const GitHubContext_base: Schema.Class<GitHubContext, Schema.Struct<{
52
69
  *
53
70
  * @public
54
71
  */
55
- declare class GitHubContext extends GitHubContext_base {}
72
+ declare class GitHubContext extends GitHubContext_base {
73
+ /**
74
+ * The branch a human means: `headRef` when present (a pull request, where
75
+ * `refName` is the useless `123/merge`), otherwise `refName`.
76
+ *
77
+ * @remarks
78
+ * This is the universal branch-scoped-cache-key fallback chain, written
79
+ * once. On a `push` or any other non-PR event `headRef` is `Option.none()`
80
+ * — including when the runner wrote it as the empty string — so the answer
81
+ * is the short ref. Note that on a tag push `refName` names the tag, not a
82
+ * branch; a consumer that must distinguish still has the full `ref`.
83
+ */
84
+ get branch(): string;
85
+ }
56
86
  declare const RunnerContext_base: Schema.Class<RunnerContext, Schema.Struct<{
57
87
  readonly os: Schema.String;
58
88
  readonly arch: Schema.String;
@@ -373,9 +403,11 @@ declare const ActionOutputError_base: Schema.Class<ActionOutputError, Schema.Tag
373
403
  * the code is running outside a GitHub runner. `writeFailed` — the file
374
404
  * exists but could not be appended to. `invalidName` — the name would
375
405
  * corrupt the file's block structure. `encodeFailed` — a value did not
376
- * satisfy its schema.
406
+ * satisfy its schema. `detached` — the member was called under
407
+ * {@link ActionOutputs.layerDetached}, where the channel it writes cannot
408
+ * reach the job that would read it.
377
409
  */
378
- readonly reason: Schema.Literals<readonly ["unavailable", "writeFailed", "invalidName", "encodeFailed"]>;
410
+ readonly reason: Schema.Literals<readonly ["unavailable", "writeFailed", "invalidName", "encodeFailed", "detached"]>;
379
411
  /** Which runner file was involved, by environment variable name. */
380
412
  readonly file: Schema.optionalKey<Schema.String>;
381
413
  /** The output or variable name, when one is involved. */
@@ -405,7 +437,17 @@ interface ActionOutputsShape {
405
437
  readonly summary: (content: string) => Effect.Effect<void, ActionOutputError>;
406
438
  /** Export an environment variable to **subsequent** steps. */
407
439
  readonly exportVariable: (name: string, value: string) => Effect.Effect<void, ActionOutputError>;
408
- /** Prepend a directory to `PATH` for subsequent steps. */
440
+ /**
441
+ * Prepend a directory to `PATH` for **subsequent** steps.
442
+ *
443
+ * @remarks
444
+ * This appends to the `GITHUB_PATH` runner file and does **nothing else** —
445
+ * unlike `@actions/core`, it does NOT mutate the live `process.env.PATH` of
446
+ * the current process. A same-process probe of a tool that was just added
447
+ * (`pnpm --version`, a shim, a binary) will not find it by name; probe by
448
+ * absolute path instead. The runner applies the addition when the next step
449
+ * starts, exactly as it does for {@link ActionOutputsShape.exportVariable}.
450
+ */
409
451
  readonly addPath: (path: string) => Effect.Effect<void, ActionOutputError>;
410
452
  /** Emit an error annotation. Does not itself set the exit code. */
411
453
  readonly setFailed: (message: string) => Effect.Effect<void>;
@@ -430,6 +472,45 @@ declare const ActionOutputs_base: Context.ServiceClass<ActionOutputs, "@effected
430
472
  */
431
473
  declare class ActionOutputs extends ActionOutputs_base {
432
474
  static readonly layer: Layer.Layer<ActionOutputs, never, ActionEnvironment | FileSystem.FileSystem>;
475
+ /**
476
+ * The outputs surface that is correct inside a **detached worker**.
477
+ *
478
+ * @remarks
479
+ * The masking model assumes the runner parses stdout. A detached worker's
480
+ * stdout is a **log file no runner parses**, so under the real layer
481
+ * `setSecret` emits `::add-mask::<plaintext>` into that file — a command
482
+ * that is simultaneously **inert** (nothing reads it, so nothing is masked)
483
+ * and a **secret leak** (the plaintext is now sitting verbatim in a log). A
484
+ * production action shipped exactly that for one round: an S3 secret and
485
+ * session token written into a worker's log by the masking call itself.
486
+ * This layer is the worker-side fix; the parent-side rule is that every
487
+ * secret a worker will hold is masked **before** the spawn, by
488
+ * `Secret.forChildEnv` under the real layer.
489
+ *
490
+ * The layer needs no environment and no filesystem — its `R` is `never` —
491
+ * so a worker composing it structurally *cannot* write a runner file.
492
+ * Per-member behavior:
493
+ *
494
+ * - **`setSecret` — silent no-op.** The value must not be written
495
+ * *anywhere*: there is no runner on this channel to hand it to, and any
496
+ * spelling of it in the log IS the leak. Masking is the parent's job,
497
+ * before the worker exists.
498
+ * - **`set`, `setJson`, `exportVariable`, `addPath`, `summary` — fail
499
+ * typed** (`reason: "detached"`, naming the file). Each writes a runner
500
+ * file that configures the parent job's *later steps* — `GITHUB_OUTPUT`,
501
+ * `GITHUB_ENV`, `GITHUB_PATH` — or is collected when the step completes
502
+ * (`GITHUB_STEP_SUMMARY`). A detached worker has no later steps, may
503
+ * outlive the job entirely, and even an inherited file path would be
504
+ * read at a moment unrelated to the write — so a worker calling one of
505
+ * these is a program error worth surfacing, not a write to degrade
506
+ * silently.
507
+ * - **`setFailed` — degrades to a plain log line** (`Console.error`,
508
+ * without the `::error::` syntax nothing here would parse). Unlike the
509
+ * file members it is log-like: "record that something failed" still
510
+ * means something in a worker's log, and its signature carries no error
511
+ * channel to fail through.
512
+ */
513
+ static readonly layerDetached: Layer.Layer<ActionOutputs>;
433
514
  /** A test double. Unstubbed members die rather than silently succeeding. */
434
515
  static readonly makeTest: (overrides?: Partial<ActionOutputsShape>) => ActionOutputsShape;
435
516
  /** {@link ActionOutputs.makeTest} behind `Layer.succeed`. */
@@ -441,10 +522,13 @@ declare const ActionStateError_base: Schema.Class<ActionStateError, Schema.Tagge
441
522
  /**
442
523
  * `missing` — no value was saved under this key in an earlier phase.
443
524
  * `malformed` — a value is there but is not JSON, or does not satisfy the
444
- * schema it was read with. `writeFailed` — the state file could not be
525
+ * schema it was read with. `notPlainJson` — caught at SAVE time: the
526
+ * schema's encoded form does not survive `JSON.stringify`/`JSON.parse`, so
527
+ * persisting it would present one phase later as a `malformed` mystery with
528
+ * no pointer to the cause. `writeFailed` — the state file could not be
445
529
  * appended to.
446
530
  */
447
- readonly reason: Schema.Literals<readonly ["missing", "malformed", "writeFailed"]>;
531
+ readonly reason: Schema.Literals<readonly ["missing", "malformed", "notPlainJson", "writeFailed"]>;
448
532
  readonly key: Schema.String;
449
533
  readonly cause: Schema.optionalKey<Schema.Defect>;
450
534
  }>, import("effect/Cause").YieldableError>;
@@ -462,7 +546,24 @@ declare class ActionStateError extends ActionStateError_base {
462
546
  * @public
463
547
  */
464
548
  interface ActionStateShape {
465
- /** Persist a value for a later phase. */
549
+ /**
550
+ * Persist a value for a later phase.
551
+ *
552
+ * @remarks
553
+ * **The schema's ENCODED form must be plain JSON** — `GITHUB_STATE` is a
554
+ * text file and the value crosses it as `JSON.stringify(encoded)`. A schema
555
+ * whose encoded side is a class instance (`Schema.Option`'s is an `Option`,
556
+ * serialized via its `toJSON` to `{"_id":"Option",…}`) writes something no
557
+ * later phase can decode; use the plain-JSON codec instead —
558
+ * `Schema.OptionFromNullOr` for an optional value.
559
+ *
560
+ * Every save proves the rule: the encoded value is round-tripped through
561
+ * `JSON.stringify`/`JSON.parse` and re-decoded, and a value that does not
562
+ * survive fails HERE, typed (`reason: "notPlainJson"`, naming the key) —
563
+ * rather than one phase later as a `malformed` mystery in `post` that
564
+ * `main` believed it saved. Action state is small by protocol, so the
565
+ * per-save round-trip costs effectively nothing.
566
+ */
466
567
  readonly save: <A, I>(key: string, value: A, schema: Schema.Codec<A, I>) => Effect.Effect<void, ActionStateError>;
467
568
  /** Read a value saved by an earlier phase. */
468
569
  readonly get: <A, I>(key: string, schema: Schema.Codec<A, I>) => Effect.Effect<A, ActionStateError>;
@@ -709,6 +810,14 @@ declare class CacheKeyError extends CacheKeyError_base {
709
810
  declare const CacheKey_base: Schema.Class<CacheKey, Schema.Struct<{
710
811
  /** The components, most general first. */
711
812
  readonly segments: Schema.NonEmptyArray<Schema.String>;
813
+ /**
814
+ * The explicit ladder policy, when one was given: how many leading
815
+ * segments each rung keeps, in the order the rungs are tried. Absent for
816
+ * the default every-prefix ladder; **empty for no ladder at all** —
817
+ * exact-match-only. See {@link CacheKey.withRestoreDepths} and
818
+ * {@link CacheKey.withoutRestoreKeys}.
819
+ */
820
+ readonly restoreDepths: Schema.optionalKey<Schema.$Array<Schema.Number>>;
712
821
  }>, {}>;
713
822
  /**
714
823
  * A GitHub Actions cache key and the restore-key ladder that goes with it.
@@ -721,6 +830,12 @@ declare const CacheKey_base: Schema.Class<CacheKey, Schema.Struct<{
721
830
  * the primary key and its fallbacks impossible to drift apart, which is the
722
831
  * failure that produces a cache that never hits and never says why.
723
832
  *
833
+ * By default every prefix is a rung. When some segments must never be dropped
834
+ * — a rung without a version digest resurrects stale cross-version caches —
835
+ * give the key an explicit ladder policy with
836
+ * {@link CacheKey.withRestoreDepths}, or none at all with
837
+ * {@link CacheKey.withoutRestoreKeys}, instead of bypassing the typed key.
838
+ *
724
839
  * @example
725
840
  * ```ts
726
841
  * import { CacheKey } from "@effected/github-actions";
@@ -739,12 +854,69 @@ declare class CacheKey extends CacheKey_base {
739
854
  * The restore keys, most specific first.
740
855
  *
741
856
  * @remarks
742
- * Each rung drops the last segment and keeps the trailing separator, so a
743
- * three-segment key falls back to two prefixes and a one-segment key falls
744
- * back to nothing — there is no rung that would match every cache in the
745
- * repository.
857
+ * By default each rung drops the last segment and keeps the trailing
858
+ * separator, so a three-segment key falls back to two prefixes and a
859
+ * one-segment key falls back to nothing — there is no rung that would match
860
+ * every cache in the repository.
861
+ *
862
+ * **The default ladder is every prefix**, which is wrong for a key whose
863
+ * later segments must never be dropped alone: a
864
+ * `platform-arch-versionHash-branchHash-lockfileHash` key derives rungs
865
+ * without the version digest, and those resurrect stale cross-version
866
+ * caches. A key built with {@link CacheKey.withRestoreDepths} carries its
867
+ * own ladder instead, and this getter answers exactly that ladder — so
868
+ * `ActionCache.restore` picks the policy up through the same typed-key
869
+ * path, no hand-built restore-key list required. An **empty** policy
870
+ * ({@link CacheKey.withoutRestoreKeys}) answers no rungs at all: only
871
+ * *absence* selects the default ladder, so "none" is never mistaken for
872
+ * "every prefix".
746
873
  */
747
874
  get restoreKeys(): ReadonlyArray<string>;
875
+ /**
876
+ * A copy of this key carrying an explicit restore-key ladder policy.
877
+ *
878
+ * @remarks
879
+ * Each depth is the number of **leading segments** the rung keeps, and the
880
+ * rungs are emitted in exactly the order given — GitHub tries restore keys
881
+ * in order, so the order IS the policy. Descending depths (most specific
882
+ * first) are what a fallback ladder almost always wants; that convention is
883
+ * recommended rather than enforced, because rung ordering is the consumer's
884
+ * policy where commas, newlines and the length limit are the runner's
885
+ * protocol. A depth outside `1..segments.length - 1` is refused at
886
+ * construction: `0` would match every cache in the repository, and
887
+ * `segments.length` would just repeat the primary key.
888
+ *
889
+ * @example
890
+ * ```ts
891
+ * import { CacheKey } from "@effected/github-actions";
892
+ *
893
+ * const key = CacheKey.of("Linux", "X64", "v1hash", "main", "lockhash").withRestoreDepths([4, 3]);
894
+ * key.restoreKeys; // ["Linux-X64-v1hash-main-", "Linux-X64-v1hash-"]
895
+ * ```
896
+ */
897
+ withRestoreDepths(depths: readonly [number, ...Array<number>]): CacheKey;
898
+ /**
899
+ * A copy of this key that restores from its **exact key only** — no ladder.
900
+ *
901
+ * @remarks
902
+ * The third point in the policy space, and the one the typed key had no
903
+ * spelling for: absence of a policy means the default every-prefix ladder,
904
+ * so exact-match-only restores (a cache-bust mode, where a stale partial
905
+ * hit is worse than a cold start) previously forced the caller back to the
906
+ * string form. This is the same `restoreDepths` field carrying **zero
907
+ * rungs** — an honest value, not a sentinel: `ActionCache.restore` sends no
908
+ * `restore_keys` at all, so the backend answers this key or a miss.
909
+ *
910
+ * @example
911
+ * ```ts
912
+ * import { CacheKey } from "@effected/github-actions";
913
+ *
914
+ * const key = CacheKey.of("Linux", "pnpm-store", "abc123").withoutRestoreKeys();
915
+ * key.key; // "Linux-pnpm-store-abc123"
916
+ * key.restoreKeys; // []
917
+ * ```
918
+ */
919
+ withoutRestoreKeys(): CacheKey;
748
920
  /** Build a key from its segments. */
749
921
  static of(...segments: readonly [string, ...ReadonlyArray<string>]): CacheKey;
750
922
  /**
@@ -772,6 +944,44 @@ declare class CacheKey extends CacheKey_base {
772
944
  /** The content hash, e.g. from {@link CacheKey.hashFiles}. Included last when given. */
773
945
  readonly hash?: string;
774
946
  }): CacheKey;
947
+ /**
948
+ * A segment-safe short digest of a string.
949
+ *
950
+ * @remarks
951
+ * {@link CacheKey.hashFiles} covers file *content*; this covers the key
952
+ * segments built from **non-file** inputs — a sorted version list, a branch
953
+ * name — that every compound key hashes and truncates by hand, each caller
954
+ * re-deciding the length and the placement. One home for the derivation:
955
+ * SHA-256, lowercase hex, the first `length` characters.
956
+ *
957
+ * The answer is **guaranteed to satisfy the segment grammar**: lowercase hex
958
+ * is nonempty (for any permitted `length`) and can never contain the comma
959
+ * or newline the restore-key protocol reserves, so the result drops straight
960
+ * into {@link CacheKey.of} with nothing to check at the call site.
961
+ *
962
+ * Deterministic and pure — the same input always answers the same digest,
963
+ * which is the entire point of putting one in a cache key. Distinct inputs
964
+ * are only *probably* distinct, as with any truncated hash; eight hex
965
+ * characters (32 bits) is the conventional balance between key legibility
966
+ * and collision risk for cache segments.
967
+ *
968
+ * A `length` outside `1..64` (or a fractional one) is a wiring mistake, not
969
+ * data, and **throws a `RangeError`** rather than failing typed: 64 is all
970
+ * SHA-256 has, and asking for more would silently answer fewer characters
971
+ * than the caller believes it got.
972
+ *
973
+ * @example
974
+ * ```ts
975
+ * import { CacheKey } from "@effected/github-actions";
976
+ *
977
+ * const key = CacheKey.of(
978
+ * "Linux",
979
+ * CacheKey.digest("node:24.4.0,pnpm:10.13.1"),
980
+ * CacheKey.digest("feat/my-branch"),
981
+ * );
982
+ * ```
983
+ */
984
+ static digest(input: string, length?: number): string;
775
985
  /**
776
986
  * Hash a set of files into a single digest.
777
987
  *
@@ -890,9 +1100,31 @@ declare class ActionCacheError extends ActionCacheError_base {
890
1100
  */
891
1101
  interface ActionCacheShape {
892
1102
  /**
893
- * Archive `paths` and store them under `key`.
1103
+ * Archive the files and directories matching `paths` and store them under
1104
+ * `key`.
894
1105
  *
895
1106
  * @remarks
1107
+ * `paths` is a list of glob **patterns**, resolved with `actions/cache`
1108
+ * parity before `tar` ever sees them: a pattern may match directories (a
1109
+ * matched directory is archived recursively, never expanded into its
1110
+ * descendants), a pattern that matches nothing — including a literal path
1111
+ * that is not on disk — is silently dropped, and a list that resolves to
1112
+ * nothing at all fails typed rather than reserving an entry no archive will
1113
+ * ever back. A leading `~` expands to the caller's home directory, relative
1114
+ * patterns root at `GITHUB_WORKSPACE` (falling back to the working
1115
+ * directory), and a leading `!` marks an exclusion.
1116
+ *
1117
+ * The entry **version** is a digest of the literal pattern list, exactly as
1118
+ * `actions/cache` computes it — resolution feeds `tar` only, never the
1119
+ * version — so {@link ActionCacheShape.restore | restore}, which resolves
1120
+ * nothing, derives the same version from the same list for free.
1121
+ *
1122
+ * One deliberate divergence from the toolkit: where `actions/cache` re-roots
1123
+ * every match relative to the workspace, resolved paths here stay
1124
+ * **absolute** and are archived with `tar -P`, so a restore puts every file
1125
+ * back where it came from regardless of the restoring step's working
1126
+ * directory.
1127
+ *
896
1128
  * A key that already exists is a **success**: cache entries are immutable, so
897
1129
  * another job having written it first is the outcome the caller wanted.
898
1130
  */
@@ -905,6 +1137,11 @@ interface ActionCacheShape {
905
1137
  * `Option.none()` is a miss, not a failure — a cold cache is the normal state
906
1138
  * of a new branch. Passing a {@link CacheKey} supplies its restore-key ladder
907
1139
  * automatically; passing a `string` and no ladder means "this key or nothing".
1140
+ *
1141
+ * `paths` only derives the entry version and MUST be the same literal list
1142
+ * the save used — no glob resolution happens here, because none is needed:
1143
+ * extraction consumes no path list (the archive already names its contents),
1144
+ * and `actions/cache` likewise hashes the un-resolved list on both sides.
908
1145
  */
909
1146
  readonly restore: (paths: ReadonlyArray<string>, key: string | CacheKey, restoreKeys?: ReadonlyArray<string>) => Effect.Effect<Option.Option<string>, ActionCacheError>;
910
1147
  }
@@ -1011,6 +1248,21 @@ declare class ActionCache extends ActionCache_base {
1011
1248
  */
1012
1249
  declare class ActionInput {
1013
1250
  private constructor();
1251
+ /**
1252
+ * The exact environment variable the runner publishes an input under.
1253
+ *
1254
+ * @remarks
1255
+ * `variable("biome-version")` is `"INPUT_BIOME-VERSION"` — GitHub
1256
+ * uppercases, replaces **spaces** with underscores, and leaves every other
1257
+ * character (**dashes included**) alone. This is the same derivation every
1258
+ * accessor on this class resolves through, exported so a test that must
1259
+ * spell a runner variable can derive it instead of hand-writing it: a
1260
+ * hand-written `INPUT_BIOME_VERSION` reads as absent on a real runner, the
1261
+ * default silently applies, and the suite goes green against the wrong
1262
+ * value. Prefer keying the test environment by input name (see
1263
+ * {@link ActionInput.provider}) so no variable is spelled at all.
1264
+ */
1265
+ static readonly variable: (name: string) => string;
1014
1266
  /**
1015
1267
  * A required string input.
1016
1268
  *
@@ -1120,14 +1372,37 @@ declare class ActionInput {
1120
1372
  */
1121
1373
  static schema<A, I>(name: string, schema: Schema.Codec<A, I>): Config.Config<A>;
1122
1374
  /**
1123
- * A `ConfigProvider` over a record of runner variables.
1375
+ * A `ConfigProvider` over a record of runner variables **or input names**.
1124
1376
  *
1125
1377
  * @remarks
1126
- * Subsumes the source package's free-standing `ActionsConfigProvider`, whose
1127
- * three behaviors are preserved exactly: the path is joined with `_`, spaces
1128
- * become underscores and the whole is uppercased; and an **empty string
1129
- * reads as absent**, because the runner sets unsupplied inputs to `""` and
1130
- * treating that as present would make every optional input look supplied.
1378
+ * The record dual-accepts, and the split is the `INPUT_` prefix:
1379
+ *
1380
+ * - A key already spelled as a runner variable (`INPUT_BIOME-VERSION`,
1381
+ * `PLAIN_VAR`) is read verbatim, exactly as before.
1382
+ * - Any other key is treated as an **input name**, `with:`-block style —
1383
+ * `{ "biome-version": "2.3.14" }` — and serves the variable this module
1384
+ * derives for it (`ActionInput.variable`). This is the spelling to
1385
+ * prefer in tests: a hand-written `INPUT_BIOME_VERSION` (underscore where
1386
+ * the runner keeps the dash) reads as absent and the default silently
1387
+ * applies, and the input-name form makes that class of bug
1388
+ * unrepresentable — the mangling never leaves this module.
1389
+ *
1390
+ * When both spellings of the same input are present and non-empty, the
1391
+ * explicit `INPUT_`-spelled entry wins.
1392
+ *
1393
+ * A **flat single-segment** lookup additionally tries the `INPUT_`
1394
+ * derivation of the name first, mirroring the order the production runtime
1395
+ * installs ({@link ActionInput.providerOver} via `layerDefault`) — so a
1396
+ * bare `Config.string("biome-version")` resolves under this provider
1397
+ * exactly as it does inside `Action.run`. Nested and numeric paths pass
1398
+ * through as the joined spelling only, as ever.
1399
+ *
1400
+ * The source package's `ActionsConfigProvider` behaviors are otherwise
1401
+ * preserved exactly: the path is joined with `_`, spaces become underscores
1402
+ * and the whole is uppercased; and an **empty string reads as absent**,
1403
+ * because the runner sets unsupplied inputs to `""` and treating that as
1404
+ * present would make every optional input look supplied. That rule applies
1405
+ * to input-name entries too: `{ "flag": "" }` is an unsupplied input.
1131
1406
  *
1132
1407
  * Taking the environment as an argument rather than reading `process.env`
1133
1408
  * is what makes inputs testable without mutating the test process.
@@ -2214,6 +2489,152 @@ type CheckRunProjection = {
2214
2489
  */
2215
2490
  declare const projectCheckState: (state: CheckState) => CheckRunProjection;
2216
2491
  //#endregion
2492
+ //#region src/ChildEnv.d.ts
2493
+ /**
2494
+ * The environment additions {@link ChildEnv.prependPath} builds: exactly one
2495
+ * entry, keyed by the spelling of `PATH` the base environment already uses.
2496
+ *
2497
+ * @public
2498
+ */
2499
+ interface PathPrependEnv {
2500
+ /** The additions to hand a child, ready to merge over the parent's block. */
2501
+ readonly env: Readonly<Record<string, string>>;
2502
+ /**
2503
+ * Always `true`, and part of the answer on purpose: `env` **without**
2504
+ * `extendEnv: true` silently replaces the child's entire environment with
2505
+ * this one entry. See {@link ChildEnv.prependPath}.
2506
+ */
2507
+ readonly extendEnv: true;
2508
+ }
2509
+ /**
2510
+ * What {@link ChildEnv.prependPath} derives the additions from.
2511
+ *
2512
+ * @public
2513
+ */
2514
+ interface PathPrependOptions {
2515
+ /**
2516
+ * The environment the child will inherit — on a runner, `process.env`.
2517
+ *
2518
+ * @remarks
2519
+ * Explicit rather than defaulted, matching this package's rule that ambient
2520
+ * process state is never read behind a caller's back — which is also what
2521
+ * makes the Windows casing branch exercisable from a test on any host.
2522
+ */
2523
+ readonly base: Readonly<Record<string, string | undefined>>;
2524
+ /**
2525
+ * The platform the child runs on, `process.platform`-shaped. Decides the
2526
+ * `PATH` entry delimiter: `;` on `win32`, `:` everywhere else.
2527
+ */
2528
+ readonly platform: string;
2529
+ }
2530
+ /**
2531
+ * Child-process environment construction: prepending directories to `PATH`
2532
+ * without falling into the three traps that each cost a real consumer a
2533
+ * cross-OS matrix round.
2534
+ *
2535
+ * @remarks
2536
+ * Spawning a child with directories prepended to `PATH` is one tiny concern
2537
+ * with three sharp edges, and every one of them fails silently or only on
2538
+ * Windows:
2539
+ *
2540
+ * 1. **`env` without `extendEnv: true` strips the child's environment.**
2541
+ * Core's `ChildProcess.CommandOptions.env` *replaces* the block unless
2542
+ * `extendEnv` says to merge, so a bare `{ env }` costs the child everything
2543
+ * — including the very `PATH` being extended.
2544
+ * 2. **Windows spells it `Path`, and casing decides who wins.** The
2545
+ * environment block is case-insensitive on win32, and Node de-duplicates a
2546
+ * merged block by keeping the **lexicographically first** spelling — an
2547
+ * undocumented internal that happens to favour `PATH` over an inherited
2548
+ * `Path`, and that nothing obliges Node to keep. Writing through the
2549
+ * inherited key's own casing means never depending on it.
2550
+ * 3. **`.cmd` shims need a shell since CVE-2024-27980.** See
2551
+ * {@link ChildEnv.needsShell}.
2552
+ *
2553
+ * The first two live in {@link ChildEnv.prependPath}; the third is a separate
2554
+ * predicate because it belongs on the spawn call, not in the environment.
2555
+ *
2556
+ * This class is total and pure — no service, no `R`, nothing read from the
2557
+ * ambient process — the same species as `Secret`'s statics, one concern per
2558
+ * member.
2559
+ *
2560
+ * @public
2561
+ */
2562
+ declare class ChildEnv {
2563
+ private constructor();
2564
+ /**
2565
+ * The key `base` spells `PATH` with — `Path` on a typical Windows block,
2566
+ * `PATH` elsewhere, and `PATH` when the base has no entry at all.
2567
+ *
2568
+ * @remarks
2569
+ * The lookup is case-insensitive because the Windows environment block is;
2570
+ * the answer preserves the base's own casing because that is the spelling a
2571
+ * merged block must write through (trap 2 above).
2572
+ */
2573
+ static pathKeyOf(base: Readonly<Record<string, string | undefined>>): string;
2574
+ /**
2575
+ * The environment additions that prepend `dirs` to the child's `PATH`,
2576
+ * paired with the `extendEnv: true` that keeps them a *prepend*.
2577
+ *
2578
+ * @remarks
2579
+ * `dirs` are joined ahead of the inherited value **in the order given** —
2580
+ * order is the caller's policy, because the first directory wins any name
2581
+ * collision (a package manager's shim directory leads so it beats a runtime
2582
+ * of the same name). The entry is written under the base's own `PATH`
2583
+ * spelling ({@link ChildEnv.pathKeyOf}), so a Windows child never receives
2584
+ * `PATH` beside its inherited `Path`. An absent or empty inherited value
2585
+ * appends nothing — deliberately not an empty trailing entry, which both
2586
+ * platforms historically read as "the current directory".
2587
+ *
2588
+ * Two consumers, two compositions, and the difference is exactly trap 1:
2589
+ *
2590
+ * - A spawner call takes the **whole pair** — spread it into the command
2591
+ * options, as in the example below, so the `extendEnv: true` that makes
2592
+ * `env` a merge rather than a replacement cannot be forgotten.
2593
+ * - An API that already merges additions over the parent itself —
2594
+ * `DetachedProcess.spawn`, whose `env` is documented as merged — takes
2595
+ * `.env` alone; `extendEnv` would be meaningless there.
2596
+ *
2597
+ * On a runner, pass `{ base: process.env, platform: process.platform }`.
2598
+ *
2599
+ * @example
2600
+ * ```ts
2601
+ * import { ChildEnv } from "@effected/github-actions";
2602
+ * import { ChildProcess } from "effect/unstable/process";
2603
+ *
2604
+ * const command = ChildProcess.make("pnpm", ["install"], {
2605
+ * ...ChildEnv.prependPath(["/opt/hostedtoolcache/pnpm/10.13.1/x64/bin"], {
2606
+ * base: process.env,
2607
+ * platform: process.platform,
2608
+ * }),
2609
+ * ...(ChildEnv.needsShell(process.platform) ? { shell: true } : {}),
2610
+ * });
2611
+ * ```
2612
+ */
2613
+ static prependPath(dirs: readonly [string, ...ReadonlyArray<string>], options: PathPrependOptions): PathPrependEnv;
2614
+ /**
2615
+ * Whether a bare-name command must be launched through a shell on this
2616
+ * platform.
2617
+ *
2618
+ * @remarks
2619
+ * On Windows every npm-installed tool on `PATH` is a `.cmd` batch shim, not
2620
+ * an executable: `CreateProcess` cannot run one, and since CVE-2024-27980
2621
+ * Node refuses to hand `.cmd`/`.bat` files to it at all unless a shell is
2622
+ * asked for — so the spawn fails at launch, before the tool ever runs.
2623
+ * Under `shell: true`, `cmd.exe` resolves the bare name off the **child's**
2624
+ * `PATH` — the one {@link ChildEnv.prependPath} builds — through `PATHEXT`,
2625
+ * where `.EXE` precedes `.CMD`, so a real binary of the same name is still
2626
+ * found first and the prepends keep working exactly as they do without a
2627
+ * shell.
2628
+ *
2629
+ * POSIX answers `false`: the direct spawn already works, and a shell would
2630
+ * only add a quoting hazard between the caller and the exit code. Note the
2631
+ * hazard `true` carries: under a shell Node concatenates the command line
2632
+ * rather than passing an argv, so arguments must be static literals, never
2633
+ * derived from input.
2634
+ */
2635
+ static needsShell(platform: string): boolean;
2636
+ }
2637
+ //#endregion
2217
2638
  //#region src/DetachedProcess.d.ts
2218
2639
  declare const DetachedProcessError_base: Schema.Class<DetachedProcessError, Schema.TaggedStruct<"DetachedProcessError", {
2219
2640
  /**
@@ -3079,106 +3500,6 @@ declare class ManagedDocument extends ManagedDocument_base {
3079
3500
  private ensureSentinel;
3080
3501
  }
3081
3502
  //#endregion
3082
- //#region src/Secret.d.ts
3083
- /**
3084
- * The declassification seam: the **only** place in this package where a
3085
- * `Redacted` value becomes a plain string.
3086
- *
3087
- * @remarks
3088
- * Some boundaries cannot carry a `Redacted`. A detached child process reads
3089
- * its configuration from environment variables; `GITHUB_STATE` is a plaintext
3090
- * file by GitHub's protocol. `Redacted` refusing to serialize is the whole
3091
- * value of the type, so this module does not try to make it serializable —
3092
- * it makes declassification **explicit, auditable, and impossible to do
3093
- * quietly**.
3094
- *
3095
- * The invariant: **masking and declassification are the same call.** Every
3096
- * member registers the value with the runner's log filter (`::add-mask::`)
3097
- * *before* returning plaintext, so a secret cannot reach a child's environment
3098
- * or a state file without the runner already knowing to redact it from logs.
3099
- *
3100
- * `Redacted.value` appears nowhere else in this package, and a test asserts
3101
- * that structurally — a reintroduction elsewhere fails the suite rather than
3102
- * review.
3103
- *
3104
- * What this deliberately does **not** do is encrypt the handoff. The child
3105
- * runs on the same runner, as the same user, as the parent; a key would have
3106
- * to travel the same channel as the secret it protects.
3107
- *
3108
- * @example
3109
- * ```ts
3110
- * import { Secret } from "@effected/github-actions";
3111
- * import { Effect } from "effect";
3112
- *
3113
- * const program = Effect.gen(function* () {
3114
- * const env = yield* Secret.forChildEnv({ MY_TOKEN: theToken });
3115
- * // every value in `env` is already masked in the runner log
3116
- * return env;
3117
- * });
3118
- * ```
3119
- *
3120
- * @public
3121
- */
3122
- declare class Secret {
3123
- private constructor();
3124
- /**
3125
- * Declassify a set of secrets for a child process's environment.
3126
- *
3127
- * @remarks
3128
- * Every value is masked before any plaintext is returned — including when
3129
- * one of them fails, because masking happens for the whole set first.
3130
- */
3131
- static readonly forChildEnv: (entries: Readonly<Record<string, Redacted.Redacted<string>>>) => Effect.Effect<Record<string, string>, never, ActionOutputs>;
3132
- /**
3133
- * Declassify one secret for a runner file (`GITHUB_STATE`, `GITHUB_OUTPUT`).
3134
- *
3135
- * @remarks
3136
- * Masks first. The runner file is plaintext regardless; the mask is what
3137
- * keeps the value out of the visible log.
3138
- */
3139
- static readonly forRunnerFile: (secret: Redacted.Redacted<string>) => Effect.Effect<string, never, ActionOutputs>;
3140
- /**
3141
- * Declassify one secret for an in-process use that needs the raw bytes.
3142
- *
3143
- * @remarks
3144
- * Request signing is the case this exists for: an HMAC cannot be computed
3145
- * over a `Redacted`, so a signing key has to become a string somewhere, and
3146
- * the point of this module is that "somewhere" is here and nowhere else.
3147
- *
3148
- * Signing named the member; it does not gate it. **Any in-process use that
3149
- * needs the raw value without writing it to a runner file is this
3150
- * operation** — handing a key to a library that takes a plain string. The
3151
- * dividing line is where the value goes, not what it is for: a value
3152
- * crossing into a **child's** environment is {@link Secret.forChildEnv},
3153
- * one written to `GITHUB_STATE` or `GITHUB_OUTPUT` is
3154
- * {@link Secret.forRunnerFile}, and one that stays in this process is this
3155
- * member. A third-party SDK that reads only the ambient environment is the
3156
- * hard case: this package never mutates `process.env` (reads are seeded
3157
- * once through `ActionEnvironment`), so if a consumer chooses that bridge,
3158
- * the mutation — and its restore discipline — lives in consumer code as
3159
- * the consumer's own tradeoff, not as a pattern this member recommends.
3160
- *
3161
- * It masks even though the value is never *written* anywhere, and that is
3162
- * deliberate rather than superstitious: a signing key that leaks does so
3163
- * through something nobody audited — a debug log of outgoing headers, a
3164
- * serialized error, a stack trace carrying the closure. Registering it with
3165
- * the runner's filter costs one call and makes every one of those redacted.
3166
- *
3167
- * Call it **once, at layer construction**, not per operation: masking is
3168
- * idempotent but a workflow command per request is noise in the log.
3169
- */
3170
- static readonly forSigning: (secret: Redacted.Redacted<string>) => Effect.Effect<string, never, ActionOutputs>;
3171
- /**
3172
- * The far side of a handoff: re-wrap a plaintext environment variable.
3173
- *
3174
- * @remarks
3175
- * A `Config`, so a missing or empty handoff is an honest `ConfigError`
3176
- * naming the variable rather than an empty `Redacted` that fails much later
3177
- * as an authentication error.
3178
- */
3179
- static readonly adopt: (name: string) => Config.Config<Redacted.Redacted<string>>;
3180
- }
3181
- //#endregion
3182
3503
  //#region src/ToolInstaller.d.ts
3183
3504
  declare const ToolInstallerError_base: Schema.Class<ToolInstallerError, Schema.TaggedStruct<"ToolInstallerError", {
3184
3505
  /**
@@ -3222,9 +3543,75 @@ declare class ToolInstallerError extends ToolInstallerError_base {
3222
3543
  interface ExtractOptions {
3223
3544
  /** The destination. A fresh temporary directory when omitted. */
3224
3545
  readonly destination?: string | undefined;
3225
- /** Flags for `tar`. Defaults to `["xzf"]`, i.e. a gzipped tarball. */
3546
+ /**
3547
+ * Flags for `tar`. Defaults to `["xzf"]`, i.e. a gzipped tarball.
3548
+ *
3549
+ * @remarks
3550
+ * The argv is assembled as `[...flags, archive, "-C", destination]` — the
3551
+ * archive path is appended directly AFTER the flags, so the LAST flag must
3552
+ * be the one that consumes the archive operand. `["xzf"]` works because `f`
3553
+ * is last inside the cluster; a custom set like `["xz", "--strip=1", "-f"]`
3554
+ * must end the same way, deliberately rather than by ordering luck.
3555
+ */
3226
3556
  readonly flags?: ReadonlyArray<string> | undefined;
3227
3557
  }
3558
+ /**
3559
+ * How a {@link ToolInstallerShape.download} should behave.
3560
+ *
3561
+ * @public
3562
+ */
3563
+ interface ToolDownloadOptions {
3564
+ /**
3565
+ * The overall time budget for ONE download attempt (request plus streaming
3566
+ * the body to disk). Defaults to five minutes.
3567
+ *
3568
+ * @remarks
3569
+ * The default is deliberately generous: toolchain archives run to hundreds
3570
+ * of megabytes, and a healthy runner connection moves tens of megabytes a
3571
+ * second — even a 500 MB archive on a degraded 2 MB/s link fits in five
3572
+ * minutes. What the budget exists for is the connection that stops moving
3573
+ * entirely: without it, a dead connection is a silent CI hang until the job
3574
+ * timeout; with it, the attempt fails typed as `downloadFailed` with no
3575
+ * status — which `retryable` classifies as a transport fault — and the
3576
+ * download's own retry policy tries again.
3577
+ */
3578
+ readonly timeout?: Duration.Input | undefined;
3579
+ }
3580
+ /**
3581
+ * What {@link ToolInstallerShape.provisionFile} should provision.
3582
+ *
3583
+ * @public
3584
+ */
3585
+ interface ProvisionFileOptions {
3586
+ /** The tool-cache name the binary is provisioned under, e.g. `"biome"`. */
3587
+ readonly tool: string;
3588
+ /** The exact version — a tool-cache path segment, so spell it canonically. */
3589
+ readonly version: string;
3590
+ /** The url the binary downloads from on a cache miss. */
3591
+ readonly url: string;
3592
+ /**
3593
+ * The cached file's name — what the tool is invoked as once its directory
3594
+ * is on the `PATH` (e.g. `"biome"`, or `"biome.exe"` on Windows).
3595
+ */
3596
+ readonly binary: string;
3597
+ }
3598
+ /**
3599
+ * Where {@link ToolInstallerShape.provisionFile} put a single-binary tool.
3600
+ *
3601
+ * @remarks
3602
+ * The same `directory`/`binDir` shape as `PackageManagerInstaller`'s
3603
+ * `CachedPackageManager`, so a consumer publishes either the same way. For a
3604
+ * single binary the two coincide: the cached directory contains exactly the
3605
+ * executable, so it IS the directory to hand to `ActionOutputs.addPath`.
3606
+ *
3607
+ * @public
3608
+ */
3609
+ interface ProvisionedFile {
3610
+ /** The cached entry: `<tool-cache>/<tool>/<version>/<arch>`. */
3611
+ readonly directory: string;
3612
+ /** The directory to `addPath` — for a single binary, the cached directory itself. */
3613
+ readonly binDir: string;
3614
+ }
3228
3615
  /**
3229
3616
  * The {@link ToolInstaller} service shape.
3230
3617
  *
@@ -3237,10 +3624,20 @@ interface ToolInstallerShape {
3237
3624
  * @remarks
3238
3625
  * Absent is not an error, and neither is an unreadable cache: both mean
3239
3626
  * "install it", which is the only thing a caller can do about either.
3627
+ *
3628
+ * **The tool cache is SHARED.** The hosted runner image pre-populates it and
3629
+ * every `setup-*` action writes into it — this package is a co-writer, not
3630
+ * the owner. A hit guarantees only the `<root>/<tool>/<version>/<arch>`
3631
+ * location contract ({@link ToolInstaller.cachePath}); the directory's
3632
+ * *interior* layout is whatever its writer produced, which is not
3633
+ * necessarily what this consumer's own `cacheDir` would have written.
3634
+ * Validate the layout of a foreign hit before relying on it — applying an
3635
+ * assumption from your own install path (an archive-wrapper subdirectory,
3636
+ * say) to a foreign entry is a real shipped bug.
3240
3637
  */
3241
3638
  readonly find: (tool: string, version: string) => Effect.Effect<Option.Option<string>>;
3242
3639
  /** Download a url to a temporary file, retrying what is worth retrying. */
3243
- readonly download: (url: string) => Effect.Effect<string, ToolInstallerError>;
3640
+ readonly download: (url: string, options?: ToolDownloadOptions) => Effect.Effect<string, ToolInstallerError>;
3244
3641
  /** Extract a tarball. Returns the directory its contents landed in. */
3245
3642
  readonly extractTar: (archive: string, options?: ExtractOptions) => Effect.Effect<string, ToolInstallerError>;
3246
3643
  /** Extract a zip. Returns the directory its contents landed in. */
@@ -3249,16 +3646,38 @@ interface ToolInstallerShape {
3249
3646
  readonly cacheDir: (source: string, tool: string, version: string) => Effect.Effect<string, ToolInstallerError>;
3250
3647
  /** Install a single file into the tool cache under `name`. Returns the cached directory. */
3251
3648
  readonly cacheFile: (source: string, name: string, tool: string, version: string) => Effect.Effect<string, ToolInstallerError>;
3649
+ /**
3650
+ * Provision a single-binary tool: the whole `find` → `download` →
3651
+ * chmod → `cacheFile` composition as one call.
3652
+ *
3653
+ * @remarks
3654
+ * The one packaged workflow among the primitives, because the single-binary
3655
+ * shape (biome, and every Rust/Go tool distributed as a bare executable) has
3656
+ * no per-tool variation left to compose: there is no archive, so no
3657
+ * extraction and no layout fixup. A cache hit whose entry contains the
3658
+ * named `binary` short-circuits; a hit *missing* it — a foreign or partial
3659
+ * entry, see {@link ToolInstallerShape.find}'s shared-cache warning — is
3660
+ * treated as a miss and reinstalled over, rather than handing back a
3661
+ * directory that cannot run the tool. On the install path the downloaded
3662
+ * file is made executable (`0o755`) BEFORE caching — skipped when
3663
+ * `RUNNER_OS` is Windows, where the bit does not exist — so the cache only
3664
+ * ever contains a runnable tool; a chmod failure is `cacheFailed` with the
3665
+ * file as its subject.
3666
+ */
3667
+ readonly provisionFile: (options: ProvisionFileOptions) => Effect.Effect<ProvisionedFile, ToolInstallerError>;
3252
3668
  }
3253
3669
  declare const ToolInstaller_base: Context.ServiceClass<ToolInstaller, "@effected/github-actions/ToolInstaller", ToolInstallerShape>;
3254
3670
  /**
3255
3671
  * Download, extract and cache a toolchain in the runner's tool cache.
3256
3672
  *
3257
3673
  * @remarks
3258
- * The primitives, not a workflow: a consumer composes `find` → `download` →
3259
- * `extractTar` → `cacheDir` in whatever order its tool needs, because the
3260
- * orders genuinely differ (a single binary skips extraction, a tarball with a
3261
- * nested root directory needs a path fixup between extract and cache).
3674
+ * The primitives, plus exactly one packaged workflow: a consumer composes
3675
+ * `find` → `download` → `extractTar` → `cacheDir` in whatever order its tool
3676
+ * needs, because the orders genuinely differ (a tarball with a nested root
3677
+ * directory needs a path fixup between extract and cache). The one shape with
3678
+ * no per-tool variation — a single bare binary, which skips extraction and
3679
+ * needs only the executable bit — ships whole as
3680
+ * {@link ToolInstallerShape.provisionFile}.
3262
3681
  *
3263
3682
  * No `@actions/tool-cache` dependency — the cache is a directory layout, and
3264
3683
  * this reproduces the layout rather than importing a package to compute it.
@@ -3305,5 +3724,339 @@ declare class ToolInstaller extends ToolInstaller_base {
3305
3724
  static readonly layerTest: (overrides?: Partial<ToolInstallerShape>) => Layer.Layer<ToolInstaller>;
3306
3725
  }
3307
3726
  //#endregion
3308
- 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, type AnnotationProperties, Artifact, ArtifactError, type ArtifactItem, type ArtifactRef, type ArtifactShape, type Blob, BlobEnvelope, BlobEnvelopeError, BlobStore, BlobStoreError, type BlobStoreShape, BlobTransferError, CacheKey, CacheKeyError, CheckDocument, CheckDocumentError, type CheckDocumentOptions, type CheckDocumentShape, type CheckDocumentSink, CheckReport, type CheckRunConclusion, type CheckRunProjection, CheckState, 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, ManagedDocument, ManagedDocumentError, type ManagedDocumentSource, OidcClaims, OidcTokenError, OidcTokenIssuer, type OidcTokenIssuerShape, ProcessId, type ProvisionOptions, type ReadOptions, type ReadinessOptions, RunnerContext, type S3Config, Secret, ToolInstaller, ToolInstallerError, type ToolInstallerShape, type UploadOptions, type UploadResult, type WithBufferOptions, WorkflowCommand, describeCause, projectCheckState };
3727
+ //#region src/PackageManagerInstaller.d.ts
3728
+ declare const PackageManagerInstallerError_base: Schema.Class<PackageManagerInstallerError, Schema.TaggedStruct<"PackageManagerInstallerError", {
3729
+ /**
3730
+ * `downloadFailed`, `extractFailed` and `cacheFailed` mirror the
3731
+ * {@link ToolInstallerError} that caused them, preserved on `cause`
3732
+ * (`cacheFailed` also covers a shim that could not be written into the
3733
+ * entry). `integrityMismatch` — the downloaded artifact does not hash to
3734
+ * what the pin declares; nothing was cached. `integrityMissing` — the pin
3735
+ * carries no integrity and the caller asked for `requireIntegrity`.
3736
+ * `unsupportedPlatform` — bun publishes no build for this runner's
3737
+ * OS/architecture pair. `layoutUnexpected` — the artifact extracted, but
3738
+ * its contents are not shaped like the package manager it claims to be.
3739
+ */
3740
+ readonly reason: Schema.Literals<readonly ["downloadFailed", "extractFailed", "integrityMismatch", "integrityMissing", "unsupportedPlatform", "layoutUnexpected", "cacheFailed"]>;
3741
+ /** The package manager being installed. */
3742
+ readonly name: Schema.Literals<readonly ["npm", "pnpm", "yarn", "bun"]>;
3743
+ /** The exact version being installed, in string form. */
3744
+ readonly version: Schema.String;
3745
+ /** What was being worked on — a url, a path, or an OS/arch pair. */
3746
+ readonly subject: Schema.optionalKey<Schema.String>;
3747
+ /** The integrity the pin declares (`<algo>.<hex>`), on a mismatch. */
3748
+ readonly expected: Schema.optionalKey<Schema.String>;
3749
+ /** The integrity the artifact actually hashed to, on a mismatch. */
3750
+ readonly actual: Schema.optionalKey<Schema.String>;
3751
+ /** The underlying failure, preserved structurally. */
3752
+ readonly cause: Schema.optionalKey<Schema.Defect>;
3753
+ }>, import("effect/Cause").YieldableError>;
3754
+ /**
3755
+ * Raised when a package manager cannot be provisioned on the runner.
3756
+ *
3757
+ * @public
3758
+ */
3759
+ declare class PackageManagerInstallerError extends PackageManagerInstallerError_base {
3760
+ get message(): string;
3761
+ }
3762
+ /**
3763
+ * How {@link PackageManagerInstallerShape.install} should behave.
3764
+ *
3765
+ * @public
3766
+ */
3767
+ interface PackageManagerInstallOptions {
3768
+ /**
3769
+ * Fail with `integrityMissing` when the pin carries no integrity hash,
3770
+ * instead of proceeding with a logged warning. Off by default, because
3771
+ * in-the-wild `devEngines` pins routinely carry no integrity.
3772
+ */
3773
+ readonly requireIntegrity?: boolean | undefined;
3774
+ /**
3775
+ * The npm registry host the npm, pnpm and yarn tarballs download from.
3776
+ * Defaults to `https://registry.npmjs.org`; a corporate mirror that serves
3777
+ * the standard `/<name>/-/<basename>-<version>.tgz` tarball routes works
3778
+ * unchanged. bun does not ship through a registry — its per-platform zip
3779
+ * always comes from GitHub releases.
3780
+ */
3781
+ readonly registry?: string | undefined;
3782
+ /**
3783
+ * Whether an npm pin may be answered by the runner's own ambient npm.
3784
+ * Defaults to `true`.
3785
+ *
3786
+ * @remarks
3787
+ * Set `false` when the run REPLACES node: a consumer that installs a pinned
3788
+ * node in the same run puts that node's bundled npm ahead of the runner's on
3789
+ * every path that matters afterward (the install child's `PATH`, and every
3790
+ * later workflow step via `GITHUB_PATH`). The ambient probe interrogates the
3791
+ * RUNNER's npm, so its exact-version match can diverge from the npm that
3792
+ * actually executes once the pinned node shadows it. Suppressing the probe
3793
+ * skips it entirely — no `npm --version` is ever spawned — and the install
3794
+ * goes straight to the tool-cache/dist path, answering
3795
+ * `source: "tool-cache"` with its own `binDir` as usual.
3796
+ */
3797
+ readonly allowAmbient?: boolean | undefined;
3798
+ }
3799
+ declare const AmbientPackageManager_base: Schema.Class<AmbientPackageManager, Schema.Struct<{
3800
+ /** The discriminant: the runner's own toolchain answered. */
3801
+ readonly source: Schema.tag<"ambient">;
3802
+ /** The package-manager name (`npm`, `pnpm`, `yarn` or `bun`). */
3803
+ readonly name: Schema.Literals<readonly ["npm", "pnpm", "yarn", "bun"]>;
3804
+ /** The exact installed version, in string form. */
3805
+ readonly version: Schema.String;
3806
+ /** Bin name → the ambient command name that invokes it. */
3807
+ readonly bins: Schema.$Record<Schema.String, Schema.String>;
3808
+ }>, {}>;
3809
+ /**
3810
+ * A package manager the runner's own toolchain already had: nothing was
3811
+ * downloaded and nothing was cached, so there is no directory to publish.
3812
+ *
3813
+ * @remarks
3814
+ * `bins` values are bare command names (`npm`, `npx`) resolved through the
3815
+ * ambient `PATH` — the toolchain already put them there.
3816
+ *
3817
+ * @public
3818
+ */
3819
+ declare class AmbientPackageManager extends AmbientPackageManager_base {}
3820
+ declare const CachedPackageManager_base: Schema.Class<CachedPackageManager, Schema.Struct<{
3821
+ /** The discriminant: the manager lives in the tool cache. */
3822
+ readonly source: Schema.tag<"tool-cache">;
3823
+ /** The package-manager name (`npm`, `pnpm`, `yarn` or `bun`). */
3824
+ readonly name: Schema.Literals<readonly ["npm", "pnpm", "yarn", "bun"]>;
3825
+ /** The exact installed version, in string form. */
3826
+ readonly version: Schema.String;
3827
+ /** The cached entry: `<tool-cache>/<name>/<version>/<arch>`. */
3828
+ readonly directory: Schema.String;
3829
+ /** The directory to `addPath` — executable shims, or bun's own directory. */
3830
+ readonly binDir: Schema.String;
3831
+ /** Bin name → the absolute path of the underlying entry point. */
3832
+ readonly bins: Schema.$Record<Schema.String, Schema.String>;
3833
+ }>, {}>;
3834
+ /**
3835
+ * A package manager living in the runner's tool cache — found there, or
3836
+ * installed into it by this call.
3837
+ *
3838
+ * @remarks
3839
+ * `binDir` is the directory a consumer hands to `ActionOutputs.addPath` to
3840
+ * make the manager invokable by name in subsequent workflow steps. For the
3841
+ * npm-registry managers it is the entry's `.bin` directory of executable
3842
+ * shims this installer writes (`#!/bin/sh` exec wrappers, or `.cmd` wrappers
3843
+ * on Windows); for bun it is the entry directory itself, because the cached
3844
+ * `bun` binary is directly executable and a shim would add nothing but a
3845
+ * fork. `bins` still maps each published bin name to the underlying entry —
3846
+ * a Node script to run with `node` for npm/pnpm/yarn, the native binary for
3847
+ * bun. Calling `addPath` remains deliberately the consumer's move, and note
3848
+ * that `addPath` targets *subsequent* steps: a same-process probe must use
3849
+ * the absolute paths in `bins` or `binDir`.
3850
+ *
3851
+ * @public
3852
+ */
3853
+ declare class CachedPackageManager extends CachedPackageManager_base {}
3854
+ /**
3855
+ * An installed package manager, discriminated by `source`: `ambient` carries
3856
+ * no directory, `tool-cache` carries the cached `directory` and the
3857
+ * `addPath`-able `binDir`. The union is a `Schema`, so the record round-trips
3858
+ * through `ActionState` for a later phase to read back.
3859
+ *
3860
+ * @remarks
3861
+ * **Construct the variant classes, never the union.** The union's inherited
3862
+ * `make` typechecks against *both* variants at once, so a wrong field yields
3863
+ * a confusing two-branch error instead of "this variant wants X" — reach for
3864
+ * {@link AmbientPackageManager}`.make` / {@link CachedPackageManager}`.make`,
3865
+ * whose `Schema.tag` fills `source` for you. The same tag is why an
3866
+ * overrides-style test helper should be typed
3867
+ * `Partial<Omit<typeof CachedPackageManager.Type, "source">>` — a plain
3868
+ * `Partial` offers `source`, which `make` auto-fills and rejects as input.
3869
+ *
3870
+ * @public
3871
+ */
3872
+ declare const InstalledPackageManager: Schema.Union<readonly [typeof AmbientPackageManager, typeof CachedPackageManager]>;
3873
+ /**
3874
+ * The decoded type of {@link (InstalledPackageManager:variable)}:
3875
+ * {@link AmbientPackageManager} `|` {@link CachedPackageManager}.
3876
+ *
3877
+ * @public
3878
+ */
3879
+ type InstalledPackageManager = typeof InstalledPackageManager.Type;
3880
+ /**
3881
+ * The {@link PackageManagerInstaller} service shape.
3882
+ *
3883
+ * @public
3884
+ */
3885
+ interface PackageManagerInstallerShape {
3886
+ /**
3887
+ * Provision the exact package-manager version a pin names, answering with
3888
+ * where it landed and how to invoke it.
3889
+ */
3890
+ readonly install: (pin: PackageManagerPin, options?: PackageManagerInstallOptions) => Effect.Effect<InstalledPackageManager, PackageManagerInstallerError>;
3891
+ }
3892
+ declare const PackageManagerInstaller_base: Context.ServiceClass<PackageManagerInstaller, "@effected/github-actions/PackageManagerInstaller", PackageManagerInstallerShape>;
3893
+ /**
3894
+ * First-class exact-version package-manager provisioning on a GitHub runner.
3895
+ *
3896
+ * @remarks
3897
+ * Takes a corepack pin (`@effected/npm`'s `PackageManagerPin`) and answers
3898
+ * with an installed manager: tool-cache `find` first, then — for npm — an
3899
+ * ambient `npm --version` probe (every Node toolchain ships npm), and only
3900
+ * then the manager's own dist: npm, pnpm and yarn 1.x from their registry
3901
+ * tarballs, yarn 2+ (Berry) from `@yarnpkg/cli-dist`, bun from its
3902
+ * per-platform GitHub-release zip. A pin that carries integrity is verified
3903
+ * fail-closed against what corepack itself hashes; one that does not proceeds
3904
+ * with a logged warning unless `requireIntegrity` says otherwise.
3905
+ *
3906
+ * Every tool-cache answer carries a `binDir` a consumer can hand straight to
3907
+ * `ActionOutputs.addPath`: the npm-registry managers get a `.bin` directory
3908
+ * of executable shims written as part of the cached entry, bun's entry is its
3909
+ * own `binDir`. Caching goes through {@link ToolInstaller}, so the
3910
+ * stage-then-swap invariant holds here for free — shims included, the tool
3911
+ * cache only ever contains complete package managers.
3912
+ *
3913
+ * @example
3914
+ * ```ts
3915
+ * import { ActionOutputs, PackageManagerInstaller } from "@effected/github-actions";
3916
+ * import { PackageManagerPin } from "@effected/npm";
3917
+ * import { Effect } from "effect";
3918
+ *
3919
+ * const provision = Effect.gen(function* () {
3920
+ * const installer = yield* PackageManagerInstaller;
3921
+ * const outputs = yield* ActionOutputs;
3922
+ * const pin = yield* PackageManagerPin.parse("pnpm@10.13.1");
3923
+ * const installed = yield* installer.install(pin);
3924
+ * if (installed.source === "tool-cache") {
3925
+ * yield* outputs.addPath(installed.binDir);
3926
+ * }
3927
+ * });
3928
+ * ```
3929
+ *
3930
+ * @public
3931
+ */
3932
+ declare class PackageManagerInstaller extends PackageManagerInstaller_base {
3933
+ static readonly layer: Layer.Layer<PackageManagerInstaller, never, ActionEnvironment | FileSystem.FileSystem | Path.Path | ChildProcessSpawner.ChildProcessSpawner | ToolInstaller>;
3934
+ /** A test double. The unstubbed member dies rather than reporting an install that did not happen. */
3935
+ static readonly makeTest: (overrides?: Partial<PackageManagerInstallerShape>) => PackageManagerInstallerShape;
3936
+ /** {@link PackageManagerInstaller.makeTest} behind `Layer.succeed`. */
3937
+ static readonly layerTest: (overrides?: Partial<PackageManagerInstallerShape>) => Layer.Layer<PackageManagerInstaller>;
3938
+ }
3939
+ //#endregion
3940
+ //#region src/Secret.d.ts
3941
+ /**
3942
+ * The declassification seam: the **only** place in this package where a
3943
+ * `Redacted` value becomes a plain string.
3944
+ *
3945
+ * @remarks
3946
+ * Some boundaries cannot carry a `Redacted`. A detached child process reads
3947
+ * its configuration from environment variables; `GITHUB_STATE` is a plaintext
3948
+ * file by GitHub's protocol. `Redacted` refusing to serialize is the whole
3949
+ * value of the type, so this module does not try to make it serializable —
3950
+ * it makes declassification **explicit, auditable, and impossible to do
3951
+ * quietly**.
3952
+ *
3953
+ * The invariant: **masking and declassification are the same call.** Every
3954
+ * member registers the value with the runner's log filter (`::add-mask::`)
3955
+ * *before* returning plaintext, so a secret cannot reach a child's environment
3956
+ * or a state file without the runner already knowing to redact it from logs.
3957
+ *
3958
+ * `Redacted.value` appears nowhere else in this package, and a test asserts
3959
+ * that structurally — a reintroduction elsewhere fails the suite rather than
3960
+ * review.
3961
+ *
3962
+ * What this deliberately does **not** do is encrypt the handoff. The child
3963
+ * runs on the same runner, as the same user, as the parent; a key would have
3964
+ * to travel the same channel as the secret it protects.
3965
+ *
3966
+ * @example
3967
+ * ```ts
3968
+ * import { Secret } from "@effected/github-actions";
3969
+ * import { Effect } from "effect";
3970
+ *
3971
+ * const program = Effect.gen(function* () {
3972
+ * const env = yield* Secret.forChildEnv({ MY_TOKEN: theToken });
3973
+ * // every value in `env` is already masked in the runner log
3974
+ * return env;
3975
+ * });
3976
+ * ```
3977
+ *
3978
+ * @public
3979
+ */
3980
+ declare class Secret {
3981
+ private constructor();
3982
+ /**
3983
+ * Declassify a set of secrets for a child process's environment.
3984
+ *
3985
+ * @remarks
3986
+ * Every value is masked before any plaintext is returned — including when
3987
+ * one of them fails, because masking happens for the whole set first.
3988
+ *
3989
+ * **Detached workers invert the masking model.** Masking works because the
3990
+ * runner parses this process's stdout; a *detached* child's stdout is a log
3991
+ * file no runner parses, so a mask emitted **inside** the worker is inert
3992
+ * and writes the plaintext verbatim into that log — a shipped incident, not
3993
+ * a hypothetical. The rule: **the parent masks, before the spawn** — call
3994
+ * this member in the parent, where the runner is listening, and hand the
3995
+ * already-masked plaintext to the worker's environment. Inside the worker,
3996
+ * compose `ActionOutputs.layerDetached`, whose `setSecret` is a documented
3997
+ * no-op, so the same code cannot re-leak what the parent already masked.
3998
+ */
3999
+ static readonly forChildEnv: (entries: Readonly<Record<string, Redacted.Redacted<string>>>) => Effect.Effect<Record<string, string>, never, ActionOutputs>;
4000
+ /**
4001
+ * Declassify one secret for a runner file (`GITHUB_STATE`, `GITHUB_OUTPUT`).
4002
+ *
4003
+ * @remarks
4004
+ * Masks first. The runner file is plaintext regardless; the mask is what
4005
+ * keeps the value out of the visible log.
4006
+ */
4007
+ static readonly forRunnerFile: (secret: Redacted.Redacted<string>) => Effect.Effect<string, never, ActionOutputs>;
4008
+ /**
4009
+ * Declassify one secret for an in-process use that needs the raw bytes.
4010
+ *
4011
+ * @remarks
4012
+ * Request signing is the case this exists for: an HMAC cannot be computed
4013
+ * over a `Redacted`, so a signing key has to become a string somewhere, and
4014
+ * the point of this module is that "somewhere" is here and nowhere else.
4015
+ *
4016
+ * Signing named the member; it does not gate it. **Any in-process use that
4017
+ * needs the raw value without writing it to a runner file is this
4018
+ * operation** — handing a key to a library that takes a plain string. The
4019
+ * dividing line is where the value goes, not what it is for: a value
4020
+ * crossing into a **child's** environment is {@link Secret.forChildEnv},
4021
+ * one written to `GITHUB_STATE` or `GITHUB_OUTPUT` is
4022
+ * {@link Secret.forRunnerFile}, and one that stays in this process is this
4023
+ * member. A third-party SDK that reads only the ambient environment is the
4024
+ * hard case: this package never mutates `process.env` (reads are seeded
4025
+ * once through `ActionEnvironment`), so if a consumer chooses that bridge,
4026
+ * the mutation — and its restore discipline — lives in consumer code as
4027
+ * the consumer's own tradeoff, not as a pattern this member recommends.
4028
+ *
4029
+ * It masks even though the value is never *written* anywhere, and that is
4030
+ * deliberate rather than superstitious: a signing key that leaks does so
4031
+ * through something nobody audited — a debug log of outgoing headers, a
4032
+ * serialized error, a stack trace carrying the closure. Registering it with
4033
+ * the runner's filter costs one call and makes every one of those redacted.
4034
+ *
4035
+ * Call it **once, at layer construction**, not per operation: masking is
4036
+ * idempotent but a workflow command per request is noise in the log.
4037
+ *
4038
+ * **In a detached worker, the mask this member emits is a leak.** The
4039
+ * `::add-mask::` command only masks when the runner parses this process's
4040
+ * stdout; a detached worker's stdout is a log file no runner parses, so
4041
+ * there the command masks nothing *and* spells the plaintext into the log
4042
+ * — a signing key shipped exactly that way for one round. A worker that
4043
+ * signs (an S3-style `BlobStore` is the canonical case) must compose
4044
+ * `ActionOutputs.layerDetached`, under which this member still returns the
4045
+ * raw key for the HMAC but the mask is a documented no-op; the masking
4046
+ * itself is the **parent's** job, done before the spawn via
4047
+ * {@link Secret.forChildEnv} under the real layer.
4048
+ */
4049
+ static readonly forSigning: (secret: Redacted.Redacted<string>) => Effect.Effect<string, never, ActionOutputs>;
4050
+ /**
4051
+ * The far side of a handoff: re-wrap a plaintext environment variable.
4052
+ *
4053
+ * @remarks
4054
+ * A `Config`, so a missing or empty handoff is an honest `ConfigError`
4055
+ * naming the variable rather than an empty `Redacted` that fails much later
4056
+ * as an authentication error.
4057
+ */
4058
+ static readonly adopt: (name: string) => Config.Config<Redacted.Redacted<string>>;
4059
+ }
4060
+ //#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 };
3309
4062
  //# sourceMappingURL=index.d.ts.map