@effected/github-actions 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { ActionEnvironment, ActionEnvironmentError, GitHubContext, RunnerContext } from "./ActionEnvironment.js";
2
2
  import { ActionInput } from "./ActionInput.js";
3
- import { WorkflowCommand } from "./WorkflowCommand.js";
4
3
  import { ActionLogger } from "./ActionLogger.js";
5
4
  import { ActionOutputs, DetachedOutputError, InvalidOutputNameError, OutputEncodeError, RecordedOutput, RunnerFileUnavailableError, RunnerFileWriteError } from "./ActionOutputs.js";
6
5
  import { ActionState, ActionStateError } from "./ActionState.js";
@@ -26,5 +25,6 @@ import { GitHubMarkdown } from "./GitHubMarkdown.js";
26
25
  import { GitHubToken, GitHubTokenError } from "./GitHubToken.js";
27
26
  import { ToolInstaller, ToolInstallerError } from "./ToolInstaller.js";
28
27
  import { AmbientPackageManager, CachedPackageManager, InstalledPackageManager, PackageManagerInstaller, PackageManagerInstallerError } from "./PackageManagerInstaller.js";
28
+ import { WorkflowCommand } from "@effected/github-commands";
29
29
 
30
30
  export { Action, ActionCache, ActionCacheError, ActionEnvironment, ActionEnvironmentError, ActionInput, ActionLogger, ActionOutputs, ActionRuntime, ActionState, ActionStateError, ActionsIdentityToken, ActionsProvenance, AmbientPackageManager, Artifact, ArtifactError, BlobEnvelope, BlobMetadataDecodeError, BlobMetadataEncodeError, BlobStore, BlobStoreError, BlobTransferError, CacheKey, CacheKeyBadPatternError, CacheKeyReadError, CachedPackageManager, CheckDocument, CheckDocumentError, CheckDocumentStamp, CheckReport, CheckState, ChildEnv, DetachedLogUnavailableError, DetachedNotReadyError, DetachedOutputError, DetachedProcess, DetachedSignalFailedError, DetachedSpawnFailedError, DryRun, GitHubCacheBlobStore, GitHubContext, GitHubMarkdown, GitHubToken, GitHubTokenError, InstalledPackageManager, InvalidOutputNameError, InvalidPidError, ManagedDocument, ManagedDocumentError, NotABlobEnvelopeError, OidcClaims, OidcTokenError, OidcTokenIssuer, OutputEncodeError, PackageManagerInstaller, PackageManagerInstallerError, ProcessId, RecordedOutput, RunnerContext, RunnerFileUnavailableError, RunnerFileWriteError, Secret, ToolInstaller, ToolInstallerError, TruncatedBlobEnvelopeError, UnsupportedBlobEnvelopeVersionError, WorkflowCommand, describeCause, projectCheckState };
@@ -2,18 +2,6 @@ import { payloadOf } from "./jwt.js";
2
2
  import { Effect, Option, Redacted, Result } from "effect";
3
3
 
4
4
  //#region src/internal/actionsResults.ts
5
- /**
6
- * Reading the runner's *results backend* coordinates — the two variables the
7
- * cache, artifact and blob-store protocols all speak through.
8
- *
9
- * @remarks
10
- * Shared by `ActionCache`, `Artifact` and `BlobStore.githubCache`, and
11
- * deliberately free of `@azure/storage-blob`: an internal helper is exactly how
12
- * a heavy import leaks into a light module's graph, so the one piece those
13
- * three modules share is the piece with no dependencies.
14
- *
15
- * @internal
16
- */
17
5
  /** The variable naming the backend. Absent outside a `uses:` step. */
18
6
  const RESULTS_URL = "ACTIONS_RESULTS_URL";
19
7
  /** The variable holding the run-scoped credential. Absent outside a `uses:` step. */
@@ -1,72 +1,6 @@
1
1
  import { ChildProcess } from "effect/process";
2
2
 
3
3
  //#region src/internal/archiveCommands.ts
