@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/ActionCache.js +128 -17
- package/ActionEnvironment.js +34 -1
- package/ActionInput.js +61 -8
- package/ActionOutputs.js +71 -2
- package/ActionState.js +33 -6
- package/Artifact.js +2 -2
- package/BlobStore.githubCache.js +2 -1
- package/CacheKey.js +147 -8
- package/ChildEnv.js +124 -0
- package/PackageManagerInstaller.js +494 -0
- package/Secret.js +21 -0
- package/ToolInstaller.js +98 -52
- package/index.d.ts +878 -125
- package/index.js +3 -1
- package/package.json +5 -4
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
|
-
/**
|
|
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. `
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
743
|
-
* three-segment key falls back to two prefixes and a
|
|
744
|
-
* back to nothing — there is no rung that would match
|
|
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
|
|
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
|
-
*
|
|
1127
|
-
*
|
|
1128
|
-
*
|
|
1129
|
-
*
|
|
1130
|
-
*
|
|
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
|
-
/**
|
|
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,
|
|
3259
|
-
* `extractTar` → `cacheDir` in whatever order its tool
|
|
3260
|
-
* orders genuinely differ (a
|
|
3261
|
-
*
|
|
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
|
-
|
|
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
|