@savvy-web/github-action-effects 0.1.0 → 0.2.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/README.md +19 -41
- package/index.d.ts +255 -73
- package/index.js +254 -102
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,18 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@savvy-web/github-action-effects)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
inputs, structured logging with buffered output, and type-safe outputs through
|
|
8
|
-
composable service layers.
|
|
7
|
+
Composable [Effect](https://effect.website) services for building Node.js 24 GitHub Actions with schema-validated inputs, structured logging, typed outputs and multi-phase state management without the boilerplate.
|
|
9
8
|
|
|
10
9
|
## Features
|
|
11
10
|
|
|
12
|
-
- **Schema-validated inputs and
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
11
|
+
- **Schema-validated inputs** — read, parse, and validate action inputs with Effect Schema
|
|
12
|
+
- **Structured logging** — three-tier logger (info/verbose/debug) with buffer-on-failure
|
|
13
|
+
- **Typed outputs** — set outputs, export variables, and write GFM job summaries
|
|
14
|
+
- **Multi-phase state** — transfer schema-serialized state across pre/main/post phases
|
|
15
|
+
- **In-memory test layers** — test every service without mocking `@actions/core`
|
|
17
16
|
|
|
18
17
|
## Installation
|
|
19
18
|
|
|
@@ -24,49 +23,28 @@ npm install @savvy-web/github-action-effects effect @actions/core
|
|
|
24
23
|
## Quick Start
|
|
25
24
|
|
|
26
25
|
```typescript
|
|
27
|
-
import { Effect,
|
|
28
|
-
import {
|
|
29
|
-
ActionInputs,
|
|
30
|
-
ActionInputsLive,
|
|
31
|
-
ActionOutputs,
|
|
32
|
-
ActionOutputsLive,
|
|
33
|
-
ActionLoggerLive,
|
|
34
|
-
ActionLoggerLayer,
|
|
35
|
-
LogLevelInput,
|
|
36
|
-
resolveLogLevel,
|
|
37
|
-
setLogLevel,
|
|
38
|
-
table,
|
|
39
|
-
} from "@savvy-web/github-action-effects";
|
|
26
|
+
import { Effect, Schema } from "effect";
|
|
27
|
+
import { Action, ActionInputs, ActionOutputs } from "@savvy-web/github-action-effects";
|
|
40
28
|
|
|
41
29
|
const program = Effect.gen(function* () {
|
|
42
30
|
const inputs = yield* ActionInputs;
|
|
43
31
|
const outputs = yield* ActionOutputs;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
yield* setLogLevel(resolveLogLevel(level));
|
|
47
|
-
|
|
48
|
-
const name = yield* inputs.get("name", Schema.String);
|
|
49
|
-
yield* outputs.set("greeting", `Hello, ${name}!`);
|
|
50
|
-
yield* outputs.summary(table(["Input", "Value"], [["name", name]]));
|
|
32
|
+
const name = yield* inputs.get("package-name", Schema.String);
|
|
33
|
+
yield* outputs.set("result", `checked ${name}`);
|
|
51
34
|
});
|
|
52
35
|
|
|
53
|
-
|
|
54
|
-
ActionInputsLive,
|
|
55
|
-
ActionOutputsLive,
|
|
56
|
-
ActionLoggerLive,
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
program.pipe(
|
|
60
|
-
Effect.provide(ActionLoggerLayer),
|
|
61
|
-
Effect.provide(MainLive),
|
|
62
|
-
Effect.runPromise,
|
|
63
|
-
);
|
|
36
|
+
Action.run(program);
|
|
64
37
|
```
|
|
65
38
|
|
|
39
|
+
`Action.run` provides all core service layers, installs the Effect logger, and catches errors with `core.setFailed` automatically.
|
|
40
|
+
|
|
41
|
+
See the [full walkthrough](./docs/example-action.md) for log level configuration, batch input reading, GFM summaries, multi-phase state, and error handling.
|
|
42
|
+
|
|
66
43
|
## Documentation
|
|
67
44
|
|
|
68
|
-
|
|
69
|
-
[
|
|
45
|
+
- [Example Action](./docs/example-action.md) — end-to-end tutorial
|
|
46
|
+
- [Architecture](./docs/architecture.md) — API reference and layer composition
|
|
47
|
+
- [Testing](./docs/testing.md) — testing with in-memory layers
|
|
70
48
|
|
|
71
49
|
## License
|
|
72
50
|
|
package/index.d.ts
CHANGED
|
@@ -13,11 +13,68 @@ import { Effect } from 'effect';
|
|
|
13
13
|
import { Equals } from 'effect/Types';
|
|
14
14
|
import { FiberRef } from 'effect';
|
|
15
15
|
import { Layer } from 'effect';
|
|
16
|
-
import { Logger } from 'effect';
|
|
16
|
+
import { Logger } from 'effect/Logger';
|
|
17
17
|
import type { Option } from 'effect';
|
|
18
18
|
import { Schema } from 'effect';
|
|
19
19
|
import { YieldableError } from 'effect/Cause';
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Namespace for top-level GitHub Action helpers.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* import { Effect } from "effect"
|
|
27
|
+
* import { Action, ActionInputs, ActionLogger } from "@savvy-web/github-action-effects"
|
|
28
|
+
*
|
|
29
|
+
* const program = Effect.gen(function* () {
|
|
30
|
+
* const inputs = yield* ActionInputs
|
|
31
|
+
* const logger = yield* ActionLogger
|
|
32
|
+
* // ... your action logic
|
|
33
|
+
* })
|
|
34
|
+
*
|
|
35
|
+
* Action.run(program)
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @public
|
|
39
|
+
*/
|
|
40
|
+
export declare const Action: {
|
|
41
|
+
/**
|
|
42
|
+
* Run a GitHub Action program with standard boilerplate handled.
|
|
43
|
+
*
|
|
44
|
+
* Handles:
|
|
45
|
+
* - Providing all standard Live layers (ActionInputs, ActionLogger, ActionOutputs)
|
|
46
|
+
* - Installing ActionLoggerLayer (routes Effect.log to core.info/debug)
|
|
47
|
+
* - Catching all errors and calling `core.setFailed`
|
|
48
|
+
* - Running with `Effect.runPromise`
|
|
49
|
+
*
|
|
50
|
+
* Returns a Promise that resolves when the action completes. In production
|
|
51
|
+
* the return value can be ignored (fire-and-forget). In tests, await it
|
|
52
|
+
* to avoid timing issues.
|
|
53
|
+
*/
|
|
54
|
+
readonly run: {
|
|
55
|
+
<E>(program: Effect.Effect<void, E, CoreServices>): Promise<void>;
|
|
56
|
+
<E, R>(program: Effect.Effect<void, E, R | CoreServices>, layer: Layer.Layer<R, never, never>): Promise<void>;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Read and validate all inputs at once, with optional cross-validation.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* const inputs = yield* Action.parseInputs({
|
|
64
|
+
* "app-id": { schema: Schema.NumberFromString, required: true },
|
|
65
|
+
* "branch": { schema: Schema.String, default: "main" },
|
|
66
|
+
* })
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
readonly parseInputs: <T extends Record<string, InputConfig<Schema.Schema.AnyNoContext>>>(config: T, crossValidate?: ((parsed: ParsedInputs<T>) => Effect.Effect<ParsedInputs<T>, ActionInputError, never>) | undefined) => Effect.Effect<ParsedInputs<T>, ActionInputError, ActionInputs>;
|
|
70
|
+
/** Create an Effect Logger that routes to GitHub Actions log functions. */
|
|
71
|
+
readonly makeLogger: () => Logger<unknown, void>;
|
|
72
|
+
/** Set the action log level for the current scope. */
|
|
73
|
+
readonly setLogLevel: (level: "debug" | "info" | "verbose") => Effect.Effect<void, never, never>;
|
|
74
|
+
/** Resolve a LogLevelInput to a concrete ActionLogLevel. */
|
|
75
|
+
readonly resolveLogLevel: (input: "auto" | "debug" | "info" | "verbose") => "debug" | "info" | "verbose";
|
|
76
|
+
};
|
|
77
|
+
|
|
21
78
|
/**
|
|
22
79
|
* Error when a GitHub Action input is missing or fails schema validation.
|
|
23
80
|
*/
|
|
@@ -62,6 +119,21 @@ export declare interface ActionInputs {
|
|
|
62
119
|
* Read a required input as a JSON string, parse and validate it.
|
|
63
120
|
*/
|
|
64
121
|
readonly getJson: <A, I>(name: string, schema: Schema.Schema<A, I, never>) => Effect.Effect<A, ActionInputError>;
|
|
122
|
+
/**
|
|
123
|
+
* Read a multiline input (newline-delimited list).
|
|
124
|
+
* Splits on newlines, trims each line, filters blank lines and comment lines (starting with #).
|
|
125
|
+
* Each remaining item is validated against the schema.
|
|
126
|
+
*/
|
|
127
|
+
readonly getMultiline: <A, I>(name: string, itemSchema: Schema.Schema<A, I, never>) => Effect.Effect<Array<A>, ActionInputError>;
|
|
128
|
+
/**
|
|
129
|
+
* Read a boolean input. Accepts "true"/"false" (case-insensitive).
|
|
130
|
+
*/
|
|
131
|
+
readonly getBoolean: (name: string) => Effect.Effect<boolean, ActionInputError>;
|
|
132
|
+
/**
|
|
133
|
+
* Read an optional boolean input with a default value.
|
|
134
|
+
* Returns the default if the input is not provided.
|
|
135
|
+
*/
|
|
136
|
+
readonly getBooleanOptional: (name: string, defaultValue: boolean) => Effect.Effect<boolean, ActionInputError>;
|
|
65
137
|
}
|
|
66
138
|
|
|
67
139
|
/**
|
|
@@ -241,6 +313,16 @@ export declare interface ActionOutputs {
|
|
|
241
313
|
* Add a directory to PATH for subsequent steps.
|
|
242
314
|
*/
|
|
243
315
|
readonly addPath: (path: string) => Effect.Effect<void>;
|
|
316
|
+
/**
|
|
317
|
+
* Mark the action as failed with a message.
|
|
318
|
+
* This is the standard way to signal action failure.
|
|
319
|
+
*/
|
|
320
|
+
readonly setFailed: (message: string) => Effect.Effect<void>;
|
|
321
|
+
/**
|
|
322
|
+
* Register a value as a secret so it is masked in logs.
|
|
323
|
+
* Use for values not read through ActionInputs (e.g., generated tokens).
|
|
324
|
+
*/
|
|
325
|
+
readonly setSecret: (value: string) => Effect.Effect<void>;
|
|
244
326
|
}
|
|
245
327
|
|
|
246
328
|
/**
|
|
@@ -280,12 +362,93 @@ export declare interface ActionOutputsTestState {
|
|
|
280
362
|
readonly summaries: Array<string>;
|
|
281
363
|
readonly variables: Array<CapturedOutput>;
|
|
282
364
|
readonly paths: Array<string>;
|
|
365
|
+
readonly secrets: Array<string>;
|
|
366
|
+
readonly failed: Array<string>;
|
|
283
367
|
}
|
|
284
368
|
|
|
285
369
|
/**
|
|
286
|
-
*
|
|
370
|
+
* Service interface for reading and writing GitHub Action state
|
|
371
|
+
* with schema-based serialization across action phases (pre/main/post).
|
|
372
|
+
*
|
|
373
|
+
* @public
|
|
287
374
|
*/
|
|
288
|
-
export declare
|
|
375
|
+
export declare interface ActionState {
|
|
376
|
+
/**
|
|
377
|
+
* Save a value to action state. Uses Schema.encode to serialize
|
|
378
|
+
* complex objects to JSON strings for storage.
|
|
379
|
+
*/
|
|
380
|
+
readonly save: <A, I>(key: string, value: A, schema: Schema.Schema<A, I, never>) => Effect.Effect<void, ActionStateError>;
|
|
381
|
+
/**
|
|
382
|
+
* Read a required state value. Uses Schema.decode to deserialize
|
|
383
|
+
* and validate the stored JSON string.
|
|
384
|
+
*/
|
|
385
|
+
readonly get: <A, I>(key: string, schema: Schema.Schema<A, I, never>) => Effect.Effect<A, ActionStateError>;
|
|
386
|
+
/**
|
|
387
|
+
* Read an optional state value. Returns Option.none() if the key
|
|
388
|
+
* has no stored value.
|
|
389
|
+
*/
|
|
390
|
+
readonly getOptional: <A, I>(key: string, schema: Schema.Schema<A, I, never>) => Effect.Effect<Option.Option<A>, ActionStateError>;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* ActionState tag for dependency injection.
|
|
395
|
+
*
|
|
396
|
+
* @public
|
|
397
|
+
*/
|
|
398
|
+
export declare const ActionState: Context.Tag<ActionState, ActionState>;
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Error when GitHub Action state reading/writing fails.
|
|
402
|
+
*/
|
|
403
|
+
export declare class ActionStateError extends ActionStateErrorBase<{
|
|
404
|
+
/** The state key name. */
|
|
405
|
+
readonly key: string;
|
|
406
|
+
/** Human-readable description of what went wrong. */
|
|
407
|
+
readonly reason: string;
|
|
408
|
+
/** The raw string value received, if any. */
|
|
409
|
+
readonly rawValue: string | undefined;
|
|
410
|
+
}> {
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Base class for ActionStateError.
|
|
415
|
+
*
|
|
416
|
+
* @internal
|
|
417
|
+
*/
|
|
418
|
+
export declare const ActionStateErrorBase: new <A extends Record<string, any> = {}>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
|
|
419
|
+
readonly _tag: "ActionStateError";
|
|
420
|
+
} & Readonly<A>;
|
|
421
|
+
|
|
422
|
+
export declare const ActionStateLive: Layer.Layer<ActionState>;
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Test implementation that captures state in memory.
|
|
426
|
+
*
|
|
427
|
+
* @example
|
|
428
|
+
* ```ts
|
|
429
|
+
* const state = ActionStateTest.empty();
|
|
430
|
+
* const layer = ActionStateTest.layer(state);
|
|
431
|
+
* ```
|
|
432
|
+
*/
|
|
433
|
+
export declare const ActionStateTest: {
|
|
434
|
+
/**
|
|
435
|
+
* Create a fresh empty test state container.
|
|
436
|
+
*/
|
|
437
|
+
readonly empty: () => ActionStateTestState;
|
|
438
|
+
/**
|
|
439
|
+
* Create a test layer from the given state.
|
|
440
|
+
* Pre-populate entries to simulate state from a previous phase.
|
|
441
|
+
*/
|
|
442
|
+
readonly layer: (state: ActionStateTestState) => Layer.Layer<ActionState, never, never>;
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* In-memory state captured by the test state layer.
|
|
447
|
+
*/
|
|
448
|
+
export declare interface ActionStateTestState {
|
|
449
|
+
/** Stored state entries (key to JSON string). */
|
|
450
|
+
readonly entries: Map<string, string>;
|
|
451
|
+
}
|
|
289
452
|
|
|
290
453
|
/**
|
|
291
454
|
* A captured output entry.
|
|
@@ -297,14 +460,6 @@ export declare const CapturedOutput: Schema.Struct<{
|
|
|
297
460
|
|
|
298
461
|
export declare type CapturedOutput = typeof CapturedOutput.Type;
|
|
299
462
|
|
|
300
|
-
/**
|
|
301
|
-
* Build a checkbox checklist.
|
|
302
|
-
*/
|
|
303
|
-
export declare const checklist: (items: readonly {
|
|
304
|
-
readonly label: string;
|
|
305
|
-
readonly checked: boolean;
|
|
306
|
-
}[]) => string;
|
|
307
|
-
|
|
308
463
|
/**
|
|
309
464
|
* A single item in a checklist.
|
|
310
465
|
*/
|
|
@@ -315,15 +470,8 @@ export declare const ChecklistItem: Schema.Struct<{
|
|
|
315
470
|
|
|
316
471
|
export declare type ChecklistItem = typeof ChecklistItem.Type;
|
|
317
472
|
|
|
318
|
-
/**
|
|
319
|
-
|
|
320
|
-
*/
|
|
321
|
-
export declare const code: (text: string) => string;
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* Fenced code block.
|
|
325
|
-
*/
|
|
326
|
-
export declare const codeBlock: (content: string, language?: string) => string;
|
|
473
|
+
/** Core services provided automatically by {@link Action.run}. */
|
|
474
|
+
export declare type CoreServices = ActionInputs | ActionLogger | ActionOutputs;
|
|
327
475
|
|
|
328
476
|
/**
|
|
329
477
|
* FiberRef that holds the current action log level for the fiber.
|
|
@@ -331,26 +479,94 @@ export declare const codeBlock: (content: string, language?: string) => string;
|
|
|
331
479
|
export declare const CurrentLogLevel: FiberRef.FiberRef<ActionLogLevel>;
|
|
332
480
|
|
|
333
481
|
/**
|
|
334
|
-
*
|
|
335
|
-
*/
|
|
336
|
-
export declare const details: (summary: string, content: string) => string;
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* Build a markdown heading.
|
|
482
|
+
* Namespace for GitHub-Flavored Markdown builder functions.
|
|
340
483
|
*
|
|
341
|
-
* @
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
*
|
|
484
|
+
* @example
|
|
485
|
+
* ```ts
|
|
486
|
+
* import { GithubMarkdown } from "@savvy-web/github-action-effects"
|
|
487
|
+
*
|
|
488
|
+
* GithubMarkdown.table(["Name", "Status"], [["build", "pass"]])
|
|
489
|
+
* GithubMarkdown.bold("hello")
|
|
490
|
+
* ```
|
|
491
|
+
*
|
|
492
|
+
* @public
|
|
347
493
|
*/
|
|
348
|
-
export declare const
|
|
494
|
+
export declare const GithubMarkdown: {
|
|
495
|
+
/**
|
|
496
|
+
* Build a GFM table from headers and rows.
|
|
497
|
+
*/
|
|
498
|
+
readonly table: (headers: readonly string[], rows: readonly (readonly string[])[]) => string;
|
|
499
|
+
/**
|
|
500
|
+
* Build a markdown heading.
|
|
501
|
+
*
|
|
502
|
+
* @param level - Heading level 1-6, defaults to 2.
|
|
503
|
+
*/
|
|
504
|
+
readonly heading: (text: string, level?: 1 | 2 | 3 | 4 | 5 | 6) => string;
|
|
505
|
+
/**
|
|
506
|
+
* Build a collapsible `<details>` block.
|
|
507
|
+
*/
|
|
508
|
+
readonly details: (summary: string, content: string) => string;
|
|
509
|
+
/**
|
|
510
|
+
* Horizontal rule.
|
|
511
|
+
*/
|
|
512
|
+
readonly rule: () => string;
|
|
513
|
+
/**
|
|
514
|
+
* Map a {@link Status} to its emoji indicator.
|
|
515
|
+
*/
|
|
516
|
+
readonly statusIcon: (status: "fail" | "pass" | "skip" | "warn") => string;
|
|
517
|
+
/**
|
|
518
|
+
* Build a markdown link.
|
|
519
|
+
*/
|
|
520
|
+
readonly link: (text: string, url: string) => string;
|
|
521
|
+
/**
|
|
522
|
+
* Build a bulleted list.
|
|
523
|
+
*/
|
|
524
|
+
readonly list: (items: readonly string[]) => string;
|
|
525
|
+
/**
|
|
526
|
+
* Build a checkbox checklist.
|
|
527
|
+
*/
|
|
528
|
+
readonly checklist: (items: readonly {
|
|
529
|
+
readonly label: string;
|
|
530
|
+
readonly checked: boolean;
|
|
531
|
+
}[]) => string;
|
|
532
|
+
/**
|
|
533
|
+
* Bold text.
|
|
534
|
+
*/
|
|
535
|
+
readonly bold: (text: string) => string;
|
|
536
|
+
/**
|
|
537
|
+
* Inline code.
|
|
538
|
+
*/
|
|
539
|
+
readonly code: (text: string) => string;
|
|
540
|
+
/**
|
|
541
|
+
* Fenced code block.
|
|
542
|
+
*/
|
|
543
|
+
readonly codeBlock: (content: string, language?: string) => string;
|
|
544
|
+
};
|
|
349
545
|
|
|
350
546
|
/**
|
|
351
|
-
*
|
|
547
|
+
* Configuration for a single input in {@link Action.parseInputs}.
|
|
548
|
+
*
|
|
549
|
+
* Precedence rules for how an input is read:
|
|
550
|
+
* 1. `json: true` — reads as JSON string, parses and validates (always required)
|
|
551
|
+
* 2. `multiline: true` — reads as newline-delimited list (always required)
|
|
552
|
+
* 3. `secret: true` — reads and masks the value (always required)
|
|
553
|
+
* 4. `default` is set — reads as optional, falls back to default if missing
|
|
554
|
+
* 5. `required: false` — reads as optional, returns undefined if missing
|
|
555
|
+
* 6. Otherwise — reads as required (default behavior)
|
|
556
|
+
*
|
|
557
|
+
* When `json`, `multiline`, or `secret` is set, the input is always
|
|
558
|
+
* treated as required regardless of `required` or `default` values.
|
|
559
|
+
*
|
|
560
|
+
* @public
|
|
352
561
|
*/
|
|
353
|
-
export declare
|
|
562
|
+
export declare interface InputConfig<S extends Schema.Schema.AnyNoContext = Schema.Schema.AnyNoContext> {
|
|
563
|
+
readonly schema: S;
|
|
564
|
+
readonly required?: boolean;
|
|
565
|
+
readonly default?: Schema.Schema.Type<S>;
|
|
566
|
+
readonly multiline?: boolean;
|
|
567
|
+
readonly secret?: boolean;
|
|
568
|
+
readonly json?: boolean;
|
|
569
|
+
}
|
|
354
570
|
|
|
355
571
|
/**
|
|
356
572
|
* Log level input values accepted by the standardized `log-level` action input.
|
|
@@ -361,30 +577,11 @@ export declare const LogLevelInput: Schema.Literal<["info", "verbose", "debug",
|
|
|
361
577
|
export declare type LogLevelInput = typeof LogLevelInput.Type;
|
|
362
578
|
|
|
363
579
|
/**
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
* - Always writes to `core.debug()` (GitHub-gated shadow channel).
|
|
367
|
-
* - Writes to user-facing output based on the action log level.
|
|
368
|
-
*/
|
|
369
|
-
export declare const makeActionLogger: () => Logger.Logger<unknown, void>;
|
|
370
|
-
|
|
371
|
-
/**
|
|
372
|
-
* Resolve a {@link LogLevelInput} to a concrete {@link ActionLogLevel}.
|
|
373
|
-
*
|
|
374
|
-
* `"auto"` resolves to `"info"` unless `RUNNER_DEBUG` is `"1"`,
|
|
375
|
-
* in which case it resolves to `"debug"`.
|
|
580
|
+
* Infer the output type from an input config record.
|
|
376
581
|
*/
|
|
377
|
-
export declare
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
* Horizontal rule.
|
|
381
|
-
*/
|
|
382
|
-
export declare const rule: () => string;
|
|
383
|
-
|
|
384
|
-
/**
|
|
385
|
-
* Set the action log level for the current scope.
|
|
386
|
-
*/
|
|
387
|
-
export declare const setLogLevel: (level: "debug" | "info" | "verbose") => Effect.Effect<void, never, never>;
|
|
582
|
+
export declare type ParsedInputs<T extends Record<string, InputConfig>> = {
|
|
583
|
+
readonly [K in keyof T]: T[K] extends InputConfig<infer S> ? Schema.Schema.Type<S> : never;
|
|
584
|
+
};
|
|
388
585
|
|
|
389
586
|
/**
|
|
390
587
|
* Status values for {@link statusIcon}.
|
|
@@ -393,21 +590,6 @@ export declare const Status: Schema.Literal<["pass", "fail", "skip", "warn"]>;
|
|
|
393
590
|
|
|
394
591
|
export declare type Status = typeof Status.Type;
|
|
395
592
|
|
|
396
|
-
/**
|
|
397
|
-
* Map a {@link Status} to its emoji indicator.
|
|
398
|
-
*/
|
|
399
|
-
export declare const statusIcon: (status: "fail" | "pass" | "skip" | "warn") => string;
|
|
400
|
-
|
|
401
|
-
/**
|
|
402
|
-
* Build a GFM table from headers and rows.
|
|
403
|
-
*
|
|
404
|
-
* @example
|
|
405
|
-
* ```ts
|
|
406
|
-
* table(["Name", "Status"], [["build", "pass"], ["test", "fail"]])
|
|
407
|
-
* ```
|
|
408
|
-
*/
|
|
409
|
-
export declare const table: (headers: readonly string[], rows: readonly (readonly string[])[]) => string;
|
|
410
|
-
|
|
411
593
|
/**
|
|
412
594
|
* Annotation type captured by the test layer.
|
|
413
595
|
*/
|
package/index.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { addPath, debug, endGroup, error as core_error, exportVariable, getInput, getMultilineInput, getState, info, notice, saveState, setFailed, setOutput, setSecret, startGroup, summary as core_summary, warning } from "@actions/core";
|
|
2
|
+
import { Cause, Context, Data, Effect, FiberRef, FiberRefs, Layer, LogLevel, Logger, Option, Schema } from "effect";
|
|
3
3
|
const ActionInputErrorBase = Data.TaggedError("ActionInputError");
|
|
4
4
|
class ActionInputError extends ActionInputErrorBase {
|
|
5
5
|
}
|
|
6
|
-
const ActionOutputErrorBase = Data.TaggedError("ActionOutputError");
|
|
7
|
-
class ActionOutputError extends ActionOutputErrorBase {
|
|
8
|
-
}
|
|
9
6
|
const ActionInputs = Context.GenericTag("ActionInputs");
|
|
10
7
|
const decodeInput = (name, raw, schema)=>Schema.decode(schema)(raw).pipe(Effect.mapError((parseError)=>new ActionInputError({
|
|
11
8
|
inputName: name,
|
|
@@ -24,6 +21,16 @@ const decodeJsonInput = (name, raw, schema)=>Effect["try"]({
|
|
|
24
21
|
reason: `Input "${name}" JSON validation failed: ${parseError.message}`,
|
|
25
22
|
rawValue: raw
|
|
26
23
|
})))));
|
|
24
|
+
const parseBoolean = (name, raw)=>{
|
|
25
|
+
const lower = raw.toLowerCase().trim();
|
|
26
|
+
if ("true" === lower) return Effect.succeed(true);
|
|
27
|
+
if ("false" === lower) return Effect.succeed(false);
|
|
28
|
+
return Effect.fail(new ActionInputError({
|
|
29
|
+
inputName: name,
|
|
30
|
+
reason: `Input "${name}" is not a valid boolean: expected "true" or "false", got "${raw}"`,
|
|
31
|
+
rawValue: raw
|
|
32
|
+
}));
|
|
33
|
+
};
|
|
27
34
|
const ActionInputsLive = Layer.succeed(ActionInputs, {
|
|
28
35
|
get: (name, schema)=>Effect.sync(()=>getInput(name, {
|
|
29
36
|
required: true
|
|
@@ -43,37 +50,20 @@ const ActionInputsLive = Layer.succeed(ActionInputs, {
|
|
|
43
50
|
}).pipe(Effect.flatMap((raw)=>decodeInput(name, raw, schema))),
|
|
44
51
|
getJson: (name, schema)=>Effect.sync(()=>getInput(name, {
|
|
45
52
|
required: true
|
|
46
|
-
})).pipe(Effect.flatMap((raw)=>decodeJsonInput(name, raw, schema)))
|
|
53
|
+
})).pipe(Effect.flatMap((raw)=>decodeJsonInput(name, raw, schema))),
|
|
54
|
+
getMultiline: (name, itemSchema)=>Effect.sync(()=>getMultilineInput(name, {
|
|
55
|
+
required: true
|
|
56
|
+
})).pipe(Effect.map((lines)=>lines.map((l)=>l.trim()).filter((l)=>l.length > 0 && !l.startsWith("#"))), Effect.flatMap((lines)=>Effect.forEach(lines, (line)=>decodeInput(name, line, itemSchema)))),
|
|
57
|
+
getBoolean: (name)=>Effect.sync(()=>getInput(name, {
|
|
58
|
+
required: true
|
|
59
|
+
})).pipe(Effect.flatMap((raw)=>parseBoolean(name, raw))),
|
|
60
|
+
getBooleanOptional: (name, defaultValue)=>Effect.sync(()=>getInput(name, {
|
|
61
|
+
required: false
|
|
62
|
+
})).pipe(Effect.flatMap((raw)=>{
|
|
63
|
+
if ("" === raw) return Effect.succeed(defaultValue);
|
|
64
|
+
return parseBoolean(name, raw);
|
|
65
|
+
}))
|
|
47
66
|
});
|
|
48
|
-
const missingInput = (name)=>new ActionInputError({
|
|
49
|
-
inputName: name,
|
|
50
|
-
reason: `Input "${name}" is required but not provided`,
|
|
51
|
-
rawValue: void 0
|
|
52
|
-
});
|
|
53
|
-
const ActionInputsTest = {
|
|
54
|
-
layer: (inputs)=>Layer.succeed(ActionInputs, {
|
|
55
|
-
get: (name, schema)=>{
|
|
56
|
-
const raw = inputs[name];
|
|
57
|
-
if (void 0 === raw) return Effect.fail(missingInput(name));
|
|
58
|
-
return decodeInput(name, raw, schema);
|
|
59
|
-
},
|
|
60
|
-
getOptional: (name, schema)=>{
|
|
61
|
-
const raw = inputs[name];
|
|
62
|
-
if (void 0 === raw || "" === raw) return Effect.succeed(Option.none());
|
|
63
|
-
return decodeInput(name, raw, schema).pipe(Effect.map((a)=>Option.some(a)));
|
|
64
|
-
},
|
|
65
|
-
getSecret: (name, schema)=>{
|
|
66
|
-
const raw = inputs[name];
|
|
67
|
-
if (void 0 === raw) return Effect.fail(missingInput(name));
|
|
68
|
-
return decodeInput(name, raw, schema);
|
|
69
|
-
},
|
|
70
|
-
getJson: (name, schema)=>{
|
|
71
|
-
const raw = inputs[name];
|
|
72
|
-
if (void 0 === raw) return Effect.fail(missingInput(name));
|
|
73
|
-
return decodeJsonInput(name, raw, schema);
|
|
74
|
-
}
|
|
75
|
-
})
|
|
76
|
-
};
|
|
77
67
|
const ActionLogger = Context.GenericTag("ActionLogger");
|
|
78
68
|
const CurrentLogLevel = FiberRef.unsafeMake("info");
|
|
79
69
|
const setLogLevel = (level)=>FiberRef.set(CurrentLogLevel, level);
|
|
@@ -131,6 +121,136 @@ const ActionLoggerLive = Layer.succeed(ActionLogger, {
|
|
|
131
121
|
void 0 !== properties ? notice(message, properties) : notice(message);
|
|
132
122
|
})
|
|
133
123
|
});
|
|
124
|
+
const ActionOutputErrorBase = Data.TaggedError("ActionOutputError");
|
|
125
|
+
class ActionOutputError extends ActionOutputErrorBase {
|
|
126
|
+
}
|
|
127
|
+
const ActionOutputs = Context.GenericTag("ActionOutputs");
|
|
128
|
+
const ActionOutputsLive = Layer.succeed(ActionOutputs, {
|
|
129
|
+
set: (name, value)=>Effect.sync(()=>setOutput(name, value)),
|
|
130
|
+
setJson: (name, value, schema)=>Schema.encode(schema)(value).pipe(Effect.tap((encoded)=>Effect.sync(()=>setOutput(name, JSON.stringify(encoded)))), Effect.asVoid, Effect.mapError((parseError)=>new ActionOutputError({
|
|
131
|
+
outputName: name,
|
|
132
|
+
reason: `Output "${name}" validation failed: ${parseError.message}`
|
|
133
|
+
}))),
|
|
134
|
+
summary: (content)=>Effect.tryPromise({
|
|
135
|
+
try: ()=>core_summary.addRaw(content).write(),
|
|
136
|
+
catch: (error)=>new ActionOutputError({
|
|
137
|
+
outputName: "summary",
|
|
138
|
+
reason: `Failed to write step summary: ${error instanceof Error ? error.message : String(error)}`
|
|
139
|
+
})
|
|
140
|
+
}).pipe(Effect.asVoid),
|
|
141
|
+
exportVariable: (name, value)=>Effect.sync(()=>exportVariable(name, value)),
|
|
142
|
+
addPath: (path)=>Effect.sync(()=>addPath(path)),
|
|
143
|
+
setFailed: (message)=>Effect.sync(()=>setFailed(message)),
|
|
144
|
+
setSecret: (value)=>Effect.sync(()=>setSecret(value))
|
|
145
|
+
});
|
|
146
|
+
const ActionLogLevel = Schema.Literal("info", "verbose", "debug").annotations({
|
|
147
|
+
identifier: "ActionLogLevel",
|
|
148
|
+
title: "Action Log Level",
|
|
149
|
+
description: "Logging verbosity for GitHub Action output"
|
|
150
|
+
});
|
|
151
|
+
const LogLevelInput = Schema.Literal("info", "verbose", "debug", "auto").annotations({
|
|
152
|
+
identifier: "LogLevelInput",
|
|
153
|
+
title: "Log Level Input",
|
|
154
|
+
description: "Logging verbosity: info, verbose, debug, or auto",
|
|
155
|
+
message: ()=>({
|
|
156
|
+
message: 'log-level must be one of: "info", "verbose", "debug", "auto"',
|
|
157
|
+
override: true
|
|
158
|
+
})
|
|
159
|
+
});
|
|
160
|
+
const resolveLogLevel = (input)=>{
|
|
161
|
+
if ("auto" !== input) return input;
|
|
162
|
+
return "1" === process.env.RUNNER_DEBUG ? "debug" : "info";
|
|
163
|
+
};
|
|
164
|
+
const CoreLive = Layer.mergeAll(ActionInputsLive, ActionLoggerLive, ActionOutputsLive);
|
|
165
|
+
const Action = {
|
|
166
|
+
run: (program, layer)=>{
|
|
167
|
+
const fullLayer = layer ? Layer.mergeAll(CoreLive, layer) : CoreLive;
|
|
168
|
+
const runnable = program.pipe(Effect.provide(fullLayer), Effect.provide(ActionLoggerLayer), Effect.catchAllCause((cause)=>{
|
|
169
|
+
const message = Cause.pretty(cause);
|
|
170
|
+
return Effect.sync(()=>setFailed(`Action failed: ${message}`));
|
|
171
|
+
}));
|
|
172
|
+
return Effect.runPromise(runnable).catch(()=>{
|
|
173
|
+
process.exitCode = 1;
|
|
174
|
+
});
|
|
175
|
+
},
|
|
176
|
+
parseInputs: (config, crossValidate)=>Effect.flatMap(ActionInputs, (svc)=>{
|
|
177
|
+
const entries = Object.entries(config);
|
|
178
|
+
return Effect.forEach(entries, ([name, cfg])=>{
|
|
179
|
+
const { schema, json, multiline, secret } = cfg;
|
|
180
|
+
const isOptional = false === cfg.required || void 0 !== cfg.default;
|
|
181
|
+
let readEffect;
|
|
182
|
+
readEffect = json ? svc.getJson(name, schema) : multiline ? svc.getMultiline(name, schema) : secret ? svc.getSecret(name, schema) : isOptional ? svc.getOptional(name, schema).pipe(Effect.map((opt)=>{
|
|
183
|
+
if ("None" === opt._tag) return cfg.default;
|
|
184
|
+
return opt.value;
|
|
185
|
+
})) : svc.get(name, schema);
|
|
186
|
+
return Effect.map(readEffect, (value)=>[
|
|
187
|
+
name,
|
|
188
|
+
value
|
|
189
|
+
]);
|
|
190
|
+
}).pipe(Effect.map((pairs)=>Object.fromEntries(pairs)), Effect.flatMap((parsed)=>crossValidate ? crossValidate(parsed) : Effect.succeed(parsed)));
|
|
191
|
+
}),
|
|
192
|
+
makeLogger: makeActionLogger,
|
|
193
|
+
setLogLevel: setLogLevel,
|
|
194
|
+
resolveLogLevel: resolveLogLevel
|
|
195
|
+
};
|
|
196
|
+
const ActionStateErrorBase = Data.TaggedError("ActionStateError");
|
|
197
|
+
class ActionStateError extends ActionStateErrorBase {
|
|
198
|
+
}
|
|
199
|
+
const missingInput = (name)=>new ActionInputError({
|
|
200
|
+
inputName: name,
|
|
201
|
+
reason: `Input "${name}" is required but not provided`,
|
|
202
|
+
rawValue: void 0
|
|
203
|
+
});
|
|
204
|
+
const ActionInputsTest_parseBoolean = (name, raw)=>{
|
|
205
|
+
const lower = raw.toLowerCase().trim();
|
|
206
|
+
if ("true" === lower) return Effect.succeed(true);
|
|
207
|
+
if ("false" === lower) return Effect.succeed(false);
|
|
208
|
+
return Effect.fail(new ActionInputError({
|
|
209
|
+
inputName: name,
|
|
210
|
+
reason: `Input "${name}" is not a valid boolean: expected "true" or "false", got "${raw}"`,
|
|
211
|
+
rawValue: raw
|
|
212
|
+
}));
|
|
213
|
+
};
|
|
214
|
+
const ActionInputsTest = {
|
|
215
|
+
layer: (inputs)=>Layer.succeed(ActionInputs, {
|
|
216
|
+
get: (name, schema)=>{
|
|
217
|
+
const raw = inputs[name];
|
|
218
|
+
if (void 0 === raw) return Effect.fail(missingInput(name));
|
|
219
|
+
return decodeInput(name, raw, schema);
|
|
220
|
+
},
|
|
221
|
+
getOptional: (name, schema)=>{
|
|
222
|
+
const raw = inputs[name];
|
|
223
|
+
if (void 0 === raw || "" === raw) return Effect.succeed(Option.none());
|
|
224
|
+
return decodeInput(name, raw, schema).pipe(Effect.map((a)=>Option.some(a)));
|
|
225
|
+
},
|
|
226
|
+
getSecret: (name, schema)=>{
|
|
227
|
+
const raw = inputs[name];
|
|
228
|
+
if (void 0 === raw) return Effect.fail(missingInput(name));
|
|
229
|
+
return decodeInput(name, raw, schema);
|
|
230
|
+
},
|
|
231
|
+
getJson: (name, schema)=>{
|
|
232
|
+
const raw = inputs[name];
|
|
233
|
+
if (void 0 === raw) return Effect.fail(missingInput(name));
|
|
234
|
+
return decodeJsonInput(name, raw, schema);
|
|
235
|
+
},
|
|
236
|
+
getMultiline: (name, itemSchema)=>{
|
|
237
|
+
const raw = inputs[name];
|
|
238
|
+
if (void 0 === raw) return Effect.fail(missingInput(name));
|
|
239
|
+
const lines = raw.split("\n").map((l)=>l.trim()).filter((l)=>l.length > 0 && !l.startsWith("#"));
|
|
240
|
+
return Effect.forEach(lines, (line)=>decodeInput(name, line, itemSchema));
|
|
241
|
+
},
|
|
242
|
+
getBoolean: (name)=>{
|
|
243
|
+
const raw = inputs[name];
|
|
244
|
+
if (void 0 === raw) return Effect.fail(missingInput(name));
|
|
245
|
+
return ActionInputsTest_parseBoolean(name, raw);
|
|
246
|
+
},
|
|
247
|
+
getBooleanOptional: (name, defaultValue)=>{
|
|
248
|
+
const raw = inputs[name];
|
|
249
|
+
if (void 0 === raw || "" === raw) return Effect.succeed(defaultValue);
|
|
250
|
+
return ActionInputsTest_parseBoolean(name, raw);
|
|
251
|
+
}
|
|
252
|
+
})
|
|
253
|
+
};
|
|
134
254
|
const makeAnnotation = (state, type)=>(message, properties)=>Effect.sync(()=>{
|
|
135
255
|
state.annotations.push({
|
|
136
256
|
type,
|
|
@@ -170,29 +290,14 @@ const ActionLoggerTest = {
|
|
|
170
290
|
annotationNotice: makeAnnotation(state, "notice")
|
|
171
291
|
})
|
|
172
292
|
};
|
|
173
|
-
const ActionOutputs = Context.GenericTag("ActionOutputs");
|
|
174
|
-
const ActionOutputsLive = Layer.succeed(ActionOutputs, {
|
|
175
|
-
set: (name, value)=>Effect.sync(()=>setOutput(name, value)),
|
|
176
|
-
setJson: (name, value, schema)=>Schema.encode(schema)(value).pipe(Effect.tap((encoded)=>Effect.sync(()=>setOutput(name, JSON.stringify(encoded)))), Effect.asVoid, Effect.mapError((parseError)=>new ActionOutputError({
|
|
177
|
-
outputName: name,
|
|
178
|
-
reason: `Output "${name}" validation failed: ${parseError.message}`
|
|
179
|
-
}))),
|
|
180
|
-
summary: (content)=>Effect.tryPromise({
|
|
181
|
-
try: ()=>core_summary.addRaw(content).write(),
|
|
182
|
-
catch: (error)=>new ActionOutputError({
|
|
183
|
-
outputName: "summary",
|
|
184
|
-
reason: `Failed to write step summary: ${error instanceof Error ? error.message : String(error)}`
|
|
185
|
-
})
|
|
186
|
-
}).pipe(Effect.asVoid),
|
|
187
|
-
exportVariable: (name, value)=>Effect.sync(()=>exportVariable(name, value)),
|
|
188
|
-
addPath: (path)=>Effect.sync(()=>addPath(path))
|
|
189
|
-
});
|
|
190
293
|
const ActionOutputsTest = {
|
|
191
294
|
empty: ()=>({
|
|
192
295
|
outputs: [],
|
|
193
296
|
summaries: [],
|
|
194
297
|
variables: [],
|
|
195
|
-
paths: []
|
|
298
|
+
paths: [],
|
|
299
|
+
secrets: [],
|
|
300
|
+
failed: []
|
|
196
301
|
}),
|
|
197
302
|
layer: (state)=>Layer.succeed(ActionOutputs, {
|
|
198
303
|
set: (name, value)=>Effect.sync(()=>{
|
|
@@ -221,9 +326,72 @@ const ActionOutputsTest = {
|
|
|
221
326
|
}),
|
|
222
327
|
addPath: (path)=>Effect.sync(()=>{
|
|
223
328
|
state.paths.push(path);
|
|
329
|
+
}),
|
|
330
|
+
setFailed: (message)=>Effect.sync(()=>{
|
|
331
|
+
state.failed.push(message);
|
|
332
|
+
}),
|
|
333
|
+
setSecret: (value)=>Effect.sync(()=>{
|
|
334
|
+
state.secrets.push(value);
|
|
224
335
|
})
|
|
225
336
|
})
|
|
226
337
|
};
|
|
338
|
+
const ActionState = Context.GenericTag("ActionState");
|
|
339
|
+
const encodeState = (key, value, schema)=>Schema.encode(schema)(value).pipe(Effect.map((encoded)=>JSON.stringify(encoded)), Effect.mapError((error)=>new ActionStateError({
|
|
340
|
+
key,
|
|
341
|
+
reason: `State "${key}" encode failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
342
|
+
rawValue: void 0
|
|
343
|
+
})));
|
|
344
|
+
const decodeState = (key, raw, schema)=>Effect["try"]({
|
|
345
|
+
try: ()=>JSON.parse(raw),
|
|
346
|
+
catch: (error)=>new ActionStateError({
|
|
347
|
+
key,
|
|
348
|
+
reason: `State "${key}" is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
349
|
+
rawValue: raw
|
|
350
|
+
})
|
|
351
|
+
}).pipe(Effect.flatMap((parsed)=>Schema.decode(schema)(parsed).pipe(Effect.mapError((parseError)=>new ActionStateError({
|
|
352
|
+
key,
|
|
353
|
+
reason: `State "${key}" decode failed: ${parseError.message}`,
|
|
354
|
+
rawValue: raw
|
|
355
|
+
})))));
|
|
356
|
+
const ActionStateLive = Layer.succeed(ActionState, {
|
|
357
|
+
save: (key, value, schema)=>encodeState(key, value, schema).pipe(Effect.tap((json)=>Effect.sync(()=>saveState(key, json))), Effect.asVoid),
|
|
358
|
+
get: (key, schema)=>Effect.sync(()=>getState(key)).pipe(Effect.flatMap((raw)=>{
|
|
359
|
+
if ("" === raw) return Effect.fail(new ActionStateError({
|
|
360
|
+
key,
|
|
361
|
+
reason: `State "${key}" is not set (phase ordering issue?)`,
|
|
362
|
+
rawValue: void 0
|
|
363
|
+
}));
|
|
364
|
+
return decodeState(key, raw, schema);
|
|
365
|
+
})),
|
|
366
|
+
getOptional: (key, schema)=>Effect.sync(()=>getState(key)).pipe(Effect.flatMap((raw)=>{
|
|
367
|
+
if ("" === raw) return Effect.succeed(Option.none());
|
|
368
|
+
return decodeState(key, raw, schema).pipe(Effect.map((a)=>Option.some(a)));
|
|
369
|
+
}))
|
|
370
|
+
});
|
|
371
|
+
const ActionStateTest = {
|
|
372
|
+
empty: ()=>({
|
|
373
|
+
entries: new Map()
|
|
374
|
+
}),
|
|
375
|
+
layer: (state)=>Layer.succeed(ActionState, {
|
|
376
|
+
save: (key, value, schema)=>encodeState(key, value, schema).pipe(Effect.tap((json)=>Effect.sync(()=>{
|
|
377
|
+
state.entries.set(key, json);
|
|
378
|
+
})), Effect.asVoid),
|
|
379
|
+
get: (key, schema)=>{
|
|
380
|
+
const raw = state.entries.get(key);
|
|
381
|
+
if (void 0 === raw) return Effect.fail(new ActionStateError({
|
|
382
|
+
key,
|
|
383
|
+
reason: `State "${key}" is not set (phase ordering issue?)`,
|
|
384
|
+
rawValue: void 0
|
|
385
|
+
}));
|
|
386
|
+
return decodeState(key, raw, schema);
|
|
387
|
+
},
|
|
388
|
+
getOptional: (key, schema)=>{
|
|
389
|
+
const raw = state.entries.get(key);
|
|
390
|
+
if (void 0 === raw) return Effect.succeed(Option.none());
|
|
391
|
+
return decodeState(key, raw, schema).pipe(Effect.map((a)=>Option.some(a)));
|
|
392
|
+
}
|
|
393
|
+
})
|
|
394
|
+
};
|
|
227
395
|
const Status = Schema.Literal("pass", "fail", "skip", "warn").annotations({
|
|
228
396
|
identifier: "Status",
|
|
229
397
|
title: "Check Status",
|
|
@@ -243,53 +411,37 @@ const CapturedOutput = Schema.Struct({
|
|
|
243
411
|
identifier: "CapturedOutput",
|
|
244
412
|
title: "Captured Output"
|
|
245
413
|
});
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
]
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
switch(status){
|
|
279
|
-
case "pass":
|
|
280
|
-
return "\u2705";
|
|
281
|
-
case "fail":
|
|
282
|
-
return "\u274C";
|
|
283
|
-
case "skip":
|
|
284
|
-
return "\uD83D\uDDC3\uFE0F";
|
|
285
|
-
case "warn":
|
|
286
|
-
return "\u26A0\uFE0F";
|
|
287
|
-
}
|
|
414
|
+
const GithubMarkdown = {
|
|
415
|
+
table: (headers, rows)=>{
|
|
416
|
+
const headerRow = `| ${headers.join(" | ")} |`;
|
|
417
|
+
const separator = `| ${headers.map(()=>"---").join(" | ")} |`;
|
|
418
|
+
const dataRows = rows.map((row)=>`| ${row.join(" | ")} |`);
|
|
419
|
+
return [
|
|
420
|
+
headerRow,
|
|
421
|
+
separator,
|
|
422
|
+
...dataRows
|
|
423
|
+
].join("\n");
|
|
424
|
+
},
|
|
425
|
+
heading: (text, level = 2)=>`${"#".repeat(level)} ${text}`,
|
|
426
|
+
details: (summary, content)=>`<details>\n<summary>${summary}</summary>\n\n${content}\n\n</details>`,
|
|
427
|
+
rule: ()=>"---",
|
|
428
|
+
statusIcon: (status)=>{
|
|
429
|
+
switch(status){
|
|
430
|
+
case "pass":
|
|
431
|
+
return "\u2705";
|
|
432
|
+
case "fail":
|
|
433
|
+
return "\u274C";
|
|
434
|
+
case "skip":
|
|
435
|
+
return "\uD83D\uDDC3\uFE0F";
|
|
436
|
+
case "warn":
|
|
437
|
+
return "\u26A0\uFE0F";
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
link: (text, url)=>`[${text}](${url})`,
|
|
441
|
+
list: (items)=>items.map((item)=>`- ${item}`).join("\n"),
|
|
442
|
+
checklist: (items)=>items.map((item)=>`- [${item.checked ? "x" : " "}] ${item.label}`).join("\n"),
|
|
443
|
+
bold: (text)=>`**${text}**`,
|
|
444
|
+
code: (text)=>`\`${text}\``,
|
|
445
|
+
codeBlock: (content, language = "")=>`\`\`\`${language}\n${content}\n\`\`\``
|
|
288
446
|
};
|
|
289
|
-
|
|
290
|
-
const list = (items)=>items.map((item)=>`- ${item}`).join("\n");
|
|
291
|
-
const checklist = (items)=>items.map((item)=>`- [${item.checked ? "x" : " "}] ${item.label}`).join("\n");
|
|
292
|
-
const bold = (text)=>`**${text}**`;
|
|
293
|
-
const code = (text)=>`\`${text}\``;
|
|
294
|
-
const codeBlock = (content, language = "")=>`\`\`\`${language}\n${content}\n\`\`\``;
|
|
295
|
-
export { ActionInputError, ActionInputErrorBase, ActionInputs, ActionInputsLive, ActionInputsTest, ActionLogLevel, ActionLogger, ActionLoggerLayer, ActionLoggerLive, ActionLoggerTest, ActionOutputError, ActionOutputErrorBase, ActionOutputs, ActionOutputsLive, ActionOutputsTest, CapturedOutput, ChecklistItem, CurrentLogLevel, GithubMarkdown_link as link, LogLevelInput, Status, bold, checklist, code, codeBlock, details, heading, list, makeActionLogger, resolveLogLevel, rule, setLogLevel, statusIcon, table };
|
|
447
|
+
export { Action, ActionInputError, ActionInputErrorBase, ActionInputs, ActionInputsLive, ActionInputsTest, ActionLogLevel, ActionLogger, ActionLoggerLayer, ActionLoggerLive, ActionLoggerTest, ActionOutputError, ActionOutputErrorBase, ActionOutputs, ActionOutputsLive, ActionOutputsTest, ActionState, ActionStateError, ActionStateErrorBase, ActionStateLive, ActionStateTest, CapturedOutput, ChecklistItem, CurrentLogLevel, GithubMarkdown, LogLevelInput, Status };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@savvy-web/github-action-effects",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Effect-based utility library for building robust, well-logged, and schema-validated GitHub Actions.",
|
|
6
6
|
"homepage": "https://github.com/savvy-web/github-action-effects#readme",
|