4
- /**
5
- * The archiver command lines this package spawns, spelled once.
6
- *
7
- * @remarks
8
- * `Artifact` (pack and unpack) and `ToolInstaller` (unpack only) each spawn
9
- * the same three shapes — a zip, an unzip, a tar extraction — and each has a
10
- * Windows half that no test on a POSIX host can execute. Two hand-written
11
- * copies drifted: `ToolInstaller.extractZip` learned the overwrite overload
12
- * and the stderr capture below, `Artifact.download` did not, and a download
13
- * into a non-empty directory kept failing on Windows with an EMPTY stderr.
14
- * This module is the one spelling; the tests pin its text verbatim, which
15
- * is the only proof the Windows halves get.
16
- *
17
- * **The pwsh unzip is deliberately belt-and-braces.** The THREE-argument
18
- * `ExtractToDirectory(src, dest, $true)` overload overwrites existing files —
19
- * the two-argument one refuses, which is what turned a repeated extraction
20
- * into a hard failure — and the `$ErrorActionPreference = 'Stop'` plus
21
- * try/catch writes the actual .NET exception text plainly to stderr and
22
- * exits 1. pwsh's own error rendering does not reliably reach a captured
23
- * stream, and an empty complaint costs a source dive to even hypothesize
24
- * about.
25
- *
26
- * **The pwsh zip drives .NET's `ZipFile` directly, not `Compress-Archive`.**
27
- * Microsoft's own reference for `Compress-Archive -Path` states both faults:
28
- * handed individual file paths it stores every entry under its bare file
29
- * name — directory structure is kept only when `-Path` names a directory —
30
- * so `dir\b.txt` landed as `b.txt`, same-named files in different
31
- * directories collided, and a Windows artifact was structurally different
32
- * from the POSIX `zip -qr` one; and `-Path` expands wildcards, so a literal
33
- * `report[1].txt` matched nothing and failed the upload. Stating every entry
34
- * name explicitly through `CreateEntryFromFile(zip, source, entryName)`
35
- * removes both: the source is `Path.Combine(root, rel)`, taken literally,
36
- * and the entry name is `rel` with `\` turned to `/`, which is what `zip`
37
- * records on POSIX.
38
- *
39
- * **The Windows script is constant-size: the file list travels in a manifest
40
- * file, never in `-Command`.** `CreateProcessW` caps the whole command line
41
- * at 32,767 characters, and inlining one `CreateEntryFromFile` statement per
42
- * file (~214 characters each) hit that ceiling at roughly 150 files with
43
- * `ENAMETOOLONG` — fewer than the `Compress-Archive` form it replaced carried.
44
- * The script now reads {@link zipManifest}'s output (one relative path per
45
- * line) with `File.ReadAllLines` and loops. **The two branches deliberately
46
- * differ in mechanism**: POSIX `zip` takes the files as argv, whose ceiling
47
- * (`ARG_MAX`, megabytes on the hosted runners) is far larger, so the only
48
- * file-count ceiling left is that POSIX argv limit. The manifest is written by
49
- * the caller (`Artifact.zip`) as UTF-8 WITHOUT a BOM, which is what
50
- * `ReadAllLines` reads by default. Its BOM detection would consume a BOM
51
- * rather than leak it into the first path (measured), so this is a
52
- * plain-bytes preference, not a correctness guard.
53
- *
54
- * **Every `CreateEntryFromFile` is assigned to `$null`.** Unassigned, pwsh
55
- * writes the returned `ZipArchiveEntry` to stdout — roughly 430 bytes of
56
- * formatted object per file — and `internal/spawn.ts` interleaves stdout with
57
- * stderr into the captured output, which is what `ArtifactError.stderr`
58
- * carries. Left in, that chatter buried the one line that matters, the .NET
59
- * exception, under kilobytes of entry listings.
60
- *
61
- * Every path handed to PowerShell is single-quoted, and a single quote inside
62
- * one is doubled — the only escape a single-quoted PowerShell literal has.
63
- *
64
- * Pure: no filesystem, no environment, no spawn. `internal/spawn.ts` is the
65
- * execution half. Reaches `effect/process` alone, so it is safe for
66
- * any module to import (`__test__/reachability.test.ts`).
67
- *
68
- * @internal
69
- */
70
4
  /** A PowerShell single-quoted literal: `'` becomes `''`, nothing else is special. */
71
5
  const pwshLiteral = (value) => `'${value.replaceAll("'", "''")}'`;
72
6
  /** A non-interactive pwsh invocation of one command line. */
@@ -2,22 +2,6 @@ import { CONFLICT, isOk, stringField } from "./twirp.js";
2
2
  import { Effect, Option } from "effect";
3
3
 
4
4
  //#region src/internal/cacheService.ts
