@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.
Files changed (147) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +131 -2
  3. package/package.json +41 -4
  4. package/src/index.ts +2 -0
  5. package/src/lib/errors/index.ts +15 -0
  6. package/src/lib/errors/internal/errors.ts +147 -0
  7. package/src/lib/kill-leftover-processes/index.ts +17 -0
  8. package/src/lib/kill-leftover-processes/internal/command-words.ts +16 -0
  9. package/src/lib/kill-leftover-processes/internal/find-process-ids.ts +36 -0
  10. package/src/lib/kill-leftover-processes/internal/kill-process-groups.ts +270 -0
  11. package/src/lib/kill-leftover-processes/internal/list-linux-processes-from-proc.ts +191 -0
  12. package/src/lib/kill-leftover-processes/internal/list-macos-processes-with-sysctl.ts +335 -0
  13. package/src/lib/kill-leftover-processes/internal/list-processes.ts +58 -0
  14. package/src/lib/kill-leftover-processes/internal/mark-run-processes.ts +367 -0
  15. package/src/lib/kill-leftover-processes/internal/process-cleanup-errors.ts +17 -0
  16. package/src/lib/kill-leftover-processes/internal/send-signal.ts +32 -0
  17. package/src/lib/kill-leftover-processes/internal/signal-run-processes.ts +48 -0
  18. package/src/lib/kill-leftover-processes/internal/started-process-groups.ts +107 -0
  19. package/src/lib/main-thread/index.ts +3 -0
  20. package/src/lib/main-thread/internal/listen-for-stop-requests.ts +102 -0
  21. package/src/lib/main-thread/internal/run-main-thread.ts +81 -0
  22. package/src/lib/main-thread/internal/run-root-task-fiber.ts +191 -0
  23. package/src/lib/main-thread/internal/stops-at-first-failure.ts +20 -0
  24. package/src/lib/main-thread/internal/trace-the-run.ts +107 -0
  25. package/src/lib/main-thread/lib/find-code-each-task-depends-on/index.ts +7 -0
  26. package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/follow-imports-from-entry-files.ts +306 -0
  27. package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/load-all-reporting-first-failure.ts +11 -0
  28. package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/make-module-graph.ts +48 -0
  29. package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/read-configured-loaders.ts +64 -0
  30. package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/resolve-module-specifiers.ts +60 -0
  31. package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/rewrite-worker-imports.ts +118 -0
  32. package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/worker-import-errors.ts +19 -0
  33. package/src/lib/main-thread/lib/run-tasks-on-workers/index.ts +7 -0
  34. package/src/lib/main-thread/lib/run-tasks-on-workers/internal/handle-worker-events.ts +348 -0
  35. package/src/lib/main-thread/lib/run-tasks-on-workers/internal/make-task-runs.ts +207 -0
  36. package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/index.ts +1 -0
  37. package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/internal/await-within-counted-limit.ts +29 -0
  38. package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/internal/execute-task.ts +272 -0
  39. package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/internal/make-task-setup-clock.ts +129 -0
  40. package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/internal/run-requested-tasks.ts +135 -0
  41. package/src/lib/main-thread/lib/run-tasks-on-workers/shared/define-task-run-state.ts +288 -0
  42. package/src/lib/main-thread/lib/run-tasks-on-workers/shared/make-stopwatch.ts +39 -0
  43. package/src/lib/main-thread/lib/run-tasks-on-workers/shared/share-standard-input-with-root-task.ts +36 -0
  44. package/src/lib/main-thread/lib/run-tasks-on-workers/shared/stop-task.ts +166 -0
  45. package/src/lib/main-thread/lib/scan-module-source/index.ts +1 -0
  46. package/src/lib/main-thread/lib/scan-module-source/internal/find-lexical-bindings.ts +154 -0
  47. package/src/lib/main-thread/lib/scan-module-source/internal/find-task-declarations.ts +205 -0
  48. package/src/lib/main-thread/lib/scan-module-source/internal/parse-module-text.ts +34 -0
  49. package/src/lib/main-thread/lib/scan-module-source/internal/read-literal-string.ts +15 -0
  50. package/src/lib/main-thread/lib/scan-module-source/internal/read-module-loads.ts +316 -0
  51. package/src/lib/main-thread/lib/scan-module-source/internal/scan-module-text.ts +70 -0
  52. package/src/lib/main-thread/lib/scan-module-source/internal/unwrap-type-assertions.ts +10 -0
  53. package/src/lib/main-thread/lib/scan-module-source/internal/visit-non-reference-identifiers.ts +21 -0
  54. package/src/lib/main-thread/shared/fail-run.ts +36 -0
  55. package/src/lib/main-thread/shared/find-failure-reason.ts +43 -0
  56. package/src/lib/main-thread/shared/find-package-directories.ts +34 -0
  57. package/src/lib/main-thread/shared/find-tasks-reachable-from-root-task-file.ts +246 -0
  58. package/src/lib/main-thread/shared/list-directory-paths-up-to-root.ts +13 -0
  59. package/src/lib/main-thread/shared/main-thread-errors.ts +373 -0
  60. package/src/lib/main-thread/shared/package-json-errors.ts +93 -0
  61. package/src/lib/main-thread/shared/provide-discovered-tasks.ts +26 -0
  62. package/src/lib/main-thread/shared/provide-process-groups.ts +63 -0
  63. package/src/lib/main-thread/shared/provide-run-root-task-config.ts +66 -0
  64. package/src/lib/main-thread/shared/provide-stop-state.ts +150 -0
  65. package/src/lib/main-thread/shared/provide-worker-pool.ts +607 -0
  66. package/src/lib/main-thread/shared/read-bare-package-name.ts +3 -0
  67. package/src/lib/main-thread/shared/read-package-json.ts +149 -0
  68. package/src/lib/main-thread/shared/report-task-failure.ts +31 -0
  69. package/src/lib/preload-rewritten-imports-into-each-worker/index.ts +5 -0
  70. package/src/lib/preload-rewritten-imports-into-each-worker/internal/load-rewritten-imports.ts +34 -0
  71. package/src/lib/supervise-the-run/index.ts +15 -0
  72. package/src/lib/supervise-the-run/internal/adopt-orphans-on-linux.ts +73 -0
  73. package/src/lib/supervise-the-run/internal/check-root-task-file.ts +8 -0
  74. package/src/lib/supervise-the-run/internal/report-ready.ts +12 -0
  75. package/src/lib/supervise-the-run/internal/share-process-groups.ts +25 -0
  76. package/src/lib/supervise-the-run/internal/start-watching-the-supervisor.ts +6 -0
  77. package/src/lib/supervise-the-run/internal/supervise-run.ts +155 -0
  78. package/src/lib/supervise-the-run/internal/talk-to-the-supervisor.ts +53 -0
  79. package/src/lib/supervise-the-run/internal/watch-the-supervisor.ts +61 -0
  80. package/src/lib/task/index.ts +5 -0
  81. package/src/lib/task/internal/make-tasks.ts +82 -0
  82. package/src/lib/task/internal/run-command.ts +194 -0
  83. package/src/lib/task/internal/run-root-task.ts +263 -0
  84. package/src/lib/task/internal/run-task-lists.ts +51 -0
  85. package/src/lib/task/internal/trust-output-mode-names.ts +18 -0
  86. package/src/lib/task/internal/trust-task-results.ts +35 -0
  87. package/src/lib/task/internal/use-task-services.ts +4 -0
  88. package/src/lib/worker-thread/index.ts +3 -0
  89. package/src/lib/worker-thread/internal/answer-main-thread-messages.ts +141 -0
  90. package/src/lib/worker-thread/internal/describe-task-body-result.ts +91 -0
  91. package/src/lib/worker-thread/internal/name-config-key-not-set.ts +57 -0
  92. package/src/lib/worker-thread/internal/run-task-body.ts +117 -0
  93. package/src/lib/worker-thread/internal/serve-on-the-parent-port.ts +63 -0
  94. package/src/lib/worker-thread/internal/start-worker-thread.ts +41 -0
  95. package/src/lib/worker-thread/lib/provide-task-services/index.ts +1 -0
  96. package/src/lib/worker-thread/lib/provide-task-services/internal/make-task-logger.ts +34 -0
  97. package/src/lib/worker-thread/lib/provide-task-services/internal/provide-task-services.ts +86 -0
  98. package/src/lib/worker-thread/lib/provide-task-services/internal/read-standard-input-from-main-thread.ts +58 -0
  99. package/src/lib/worker-thread/lib/provide-task-services/internal/resolve-paths-from-package-directory.ts +61 -0
  100. package/src/lib/worker-thread/lib/provide-task-services/internal/run-commands-in-package-directory.ts +24 -0
  101. package/src/lib/worker-thread/lib/provide-task-services/internal/spawn-commands-with-bun.ts +545 -0
  102. package/src/lib/worker-thread/lib/provide-task-services/internal/spawn-reported-process.ts +151 -0
  103. package/src/lib/worker-thread/shared/await-main-thread-answers.ts +76 -0
  104. package/src/lib/worker-thread/shared/decode-task-result-json.ts +114 -0
  105. package/src/lib/worker-thread/shared/define-task-body-record.ts +11 -0
  106. package/src/lib/worker-thread/shared/describe-issue-input.ts +52 -0
  107. package/src/lib/worker-thread/shared/plain-object.ts +8 -0
  108. package/src/lib/worker-thread/shared/provide-worker-state.ts +160 -0
  109. package/src/lib/worker-thread/shared/worker-thread-errors.ts +41 -0
  110. package/src/shared/attribute-reported-failures.ts +25 -0
  111. package/src/shared/claim-processes-to-report.ts +22 -0
  112. package/src/shared/command-errors.ts +44 -0
  113. package/src/shared/complete-discarded-calls-once-sent.ts +12 -0
  114. package/src/shared/cut-to-graphemes.ts +12 -0
  115. package/src/shared/environment-config-errors.ts +74 -0
  116. package/src/shared/error-fields.ts +13 -0
  117. package/src/shared/find-bun-loader-for-script.ts +32 -0
  118. package/src/shared/find-node-modules-package-directory.ts +35 -0
  119. package/src/shared/find-user-locations-in-stack-traces.ts +64 -0
  120. package/src/shared/format-error-lines.ts +53 -0
  121. package/src/shared/format-task-lines.ts +21 -0
  122. package/src/shared/hold-task-logs.ts +24 -0
  123. package/src/shared/invariant-errors.ts +28 -0
  124. package/src/shared/mark-task-worker-thread-contexts.ts +29 -0
  125. package/src/shared/module-graph-errors.ts +49 -0
  126. package/src/shared/name-config-key.ts +42 -0
  127. package/src/shared/names-from-package-json.ts +7 -0
  128. package/src/shared/print-to-standard-error.ts +48 -0
  129. package/src/shared/process-errors.ts +191 -0
  130. package/src/shared/read-environment-config.ts +239 -0
  131. package/src/shared/record-tasks-made.ts +10 -0
  132. package/src/shared/report-cause.ts +140 -0
  133. package/src/shared/report-errors.ts +121 -0
  134. package/src/shared/resolve-task-options.ts +20 -0
  135. package/src/shared/rpc.ts +161 -0
  136. package/src/shared/run-in-child-fiber-and-join.ts +16 -0
  137. package/src/shared/run-marker.ts +7 -0
  138. package/src/shared/save-standard-stream-flags.ts +57 -0
  139. package/src/shared/select-cause-reasons.ts +14 -0
  140. package/src/shared/signal-run-called-twice.ts +11 -0
  141. package/src/shared/standard-input-streams.ts +41 -0
  142. package/src/shared/stop-signals.ts +41 -0
  143. package/src/shared/task-errors.ts +224 -0
  144. package/src/shared/task-package.ts +5 -0
  145. package/src/shared/task-results.ts +55 -0
  146. package/src/shared/task-services.ts +30 -0
  147. package/src/shared/write-trace-file.ts +112 -0
