@effected/github-actions 0.12.0 → 0.13.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/ActionOutputs.js CHANGED
@@ -103,6 +103,44 @@ const delimiterFor = (value) => {
103
103
  };
104
104
  /** A name that would break the block structure it lives in. */
105
105
  const isUsableName = (name) => name !== "" && !/[\r\n]/.test(name);
106
+ /**
107
+ * The JSON text `setJson` publishes for `value`: encoded through `schema`,
108
+ * then stringified. The one step of `setJson` that can fail, shared by the
109
+ * real layer and both test doubles so that none of them can skip it.
110
+ */
111
+ const encodeJson = (name, value, schema) => Schema.encodeUnknownEffect(schema)(value).pipe(Effect.mapError((cause) => new OutputEncodeError({
112
+ name,
113
+ cause
114
+ })), Effect.map((encoded) => JSON.stringify(encoded)));
115
+ /**
116
+ * One call an {@link ActionOutputs.recording} double observed.
117
+ *
118
+ * @remarks
119
+ * `value` is always the string the runner would have seen: for `setJson` that
120
+ * is the **encoded** JSON text, after the same schema encode the real layer
121
+ * performs, so a test asserting on it reads exactly what a later step's
122
+ * `steps.<id>.outputs.<name>` expression would. `name` is present for the
123
+ * members that take one (`set`, `setJson`, `exportVariable`) and absent
124
+ * otherwise.
125
+ *
126
+ * @public
127
+ */
128
+ var RecordedOutput = class extends Schema.Class("RecordedOutput")({
129
+ /** Which {@link ActionOutputsShape} member was called. */
130
+ member: Schema.Literals([
131
+ "set",
132
+ "setJson",
133
+ "summary",
134
+ "exportVariable",
135
+ "addPath",
136
+ "setFailed",
137
+ "setSecret"
138
+ ]),
139
+ /** The output or variable name, for the members that take one. */
140
+ name: Schema.optionalKey(Schema.String),
141
+ /** What was published — value, message, path, content or secret — as the runner would have read it. */
142
+ value: Schema.String
143
+ }) {};
106
144
  const make = Effect.gen(function* () {
107
145
  const env = yield* ActionEnvironment;
108
146
  const fs = yield* FileSystem.FileSystem;
@@ -124,10 +162,7 @@ const make = Effect.gen(function* () {
124
162
  const set = (name, value) => appendBlock("GITHUB_OUTPUT", name, value);
125
163
  return {
126
164
  set,
127
- setJson: (name, value, schema) => Schema.encodeUnknownEffect(schema)(value).pipe(Effect.mapError((cause) => new OutputEncodeError({
128
- name,
129
- cause
130
- })), Effect.flatMap((encoded) => set(name, JSON.stringify(encoded)))),
165
+ setJson: (name, value, schema) => encodeJson(name, value, schema).pipe(Effect.flatMap((json) => set(name, json))),
131
166
  summary: (content) => append("GITHUB_STEP_SUMMARY", content),
132
167
  exportVariable: (name, value) => appendBlock("GITHUB_ENV", name, value),
133
168
  addPath: (path) => append("GITHUB_PATH", `${path}\n`),
@@ -211,20 +246,90 @@ var ActionOutputs = class ActionOutputs extends Context.Service()("@effected/git
211
246
  setFailed: (message) => Console.error(message),
212
247
  setSecret: () => Effect.void
213
248
  });
214
- /** A test double. Unstubbed members die rather than silently succeeding. */
215
- static makeTest = (overrides = {}) => ({
216
- set: () => Effect.sync(() => unimplemented("set")),
217
- setJson: () => Effect.sync(() => unimplemented("setJson")),
218
- summary: () => Effect.sync(() => unimplemented("summary")),
219
- exportVariable: () => Effect.sync(() => unimplemented("exportVariable")),
220
- addPath: () => Effect.sync(() => unimplemented("addPath")),
221
- setFailed: () => Effect.sync(() => unimplemented("setFailed")),
222
- setSecret: () => Effect.sync(() => unimplemented("setSecret")),
223
- ...overrides
224
- });
249
+ /**
250
+ * A test double. Unstubbed members die rather than silently succeeding.
251
+ *
252
+ * @remarks
253
+ * **`setJson` always encodes first.** The value is encoded through its
254
+ * schema exactly as the real layer does — failing typed with
255
+ * {@link OutputEncodeError} — and only then is a supplied `setJson`
256
+ * override called, with the original `(name, value, schema)`. An override
257
+ * that accepts `schema` and ignores it therefore **cannot disable
258
+ * output-schema checking**: a value/schema drift is a typed failure under
259
+ * this double whether or not the override looks at the schema. Without an
260
+ * override, a valid value still dies unimplemented as every other unstubbed
261
+ * member does. Most tests want neither: reach for
262
+ * {@link ActionOutputs.recording} instead.
263
+ */
264
+ static makeTest = (overrides = {}) => {
265
+ const setJson = overrides.setJson ?? (() => Effect.sync(() => unimplemented("setJson")));
266
+ return {
267
+ set: () => Effect.sync(() => unimplemented("set")),
268
+ summary: () => Effect.sync(() => unimplemented("summary")),
269
+ exportVariable: () => Effect.sync(() => unimplemented("exportVariable")),
270
+ addPath: () => Effect.sync(() => unimplemented("addPath")),
271
+ setFailed: () => Effect.sync(() => unimplemented("setFailed")),
272
+ setSecret: () => Effect.sync(() => unimplemented("setSecret")),
273
+ ...overrides,
274
+ setJson: (name, value, schema) => encodeJson(name, value, schema).pipe(Effect.flatMap(() => setJson(name, value, schema)))
275
+ };
276
+ };
225
277
  /** {@link ActionOutputs.makeTest} behind `Layer.succeed`. */
226
278
  static layerTest = (overrides = {}) => Layer.succeed(ActionOutputs, ActionOutputs.makeTest(overrides));
279
+ /**
280
+ * A recording double: every member appends a {@link RecordedOutput} to a
281
+ * journal the test reads back, in call order.
282
+ *
283
+ * @remarks
284
+ * This is the double most tests of an action want — "what did it publish?"
285
+ * — shipped so that nobody hand-writes a `setJson` override that drops the
286
+ * schema encode (the trap `makeTest` now closes too). `setJson` encodes
287
+ * through its schema exactly as the real layer does, failing typed with
288
+ * {@link OutputEncodeError} and recording nothing on a drift, and records
289
+ * the **encoded JSON text** — what the runner would have read — not the
290
+ * decoded value. `set`, `exportVariable` and `setJson` refuse an unusable
291
+ * name with {@link InvalidOutputNameError}, as the real layer does, rather
292
+ * than recording it.
293
+ *
294
+ * `setSecret` records the secret's **plaintext** under
295
+ * `member: "setSecret"`. That is deliberate for a recording double: the
296
+ * journal never leaves the test, and a test asserting that a value *was*
297
+ * masked needs to see which one. Do not hand this journal to anything that
298
+ * logs.
299
+ *
300
+ * Each call returns a fresh, independent journal — there is no state shared
301
+ * between two `recording()` calls.
302
+ */
303
+ static recording = () => {
304
+ const entries = [];
305
+ const record = (member, value, name) => Effect.sync(() => {
306
+ entries.push(RecordedOutput.make({
307
+ member,
308
+ value,
309
+ ...name === void 0 ? {} : { name }
310
+ }));
311
+ });
312
+ const recordNamed = (member, file, name, value) => isUsableName(name) ? record(member, value, name) : Effect.fail(new InvalidOutputNameError({
313
+ file,
314
+ name
315
+ }));
316
+ return {
317
+ layer: Layer.succeed(ActionOutputs, {
318
+ set: (name, value) => recordNamed("set", "GITHUB_OUTPUT", name, value),
319
+ setJson: (name, value, schema) => isUsableName(name) ? encodeJson(name, value, schema).pipe(Effect.flatMap((json) => record("setJson", json, name))) : Effect.fail(new InvalidOutputNameError({
320
+ file: "GITHUB_OUTPUT",
321
+ name
322
+ })),
323
+ summary: (content) => record("summary", content),
324
+ exportVariable: (name, value) => recordNamed("exportVariable", "GITHUB_ENV", name, value),
325
+ addPath: (path) => record("addPath", path),
326
+ setFailed: (message) => record("setFailed", message),
327
+ setSecret: (value) => record("setSecret", value)
328
+ }),
329
+ entries: () => entries.slice()
330
+ };
331
+ };
227
332
  };
228
333
 
229
334
  //#endregion
230
- export { ActionOutputs, DetachedOutputError, InvalidOutputNameError, OutputEncodeError, RunnerFileUnavailableError, RunnerFileWriteError };
335
+ export { ActionOutputs, DetachedOutputError, InvalidOutputNameError, OutputEncodeError, RecordedOutput, RunnerFileUnavailableError, RunnerFileWriteError };
package/index.d.ts CHANGED
@@ -586,6 +586,46 @@ export declare class DetachedOutputError extends DetachedOutputError_base {
586
586
  * @public
587
587
  */
588
588
  type ActionOutputError = RunnerFileUnavailableError | RunnerFileWriteError | InvalidOutputNameError | OutputEncodeError | DetachedOutputError;
589
+ declare const RecordedOutput_base: Schema.Class<RecordedOutput, Schema.Struct<{
590
+ /** Which {@link ActionOutputsShape} member was called. */
591
+ readonly member: Schema.Literals<readonly ["set", "setJson", "summary", "exportVariable", "addPath", "setFailed", "setSecret"]>;
592
+ /** The output or variable name, for the members that take one. */
593
+ readonly name: Schema.optionalKey<Schema.String>;
594
+ /** What was published — value, message, path, content or secret — as the runner would have read it. */
595
+ readonly value: Schema.String;
596
+ }>, {}>;
597
+ /**
598
+ * One call an {@link ActionOutputs.recording} double observed.
599
+ *
600
+ * @remarks
601
+ * `value` is always the string the runner would have seen: for `setJson` that
602
+ * is the **encoded** JSON text, after the same schema encode the real layer
603
+ * performs, so a test asserting on it reads exactly what a later step's
604
+ * `steps.<id>.outputs.<name>` expression would. `name` is present for the
605
+ * members that take one (`set`, `setJson`, `exportVariable`) and absent
606
+ * otherwise.
607
+ *
608
+ * @public
609
+ */
610
+ export declare class RecordedOutput extends RecordedOutput_base {}
611
+ /**
612
+ * The member names a {@link RecordedOutput} can carry.
613
+ *
614
+ * @public
615
+ */
616
+ type RecordedOutputMember = RecordedOutput["member"];
617
+ /**
618
+ * What {@link ActionOutputs.recording} returns: the layer to provide and the
619
+ * journal it fills.
620
+ *
621
+ * @public
622
+ */
623
+ interface RecordingOutputs {
624
+ /** An {@link ActionOutputs} whose every member records into this journal. */
625
+ readonly layer: Layer.Layer<ActionOutputs>;
626
+ /** A snapshot of every recorded call so far, in call order. */
627
+ readonly entries: () => ReadonlyArray<RecordedOutput>;
628
+ }
589
629
  /**
590
630
  * The {@link ActionOutputs} service shape.
591
631
  *
@@ -674,10 +714,49 @@ export declare class ActionOutputs extends ActionOutputs_base {
674
714
  * channel to fail through.
675
715
  */
676
716
  static readonly layerDetached: Layer.Layer<ActionOutputs>;
677
- /** A test double. Unstubbed members die rather than silently succeeding. */
717
+ /**
718
+ * A test double. Unstubbed members die rather than silently succeeding.
719
+ *
720
+ * @remarks
721
+ * **`setJson` always encodes first.** The value is encoded through its
722
+ * schema exactly as the real layer does — failing typed with
723
+ * {@link OutputEncodeError} — and only then is a supplied `setJson`
724
+ * override called, with the original `(name, value, schema)`. An override
725
+ * that accepts `schema` and ignores it therefore **cannot disable
726
+ * output-schema checking**: a value/schema drift is a typed failure under
727
+ * this double whether or not the override looks at the schema. Without an
728
+ * override, a valid value still dies unimplemented as every other unstubbed
729
+ * member does. Most tests want neither: reach for
730
+ * {@link ActionOutputs.recording} instead.
731
+ */
678
732
  static readonly makeTest: (overrides?: Partial<ActionOutputsShape>) => ActionOutputsShape;
679
733
  /** {@link ActionOutputs.makeTest} behind `Layer.succeed`. */
680
734
  static readonly layerTest: (overrides?: Partial<ActionOutputsShape>) => Layer.Layer<ActionOutputs>;
735
+ /**
736
+ * A recording double: every member appends a {@link RecordedOutput} to a
737
+ * journal the test reads back, in call order.
738
+ *
739
+ * @remarks
740
+ * This is the double most tests of an action want — "what did it publish?"
741
+ * — shipped so that nobody hand-writes a `setJson` override that drops the
742
+ * schema encode (the trap `makeTest` now closes too). `setJson` encodes
743
+ * through its schema exactly as the real layer does, failing typed with
744
+ * {@link OutputEncodeError} and recording nothing on a drift, and records
745
+ * the **encoded JSON text** — what the runner would have read — not the
746
+ * decoded value. `set`, `exportVariable` and `setJson` refuse an unusable
747
+ * name with {@link InvalidOutputNameError}, as the real layer does, rather
748
+ * than recording it.
749
+ *
750
+ * `setSecret` records the secret's **plaintext** under
751
+ * `member: "setSecret"`. That is deliberate for a recording double: the
752
+ * journal never leaves the test, and a test asserting that a value *was*
753
+ * masked needs to see which one. Do not hand this journal to anything that
754
+ * logs.
755
+ *
756
+ * Each call returns a fresh, independent journal — there is no state shared
757
+ * between two `recording()` calls.
758
+ */
759
+ static readonly recording: () => RecordingOutputs;
681
760
  }
682
761
  //#endregion
683
762
  //#region src/ActionState.d.ts
@@ -4791,5 +4870,5 @@ export declare class Secret {
4791
4870
  static readonly adopt: (name: string) => Config.Config<Redacted.Redacted<string>>;
4792
4871
  }
4793
4872
  //#endregion
4794
- export type { ActionCacheShape, ActionEnvironmentShape, ActionLoggerShape, ActionOutputError, ActionOutputsShape, ActionRunOptions, ActionServices, ActionStateShape, AnnotationProperties, ArtifactItem, ArtifactRef, ArtifactShape, BlobEnvelopeError, BlobStoreShape, CacheKeyError, CheckDocumentOptions, CheckDocumentShape, CheckDocumentSink, CheckFlushOutcome, CheckRunConclusion, CheckRunProjection, ClientLayerOptions, DataBlobTransfer, DetachedProcessError, DetachedProcessOps, DetachedSpawnOptions, DownloadOptions, DownloadResult, DryRunShape, ExtractOptions, FileBlobTransfer, GitHubHeadingDepth, GitHubListOptions, GitHubRowSchema, GitHubSchemaTable, GitHubSchemaTableColumn, GitHubSchemaTableColumns, GitHubSchemaTableFormatRequiredKeys, GitHubSchemaTableFormattedColumn, GitHubSchemaTableOptions, ManagedDocumentSource, OidcTokenIssuerShape, PackageManagerInstallOptions, PackageManagerInstallerShape, PairsOptions, PathPrependEnv, PathPrependOptions, ProvisionFileOptions, ProvisionOptions, ProvisionedFile, ReadOptions, ReadinessOptions, S3Config, StoredBlob, ToolDownloadOptions, ToolInstallerShape, UploadOptions, UploadResult, WithBufferOptions, WithStepOptions };
4873
+ export type { ActionCacheShape, ActionEnvironmentShape, ActionLoggerShape, ActionOutputError, ActionOutputsShape, ActionRunOptions, ActionServices, ActionStateShape, AnnotationProperties, ArtifactItem, ArtifactRef, ArtifactShape, BlobEnvelopeError, BlobStoreShape, CacheKeyError, CheckDocumentOptions, CheckDocumentShape, CheckDocumentSink, CheckFlushOutcome, CheckRunConclusion, CheckRunProjection, ClientLayerOptions, DataBlobTransfer, DetachedProcessError, DetachedProcessOps, DetachedSpawnOptions, DownloadOptions, DownloadResult, DryRunShape, ExtractOptions, FileBlobTransfer, GitHubHeadingDepth, GitHubListOptions, GitHubRowSchema, GitHubSchemaTable, GitHubSchemaTableColumn, GitHubSchemaTableColumns, GitHubSchemaTableFormatRequiredKeys, GitHubSchemaTableFormattedColumn, GitHubSchemaTableOptions, ManagedDocumentSource, OidcTokenIssuerShape, PackageManagerInstallOptions, PackageManagerInstallerShape, PairsOptions, PathPrependEnv, PathPrependOptions, ProvisionFileOptions, ProvisionOptions, ProvisionedFile, ReadOptions, ReadinessOptions, RecordedOutputMember, RecordingOutputs, S3Config, StoredBlob, ToolDownloadOptions, ToolInstallerShape, UploadOptions, UploadResult, WithBufferOptions, WithStepOptions };
4795
4874
  //# sourceMappingURL=index.d.ts.map
package/index.js CHANGED
@@ -2,7 +2,7 @@ import { ActionEnvironment, ActionEnvironmentError, GitHubContext, RunnerContext
2
2
  import { ActionInput } from "./ActionInput.js";
3
3
  import { WorkflowCommand } from "./WorkflowCommand.js";
4
4
  import { ActionLogger } from "./ActionLogger.js";
5
- import { ActionOutputs, DetachedOutputError, InvalidOutputNameError, OutputEncodeError, RunnerFileUnavailableError, RunnerFileWriteError } from "./ActionOutputs.js";
5
+ import { ActionOutputs, DetachedOutputError, InvalidOutputNameError, OutputEncodeError, RecordedOutput, RunnerFileUnavailableError, RunnerFileWriteError } from "./ActionOutputs.js";
6
6
  import { ActionState, ActionStateError } from "./ActionState.js";
7
7
  import { Action, ActionRuntime, describeCause } from "./Action.js";
8
8
  import { BlobTransferError } from "./BlobTransfer.js";
@@ -27,4 +27,4 @@ import { GitHubToken, GitHubTokenError } from "./GitHubToken.js";
27
27
  import { ToolInstaller, ToolInstallerError } from "./ToolInstaller.js";
28
28
  import { AmbientPackageManager, CachedPackageManager, InstalledPackageManager, PackageManagerInstaller, PackageManagerInstallerError } from "./PackageManagerInstaller.js";
29
29
 
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, RunnerContext, RunnerFileUnavailableError, RunnerFileWriteError, Secret, ToolInstaller, ToolInstallerError, TruncatedBlobEnvelopeError, UnsupportedBlobEnvelopeVersionError, WorkflowCommand, describeCause, projectCheckState };
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effected/github-actions",
3
- "version": "0.12.0",
3
+ "version": "0.13.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,11 +39,11 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@azure/storage-blob": "^12.33.0",
42
- "@effected/github": "^0.10.0",
42
+ "@effected/github": "^0.10.1",
43
43
  "@effected/glob": "^0.6.0",
44
44
  "@effected/markdown": "^0.10.0",
45
45
  "@effected/npm": "^0.14.0",
46
- "@effected/sbom": "^0.6.0",
46
+ "@effected/sbom": "^0.6.1",
47
47
  "@effected/templates": "^0.6.0"
48
48
  },
49
49
  "peerDependencies": {