5
- /**
6
- * The Actions `CacheService` choreography — reserve, upload, finalize; look up
7
- * — spelled once for the two services that speak it.
8
- *
9
- * @remarks
10
- * `ActionCache` files a tar archive under a path-derived version and
11
- * `BlobStore.githubCache` files an envelope under a constant one, but the
12
- * three RPCs between them and their conflict semantics are identical. Each
13
- * caller supplies its own per-key `call` and its own `refused` constructor, so
14
- * the errors stay the caller's; this module owns only the protocol. It reaches
15
- * `effect` and the Twirp client and nothing heavier — the Azure transfer of
16
- * the bytes between the two RPCs stays in the three modules licensed to import
17
- * it (`__test__/reachability.test.ts`).
18
- *
19
- * @internal
20
- */
21
5
  /** The Twirp service the Actions cache protocol lives under. @internal */
22
6
  const CACHE_SERVICE = "github.actions.results.api.v1.CacheService";
23
7
  /**
@@ -3,19 +3,6 @@ import { createHash } from "node:crypto";
3
3
  import * as Hex from "effect/encoding/Hex";
4
4
 
5
5
  //#region src/internal/digest.ts
6
- /**
7
- * The sanctioned `node:crypto` digests, spelled once.
8
- *
9
- * @remarks
10
- * Core `Crypto` offers one-shot SHA digests over bytes already in memory, but
11
- * no HMAC and no incremental hasher, so this package's licence for a `node:`
12
- * import covers hashing (see the Module concept in `okf/`). Keeping every
13
- * `createHash` behind this module is what makes that licence auditable in one
14
- * place rather than six, and keeps the streamed file digest — the shape a
15
- * multi-gigabyte toolchain archive needs — from being re-derived per caller.
16
- *
17
- * @internal
18
- */
19
6
  /** The raw SHA-256 of a string or byte array held in memory. @internal */
20
7
  const sha256 = (value) => new Uint8Array(createHash("sha256").update(value).digest());
21
8
  /** The hex SHA-256 of a string or byte array held in memory. @internal */
package/internal/jwt.js CHANGED
@@ -3,23 +3,6 @@ import * as Base64Url from "effect/encoding/Base64Url";
3
3
 
4
4
  //#region src/internal/jwt.ts
5
5
  /**
6
- * The JWT segment codec — read a payload, or frame an unsigned token — for the
7
- * two places this package reads a runner-issued token's claims.
8
- *
9
- * @remarks
10
- * **No signature verification, deliberately.** Every token read here was
11
- * handed to this process by the runner that started it, and the claim read
12
- * scopes a request (`actionsResults`) or is republished for a verifier
13
- * elsewhere (`OidcTokenIssuer`); neither authorizes anything on the strength
14
- * of the claim alone. Do not "fix" it here.
15
- *
16
- * Core `Base64Url` does the base64url work, so no `Buffer` is involved: it is
17
- * strict about the alphabet where Node's decoder is forgiving, which is why
18
- * there is a test whose payload actually contains `-` and `_`.
19
- *
20
- * @internal
21
- */
22
- /**
23
6
  * The decoded payload of `token` — the middle segment, base64url JSON —
24
7
  * without any check on the signature.
25
8
  *
@@ -3,21 +3,6 @@ import * as Hex from "effect/encoding/Hex";
3
3
  import * as Base64 from "effect/encoding/Base64";
4
4
 
5
5
  //#region src/internal/pnpmExe.ts
6
- /**
7
- * The pnpm native-binary layout: from pnpm 12 the `pnpm` registry package is a
8
- * thin wrapper whose `pnpm` bin is a shebang-less placeholder that the
9
- * package's own install script overwrites with the host's native executable,
10
- * fetched as an optional dependency named `@pnpm/exe.<os>-<arch>[-musl]`.
11
- * The installer never runs lifecycle scripts, so it performs that overlay
12
- * itself; the pure pieces of that live here, where they are testable without
13
- * a tarball.
14
- *
15
- * Detection is by LAYOUT (the manifest's `optionalDependencies` naming an
16
- * `@pnpm/exe.*` package), never by major version — pnpm 11 and earlier ship
17
- * no such dependency and keep their Node entry points.
18
- *
19
- * @internal
20
- */
21
6
  /** The prefix every native-binary package name carries. @internal */
22
7
  const PNPM_EXE_PREFIX = "@pnpm/exe.";