@@ -0,0 +1,141 @@
1
+ import { Cause, Clock, Deferred, Effect, Exit, Logger, Result, Schema, Tracer } from "effect";
2
+ import { RpcServer } from "effect/rpc";
3
+
4
+ import { TASK_ERROR_FIELDS } from "../../../shared/error-fields.ts";
5
+ import { reportCause } from "../../../shared/report-cause.ts";
6
+ import {
7
+ describeCauseMessage,
8
+ THROWN_VALUE_WITHOUT_MESSAGE,
9
+ } from "../../../shared/report-errors.ts";
10
+ import { TaskEvent, TaskFiberResult, TaskWorkerRpcs } from "../../../shared/rpc.ts";
11
+ import type { StartTaskFiber } from "../../../shared/rpc.ts";
12
+ import { makeTraceFile } from "../../../shared/write-trace-file.ts";
13
+ import {
14
+ forgetEndedProcessGroups,
15
+ killTaskProcessGroups,
16
+ } from "../../kill-leftover-processes/index.ts";
17
+ import { makeTaskBodyRecord } from "../shared/define-task-body-record.ts";
18
+ import { WorkerState, reportToMainThread } from "../shared/provide-worker-state.ts";
19
+ import { describeCleanupFailure, describeTaskBodyResult } from "./describe-task-body-result.ts";
20
+ import { runTaskBody } from "./run-task-body.ts";
21
+
22
+ class TaskResultNotHandedOver extends Schema.TaggedError<TaskResultNotHandedOver>()(
23
+ "TaskResultNotHandedOver",
24
+ { ...TASK_ERROR_FIELDS, taskName: Schema.String, cause: Schema.Defect() },
25
+ ) {
26
+ public override get message() {
27
+ return `could not hand the result of "${this.taskName}" to the main thread because ${describeCauseMessage(this.cause)}`;
28
+ }
29
+ }
30
+
31
+ const describeResultNotHandedOver = (notHandedOver: TaskResultNotHandedOver) =>
32
+ TaskFiberResult.cases.Failed.make(reportCause(Cause.fail(notHandedOver)));
33
+
34
+ const endTaskBody = Effect.fn("endTaskBody")(function* (
35
+ startMessage: typeof StartTaskFiber.Type,
36
+ body: ReturnType<typeof makeTaskBodyRecord>,
37
+ taskExit: Exit.Exit<unknown, unknown>,
38
+ ) {
39
+ const elapsedMilliseconds = (yield* Clock.currentTimeMillis) - body.startedAtMilliseconds;
40
+ const workerState = yield* WorkerState;
41
+ const cleanupExit = yield* Effect.exit(
42
+ killTaskProcessGroups(body.startedProcessGroups, startMessage.taskName, (processes) =>
43
+ workerState.claimProcessesToReport(startMessage.taskName, processes),
44
+ ).pipe(Effect.flatMap(forgetEndedProcessGroups(body.startedProcessGroups))),
45
+ );
46
+ const sendTaskFiberEnded = (result: typeof TaskFiberResult.Type) =>
47
+ reportToMainThread(
48
+ TaskEvent.cases.TaskFiberEnded.make({
49
+ taskName: startMessage.taskName,
50
+ result,
51
+ logs: body.logs,
52
+ }),
53
+ );
54
+ yield* Effect.suspend((): Effect.Effect<typeof TaskFiberResult.Type> => {
55
+ const cleanupFailure = describeCleanupFailure(body, cleanupExit, taskExit);
56
+ return cleanupFailure === null
57
+ ? describeTaskBodyResult(
58
+ startMessage,
59
+ body,
60
+ Exit.isFailure(cleanupExit) ? cleanupExit : taskExit,
61
+ elapsedMilliseconds,
62
+ )
63
+ : Effect.succeed(cleanupFailure);
64
+ }).pipe(
65
+ Effect.flatMap(sendTaskFiberEnded),
66
+ Effect.matchCauseEffect({
67
+ onFailure: (failureCause) => {
68
+ const defect = Cause.findDefect(failureCause);
69
+ return Result.isFailure(defect)
70
+ ? Effect.failCause(defect.failure)
71
+ : Effect.try(() =>
72
+ describeResultNotHandedOver(
73
+ new TaskResultNotHandedOver({
74
+ taskName: startMessage.taskName,
75
+ cause: defect.success,
76
+ }),
77
+ ),
78
+ ).pipe(
79
+ Effect.catchTag("UnknownError", () =>
80
+ Effect.sync(() =>
81
+ describeResultNotHandedOver(
82
+ new TaskResultNotHandedOver({
83
+ taskName: startMessage.taskName,
84
+ cause: THROWN_VALUE_WITHOUT_MESSAGE,
85
+ }),
86
+ ),
87
+ ),
88
+ ),
89
+ Effect.flatMap(sendTaskFiberEnded),
90
+ );
91
+ },
92
+ onSuccess: (success) => Effect.succeed(success),
93
+ }),
94
+ );
95
+ });
96
+
97
+ const startTaskFiber = Effect.fn("startTaskFiber")(function* (start: typeof StartTaskFiber.Type) {
98
+ const workerState = yield* WorkerState;
99
+ const body = makeTaskBodyRecord();
100
+ yield* workerState.startTaskBodyFiber(
101
+ start.taskName,
102
+ runTaskBody(start, body).pipe(
103
+ Effect.withParentSpan(Tracer.externalSpan({ traceId: start.traceId, spanId: start.spanId })),
104
+ Effect.onExit((taskExit) => endTaskBody(start, body, taskExit)),
105
+ ),
106
+ );
107
+ });
108
+
109
+ const serveUntilExitRequested = Effect.gen(function* () {
110
+ const workerState = yield* WorkerState;
111
+ const exitRequested = yield* Deferred.make<void>();
112
+ const handlers = yield* TaskWorkerRpcs.toHandlers({
113
+ StartTaskFiber: startTaskFiber,
114
+ StopTaskFiber: (stop) => workerState.requestTaskBodyInterruption(stop.taskName),
115
+ AnswerIfResponsive: () => Effect.void,
116
+ ExitWorker: () => Deferred.done(exitRequested, Exit.void).pipe(Effect.asVoid),
117
+ });
118
+ yield* RpcServer.make(TaskWorkerRpcs, { disableTracing: true }).pipe(
119
+ Effect.provideContext(handlers),
120
+ Effect.interruptible,
121
+ Effect.forkScoped,
122
+ );
123
+ yield* Deferred.await(exitRequested);
124
+ });
125
+
126
+ export const serveTheMainThread = Effect.gen(function* () {
127
+ const workerState = yield* WorkerState;
128
+ const traceFile = yield* makeTraceFile({
129
+ traceFilePath: workerState.traceFilePath,
130
+ thread: "worker",
131
+ maxBatchSize: 1,
132
+ onIncomplete: (reason) =>
133
+ Effect.sync(() => {
134
+ workerState.reportToMainThreadNow(TaskEvent.cases.TraceWriteFailed.make({ reason }));
135
+ }),
136
+ });
137
+ yield* serveUntilExitRequested.pipe(
138
+ Effect.provideService(Tracer.Tracer, traceFile.tracer),
139
+ Effect.provideService(Logger.CurrentLoggers, new Set([traceFile.logger])),
140
+ );
141
+ });
@@ -0,0 +1,91 @@
1
+ import { Cause, Effect, Exit, Option, Schema } from "effect";
2
+
3
+ import { reportCause } from "../../../shared/report-cause.ts";
4
+ import { TaskFiberResult } from "../../../shared/rpc.ts";
5
+ import type { StartTaskFiber } from "../../../shared/rpc.ts";
6
+ import { failuresOfCause } from "../../../shared/select-cause-reasons.ts";
7
+ import { isAwaitedTasksFailure } from "../../../shared/task-errors.ts";
8
+ import { CommandFailed } from "../../errors/index.ts";
9
+ import { decodeTaskResultJson } from "../shared/decode-task-result-json.ts";
10
+ import type { makeTaskBodyRecord } from "../shared/define-task-body-record.ts";
11
+
12
+ const isCommandFailed = Schema.is(CommandFailed);
13
+
14
+ export const describeBodyFailure = (cause: Cause.Cause<unknown>) => {
15
+ const failureCause = failuresOfCause(cause);
16
+ const hasOnlyOneFailure = failureCause.reasons.length === 1;
17
+ const firstFailure = Cause.findErrorOption(failureCause);
18
+ const awaitedTasksFailure = Option.filter(firstFailure, isAwaitedTasksFailure);
19
+ const hasOnlyAwaitedFailure = hasOnlyOneFailure && Option.isSome(awaitedTasksFailure);
20
+ if (hasOnlyAwaitedFailure) {
21
+ return TaskFiberResult.cases.AwaitedTasksFailed.make({ failure: awaitedTasksFailure.value });
22
+ }
23
+ const failedCommand = Option.filter(firstFailure, isCommandFailed);
24
+ const hasOnlyCommandFailure = hasOnlyOneFailure && Option.isSome(failedCommand);
25
+ return TaskFiberResult.cases.Failed.make({
26
+ ...reportCause(cause),
27
+ ...(hasOnlyCommandFailure ? { failedCommandExitCode: failedCommand.value.code } : {}),
28
+ });
29
+ };
30
+
31
+ export const describeTaskBodyResult = (
32
+ startMessage: typeof StartTaskFiber.Type,
33
+ body: ReturnType<typeof makeTaskBodyRecord>,
34
+ taskExit: Exit.Exit<unknown, unknown>,
35
+ elapsedMilliseconds: number,
36
+ ) => {
37
+ if (Exit.isSuccess(taskExit)) {
38
+ return decodeTaskResultJson(startMessage.taskName, "returned", taskExit.value).pipe(
39
+ Effect.map((returnedValue) =>
40
+ TaskFiberResult.cases.Returned.make({
41
+ ...(returnedValue === undefined ? {} : { value: returnedValue }),
42
+ elapsedMilliseconds,
43
+ }),
44
+ ),
45
+ Effect.catchTag("TaskResultNotJson", (error) =>
46
+ Effect.succeed(TaskFiberResult.cases.Failed.make(reportCause(Cause.fail(error)))),
47
+ ),
48
+ );
49
+ }
50
+ if (!Cause.hasInterruptsOnly(taskExit.cause)) {
51
+ return Effect.succeed(describeBodyFailure(taskExit.cause));
52
+ }
53
+ return Effect.succeed(
54
+ Option.match(body.causeReportedBeforeCleanup, {
55
+ onNone: () => TaskFiberResult.cases.Interrupted.make({}),
56
+ onSome: describeBodyFailure,
57
+ }),
58
+ );
59
+ };
60
+
61
+ const failureValueOf = (reason: Cause.Reason<unknown>): unknown => {
62
+ if (Cause.isFailReason(reason)) {
63
+ return reason.error;
64
+ }
65
+ return Cause.isDieReason(reason) ? reason.defect : reason;
66
+ };
67
+
68
+ export const describeCleanupFailure = (
69
+ body: ReturnType<typeof makeTaskBodyRecord>,
70
+ cleanupExit: Exit.Exit<unknown, unknown>,
71
+ taskExit: Exit.Exit<unknown, unknown>,
72
+ ) => {
73
+ if (Option.isNone(body.causeReportedBeforeCleanup)) {
74
+ return null;
75
+ }
76
+ const causeReportedBeforeCleanup = body.causeReportedBeforeCleanup.value;
77
+ const reportedFailureValues = new Set(causeReportedBeforeCleanup.reasons.map(failureValueOf));
78
+ const reasonsAddedByFinalizers = Exit.isFailure(taskExit)
79
+ ? taskExit.cause.reasons.filter(
80
+ (reason) =>
81
+ !Cause.isInterruptReason(reason) && !reportedFailureValues.has(failureValueOf(reason)),
82
+ )
83
+ : [];
84
+ const reasonsOfProcessCleanup = Exit.isFailure(cleanupExit) ? cleanupExit.cause.reasons : [];
85
+ const cleanupFailureReasons = [...reasonsOfProcessCleanup, ...reasonsAddedByFinalizers];
86
+ return cleanupFailureReasons.length === 0
87
+ ? null
88
+ : TaskFiberResult.cases.CleanupFailed.make(
89
+ reportCause(Cause.fromReasons(cleanupFailureReasons)),
90
+ );
91
+ };
@@ -0,0 +1,57 @@
1
+ import { Config, Effect, Exit, Option, Schema, SchemaAST, SchemaIssue } from "effect";
2
+
3
+ import {
4
+ nameConfigKey,
5
+ nameEnvironmentConfigVariable,
6
+ observeConfigLoads,
7
+ } from "../../../shared/name-config-key.ts";
8
+ import { ConfigKeyNotSet } from "../../../shared/task-errors.ts";
9
+
10
+ const isConfigError = Schema.is(Schema.instanceOf(Config.ConfigError));
11
+
12
+ const decodeConfigErrorAtPath = Schema.decodeUnknownOption(
13
+ Schema.Struct({ cause: Schema.Struct({ issue: Schema.instanceOf(SchemaIssue.Pointer) }) }),
14
+ );
15
+
16
+ const expectsManyVariables = (issue: SchemaIssue.Issue) =>
17
+ issue instanceof SchemaIssue.InvalidType &&
18
+ (SchemaAST.isObjects(issue.ast) || SchemaAST.isArrays(issue.ast));
19
+
20
+ export const nameConfigKeyNotSet = <Success, Failure, Services>(
21
+ effect: Effect.Effect<Success, Failure, Services>,
22
+ ) =>
23
+ Effect.suspend(() => {
24
+ const missingKeys = new Map<string, { readonly readsRuntimeConfigProvider: boolean }>();
25
+ return effect.pipe(
26
+ observeConfigLoads(({ configPath, readsRuntimeConfigProvider, loaded }) => {
27
+ const loadedNothing = Exit.isSuccess(loaded) && loaded.value === undefined;
28
+ if (loadedNothing) {
29
+ missingKeys.set(nameConfigKey(configPath), { readsRuntimeConfigProvider });
30
+ }
31
+ }),
32
+ Effect.mapError((failure): ConfigKeyNotSet | Failure => {
33
+ if (!isConfigError(failure)) {
34
+ return failure;
35
+ }
36
+ const configError = failure;
37
+ const decodedConfigError = decodeConfigErrorAtPath(configError);
38
+ if (Option.isNone(decodedConfigError)) {
39
+ return configError;
40
+ }
41
+ const pointer = decodedConfigError.value.cause.issue;
42
+ const configPath = pointer.path.map(String);
43
+ const missing = missingKeys.get(nameConfigKey(configPath));
44
+ if (missing === undefined) {
45
+ return configError;
46
+ }
47
+ const namesOneEnvironmentConfigVariable =
48
+ missing.readsRuntimeConfigProvider && !expectsManyVariables(pointer.issue);
49
+ return new ConfigKeyNotSet({
50
+ configKey: nameConfigKey(configPath),
51
+ environmentConfigVariable: namesOneEnvironmentConfigVariable
52
+ ? nameEnvironmentConfigVariable(configPath)
53
+ : null,
54
+ });
55
+ }),
56
+ );
57
+ });
@@ -0,0 +1,117 @@
1
+ import { Effect, Exit, Schema, Cause, Clock, Option } from "effect";
2
+
3
+ import {
4
+ InsideTaskBody,
5
+ markImportingTaskModule,
6
+ } from "../../../shared/mark-task-worker-thread-contexts.ts";
7
+ import { TASK_TRACE_LABEL_PREFIX } from "../../../shared/names-from-package-json.ts";
8
+ import { findTasksMadeWithName } from "../../../shared/record-tasks-made.ts";
9
+ import { CauseWithoutFailure } from "../../../shared/report-errors.ts";
10
+ import { TaskEvent } from "../../../shared/rpc.ts";
11
+ import type { StartTaskFiber } from "../../../shared/rpc.ts";
12
+ import { runInChildFiberAndJoin } from "../../../shared/run-in-child-fiber-and-join.ts";
13
+ import { RunCalledDuringImport, TaskModuleImportFailed } from "../../../shared/task-errors.ts";
14
+ import { provideTaskServices } from "../lib/provide-task-services/index.ts";
15
+ import type { makeTaskBodyRecord } from "../shared/define-task-body-record.ts";
16
+ import { WorkerState, reportToMainThread } from "../shared/provide-worker-state.ts";
17
+ import {
18
+ TaskNotRegisteredInWorker,
19
+ TaskRegisteredAgainInWorker,
20
+ } from "../shared/worker-thread-errors.ts";
21
+ import { describeBodyFailure } from "./describe-task-body-result.ts";
22
+ import { nameConfigKeyNotSet } from "./name-config-key-not-set.ts";
23
+
24
+ const isRunCalledDuringImport = Schema.is(RunCalledDuringImport);
25
+
26
+ export const runTaskBody = Effect.fn("runTaskBody")(
27
+ function* (
28
+ startMessage: typeof StartTaskFiber.Type,
29
+ body: ReturnType<typeof makeTaskBodyRecord>,
30
+ ) {
31
+ yield* reportToMainThread(
32
+ TaskEvent.cases.TaskFiberLoading.make({ taskName: startMessage.taskName }),
33
+ );
34
+ yield* markImportingTaskModule(
35
+ Effect.tryPromise({
36
+ try: () => import(startMessage.taskFilePath),
37
+ catch: (cause) =>
38
+ isRunCalledDuringImport(cause)
39
+ ? cause
40
+ : new TaskModuleImportFailed({ filePath: startMessage.taskFilePath, cause }),
41
+ }),
42
+ );
43
+ const tasksMade = findTasksMadeWithName(startMessage.taskName);
44
+ const [task] = tasksMade;
45
+ if (task === undefined) {
46
+ return yield* new TaskNotRegisteredInWorker({
47
+ taskName: startMessage.taskName,
48
+ filePath: startMessage.taskFilePath,
49
+ });
50
+ }
51
+ if (tasksMade.length > 1) {
52
+ return yield* new TaskRegisteredAgainInWorker({
53
+ taskName: startMessage.taskName,
54
+ filePath: startMessage.taskFilePath,
55
+ madeCount: tasksMade.length,
56
+ });
57
+ }
58
+ body.startedAtMilliseconds = yield* Clock.currentTimeMillis;
59
+ yield* reportToMainThread(
60
+ TaskEvent.cases.TaskFiberStarted.make({ taskName: startMessage.taskName }),
61
+ );
62
+ const workerState = yield* WorkerState;
63
+ return yield* Effect.uninterruptibleMask((restore) =>
64
+ restore(task.body).pipe(
65
+ nameConfigKeyNotSet,
66
+ Effect.provideService(InsideTaskBody, true),
67
+ Effect.onExit((bodyExit) =>
68
+ Effect.gen(function* () {
69
+ const endedWithOnlyInterrupts =
70
+ Exit.isFailure(bodyExit) && Cause.hasInterruptsOnly(bodyExit.cause);
71
+ if (!endedWithOnlyInterrupts) {
72
+ return;
73
+ }
74
+ if (yield* workerState.wasAskedToStop(startMessage.taskName)) {
75
+ return;
76
+ }
77
+ const interruptedItself = Cause.fail(
78
+ new CauseWithoutFailure({ details: Cause.pretty(bodyExit.cause) }),
79
+ );
80
+ body.causeReportedBeforeCleanup = Option.some(interruptedItself);
81
+ yield* reportToMainThread(
82
+ TaskEvent.cases.TaskFiberFailedBeforeCleanup.make({
83
+ taskName: startMessage.taskName,
84
+ failure: describeBodyFailure(interruptedItself),
85
+ }),
86
+ );
87
+ }),
88
+ ),
89
+ Effect.tapCause((cause) => {
90
+ if (Cause.hasInterruptsOnly(cause)) {
91
+ return Effect.void;
92
+ }
93
+ body.causeReportedBeforeCleanup = Option.some(cause);
94
+ return reportToMainThread(
95
+ TaskEvent.cases.TaskFiberFailedBeforeCleanup.make({
96
+ taskName: startMessage.taskName,
97
+ failure: describeBodyFailure(cause),
98
+ }),
99
+ );
100
+ }),
101
+ ),
102
+ );
103
+ },
104
+ (effect, startMessage, body) =>
105
+ Effect.gen(function* () {
106
+ const workerState = yield* WorkerState;
107
+ yield* Effect.annotateCurrentSpan({
108
+ [`${TASK_TRACE_LABEL_PREFIX}.task`]: startMessage.taskName,
109
+ [`${TASK_TRACE_LABEL_PREFIX}.worker`]: workerState.workerId,
110
+ });
111
+ return yield* effect.pipe(
112
+ Effect.scoped,
113
+ runInChildFiberAndJoin,
114
+ provideTaskServices(startMessage, workerState, body),
115
+ );
116
+ }),
117
+ );
@@ -0,0 +1,63 @@
1
+ import { parentPort } from "node:worker_threads";
2
+
3
+ import { Deferred, Effect, Exit, FiberSet, Layer } from "effect";
4
+ import { WorkerRunner } from "effect/workers";
5
+ import { WorkerError, WorkerSpawnError } from "effect/workers/WorkerError";
6
+
7
+ export const ParentPortWorkerRunnerLive = Layer.succeed(WorkerRunner.WorkerRunnerPlatform)({
8
+ start: <Outgoing, Incoming>(): Effect.Effect<
9
+ WorkerRunner.WorkerRunner<Outgoing, Incoming>,
10
+ WorkerError
11
+ > =>
12
+ Effect.gen(function* () {
13
+ const port = yield* Effect.fromNullishOr(parentPort).pipe(
14
+ Effect.mapError(
15
+ () =>
16
+ new WorkerError({
17
+ reason: new WorkerSpawnError({ message: "not running in a worker thread" }),
18
+ }),
19
+ ),
20
+ );
21
+ const sendUnsafe = (_portId: number, message: Outgoing) => {
22
+ port.postMessage([1, message]);
23
+ };
24
+ return {
25
+ run: <Success, Failure, Services>(
26
+ handler: (
27
+ portId: number,
28
+ message: Incoming,
29
+ ) => Effect.Effect<Success, Failure, Services> | void,
30
+ ) =>
31
+ Effect.gen(function* () {
32
+ const runInWorkerScope = yield* FiberSet.makeRuntime<Services, Success, Failure>();
33
+ const closed = yield* Deferred.make<void>();
34
+ const onMessage = (message: WorkerRunner.PlatformMessage<Incoming>) => {
35
+ if (message[0] === 1) {
36
+ Deferred.doneUnsafe(closed, Exit.void);
37
+ return;
38
+ }
39
+ const handled = handler(0, message[1]);
40
+ if (Effect.isEffect(handled)) {
41
+ runInWorkerScope(handled);
42
+ }
43
+ };
44
+ yield* Effect.acquireRelease(
45
+ Effect.sync(() => {
46
+ port.on("message", onMessage);
47
+ port.postMessage([0]);
48
+ }),
49
+ () =>
50
+ Effect.sync(() => {
51
+ port.off("message", onMessage);
52
+ }),
53
+ );
54
+ yield* Deferred.await(closed);
55
+ }).pipe(Effect.scoped),
56
+ send: (portId: number, message: Outgoing) =>
57
+ Effect.sync(() => {
58
+ sendUnsafe(portId, message);
59
+ }),
60
+ sendUnsafe,
61
+ };
62
+ }),
63
+ });
@@ -0,0 +1,41 @@
1
+ import { parentPort } from "node:worker_threads";
2
+
3
+ import * as BunCrypto from "@effect/platform-bun/BunCrypto";
4
+ import * as BunServices from "@effect/platform-bun/BunServices";
5
+ import { Layer, Effect, Exit } from "effect";
6
+ import { FetchHttpClient } from "effect/http";
7
+ import { OtlpExporter, OtlpSerialization } from "effect/observability";
8
+ import { RpcServer } from "effect/rpc";
9
+
10
+ import { runtimeConfigProviderLayer } from "../../../shared/read-environment-config.ts";
11
+ import { WorkerStateLive } from "../shared/provide-worker-state.ts";
12
+ import { serveTheMainThread } from "./answer-main-thread-messages.ts";
13
+ import { ParentPortWorkerRunnerLive } from "./serve-on-the-parent-port.ts";
14
+
15
+ const workerThreadLayer = WorkerStateLive.pipe(
16
+ Layer.provideMerge(RpcServer.layerProtocolWorkerRunner),
17
+ Layer.provide(ParentPortWorkerRunnerLive),
18
+ Layer.provideMerge(
19
+ Layer.mergeAll(
20
+ BunServices.layer,
21
+ BunCrypto.layerDigest,
22
+ BunCrypto.layerSecureRandom,
23
+ FetchHttpClient.layer,
24
+ runtimeConfigProviderLayer,
25
+ OtlpExporter.layerFlusher,
26
+ OtlpSerialization.layerJson,
27
+ ),
28
+ ),
29
+ );
30
+
31
+ export const startWorkerThread = () => {
32
+ if (parentPort !== null) {
33
+ Effect.runFork(
34
+ serveTheMainThread.pipe(
35
+ Effect.provide(workerThreadLayer),
36
+ Effect.scoped,
37
+ Effect.onExit((exit) => Effect.sync(() => process.exit(Exit.isSuccess(exit) ? 0 : 1))),
38
+ ),
39
+ );
40
+ }
41
+ };
@@ -0,0 +1 @@
1
+ export { provideTaskServices } from "./internal/provide-task-services.ts";
@@ -0,0 +1,34 @@
1
+ import { Array as Arrays, Logger, Formatter } from "effect";
2
+
3
+ import { formatCauseLines } from "../../../../../shared/format-error-lines.ts";
4
+ import { prefixLines } from "../../../../../shared/format-task-lines.ts";
5
+ import { waitWhileTaskLogsAreHeld } from "../../../../../shared/hold-task-logs.ts";
6
+ import { TaskEvent } from "../../../../../shared/rpc.ts";
7
+ import type { StartTaskFiber } from "../../../../../shared/rpc.ts";
8
+ import type { makeTaskBodyRecord } from "../../../shared/define-task-body-record.ts";
9
+
10
+ export const makeTaskLogger = (
11
+ startMessage: typeof StartTaskFiber.Type,
12
+ body: ReturnType<typeof makeTaskBodyRecord>,
13
+ taskLogHold: Int32Array,
14
+ reportToMainThreadNow: (event: typeof TaskEvent.Type) => void,
15
+ ) =>
16
+ Logger.make((logEntry) => {
17
+ const text = Arrays.ensure(logEntry.message)
18
+ .map((part) => (typeof part === "string" ? part : Formatter.format(part)))
19
+ .join(" ");
20
+ const line = [
21
+ ...(text === "" ? [] : [text]),
22
+ ...(logEntry.cause.reasons.length === 0
23
+ ? []
24
+ : [formatCauseLines(logEntry.cause, startMessage.taskName)]),
25
+ ].join("\n");
26
+ body.logs.push(line);
27
+ waitWhileTaskLogsAreHeld(taskLogHold);
28
+ reportToMainThreadNow(
29
+ TaskEvent.cases.StandardErrorLineRequested.make({
30
+ logLevel: logEntry.logLevel,
31
+ text: prefixLines(startMessage.taskName, line),
32
+ }),
33
+ );
34
+ });
@@ -0,0 +1,86 @@
1
+ import { Effect, FileSystem, Logger, Path } from "effect";
2
+ import { ChildProcessSpawner } from "effect/process";
3
+
4
+ import { TASK_TRACE_LABEL_PREFIX } from "../../../../../shared/names-from-package-json.ts";
5
+ import { TaskEvent } from "../../../../../shared/rpc.ts";
6
+ import type { StartTaskFiber } from "../../../../../shared/rpc.ts";
7
+ import { Serving, Tasks } from "../../../../../shared/task-services.ts";
8
+ import { decodeTaskResultJson } from "../../../shared/decode-task-result-json.ts";
9
+ import type { OptionalJson } from "../../../shared/decode-task-result-json.ts";
10
+ import type { makeTaskBodyRecord } from "../../../shared/define-task-body-record.ts";
11
+ import { reportToMainThread, WorkerState } from "../../../shared/provide-worker-state.ts";
12
+ import { makeTaskLogger } from "./make-task-logger.ts";
13
+ import { provideTaskStdio } from "./read-standard-input-from-main-thread.ts";
14
+ import { resolvePathsFromPackageDirectory } from "./resolve-paths-from-package-directory.ts";
15
+ import { runCommandsInPackageDirectory } from "./run-commands-in-package-directory.ts";
16
+ import { spawnReportedProcess } from "./spawn-reported-process.ts";
17
+
18
+ const serve = (startMessage: typeof StartTaskFiber.Type, served: typeof OptionalJson.Type) =>
19
+ reportToMainThread(
20
+ TaskEvent.cases.TaskFiberServed.make({
21
+ taskName: startMessage.taskName,
22
+ ...(served === undefined ? {} : { value: served }),
23
+ }),
24
+ ).pipe(Effect.andThen(Effect.never));
25
+
26
+ export const provideTaskServices =
27
+ (
28
+ startMessage: typeof StartTaskFiber.Type,
29
+ workerState: WorkerState["Service"],
30
+ body: ReturnType<typeof makeTaskBodyRecord>,
31
+ ) =>
32
+ <Success, Failure, Services>(effect: Effect.Effect<Success, Failure, Services>) =>
33
+ Effect.gen(function* () {
34
+ const { fileSystem, path } = resolvePathsFromPackageDirectory(
35
+ yield* Path.Path,
36
+ yield* FileSystem.FileSystem,
37
+ startMessage.taskPackageDirectoryPath,
38
+ );
39
+ return yield* effect.pipe(
40
+ provideTaskStdio(startMessage, workerState),
41
+ Effect.provideService(Path.Path, path),
42
+ Effect.provideService(FileSystem.FileSystem, fileSystem),
43
+ Effect.provideService(
44
+ Tasks,
45
+ Tasks.of({
46
+ requestResult: (dependencyTaskDefinition) =>
47
+ workerState.requestDependencyTaskResult(
48
+ dependencyTaskDefinition.name,
49
+ startMessage.taskName,
50
+ ),
51
+ }),
52
+ ),
53
+ Effect.provideService(
54
+ Serving,
55
+ Serving.of({
56
+ serve: (servedValue) =>
57
+ decodeTaskResultJson(startMessage.taskName, "served", servedValue).pipe(
58
+ Effect.flatMap((served) => serve(startMessage, served)),
59
+ Effect.provideService(WorkerState, workerState),
60
+ ),
61
+ }),
62
+ ),
63
+ Effect.provideService(
64
+ ChildProcessSpawner.ChildProcessSpawner,
65
+ runCommandsInPackageDirectory(path, startMessage.taskPackageDirectoryPath, (command) =>
66
+ spawnReportedProcess(startMessage, body, command).pipe(
67
+ Effect.provideService(WorkerState, workerState),
68
+ ),
69
+ ),
70
+ ),
71
+ Effect.provideService(
72
+ Logger.CurrentLoggers,
73
+ new Set([
74
+ ...(yield* Logger.CurrentLoggers),
75
+ makeTaskLogger(
76
+ startMessage,
77
+ body,
78
+ workerState.taskLogHold,
79
+ workerState.reportToMainThreadNow,
80
+ ),
81
+ ]),
82
+ ),
83
+ Effect.annotateLogs(`${TASK_TRACE_LABEL_PREFIX}.task`, startMessage.taskName),
84
+ workerState.provideRuntimeServices,
85
+ );
86
+ });