@sshkeda/task 0.0.0-stage → 1.0.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/LICENSE +21 -0
- package/README.md +131 -2
- package/package.json +41 -4
- package/src/index.ts +2 -0
- package/src/lib/errors/index.ts +15 -0
- package/src/lib/errors/internal/errors.ts +147 -0
- package/src/lib/kill-leftover-processes/index.ts +17 -0
- package/src/lib/kill-leftover-processes/internal/command-words.ts +16 -0
- package/src/lib/kill-leftover-processes/internal/find-process-ids.ts +36 -0
- package/src/lib/kill-leftover-processes/internal/kill-process-groups.ts +270 -0
- package/src/lib/kill-leftover-processes/internal/list-linux-processes-from-proc.ts +191 -0
- package/src/lib/kill-leftover-processes/internal/list-macos-processes-with-sysctl.ts +335 -0
- package/src/lib/kill-leftover-processes/internal/list-processes.ts +58 -0
- package/src/lib/kill-leftover-processes/internal/mark-run-processes.ts +367 -0
- package/src/lib/kill-leftover-processes/internal/process-cleanup-errors.ts +17 -0
- package/src/lib/kill-leftover-processes/internal/send-signal.ts +32 -0
- package/src/lib/kill-leftover-processes/internal/signal-run-processes.ts +48 -0
- package/src/lib/kill-leftover-processes/internal/started-process-groups.ts +107 -0
- package/src/lib/main-thread/index.ts +3 -0
- package/src/lib/main-thread/internal/listen-for-stop-requests.ts +102 -0
- package/src/lib/main-thread/internal/run-main-thread.ts +81 -0
- package/src/lib/main-thread/internal/run-root-task-fiber.ts +191 -0
- package/src/lib/main-thread/internal/stops-at-first-failure.ts +20 -0
- package/src/lib/main-thread/internal/trace-the-run.ts +107 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/index.ts +7 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/follow-imports-from-entry-files.ts +306 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/load-all-reporting-first-failure.ts +11 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/make-module-graph.ts +48 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/read-configured-loaders.ts +64 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/resolve-module-specifiers.ts +60 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/rewrite-worker-imports.ts +118 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/worker-import-errors.ts +19 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/index.ts +7 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/internal/handle-worker-events.ts +348 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/internal/make-task-runs.ts +207 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/index.ts +1 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/internal/await-within-counted-limit.ts +29 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/internal/execute-task.ts +272 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/internal/make-task-setup-clock.ts +129 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/internal/run-requested-tasks.ts +135 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/shared/define-task-run-state.ts +288 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/shared/make-stopwatch.ts +39 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/shared/share-standard-input-with-root-task.ts +36 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/shared/stop-task.ts +166 -0
- package/src/lib/main-thread/lib/scan-module-source/index.ts +1 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/find-lexical-bindings.ts +154 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/find-task-declarations.ts +205 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/parse-module-text.ts +34 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/read-literal-string.ts +15 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/read-module-loads.ts +316 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/scan-module-text.ts +70 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/unwrap-type-assertions.ts +10 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/visit-non-reference-identifiers.ts +21 -0
- package/src/lib/main-thread/shared/fail-run.ts +36 -0
- package/src/lib/main-thread/shared/find-failure-reason.ts +43 -0
- package/src/lib/main-thread/shared/find-package-directories.ts +34 -0
- package/src/lib/main-thread/shared/find-tasks-reachable-from-root-task-file.ts +246 -0
- package/src/lib/main-thread/shared/list-directory-paths-up-to-root.ts +13 -0
- package/src/lib/main-thread/shared/main-thread-errors.ts +373 -0
- package/src/lib/main-thread/shared/package-json-errors.ts +93 -0
- package/src/lib/main-thread/shared/provide-discovered-tasks.ts +26 -0
- package/src/lib/main-thread/shared/provide-process-groups.ts +63 -0
- package/src/lib/main-thread/shared/provide-run-root-task-config.ts +66 -0
- package/src/lib/main-thread/shared/provide-stop-state.ts +150 -0
- package/src/lib/main-thread/shared/provide-worker-pool.ts +607 -0
- package/src/lib/main-thread/shared/read-bare-package-name.ts +3 -0
- package/src/lib/main-thread/shared/read-package-json.ts +149 -0
- package/src/lib/main-thread/shared/report-task-failure.ts +31 -0
- package/src/lib/preload-rewritten-imports-into-each-worker/index.ts +5 -0
- package/src/lib/preload-rewritten-imports-into-each-worker/internal/load-rewritten-imports.ts +34 -0
- package/src/lib/supervise-the-run/index.ts +15 -0
- package/src/lib/supervise-the-run/internal/adopt-orphans-on-linux.ts +73 -0
- package/src/lib/supervise-the-run/internal/check-root-task-file.ts +8 -0
- package/src/lib/supervise-the-run/internal/report-ready.ts +12 -0
- package/src/lib/supervise-the-run/internal/share-process-groups.ts +25 -0
- package/src/lib/supervise-the-run/internal/start-watching-the-supervisor.ts +6 -0
- package/src/lib/supervise-the-run/internal/supervise-run.ts +155 -0
- package/src/lib/supervise-the-run/internal/talk-to-the-supervisor.ts +53 -0
- package/src/lib/supervise-the-run/internal/watch-the-supervisor.ts +61 -0
- package/src/lib/task/index.ts +5 -0
- package/src/lib/task/internal/make-tasks.ts +82 -0
- package/src/lib/task/internal/run-command.ts +194 -0
- package/src/lib/task/internal/run-root-task.ts +263 -0
- package/src/lib/task/internal/run-task-lists.ts +51 -0
- package/src/lib/task/internal/trust-output-mode-names.ts +18 -0
- package/src/lib/task/internal/trust-task-results.ts +35 -0
- package/src/lib/task/internal/use-task-services.ts +4 -0
- package/src/lib/worker-thread/index.ts +3 -0
- package/src/lib/worker-thread/internal/answer-main-thread-messages.ts +141 -0
- package/src/lib/worker-thread/internal/describe-task-body-result.ts +91 -0
- package/src/lib/worker-thread/internal/name-config-key-not-set.ts +57 -0
- package/src/lib/worker-thread/internal/run-task-body.ts +117 -0
- package/src/lib/worker-thread/internal/serve-on-the-parent-port.ts +63 -0
- package/src/lib/worker-thread/internal/start-worker-thread.ts +41 -0
- package/src/lib/worker-thread/lib/provide-task-services/index.ts +1 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/make-task-logger.ts +34 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/provide-task-services.ts +86 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/read-standard-input-from-main-thread.ts +58 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/resolve-paths-from-package-directory.ts +61 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/run-commands-in-package-directory.ts +24 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/spawn-commands-with-bun.ts +545 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/spawn-reported-process.ts +151 -0
- package/src/lib/worker-thread/shared/await-main-thread-answers.ts +76 -0
- package/src/lib/worker-thread/shared/decode-task-result-json.ts +114 -0
- package/src/lib/worker-thread/shared/define-task-body-record.ts +11 -0
- package/src/lib/worker-thread/shared/describe-issue-input.ts +52 -0
- package/src/lib/worker-thread/shared/plain-object.ts +8 -0
- package/src/lib/worker-thread/shared/provide-worker-state.ts +160 -0
- package/src/lib/worker-thread/shared/worker-thread-errors.ts +41 -0
- package/src/shared/attribute-reported-failures.ts +25 -0
- package/src/shared/claim-processes-to-report.ts +22 -0
- package/src/shared/command-errors.ts +44 -0
- package/src/shared/complete-discarded-calls-once-sent.ts +12 -0
- package/src/shared/cut-to-graphemes.ts +12 -0
- package/src/shared/environment-config-errors.ts +74 -0
- package/src/shared/error-fields.ts +13 -0
- package/src/shared/find-bun-loader-for-script.ts +32 -0
- package/src/shared/find-node-modules-package-directory.ts +35 -0
- package/src/shared/find-user-locations-in-stack-traces.ts +64 -0
- package/src/shared/format-error-lines.ts +53 -0
- package/src/shared/format-task-lines.ts +21 -0
- package/src/shared/hold-task-logs.ts +24 -0
- package/src/shared/invariant-errors.ts +28 -0
- package/src/shared/mark-task-worker-thread-contexts.ts +29 -0
- package/src/shared/module-graph-errors.ts +49 -0
- package/src/shared/name-config-key.ts +42 -0
- package/src/shared/names-from-package-json.ts +7 -0
- package/src/shared/print-to-standard-error.ts +48 -0
- package/src/shared/process-errors.ts +191 -0
- package/src/shared/read-environment-config.ts +239 -0
- package/src/shared/record-tasks-made.ts +10 -0
- package/src/shared/report-cause.ts +140 -0
- package/src/shared/report-errors.ts +121 -0
- package/src/shared/resolve-task-options.ts +20 -0
- package/src/shared/rpc.ts +161 -0
- package/src/shared/run-in-child-fiber-and-join.ts +16 -0
- package/src/shared/run-marker.ts +7 -0
- package/src/shared/save-standard-stream-flags.ts +57 -0
- package/src/shared/select-cause-reasons.ts +14 -0
- package/src/shared/signal-run-called-twice.ts +11 -0
- package/src/shared/standard-input-streams.ts +41 -0
- package/src/shared/stop-signals.ts +41 -0
- package/src/shared/task-errors.ts +224 -0
- package/src/shared/task-package.ts +5 -0
- package/src/shared/task-results.ts +55 -0
- package/src/shared/task-services.ts +30 -0
- package/src/shared/write-trace-file.ts +112 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Effect, Effectable } from "effect";
|
|
2
|
+
import type { FileSystem, Path, Scope, Stdio, Terminal } from "effect";
|
|
3
|
+
import type { Digest, SecureRandom } from "effect/crypto";
|
|
4
|
+
import type { HttpClient } from "effect/http";
|
|
5
|
+
import type { ChildProcessSpawner } from "effect/process";
|
|
6
|
+
|
|
7
|
+
import { recordTaskMade } from "../../../shared/record-tasks-made.ts";
|
|
8
|
+
import { resolveTaskOptions } from "../../../shared/resolve-task-options.ts";
|
|
9
|
+
import type { TaskRequestFailure } from "../../../shared/task-errors.ts";
|
|
10
|
+
import { TASK_PACKAGE } from "../../../shared/task-package.ts";
|
|
11
|
+
import type { TaskResult } from "../../../shared/task-results.ts";
|
|
12
|
+
import { Tasks } from "../../../shared/task-services.ts";
|
|
13
|
+
import type { Served, Serving } from "../../../shared/task-services.ts";
|
|
14
|
+
import { trustTaskResult } from "./trust-task-results.ts";
|
|
15
|
+
|
|
16
|
+
const TYPE_ID = Symbol.for(`~${TASK_PACKAGE.name}/Task`);
|
|
17
|
+
|
|
18
|
+
export class Task<
|
|
19
|
+
Success = unknown,
|
|
20
|
+
Services extends Effect.Services<Task["body"]> =
|
|
21
|
+
| ChildProcessSpawner.ChildProcessSpawner
|
|
22
|
+
| Digest.Digest
|
|
23
|
+
| FileSystem.FileSystem
|
|
24
|
+
| HttpClient.HttpClient
|
|
25
|
+
| Path.Path
|
|
26
|
+
| Scope.Scope
|
|
27
|
+
| SecureRandom.SecureRandom
|
|
28
|
+
| Serving
|
|
29
|
+
| Stdio.Stdio
|
|
30
|
+
| Tasks
|
|
31
|
+
| Terminal.Terminal,
|
|
32
|
+
> extends Effectable.Class<Success, typeof TaskRequestFailure.Type, Tasks> {
|
|
33
|
+
public readonly [TYPE_ID] = TYPE_ID;
|
|
34
|
+
|
|
35
|
+
public readonly name: string;
|
|
36
|
+
|
|
37
|
+
public readonly body: Effect.Effect<unknown, { readonly _tag: string }, Services>;
|
|
38
|
+
|
|
39
|
+
public readonly options: ReturnType<typeof resolveTaskOptions>;
|
|
40
|
+
|
|
41
|
+
public constructor(
|
|
42
|
+
name: string,
|
|
43
|
+
body: Effect.Effect<unknown, { readonly _tag: string }, Services>,
|
|
44
|
+
options: ReturnType<typeof resolveTaskOptions>,
|
|
45
|
+
) {
|
|
46
|
+
super();
|
|
47
|
+
this.name = name;
|
|
48
|
+
this.body = body;
|
|
49
|
+
this.options = options;
|
|
50
|
+
recordTaskMade(this);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public asEffect() {
|
|
54
|
+
return Tasks.use((tasks) => tasks.requestResult(this)).pipe(
|
|
55
|
+
Effect.flatMap((taskResult) => trustTaskResult<Success>(taskResult)),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const make = <
|
|
61
|
+
BodySuccess,
|
|
62
|
+
Failure extends { readonly _tag: string },
|
|
63
|
+
Services extends Effect.Services<Task["body"]>,
|
|
64
|
+
>(
|
|
65
|
+
name: string,
|
|
66
|
+
body: Effect.Effect<BodySuccess, Failure, Services> &
|
|
67
|
+
NoInfer<
|
|
68
|
+
Effect.Effect<
|
|
69
|
+
[Serving extends Services ? Extract<BodySuccess, Served<unknown>> : true] extends [never]
|
|
70
|
+
? "serves one value and returns another"
|
|
71
|
+
: BodySuccess extends Served<infer Success>
|
|
72
|
+
? Served<Success extends void ? Success : typeof TaskResult.Type>
|
|
73
|
+
: BodySuccess extends void
|
|
74
|
+
? BodySuccess
|
|
75
|
+
: typeof TaskResult.Type,
|
|
76
|
+
Failure,
|
|
77
|
+
Services
|
|
78
|
+
>
|
|
79
|
+
>,
|
|
80
|
+
options: Parameters<typeof resolveTaskOptions>[0] = {},
|
|
81
|
+
): Task<BodySuccess extends Served<infer Success> ? Success : BodySuccess, Services> =>
|
|
82
|
+
new Task(name, body, resolveTaskOptions(options));
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { Cause, Deferred, Effect, Exit, Fiber, Redacted, Scope, Stream } from "effect";
|
|
2
|
+
import type { PlatformError } from "effect/PlatformError";
|
|
3
|
+
import * as PlatformErrors from "effect/PlatformError";
|
|
4
|
+
import { ChildProcess } from "effect/process";
|
|
5
|
+
|
|
6
|
+
import { environmentEveryCommandSees } from "../../../shared/read-environment-config.ts";
|
|
7
|
+
import { CauseWithoutFailure } from "../../../shared/report-errors.ts";
|
|
8
|
+
import { failureValuesOfCause } from "../../../shared/select-cause-reasons.ts";
|
|
9
|
+
import {
|
|
10
|
+
isStandardInputClosedByCommand,
|
|
11
|
+
makeStandardInputRunner,
|
|
12
|
+
} from "../../../shared/standard-input-streams.ts";
|
|
13
|
+
import { Tasks } from "../../../shared/task-services.ts";
|
|
14
|
+
import {
|
|
15
|
+
CapturedCommandFailed,
|
|
16
|
+
CommandFailed,
|
|
17
|
+
CommandNotStarted,
|
|
18
|
+
CommandStandardInputFailed,
|
|
19
|
+
} from "../../errors/index.ts";
|
|
20
|
+
import { trustOutputModeName } from "./trust-output-mode-names.ts";
|
|
21
|
+
|
|
22
|
+
const logAndCapture = (output: Stream.Stream<Uint8Array, PlatformError>) =>
|
|
23
|
+
Effect.suspend(() => {
|
|
24
|
+
const texts: string[] = [];
|
|
25
|
+
return Stream.decodeText(output).pipe(
|
|
26
|
+
Stream.tap((text) =>
|
|
27
|
+
Effect.sync(() => {
|
|
28
|
+
texts.push(text);
|
|
29
|
+
}),
|
|
30
|
+
),
|
|
31
|
+
Stream.splitLines,
|
|
32
|
+
Stream.runForEachArray((lines) => Effect.log(lines.join("\n"))),
|
|
33
|
+
Effect.map(() => texts.join("")),
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const OUTPUT_MODES = {
|
|
38
|
+
logged: {
|
|
39
|
+
readOutput: logAndCapture,
|
|
40
|
+
returnOutput: (standardOutput: string) => standardOutput,
|
|
41
|
+
makeFailure: (failure: ConstructorParameters<typeof CommandFailed>[0]) =>
|
|
42
|
+
new CommandFailed(failure),
|
|
43
|
+
},
|
|
44
|
+
secret: {
|
|
45
|
+
readOutput: (output: Stream.Stream<Uint8Array, PlatformError>) =>
|
|
46
|
+
Stream.mkString(Stream.decodeText(output)),
|
|
47
|
+
returnOutput: (standardOutput: string) => Redacted.make(standardOutput),
|
|
48
|
+
makeFailure: (failure: ConstructorParameters<typeof CommandFailed>[0]) =>
|
|
49
|
+
new CapturedCommandFailed({
|
|
50
|
+
command: failure.command,
|
|
51
|
+
code: failure.code,
|
|
52
|
+
standardOutput: Redacted.make(failure.standardOutput),
|
|
53
|
+
standardError: Redacted.make(failure.standardError),
|
|
54
|
+
}),
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const outputModes: {
|
|
59
|
+
readonly [Output in keyof typeof OUTPUT_MODES]: {
|
|
60
|
+
readonly readOutput: (typeof OUTPUT_MODES)["logged"]["readOutput"];
|
|
61
|
+
readonly returnOutput: (
|
|
62
|
+
standardOutput: string,
|
|
63
|
+
) => ReturnType<(typeof OUTPUT_MODES)[Output]["returnOutput"]>;
|
|
64
|
+
readonly makeFailure: (
|
|
65
|
+
failure: ConstructorParameters<typeof CommandFailed>[0],
|
|
66
|
+
) => ReturnType<(typeof OUTPUT_MODES)[Output]["makeFailure"]>;
|
|
67
|
+
};
|
|
68
|
+
} = OUTPUT_MODES;
|
|
69
|
+
|
|
70
|
+
export const exec = <Secret extends boolean = false>(
|
|
71
|
+
name: string,
|
|
72
|
+
commandArguments: readonly string[],
|
|
73
|
+
...[commandOptions = {}]: [Secret] extends [true]
|
|
74
|
+
? [
|
|
75
|
+
commandOptions: {
|
|
76
|
+
readonly cwd?: string;
|
|
77
|
+
readonly env?: Readonly<Record<string, Redacted.Redacted | string>>;
|
|
78
|
+
readonly stdin?: Stream.Stream<Uint8Array, PlatformError>;
|
|
79
|
+
readonly secret: Secret;
|
|
80
|
+
},
|
|
81
|
+
]
|
|
82
|
+
: [
|
|
83
|
+
commandOptions?: {
|
|
84
|
+
readonly cwd?: string;
|
|
85
|
+
readonly env?: Readonly<Record<string, Redacted.Redacted | string>>;
|
|
86
|
+
readonly stdin?: Stream.Stream<Uint8Array, PlatformError>;
|
|
87
|
+
readonly secret?: Secret;
|
|
88
|
+
},
|
|
89
|
+
]
|
|
90
|
+
) =>
|
|
91
|
+
Tasks.use(() =>
|
|
92
|
+
Effect.scoped(
|
|
93
|
+
Effect.gen(function* () {
|
|
94
|
+
const outputMode = outputModes[yield* trustOutputModeName(commandOptions.secret)];
|
|
95
|
+
const command = name.slice(name.lastIndexOf("/") + 1);
|
|
96
|
+
const outputScope = yield* Scope.fork(yield* Effect.scope);
|
|
97
|
+
const commandReleased = yield* Deferred.make<void>();
|
|
98
|
+
const child = yield* ChildProcess.make(name, commandArguments, {
|
|
99
|
+
...(commandOptions.cwd === undefined ? {} : { cwd: commandOptions.cwd }),
|
|
100
|
+
env: { ...(yield* environmentEveryCommandSees), ...commandOptions.env },
|
|
101
|
+
stdin: commandOptions.stdin === undefined ? "ignore" : "pipe",
|
|
102
|
+
stderr: "pipe",
|
|
103
|
+
}).pipe(Effect.mapError((failure) => new CommandNotStarted({ command, cause: failure })));
|
|
104
|
+
const standardInput = makeStandardInputRunner();
|
|
105
|
+
const standardInputFiber = yield* Effect.forkScoped(
|
|
106
|
+
commandOptions.stdin === undefined
|
|
107
|
+
? Effect.void
|
|
108
|
+
: standardInput.run(Stream.run(commandOptions.stdin, child.stdin)),
|
|
109
|
+
);
|
|
110
|
+
const readUntilCommandReleased = (output: Stream.Stream<Uint8Array, PlatformError>) =>
|
|
111
|
+
outputMode.readOutput(output.pipe(Stream.interruptWhen(Deferred.await(commandReleased))));
|
|
112
|
+
const outputFiber = yield* Effect.forkIn(
|
|
113
|
+
Effect.all(
|
|
114
|
+
[readUntilCommandReleased(child.stdout), readUntilCommandReleased(child.stderr)],
|
|
115
|
+
{ concurrency: "unbounded" },
|
|
116
|
+
),
|
|
117
|
+
outputScope,
|
|
118
|
+
);
|
|
119
|
+
yield* Scope.addFinalizer(
|
|
120
|
+
outputScope,
|
|
121
|
+
Deferred.done(commandReleased, Exit.void).pipe(Effect.andThen(Fiber.await(outputFiber))),
|
|
122
|
+
);
|
|
123
|
+
const describeStandardInputFailures = (standardInputExit: Exit.Exit<unknown, unknown>) => {
|
|
124
|
+
if (Exit.isSuccess(standardInputExit)) {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
if (Cause.hasInterruptsOnly(standardInputExit.cause)) {
|
|
128
|
+
return standardInput.interruptedItself()
|
|
129
|
+
? [new CauseWithoutFailure({ details: Cause.pretty(standardInputExit.cause) })]
|
|
130
|
+
: [];
|
|
131
|
+
}
|
|
132
|
+
return failureValuesOfCause(standardInputExit.cause).filter(
|
|
133
|
+
(failureValue) =>
|
|
134
|
+
!(
|
|
135
|
+
PlatformErrors.isPlatformError(failureValue) &&
|
|
136
|
+
isStandardInputClosedByCommand(failureValue)
|
|
137
|
+
),
|
|
138
|
+
);
|
|
139
|
+
};
|
|
140
|
+
const exitBeforeStandardInputFailed = yield* Effect.raceFirst(
|
|
141
|
+
Effect.raceFirst(
|
|
142
|
+
Effect.exit(child.exitCode),
|
|
143
|
+
Fiber.await(standardInputFiber).pipe(
|
|
144
|
+
Effect.flatMap((standardInputExit) =>
|
|
145
|
+
describeStandardInputFailures(standardInputExit).length > 0
|
|
146
|
+
? Effect.succeed(null)
|
|
147
|
+
: Effect.never,
|
|
148
|
+
),
|
|
149
|
+
),
|
|
150
|
+
),
|
|
151
|
+
Fiber.join(outputFiber).pipe(Effect.andThen(Effect.never)),
|
|
152
|
+
);
|
|
153
|
+
const exitCodeExit =
|
|
154
|
+
exitBeforeStandardInputFailed ??
|
|
155
|
+
(yield* child.kill().pipe(Effect.andThen(Effect.exit(child.exitCode))));
|
|
156
|
+
yield* Fiber.interrupt(standardInputFiber);
|
|
157
|
+
const standardInputFailures = Cause.fromReasons(
|
|
158
|
+
describeStandardInputFailures(yield* Fiber.await(standardInputFiber)).map(
|
|
159
|
+
(failureValue) =>
|
|
160
|
+
Cause.makeFailReason(
|
|
161
|
+
new CommandStandardInputFailed({ command, cause: failureValue }),
|
|
162
|
+
),
|
|
163
|
+
),
|
|
164
|
+
);
|
|
165
|
+
const standardInputFailed = standardInputFailures.reasons.length > 0;
|
|
166
|
+
if (Exit.isFailure(exitCodeExit)) {
|
|
167
|
+
return yield* standardInputFailed
|
|
168
|
+
? Effect.failCause(standardInputFailures)
|
|
169
|
+
: Effect.failCause(exitCodeExit.cause);
|
|
170
|
+
}
|
|
171
|
+
if (exitCodeExit.value === 0) {
|
|
172
|
+
if (standardInputFailed) {
|
|
173
|
+
return yield* Effect.failCause(standardInputFailures);
|
|
174
|
+
}
|
|
175
|
+
const [standardOutput] = yield* Fiber.join(outputFiber);
|
|
176
|
+
return outputMode.returnOutput(standardOutput);
|
|
177
|
+
}
|
|
178
|
+
const [standardOutput, standardError] = yield* Fiber.join(outputFiber);
|
|
179
|
+
return yield* Effect.failCause(
|
|
180
|
+
Cause.combine(
|
|
181
|
+
Cause.fail(
|
|
182
|
+
outputMode.makeFailure({
|
|
183
|
+
command,
|
|
184
|
+
code: exitCodeExit.value,
|
|
185
|
+
standardOutput,
|
|
186
|
+
standardError,
|
|
187
|
+
}),
|
|
188
|
+
),
|
|
189
|
+
standardInputFailures,
|
|
190
|
+
),
|
|
191
|
+
);
|
|
192
|
+
}),
|
|
193
|
+
),
|
|
194
|
+
);
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { isatty } from "node:tty";
|
|
2
|
+
|
|
3
|
+
import * as BunCrypto from "@effect/platform-bun/BunCrypto";
|
|
4
|
+
import * as BunServices from "@effect/platform-bun/BunServices";
|
|
5
|
+
import {
|
|
6
|
+
Deferred,
|
|
7
|
+
Duration,
|
|
8
|
+
Effect,
|
|
9
|
+
Exit,
|
|
10
|
+
Fiber,
|
|
11
|
+
flow,
|
|
12
|
+
Layer,
|
|
13
|
+
LogLevel,
|
|
14
|
+
Option,
|
|
15
|
+
Queue,
|
|
16
|
+
Runtime,
|
|
17
|
+
Schema,
|
|
18
|
+
Stdio,
|
|
19
|
+
Stream,
|
|
20
|
+
} from "effect";
|
|
21
|
+
import type { Cause } from "effect";
|
|
22
|
+
import type { PlatformError } from "effect/PlatformError";
|
|
23
|
+
|
|
24
|
+
import { readStackAbove } from "../../../shared/find-user-locations-in-stack-traces.ts";
|
|
25
|
+
import { formatFailureLines } from "../../../shared/format-error-lines.ts";
|
|
26
|
+
import {
|
|
27
|
+
isImportingTaskModule,
|
|
28
|
+
isInsideTaskBody,
|
|
29
|
+
isInsideTaskWorkerThread,
|
|
30
|
+
} from "../../../shared/mark-task-worker-thread-contexts.ts";
|
|
31
|
+
import {
|
|
32
|
+
printLine,
|
|
33
|
+
StandardErrorWriteFailed,
|
|
34
|
+
standardErrorClosedDeferred,
|
|
35
|
+
standardErrorLinesDeferred,
|
|
36
|
+
standardErrorWriteFailedDeferred,
|
|
37
|
+
} from "../../../shared/print-to-standard-error.ts";
|
|
38
|
+
import { bunWatchModeFlag } from "../../../shared/read-environment-config.ts";
|
|
39
|
+
import type { StandardErrorLine } from "../../../shared/rpc.ts";
|
|
40
|
+
import { saveStandardStreamFlags } from "../../../shared/save-standard-stream-flags.ts";
|
|
41
|
+
import {
|
|
42
|
+
laterRunCallDeferred,
|
|
43
|
+
runningRootTaskDeferred,
|
|
44
|
+
} from "../../../shared/signal-run-called-twice.ts";
|
|
45
|
+
import {
|
|
46
|
+
exitCodeAfterStopSignal,
|
|
47
|
+
listenToSignals,
|
|
48
|
+
STOP_SIGNALS,
|
|
49
|
+
} from "../../../shared/stop-signals.ts";
|
|
50
|
+
import {
|
|
51
|
+
Exited,
|
|
52
|
+
RunCalledDuringImport,
|
|
53
|
+
RunCalledInBunWatchMode,
|
|
54
|
+
RunCalledInStandaloneExecutable,
|
|
55
|
+
RunCalledInTaskBody,
|
|
56
|
+
} from "../../../shared/task-errors.ts";
|
|
57
|
+
import type { Task } from "./make-tasks.ts";
|
|
58
|
+
|
|
59
|
+
const STANDARD_ERROR_FILE_DESCRIPTOR = 2;
|
|
60
|
+
|
|
61
|
+
const READER_WAIT_AFTER_A_STOP_SIGNAL = Duration.seconds(1);
|
|
62
|
+
|
|
63
|
+
class StandardErrorNotRead extends Schema.TaggedError<StandardErrorNotRead>()(
|
|
64
|
+
"StandardErrorNotRead",
|
|
65
|
+
{ waitedMilliseconds: Schema.Finite },
|
|
66
|
+
) {
|
|
67
|
+
public override get message() {
|
|
68
|
+
return `nobody read the last lines on standard error within ${this.waitedMilliseconds}ms after a stop signal, so task exited without them`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public override get [Runtime.errorReported]() {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const findErrnoCode = flow(
|
|
77
|
+
Schema.decodeUnknownOption(Schema.Struct({ cause: Schema.Struct({ code: Schema.String }) })),
|
|
78
|
+
Option.map((reason) => reason.cause.code),
|
|
79
|
+
Option.getOrNull,
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const readerHasGone = (errnoCode: string | null) =>
|
|
83
|
+
errnoCode === "EPIPE" || (errnoCode === "EIO" && isatty(STANDARD_ERROR_FILE_DESCRIPTOR));
|
|
84
|
+
|
|
85
|
+
const recordWriteFailure = (failure: PlatformError) =>
|
|
86
|
+
Effect.sync(() => {
|
|
87
|
+
const errnoCode = findErrnoCode(failure.reason);
|
|
88
|
+
if (readerHasGone(errnoCode)) {
|
|
89
|
+
return Deferred.doneUnsafe(standardErrorClosedDeferred, Exit.void);
|
|
90
|
+
}
|
|
91
|
+
return Deferred.doneUnsafe(
|
|
92
|
+
standardErrorWriteFailedDeferred,
|
|
93
|
+
Exit.succeed(new StandardErrorWriteFailed({ errnoCode, reason: failure.message })),
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const writeLines = (
|
|
98
|
+
stdio: Stdio.Stdio,
|
|
99
|
+
lines: Queue.Queue<typeof StandardErrorLine.Type, Cause.Done>,
|
|
100
|
+
minimumLogLevel: LogLevel.LogLevel,
|
|
101
|
+
) =>
|
|
102
|
+
Stream.fromQueue(lines).pipe(
|
|
103
|
+
Stream.filter((line) => LogLevel.isGreaterThanOrEqualTo(line.logLevel, minimumLogLevel)),
|
|
104
|
+
Stream.map((line) => line.text),
|
|
105
|
+
Stream.run(stdio.stderr()),
|
|
106
|
+
Effect.catchTag("PlatformError", (error) =>
|
|
107
|
+
recordWriteFailure(error).pipe(Effect.andThen(Queue.end(lines))),
|
|
108
|
+
),
|
|
109
|
+
Effect.asVoid,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const exitCodeAfterWriting = <Ended extends NodeJS.Signals | number>(ended: Ended) =>
|
|
113
|
+
Effect.gen(function* () {
|
|
114
|
+
if (Deferred.isDoneUnsafe(standardErrorWriteFailedDeferred)) {
|
|
115
|
+
return yield* yield* Deferred.await(standardErrorWriteFailedDeferred);
|
|
116
|
+
}
|
|
117
|
+
const readerLeftAfterTheRun = ended === 0 && Deferred.isDoneUnsafe(standardErrorClosedDeferred);
|
|
118
|
+
return readerLeftAfterTheRun ? exitCodeAfterStopSignal("SIGPIPE") : ended;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const printLinesDuring = <Ended extends NodeJS.Signals | number, Failure, Services>(
|
|
122
|
+
program: Effect.Effect<Ended, Failure, Services>,
|
|
123
|
+
minimumLogLevel: LogLevel.LogLevel,
|
|
124
|
+
) =>
|
|
125
|
+
Effect.gen(function* () {
|
|
126
|
+
const stdio = yield* Stdio.Stdio;
|
|
127
|
+
const stopSignals = yield* listenToSignals(STOP_SIGNALS);
|
|
128
|
+
const lines = yield* Queue.unbounded<typeof StandardErrorLine.Type, Cause.Done>();
|
|
129
|
+
yield* Deferred.succeed(standardErrorLinesDeferred, lines);
|
|
130
|
+
const writer = yield* writeLines(stdio, lines, minimumLogLevel).pipe(
|
|
131
|
+
Effect.interruptible,
|
|
132
|
+
Effect.forkScoped,
|
|
133
|
+
);
|
|
134
|
+
const ended = yield* program;
|
|
135
|
+
yield* Queue.end(lines);
|
|
136
|
+
const linesWritten = Fiber.join(writer);
|
|
137
|
+
yield* Effect.raceFirst(
|
|
138
|
+
linesWritten,
|
|
139
|
+
Queue.take(stopSignals).pipe(
|
|
140
|
+
Effect.andThen(
|
|
141
|
+
linesWritten.pipe(
|
|
142
|
+
Effect.timeoutOrElse({
|
|
143
|
+
duration: READER_WAIT_AFTER_A_STOP_SIGNAL,
|
|
144
|
+
orElse: () =>
|
|
145
|
+
Effect.fail(
|
|
146
|
+
new StandardErrorNotRead({
|
|
147
|
+
waitedMilliseconds: Duration.toMillis(READER_WAIT_AFTER_A_STOP_SIGNAL),
|
|
148
|
+
}),
|
|
149
|
+
),
|
|
150
|
+
}),
|
|
151
|
+
),
|
|
152
|
+
),
|
|
153
|
+
),
|
|
154
|
+
);
|
|
155
|
+
return yield* exitCodeAfterWriting(ended);
|
|
156
|
+
}).pipe(Effect.scoped);
|
|
157
|
+
|
|
158
|
+
const restoreStandardStreamFlagsAfter = <Ended, Failure, Services>(
|
|
159
|
+
program: Effect.Effect<Ended, Failure, Services>,
|
|
160
|
+
) =>
|
|
161
|
+
Effect.gen(function* () {
|
|
162
|
+
const restoreStandardStreamFlags = yield* saveStandardStreamFlags;
|
|
163
|
+
return yield* program.pipe(Effect.tap(() => restoreStandardStreamFlags));
|
|
164
|
+
}).pipe(Effect.scoped);
|
|
165
|
+
|
|
166
|
+
const MINIMUM_LOG_LEVEL_OF_LOG_OPTION = {
|
|
167
|
+
info: "Info",
|
|
168
|
+
error: "Error",
|
|
169
|
+
} as const satisfies Record<string, LogLevel.LogLevel>;
|
|
170
|
+
|
|
171
|
+
const runMain = Runtime.makeRunMain(({ fiber, teardown }) => {
|
|
172
|
+
fiber.addObserver((taskExit) => {
|
|
173
|
+
teardown(taskExit, (exitCode) => {
|
|
174
|
+
process.exit(exitCode);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
export const main = (
|
|
180
|
+
rootTask: Task,
|
|
181
|
+
options: { readonly log?: keyof typeof MINIMUM_LOG_LEVEL_OF_LOG_OPTION } = {},
|
|
182
|
+
) => {
|
|
183
|
+
if (isInsideTaskWorkerThread()) {
|
|
184
|
+
const isCalledWhileImportingTaskModule = !isInsideTaskBody() && isImportingTaskModule();
|
|
185
|
+
if (isCalledWhileImportingTaskModule) {
|
|
186
|
+
throw new RunCalledDuringImport();
|
|
187
|
+
}
|
|
188
|
+
throw new RunCalledInTaskBody({ rootTaskName: rootTask.name });
|
|
189
|
+
}
|
|
190
|
+
const runCall = Exit.succeed({
|
|
191
|
+
rootTaskName: rootTask.name,
|
|
192
|
+
stack: readStackAbove(main),
|
|
193
|
+
});
|
|
194
|
+
const isFirstRunCall = Deferred.doneUnsafe(runningRootTaskDeferred, runCall);
|
|
195
|
+
if (!isFirstRunCall) {
|
|
196
|
+
Deferred.doneUnsafe(laterRunCallDeferred, runCall);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
runMain(
|
|
200
|
+
Effect.gen(function* () {
|
|
201
|
+
const signals = yield* listenToSignals(STOP_SIGNALS);
|
|
202
|
+
if (Bun.isStandaloneExecutable) {
|
|
203
|
+
yield* printLine(
|
|
204
|
+
"Error",
|
|
205
|
+
formatFailureLines(
|
|
206
|
+
new RunCalledInStandaloneExecutable({ rootTaskName: rootTask.name }),
|
|
207
|
+
null,
|
|
208
|
+
),
|
|
209
|
+
);
|
|
210
|
+
return 1;
|
|
211
|
+
}
|
|
212
|
+
const watchModeFlag = yield* bunWatchModeFlag;
|
|
213
|
+
if (Option.isSome(watchModeFlag)) {
|
|
214
|
+
yield* printLine(
|
|
215
|
+
"Error",
|
|
216
|
+
formatFailureLines(new RunCalledInBunWatchMode({ flag: watchModeFlag.value }), null),
|
|
217
|
+
);
|
|
218
|
+
return 1;
|
|
219
|
+
}
|
|
220
|
+
const supervision = yield* Effect.promise(() => import("../../supervise-the-run/index.ts"));
|
|
221
|
+
const supervisorProcessId = yield* supervision.readSupervisorProcessId;
|
|
222
|
+
const superviseThisRun =
|
|
223
|
+
supervisorProcessId === null && (yield* supervision.isRootTaskFileThere);
|
|
224
|
+
if (superviseThisRun) {
|
|
225
|
+
return yield* supervision.superviseRun(signals);
|
|
226
|
+
}
|
|
227
|
+
if (supervisorProcessId !== null) {
|
|
228
|
+
yield* supervision.reportReady;
|
|
229
|
+
}
|
|
230
|
+
const mainThread = yield* Effect.promise(() => import("../../main-thread/index.ts"));
|
|
231
|
+
return yield* mainThread.runRootTaskFiber.pipe(
|
|
232
|
+
Effect.provide(mainThread.MainThreadLive(rootTask, signals, supervisorProcessId)),
|
|
233
|
+
mainThread.exitCodeOfTheRun,
|
|
234
|
+
Effect.provide(mainThread.StopStateLive),
|
|
235
|
+
);
|
|
236
|
+
}).pipe(
|
|
237
|
+
Effect.catchTags({
|
|
238
|
+
RootTaskFileNotChecked: (failure) =>
|
|
239
|
+
printLine("Error", formatFailureLines(failure, null)).pipe(Effect.as(1)),
|
|
240
|
+
SupervisorSocketNotOpened: (failure) =>
|
|
241
|
+
printLine("Error", formatFailureLines(failure, null)).pipe(Effect.as(1)),
|
|
242
|
+
}),
|
|
243
|
+
Effect.scoped,
|
|
244
|
+
(program) =>
|
|
245
|
+
printLinesDuring(program, MINIMUM_LOG_LEVEL_OF_LOG_OPTION[options.log ?? "info"]),
|
|
246
|
+
Effect.provide(Layer.mergeAll(BunServices.layer, BunCrypto.layerSecureRandom)),
|
|
247
|
+
restoreStandardStreamFlagsAfter,
|
|
248
|
+
Effect.uninterruptible,
|
|
249
|
+
Effect.flatMap((ended) =>
|
|
250
|
+
typeof ended === "number"
|
|
251
|
+
? Effect.void.pipe(
|
|
252
|
+
Effect.filterOrFail(
|
|
253
|
+
() => ended === 0,
|
|
254
|
+
() => new Exited({ code: ended }),
|
|
255
|
+
),
|
|
256
|
+
)
|
|
257
|
+
: Effect.sync(() => process.kill(process.pid, ended)).pipe(
|
|
258
|
+
Effect.andThen(Effect.fail(new Exited({ code: 1 }))),
|
|
259
|
+
),
|
|
260
|
+
),
|
|
261
|
+
),
|
|
262
|
+
);
|
|
263
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Array as Arrays, Effect, Result, Schema } from "effect";
|
|
2
|
+
import type { Types } from "effect";
|
|
3
|
+
|
|
4
|
+
import { TaskRequestRefusal } from "../../../shared/task-errors.ts";
|
|
5
|
+
import { SettledTasksFailed } from "../../errors/index.ts";
|
|
6
|
+
import { Task } from "./make-tasks.ts";
|
|
7
|
+
|
|
8
|
+
const tasksIn = (items: readonly (Readonly<Record<string, Task>> | Task)[]) =>
|
|
9
|
+
items.flatMap((listedItem) =>
|
|
10
|
+
listedItem instanceof Task ? [listedItem] : Object.values(listedItem),
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
export const all = (
|
|
14
|
+
items: Parameters<typeof tasksIn>[0],
|
|
15
|
+
options: { readonly concurrency?: Types.Concurrency } = {},
|
|
16
|
+
) => Effect.all(tasksIn(items), { concurrency: options.concurrency ?? "unbounded", discard: true });
|
|
17
|
+
|
|
18
|
+
const isTaskRequestRefusal = Schema.is(TaskRequestRefusal);
|
|
19
|
+
|
|
20
|
+
export const allSettled = (
|
|
21
|
+
items: Parameters<typeof tasksIn>[0],
|
|
22
|
+
options: { readonly concurrency?: Types.Concurrency } = {},
|
|
23
|
+
) =>
|
|
24
|
+
Effect.gen(function* () {
|
|
25
|
+
const settledTasks = [
|
|
26
|
+
...new Map(tasksIn(items).map((settledTask) => [settledTask.name, settledTask])).values(),
|
|
27
|
+
];
|
|
28
|
+
const settledResults = yield* Effect.all(settledTasks, {
|
|
29
|
+
concurrency: options.concurrency ?? "unbounded",
|
|
30
|
+
mode: "result",
|
|
31
|
+
});
|
|
32
|
+
const failedSettledTasks = Arrays.zip(settledTasks, settledResults).flatMap(
|
|
33
|
+
([settledTask, settledResult]) =>
|
|
34
|
+
Result.isFailure(settledResult)
|
|
35
|
+
? [{ failedTaskName: settledTask.name, failure: settledResult.failure }]
|
|
36
|
+
: [],
|
|
37
|
+
);
|
|
38
|
+
const failures = failedSettledTasks.map((failedSettledTask) => failedSettledTask.failure);
|
|
39
|
+
const refusedRequest = failures.find((failure) => isTaskRequestRefusal(failure));
|
|
40
|
+
if (refusedRequest !== undefined) {
|
|
41
|
+
return yield* refusedRequest;
|
|
42
|
+
}
|
|
43
|
+
if (failures.length > 0) {
|
|
44
|
+
return yield* new SettledTasksFailed({
|
|
45
|
+
settledTaskCount: settledTasks.length,
|
|
46
|
+
failedTaskNames: failedSettledTasks.map(
|
|
47
|
+
(failedSettledTask) => failedSettledTask.failedTaskName,
|
|
48
|
+
),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
import { dieOnInvariantFailure } from "../../../shared/invariant-errors.ts";
|
|
4
|
+
|
|
5
|
+
export const trustOutputModeName = <Secret extends boolean>(secret: Secret | undefined) => {
|
|
6
|
+
const outputModeName = secret === true ? "secret" : "logged";
|
|
7
|
+
return Schema.decodeUnknownEffect(
|
|
8
|
+
Schema.declare(
|
|
9
|
+
(candidate): candidate is Secret extends true ? "secret" : "logged" =>
|
|
10
|
+
candidate === outputModeName,
|
|
11
|
+
),
|
|
12
|
+
)(outputModeName).pipe(
|
|
13
|
+
dieOnInvariantFailure(
|
|
14
|
+
"Task.exec",
|
|
15
|
+
"the output mode named by the secret option matches the secret option's type",
|
|
16
|
+
),
|
|
17
|
+
);
|
|
18
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
import { InternalInvariantBroken } from "../../../shared/invariant-errors.ts";
|
|
4
|
+
import {
|
|
5
|
+
holdsRedactedTaskResultKey,
|
|
6
|
+
reviveRedactedTaskResults,
|
|
7
|
+
TaskResult,
|
|
8
|
+
} from "../../../shared/task-results.ts";
|
|
9
|
+
|
|
10
|
+
export const trustTaskResult = <Success>(taskResult: Schema.Json | undefined) => {
|
|
11
|
+
const isBranch = taskResult !== null && typeof taskResult === "object";
|
|
12
|
+
const holdsRedacted = isBranch && holdsRedactedTaskResultKey(taskResult);
|
|
13
|
+
return (
|
|
14
|
+
holdsRedacted
|
|
15
|
+
? reviveRedactedTaskResults(taskResult)
|
|
16
|
+
: Effect.succeed<typeof TaskResult.Type | undefined>(taskResult)
|
|
17
|
+
).pipe(
|
|
18
|
+
Effect.flatMap((revived) =>
|
|
19
|
+
Schema.decodeUnknownEffect(
|
|
20
|
+
Schema.declare((candidate): candidate is Success =>
|
|
21
|
+
Schema.is(Schema.UndefinedOr(Schema.Union([Schema.Json, TaskResult])))(candidate),
|
|
22
|
+
),
|
|
23
|
+
)(revived),
|
|
24
|
+
),
|
|
25
|
+
Effect.catchTag("SchemaError", (cause) =>
|
|
26
|
+
Effect.die(
|
|
27
|
+
new InternalInvariantBroken({
|
|
28
|
+
area: "task results",
|
|
29
|
+
invariant: "a task's result is the JSON or Redacted strings its body returned",
|
|
30
|
+
cause,
|
|
31
|
+
}),
|
|
32
|
+
),
|
|
33
|
+
),
|
|
34
|
+
);
|
|
35
|
+
};
|