23
8
  /**
@@ -1,20 +1,4 @@
1
1
  //#region src/internal/runnerFile.ts
2
- /**
3
- * The runner-file heredoc protocol — `GITHUB_OUTPUT`, `GITHUB_ENV` and
4
- * `GITHUB_STATE` all take `name<<DELIM\nvalue\nDELIM\n` — spelled once for
5
- * `ActionOutputs` and `ActionState`.
6
- *
7
- * @remarks
8
- * **Delimiters are derived, never random.** GitHub's own toolkit uses a
9
- * random UUID here and accepts the (tiny) chance of collision. Deriving the
10
- * delimiter instead makes collision **impossible** rather than improbable,
11
- * needs no randomness — so no `Crypto` in `R` and no unreproducible output —
12
- * and is trivially testable. A value that contains a delimiter would
13
- * terminate its block early and corrupt every entry after it, which is a
14
- * value-controlled injection into the runner's own file.
15
- *
16
- * @internal
17
- */
18
2
  /** The base delimiter for a heredoc block. */
19
3
  const BASE_DELIMITER = "EFFECTED_EOF";
20
4
  /** A delimiter guaranteed absent from `value`. @internal */
package/internal/sigv4.js CHANGED
@@ -45,10 +45,7 @@ const timestamps = (now) => {
45
45
  * @remarks
46
46
  * Exported separately from {@link sign} so a test can pin it against AWS's
47
47
  * **own documented** canonical request rather than against this
48
- * implementation's output. That distinction is not pedantry: the first draft of
49
- * this module was checked against a remembered signature constant that turned
50
- * out to be from a different example, and the only thing that settled which side
51
- * was wrong was reproducing a value AWS publishes.
48
+ * implementation's output.
52
49
  */
53
50
  const canonicalize = (request, credentials) => {
54
51
  const { amzDate, dateStamp } = timestamps(request.now);
@@ -2,18 +2,6 @@ import { Effect } from "effect";
2
2
 
3
3
  //#region src/internal/unstubbed.ts
4
4
  /**
5
- * The one way an unstubbed test-double member dies, for every `makeTest` in
6
- * this package.
7
- *
8
- * @remarks
9
- * A double's unstubbed member must die rather than silently succeed — a stub
10
- * that returns nothing teaches a test that nothing happened. The message names
11
- * the double, the member and the override that fixes it, and it is spelled
12
- * here once so a wording change is one edit rather than ten.
13
- *
14
- * @internal
15
- */
16
- /**
17
5
  * The die-on-call members of `<double>.makeTest`: `dies("save")` is an
18
6
  * `Effect` that defects naming `save` when run. Each package double binds its
19
7
  * own name once, then lists its members.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effected/github-actions",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "private": false,
5
5
  "description": "The GitHub Actions runtime: env, inputs, outputs, state, logging, cache, artifacts, blob storage, OIDC and the token bridge.",
6
6
  "keywords": [
@@ -39,18 +39,19 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@azure/storage-blob": "^12.33.0",
42
- "@effected/github": "^0.14.0",
43
- "@effected/glob": "^0.9.0",
44
- "@effected/markdown": "^0.14.0",
45
- "@effected/npm": "^0.19.0",
46
- "@effected/sbom": "^0.9.1",
47
- "@effected/semver": "^0.10.1",
48
- "@effected/templates": "^0.9.1",
49
- "@effected/walker": "^0.13.0"
42
+ "@effected/github": "^0.15.0",
43
+ "@effected/github-commands": "^0.1.0",
44
+ "@effected/glob": "^0.10.0",
45
+ "@effected/markdown": "^0.15.0",
46
+ "@effected/npm": "^0.20.0",
47
+ "@effected/sbom": "^0.10.0",
48
+ "@effected/semver": "^0.11.0",
49
+ "@effected/templates": "^0.10.0",
50
+ "@effected/walker": "^0.15.0"
50
51
  },
51
52
  "peerDependencies": {
52
- "@effect/platform-node": "4.0.0-rc.118",
53
- "effect": "4.0.0-rc.118"
53
+ "@effect/platform-node": "^4.0.0",
54
+ "effect": "^4.0.0"
54
55
  },
55
56
  "engines": {
56
57
  "node": ">=24.11.0"
@@ -1,100 +0,0 @@
1
- //#region src/WorkflowCommand.ts
2
- /**
3
- * Escape a command's message.
4
- *
5
- * @remarks
6
- * The percent sign is replaced **first**, and the order is load-bearing: doing
7
- * it last would re-escape the `%` of an escape sequence this function just
8
- * produced, turning `%0A` into `%250A`.
9
- */
10
- const escapeMessage = (value) => value.replaceAll("%", "%25").replaceAll("\r", "%0D").replaceAll("\n", "%0A");
11
- /**
12
- * Escape a property value.
13
- *
14
- * @remarks
15
- * Everything a message escapes, plus `:` and `,` — the two characters that
16
- * delimit the property list and terminate it. An unescaped one truncates the
17
- * command or lets a value be read as a new property.
18
- */
19
- const escapeProperty = (value) => escapeMessage(value).replaceAll(":", "%3A").replaceAll(",", "%2C");
20
- /**
21
- * The GitHub Actions workflow-command wire protocol: `::name key=value::message`.
22
- *
23
- * @remarks
24
- * **Pure.** This module renders strings and nothing else — it performs no IO
25
- * and holds no service — which is what makes the escaping rules testable
26
- * without a runner, and what lets a non-Actions consumer reuse the protocol.
27
- * Writing a rendered command to stdout is {@link ActionOutputs}'s job.
28
- *
29
- * The escaping is the whole point of the module. A message carrying a raw
30
- * newline does not merely render oddly: the runner reads the text after it as
31
- * a **new command**, so an unescaped log line is a command-injection vector.
32
- *
33
- * @example
34
- * ```ts
35
- * import { WorkflowCommand } from "@effected/github-actions";
36
- *
37
- * WorkflowCommand.error("build failed", { file: "src/main.ts", startLine: 12 });
38
- * // "::error file=src/main.ts,line=12::build failed"
39
- * ```
40
- *
41
- * @public
42
- */
43
- var WorkflowCommand = class WorkflowCommand {
44
- constructor() {}
45
- /**
46
- * Render an arbitrary command. The primitive every other member uses.
47
- *
48
- * @remarks
49
- * The property type is written out structurally rather than as the module's
50
- * `CommandProperties` alias: an internal named type on a `@public` signature
51
- * fails the API Extractor gate, and neither an `@internal` tag nor a second
52
- * alias helps — only inlining does.
53
- */
54
- static render(name, properties, message) {
55
- const rendered = Object.entries(properties).filter((entry) => entry[1] !== void 0).map(([key, value]) => `${key}=${escapeProperty(String(value))}`).join(",");
56
- return `${rendered === "" ? `::${name}::` : `::${name} ${rendered}::`}${escapeMessage(message)}`;
57
- }
58
- /** `::debug::` — shown only when `ACTIONS_STEP_DEBUG` is enabled. */
59
- static debug(message) {
60
- return WorkflowCommand.render("debug", {}, message);
61
- }
62
- /** `::notice::` with optional source annotation. */
63
- static notice(message, properties = {}) {
64
- return WorkflowCommand.render("notice", WorkflowCommand.annotation(properties), message);
65
- }
66
- /** `::warning::` with optional source annotation. */
67
- static warning(message, properties = {}) {
68
- return WorkflowCommand.render("warning", WorkflowCommand.annotation(properties), message);
69
- }
70
- /** `::error::` with optional source annotation. */
71
- static error(message, properties = {}) {
72
- return WorkflowCommand.render("error", WorkflowCommand.annotation(properties), message);
73
- }
74
- /** `::group::` — opens a collapsible section in the runner log. */
75
- static group(name) {
76
- return WorkflowCommand.render("group", {}, name);
77
- }
78
- /** `::endgroup::` — closes the innermost open group. */
79
- static endGroup() {
80
- return WorkflowCommand.render("endgroup", {}, "");
81
- }
82
- /** `::add-mask::` — registers a value for redaction in the runner log. */
83
- static addMask(value) {
84
- return WorkflowCommand.render("add-mask", {}, value);
85
- }
86
- /** Map readable annotation fields onto GitHub's abbreviated wire names. */
87
- static annotation(properties) {
88
- return {
89
- title: properties.title,
90
- file: properties.file,
91
- line: properties.startLine,
92
- endLine: properties.endLine,
93
- col: properties.startColumn,
94
- endColumn: properties.endColumn
95
- };
96
- }
97
- };
98
-
99
- //#endregion
100
- export { WorkflowCommand };