@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,191 @@
1
+ import { Schema } from "effect";
2
+
3
+ import { TASK_ERROR_FIELDS, UNATTRIBUTED_FAILURE_FIELDS } from "./error-fields.ts";
4
+ import { describeCauseMessage } from "./report-errors.ts";
5
+
6
+ export class ProcDirectoryUnlistable extends Schema.TaggedError<ProcDirectoryUnlistable>()(
7
+ "ProcDirectoryUnlistable",
8
+ {
9
+ ...TASK_ERROR_FIELDS,
10
+ taskName: Schema.NullOr(Schema.String),
11
+ directoryPath: Schema.String,
12
+ cause: Schema.Defect(),
13
+ },
14
+ ) {
15
+ public override get message() {
16
+ return `could not list ${this.directoryPath} to find leftover processes because ${describeCauseMessage(this.cause)}`;
17
+ }
18
+ }
19
+
20
+ export class ProcStatUnreadable extends Schema.TaggedError<ProcStatUnreadable>()(
21
+ "ProcStatUnreadable",
22
+ {
23
+ ...TASK_ERROR_FIELDS,
24
+ taskName: Schema.NullOr(Schema.String),
25
+ filePath: Schema.String,
26
+ cause: Schema.Defect(),
27
+ },
28
+ ) {
29
+ public override get message() {
30
+ return `could not be read to find leftover processes because ${describeCauseMessage(this.cause)}`;
31
+ }
32
+ }
33
+
34
+ export class ProcStatMalformed extends Schema.TaggedError<ProcStatMalformed>()(
35
+ "ProcStatMalformed",
36
+ {
37
+ ...TASK_ERROR_FIELDS,
38
+ taskName: Schema.NullOr(Schema.String),
39
+ filePath: Schema.String,
40
+ cause: Schema.Defect(),
41
+ },
42
+ ) {
43
+ public override get message() {
44
+ return `has no readable state, parent, group and start time because ${describeCauseMessage(this.cause)}`;
45
+ }
46
+ }
47
+
48
+ export class ProcCommandLineUnreadable extends Schema.TaggedError<ProcCommandLineUnreadable>()(
49
+ "ProcCommandLineUnreadable",
50
+ {
51
+ ...TASK_ERROR_FIELDS,
52
+ taskName: Schema.NullOr(Schema.String),
53
+ filePath: Schema.String,
54
+ cause: Schema.Defect(),
55
+ },
56
+ ) {
57
+ public override get message() {
58
+ return `could not be read because ${describeCauseMessage(this.cause)}`;
59
+ }
60
+ }
61
+
62
+ export class ProcRunMarkerUnreadable extends Schema.TaggedError<ProcRunMarkerUnreadable>()(
63
+ "ProcRunMarkerUnreadable",
64
+ {
65
+ ...TASK_ERROR_FIELDS,
66
+ taskName: Schema.NullOr(Schema.String),
67
+ filePath: Schema.String,
68
+ cause: Schema.Defect(),
69
+ },
70
+ ) {
71
+ public override get message() {
72
+ return `could not be read because ${describeCauseMessage(this.cause)}`;
73
+ }
74
+ }
75
+
76
+ export class ProcessNotSignaled extends Schema.TaggedError<ProcessNotSignaled>()(
77
+ "ProcessNotSignaled",
78
+ {
79
+ ...TASK_ERROR_FIELDS,
80
+ taskName: Schema.NullOr(Schema.String),
81
+ processId: Schema.Int,
82
+ signal: Schema.Literals(["SIGKILL", "SIGSTOP", "SIGCONT", 0]),
83
+ cause: Schema.Defect(),
84
+ },
85
+ ) {
86
+ public override get message() {
87
+ return `could not send ${this.signal === 0 ? "a liveness check" : this.signal} to ${this.processId < 0 ? `process group ${-this.processId}` : `process ${this.processId}`} because ${describeCauseMessage(this.cause)}`;
88
+ }
89
+ }
90
+
91
+ export class SystemLibraryNotLoaded extends Schema.TaggedError<SystemLibraryNotLoaded>()(
92
+ "SystemLibraryNotLoaded",
93
+ {
94
+ ...TASK_ERROR_FIELDS,
95
+ taskName: Schema.NullOr(Schema.String),
96
+ libraryPath: Schema.String,
97
+ cause: Schema.Defect(),
98
+ },
99
+ ) {
100
+ public override get message() {
101
+ return `could not load ${this.libraryPath} to list processes because ${describeCauseMessage(this.cause)}`;
102
+ }
103
+ }
104
+
105
+ export class ProcessArgumentsMalformed extends Schema.TaggedError<ProcessArgumentsMalformed>()(
106
+ "ProcessArgumentsMalformed",
107
+ {
108
+ ...TASK_ERROR_FIELDS,
109
+ processId: Schema.Int,
110
+ argumentCount: Schema.Int,
111
+ argumentsFound: Schema.Int,
112
+ },
113
+ ) {
114
+ public override get message() {
115
+ return `kern.procargs2 for process ${this.processId} declares ${this.argumentCount} arguments but holds ${this.argumentsFound} NUL-terminated strings after its executable path`;
116
+ }
117
+ }
118
+
119
+ export class SystemCallFailed extends Schema.TaggedError<SystemCallFailed>()("SystemCallFailed", {
120
+ ...TASK_ERROR_FIELDS,
121
+ call: Schema.String,
122
+ processId: Schema.NullOr(Schema.Int),
123
+ errorNumber: Schema.Int,
124
+ errorName: Schema.String,
125
+ }) {
126
+ public override get message() {
127
+ return `${this.call}${this.processId === null ? "" : ` for process ${this.processId}`} failed with ${this.errorName} (errno ${this.errorNumber})`;
128
+ }
129
+ }
130
+
131
+ export class ProcessCommandLineNotRead extends Schema.TaggedError<ProcessCommandLineNotRead>()(
132
+ "ProcessCommandLineNotRead",
133
+ {
134
+ ...TASK_ERROR_FIELDS,
135
+ taskName: Schema.NullOr(Schema.String),
136
+ processId: Schema.Int,
137
+ cause: Schema.Union([SystemCallFailed, ProcessArgumentsMalformed]),
138
+ },
139
+ ) {
140
+ public override get message() {
141
+ return `could not read the command line of leftover process ${this.processId} because ${this.cause.message}`;
142
+ }
143
+ }
144
+
145
+ export class ProcessTableNotRead extends Schema.TaggedError<ProcessTableNotRead>()(
146
+ "ProcessTableNotRead",
147
+ {
148
+ ...TASK_ERROR_FIELDS,
149
+ taskName: Schema.NullOr(Schema.String),
150
+ cause: Schema.Union([SystemCallFailed, ProcessArgumentsMalformed]),
151
+ },
152
+ ) {
153
+ public override get message() {
154
+ return `could not read the process table to find leftover processes because ${this.cause.message}`;
155
+ }
156
+ }
157
+
158
+ export const UncaughtWorkerError = Schema.Struct({
159
+ message: Schema.String,
160
+ location: Schema.NullOr(Schema.String),
161
+ });
162
+
163
+ export class WorkerExited extends Schema.TaggedError<WorkerExited>()("WorkerExited", {
164
+ ...TASK_ERROR_FIELDS,
165
+ taskName: Schema.NullOr(Schema.String),
166
+ workerId: Schema.Int,
167
+ exitCode: Schema.Int,
168
+ uncaughtError: Schema.NullOr(UncaughtWorkerError),
169
+ }) {
170
+ public get location() {
171
+ return this.uncaughtError?.location ?? undefined;
172
+ }
173
+ public override get message() {
174
+ const howItStopped =
175
+ this.uncaughtError === null
176
+ ? `exited on its own with code ${this.exitCode}`
177
+ : `stopped on the uncaught error ${this.uncaughtError.message}`;
178
+ return this.taskName === null
179
+ ? `worker ${this.workerId} ${howItStopped}`
180
+ : `its worker ${howItStopped}`;
181
+ }
182
+ }
183
+
184
+ export class SupervisorSocketNotOpened extends Schema.TaggedError<SupervisorSocketNotOpened>()(
185
+ "SupervisorSocketNotOpened",
186
+ { ...TASK_ERROR_FIELDS, ...UNATTRIBUTED_FAILURE_FIELDS, cause: Schema.Defect() },
187
+ ) {
188
+ public override get message() {
189
+ return `could not open the socket the run reports to its supervisor on because ${describeCauseMessage(this.cause)}`;
190
+ }
191
+ }
@@ -0,0 +1,239 @@
1
+ import {
2
+ Array as Arrays,
3
+ Effect,
4
+ Config,
5
+ ConfigProvider,
6
+ Duration,
7
+ Option,
8
+ Path,
9
+ Schema,
10
+ } from "effect";
11
+
12
+ import {
13
+ BunVersionUnsupported,
14
+ HomeDirectoryPathMissing,
15
+ SetupLimitInvalid,
16
+ StateDirectoryPathBlank,
17
+ WorkerCountAboveMaximum,
18
+ WorkerCountInvalid,
19
+ } from "./environment-config-errors.ts";
20
+ import { dieOnInvariantFailure } from "./invariant-errors.ts";
21
+ import { markRuntimeConfigProvider } from "./name-config-key.ts";
22
+ import { TASK_STATE_DIRECTORY_NAME, packageNameParts } from "./names-from-package-json.ts";
23
+ import { TASK_PACKAGE } from "./task-package.ts";
24
+
25
+ const TASK_ENVIRONMENT_VARIABLE_PREFIX = packageNameParts.join("_").toUpperCase();
26
+
27
+ export const availableParallelism = Effect.sync(() => navigator.hardwareConcurrency);
28
+
29
+ export const MAXIMUM_WORKER_COUNT = 1024;
30
+
31
+ export const workingDirectoryPath = Effect.sync(() => process.cwd());
32
+
33
+ const FIRST_SCRIPT_ARGUMENT_INDEX = 2;
34
+
35
+ export const commandLineArguments = Effect.sync(() =>
36
+ process.argv.slice(FIRST_SCRIPT_ARGUMENT_INDEX),
37
+ );
38
+
39
+ export const bunArguments = Effect.sync(() => [...process.execArgv]);
40
+
41
+ const BUN_WATCH_MODE_FLAGS = ["--watch", "--hot"] as const;
42
+
43
+ export const bunWatchModeFlag = Effect.sync(() =>
44
+ Option.fromUndefinedOr(BUN_WATCH_MODE_FLAGS.find((flag) => process.execArgv.includes(flag))),
45
+ );
46
+
47
+ const isWorkingDirectoryFlag = (flag: string) => flag === "--cwd" || flag.startsWith("--cwd=");
48
+
49
+ const SUPERVISOR_SOCKET_ARGUMENT_PREFIX = `--${packageNameParts.join("-")}-supervisor-socket=`;
50
+
51
+ export const supervisedRunCommand = (preloadFlag: string, supervisorSocketPath: string) =>
52
+ Effect.sync(() => [
53
+ process.execPath,
54
+ preloadFlag,
55
+ ...process.execArgv,
56
+ ...(process.execArgv.some((flag) => isWorkingDirectoryFlag(flag))
57
+ ? [`--cwd=${process.cwd()}`]
58
+ : []),
59
+ ...process.argv.slice(1, FIRST_SCRIPT_ARGUMENT_INDEX),
60
+ `${SUPERVISOR_SOCKET_ARGUMENT_PREFIX}${supervisorSocketPath}`,
61
+ ...process.argv.slice(FIRST_SCRIPT_ARGUMENT_INDEX),
62
+ ]);
63
+
64
+ export const takeSupervisorSocketPath = Effect.sync(() => {
65
+ const supervisorSocketArgument = process.argv[FIRST_SCRIPT_ARGUMENT_INDEX] ?? "";
66
+ if (!supervisorSocketArgument.startsWith(SUPERVISOR_SOCKET_ARGUMENT_PREFIX)) {
67
+ return Option.none();
68
+ }
69
+ process.argv.splice(FIRST_SCRIPT_ARGUMENT_INDEX, 1);
70
+ return Option.some(supervisorSocketArgument.slice(SUPERVISOR_SOCKET_ARGUMENT_PREFIX.length));
71
+ });
72
+
73
+ const NON_BLANK_TEXT = Schema.String.check(Schema.isPattern(/\S/u));
74
+
75
+ const readEnvironmentKeepingEmptyValues = () =>
76
+ ConfigProvider.fromEnv({ preserveEmptyStrings: true });
77
+
78
+ const taskOwnConfigProvider = readEnvironmentKeepingEmptyValues();
79
+
80
+ export const runtimeConfigProviderLayer = ConfigProvider.layer(
81
+ Effect.sync(() => markRuntimeConfigProvider(readEnvironmentKeepingEmptyValues())),
82
+ );
83
+
84
+ const STATE_DIRECTORY_VARIABLE_NAME = `${TASK_ENVIRONMENT_VARIABLE_PREFIX}_STATE_DIRECTORY`;
85
+
86
+ const WORKER_COUNT_VARIABLE_NAME = `${TASK_ENVIRONMENT_VARIABLE_PREFIX}_WORKERS`;
87
+
88
+ const readEnvironmentVariable = (variableName: string) =>
89
+ Config.String(variableName)
90
+ .pipe(Config.option)
91
+ .parse(taskOwnConfigProvider)
92
+ .pipe(
93
+ dieOnInvariantFailure(
94
+ "configuration",
95
+ `the environment provides ${variableName} or omits it`,
96
+ ),
97
+ );
98
+
99
+ const readEnvironmentVariableNow = (variableName: string) =>
100
+ Effect.suspend(() =>
101
+ Config.String(variableName).pipe(Config.option).parse(readEnvironmentKeepingEmptyValues()),
102
+ ).pipe(
103
+ dieOnInvariantFailure("configuration", `the environment provides ${variableName} or omits it`),
104
+ );
105
+
106
+ const isNonBlankText = Schema.is(NON_BLANK_TEXT);
107
+
108
+ const isWholeDecimalNumberAboveZero = Schema.is(
109
+ Schema.String.check(Schema.isPattern(/^0*[1-9]\d*$/u)),
110
+ );
111
+
112
+ export const readStateDirectory = Effect.fn("readStateDirectory")(function* () {
113
+ const path = yield* Path.Path;
114
+ const configuredStateDirectoryPath = yield* readEnvironmentVariable(
115
+ STATE_DIRECTORY_VARIABLE_NAME,
116
+ );
117
+ if (Option.isSome(configuredStateDirectoryPath)) {
118
+ if (!isNonBlankText(configuredStateDirectoryPath.value)) {
119
+ return yield* new StateDirectoryPathBlank({
120
+ variableName: STATE_DIRECTORY_VARIABLE_NAME,
121
+ value: configuredStateDirectoryPath.value,
122
+ });
123
+ }
124
+ const stateDirectoryPath = path.resolve(configuredStateDirectoryPath.value);
125
+ return { stateDirectoryPath, configuredStateDirectoryPath: stateDirectoryPath };
126
+ }
127
+ const homeDirectoryPath = yield* readEnvironmentVariable("HOME");
128
+ const isHomeDirectoryPathMissing =
129
+ Option.isNone(homeDirectoryPath) || !isNonBlankText(homeDirectoryPath.value);
130
+ if (isHomeDirectoryPathMissing) {
131
+ return yield* new HomeDirectoryPathMissing({
132
+ stateDirectoryVariableName: STATE_DIRECTORY_VARIABLE_NAME,
133
+ homeValue: Option.getOrNull(homeDirectoryPath),
134
+ });
135
+ }
136
+ return {
137
+ stateDirectoryPath: path.resolve(path.join(homeDirectoryPath.value, TASK_STATE_DIRECTORY_NAME)),
138
+ configuredStateDirectoryPath: null,
139
+ };
140
+ });
141
+
142
+ export const readWorkerCount = Effect.fn("readWorkerCount")(function* () {
143
+ const configuredWorkerCount = yield* readEnvironmentVariable(WORKER_COUNT_VARIABLE_NAME);
144
+ if (Option.isNone(configuredWorkerCount)) {
145
+ return yield* availableParallelism;
146
+ }
147
+ if (!isWholeDecimalNumberAboveZero(configuredWorkerCount.value)) {
148
+ return yield* new WorkerCountInvalid({
149
+ variableName: WORKER_COUNT_VARIABLE_NAME,
150
+ value: configuredWorkerCount.value,
151
+ });
152
+ }
153
+ const workerCount = Number(configuredWorkerCount.value);
154
+ if (workerCount > MAXIMUM_WORKER_COUNT) {
155
+ return yield* new WorkerCountAboveMaximum({
156
+ variableName: WORKER_COUNT_VARIABLE_NAME,
157
+ value: configuredWorkerCount.value,
158
+ maximum: MAXIMUM_WORKER_COUNT,
159
+ });
160
+ }
161
+ return workerCount;
162
+ });
163
+
164
+ const SETUP_LIMIT_VARIABLE_NAME = `${TASK_ENVIRONMENT_VARIABLE_PREFIX}_SETUP_LIMIT`;
165
+
166
+ const DEFAULT_SETUP_LIMIT = Duration.minutes(1);
167
+
168
+ const decodeDurationOption = Schema.decodeUnknownOption(Schema.DurationFromString);
169
+
170
+ export const readSetupLimit = Effect.fn("readSetupLimit")(function* () {
171
+ const configuredSetupLimit = yield* readEnvironmentVariable(SETUP_LIMIT_VARIABLE_NAME);
172
+ if (Option.isNone(configuredSetupLimit)) {
173
+ return DEFAULT_SETUP_LIMIT;
174
+ }
175
+ const setupLimit = decodeDurationOption(configuredSetupLimit.value);
176
+ const isFiniteDurationAboveZero =
177
+ Option.isSome(setupLimit) &&
178
+ Duration.isFinite(setupLimit.value) &&
179
+ Duration.isGreaterThan(setupLimit.value, Duration.zero);
180
+ if (!isFiniteDurationAboveZero) {
181
+ return yield* new SetupLimitInvalid({
182
+ variableName: SETUP_LIMIT_VARIABLE_NAME,
183
+ value: configuredSetupLimit.value,
184
+ });
185
+ }
186
+ return setupLimit.value;
187
+ });
188
+
189
+ export const inheritedEnvironment = Effect.sync(() => ({ ...process.env }));
190
+
191
+ export const RUN_MARKERS_VARIABLE_NAME = `${TASK_ENVIRONMENT_VARIABLE_PREFIX}_RUN_MARKERS`;
192
+
193
+ export const inheritedRunMarkers = readEnvironmentVariableNow(RUN_MARKERS_VARIABLE_NAME).pipe(
194
+ Effect.map((runMarkers) =>
195
+ Option.getOrElse(runMarkers, () => "")
196
+ .split(" ")
197
+ .filter((token) => token !== ""),
198
+ ),
199
+ );
200
+
201
+ const VARIABLES_EVERY_COMMAND_SEES = ["HOME", "PATH", "TMPDIR"] as const;
202
+
203
+ const TASK_CONFIG_EVERY_COMMAND_SEES = [
204
+ STATE_DIRECTORY_VARIABLE_NAME,
205
+ WORKER_COUNT_VARIABLE_NAME,
206
+ SETUP_LIMIT_VARIABLE_NAME,
207
+ ] as const;
208
+
209
+ export const environmentEveryCommandSees = Effect.forEach(
210
+ [...VARIABLES_EVERY_COMMAND_SEES, ...TASK_CONFIG_EVERY_COMMAND_SEES],
211
+ (variableName) =>
212
+ readEnvironmentVariableNow(variableName).pipe(
213
+ Effect.map((value) =>
214
+ Option.map(value, (presentValue) => [variableName, presentValue] as const),
215
+ ),
216
+ ),
217
+ ).pipe(Effect.map((entries) => Object.fromEntries(Arrays.getSomes(entries))));
218
+
219
+ export const passStateDirectoryToCommands = (configuredStateDirectoryPath: string | null) =>
220
+ Effect.sync(() => {
221
+ if (configuredStateDirectoryPath !== null) {
222
+ process.env[STATE_DIRECTORY_VARIABLE_NAME] = configuredStateDirectoryPath;
223
+ }
224
+ });
225
+
226
+ export const requireSupportedBunVersion = (
227
+ bunVersion: string,
228
+ supportedRange: string = TASK_PACKAGE.engines.bun,
229
+ ) =>
230
+ Bun.semver.satisfies(bunVersion.replace(/[-+].*$/u, ""), supportedRange)
231
+ ? Effect.void
232
+ : Effect.fail(
233
+ new BunVersionUnsupported({
234
+ bunVersion,
235
+ supportedRange,
236
+ taskPackageName: TASK_PACKAGE.name,
237
+ taskVersion: TASK_PACKAGE.version,
238
+ }),
239
+ );
@@ -0,0 +1,10 @@
1
+ import type { Task } from "../lib/task/index.ts";
2
+
3
+ const tasksMadeByName = new Map<string, Task[]>();
4
+
5
+ export const recordTaskMade = (madeTask: Task) => {
6
+ tasksMadeByName.set(madeTask.name, [...(tasksMadeByName.get(madeTask.name) ?? []), madeTask]);
7
+ };
8
+
9
+ export const findTasksMadeWithName = (taskName: string): readonly Task[] =>
10
+ tasksMadeByName.get(taskName) ?? [];
@@ -0,0 +1,140 @@
1
+ import { Array as Arrays, Cause, Formatter, Option, Predicate, Result, Schema } from "effect";
2
+
3
+ import { PrintableError } from "../lib/errors/index.ts";
4
+ import { CommandRefusal } from "./command-errors.ts";
5
+ import { findUserLocationInStack } from "./find-user-locations-in-stack-traces.ts";
6
+ import {
7
+ CauseWithoutFailure,
8
+ describeCauseMessage,
9
+ ErrorStack,
10
+ reportError,
11
+ TaskRaisedError,
12
+ UntaggedFailure,
13
+ } from "./report-errors.ts";
14
+ import { failureValuesOfReason } from "./select-cause-reasons.ts";
15
+ import { TASK_PACKAGE } from "./task-package.ts";
16
+
17
+ const TaskRaisedContainer = Schema.Struct({
18
+ raisedBy: Schema.Literal(TASK_PACKAGE.name),
19
+ reportedErrors: Schema.NonEmptyArray(PrintableError),
20
+ });
21
+
22
+ const UserError = Schema.Struct({ name: Schema.String, message: Schema.String });
23
+
24
+ const UNTAGGED_ERROR_NAMES = new Set(["", "Error"]);
25
+
26
+ const ErrorFields = Schema.Record(Schema.String, Schema.Unknown);
27
+
28
+ const userTagOf = (failureValue: unknown) =>
29
+ Option.flatMap(Schema.decodeUnknownOption(ErrorFields)(failureValue), ({ _tag }) =>
30
+ Option.liftPredicate(_tag, Predicate.isString),
31
+ );
32
+
33
+ const ErrorCausedByCommandRefusal = Schema.Struct({ cause: CommandRefusal });
34
+
35
+ const ENGINE_ERROR_NAMES = new Set(["TypeError", "ReferenceError"]);
36
+
37
+ const ENGINE_SOURCE_EXCERPT =
38
+ /^(?<statement>[\s\S]*?) \(In '[\s\S]*'(?:, (?<valueNote>'[\s\S]*' is [\s\S]*))?\)$/u;
39
+
40
+ const withoutEngineSourceExcerpt = (errorName: string, defectMessage: string) => {
41
+ const [, statement, valueNote] = ENGINE_ERROR_NAMES.has(errorName)
42
+ ? (ENGINE_SOURCE_EXCERPT.exec(defectMessage) ?? [])
43
+ : [];
44
+ if (statement === undefined) {
45
+ return defectMessage;
46
+ }
47
+ return valueNote === undefined ? statement : `${statement} (${valueNote})`;
48
+ };
49
+
50
+ const reportReason = (
51
+ reason: Cause.Reason<unknown>,
52
+ foreignMessagePrefix: string,
53
+ ): readonly PrintableError[] => {
54
+ if (Cause.isInterruptReason(reason)) {
55
+ return [];
56
+ }
57
+ const [failureValue] = failureValuesOfReason(reason);
58
+ const stackLocation = Option.flatMap(
59
+ Schema.decodeUnknownOption(ErrorStack)(failureValue),
60
+ (withStack) => Option.fromUndefinedOr(findUserLocationInStack(withStack.stack)),
61
+ );
62
+ const userLocation = Option.match(stackLocation, {
63
+ onSome: (location) => ({ location }),
64
+ onNone: () => ({}),
65
+ });
66
+ return Option.match(Schema.decodeUnknownOption(TaskRaisedContainer)(failureValue), {
67
+ onSome: (container) => container.reportedErrors,
68
+ onNone: () =>
69
+ Option.match(Schema.decodeUnknownOption(ErrorCausedByCommandRefusal)(failureValue), {
70
+ onSome: (causedByRefusal) => [reportError(causedByRefusal.cause)],
71
+ onNone: () =>
72
+ Option.match(Schema.decodeUnknownOption(TaskRaisedError)(failureValue), {
73
+ onSome: (taskRaised) => [reportError(taskRaised)],
74
+ onNone: () =>
75
+ Option.match(
76
+ Option.filter(
77
+ Schema.decodeUnknownOption(UserError)(failureValue),
78
+ (userError) =>
79
+ Option.isSome(userTagOf(failureValue)) ||
80
+ !UNTAGGED_ERROR_NAMES.has(userError.name),
81
+ ),
82
+ {
83
+ onSome: (userError) => {
84
+ const tag = Option.getOrElse(userTagOf(failureValue), () => userError.name);
85
+ const message = Cause.isDieReason(reason)
86
+ ? withoutEngineSourceExcerpt(userError.name, userError.message)
87
+ : userError.message;
88
+ return [
89
+ {
90
+ tag,
91
+ message: `${foreignMessagePrefix}${message === "" ? tag : message}`,
92
+ ...userLocation,
93
+ },
94
+ ];
95
+ },
96
+ onNone: () => [
97
+ reportError(
98
+ new UntaggedFailure({
99
+ failureMessage: `${foreignMessagePrefix}${describeCauseMessage(failureValue)}`,
100
+ ...userLocation,
101
+ }),
102
+ ),
103
+ ],
104
+ },
105
+ ),
106
+ }),
107
+ }),
108
+ });
109
+ };
110
+
111
+ const isRangeError = Schema.is(Schema.instanceOf(RangeError));
112
+
113
+ export const causeWithPrintableErrors = <Failure>(cause: Cause.Cause<Failure>) =>
114
+ Result.getOrElse(
115
+ Result.try(() => {
116
+ Cause.pretty(cause);
117
+ return cause;
118
+ }),
119
+ (formattingFailure) => {
120
+ if (!isRangeError(formattingFailure)) {
121
+ throw formattingFailure;
122
+ }
123
+ return Cause.die(
124
+ new Error(Formatter.format({ cause, formattingFailure }, { ignoreToString: true })),
125
+ );
126
+ },
127
+ );
128
+
129
+ export const reportCause = <Failure>(cause: Cause.Cause<Failure>, foreignMessagePrefix = "") => {
130
+ const details = Cause.pretty(causeWithPrintableErrors(cause));
131
+ const reportedErrors = cause.reasons.flatMap((reason) =>
132
+ reportReason(reason, foreignMessagePrefix),
133
+ );
134
+ return {
135
+ failures: Arrays.isArrayNonEmpty(reportedErrors)
136
+ ? reportedErrors
137
+ : Arrays.of(reportError(new CauseWithoutFailure({ details }))),
138
+ details,
139
+ };
140
+ };
@@ -0,0 +1,121 @@
1
+ import { Formatter, Option, PlatformError, Predicate, Schema, SchemaTransformation } from "effect";
2
+
3
+ import type { PrintableError } from "../lib/errors/index.ts";
4
+ import { cutToGraphemes } from "./cut-to-graphemes.ts";
5
+ import { TASK_ERROR_FIELDS } from "./error-fields.ts";
6
+ import { TASK_PACKAGE } from "./task-package.ts";
7
+
8
+ export const withLocationOrElse = (printable: PrintableError, fallbackLocation: string) => {
9
+ const { location, ...printableWithoutLocation } = printable;
10
+ return { ...printableWithoutLocation, location: location ?? fallbackLocation };
11
+ };
12
+
13
+ export const ErrorStack = Schema.Struct({ stack: Schema.String });
14
+
15
+ export const TaskRaisedError = Schema.Struct({
16
+ raisedBy: Schema.Literal(TASK_PACKAGE.name),
17
+ name: Schema.String,
18
+ message: Schema.String,
19
+ location: Schema.optional(Schema.String),
20
+ filePath: Schema.optional(Schema.String),
21
+ taskName: Schema.optional(Schema.NullOr(Schema.String)),
22
+ });
23
+
24
+ export const reportError = (error: typeof TaskRaisedError.Type): PrintableError => {
25
+ const location = error.location ?? error.filePath ?? error.taskName ?? undefined;
26
+ return {
27
+ tag: error.name,
28
+ ...(location === undefined ? {} : { location }),
29
+ message: error.message === "" ? error.name : error.message,
30
+ };
31
+ };
32
+
33
+ const ErrnoCode = Schema.Struct({
34
+ code: Schema.String.check(Schema.isPattern(/^E[A-Z]+$/u)),
35
+ });
36
+
37
+ const SystemErrorMessage = Schema.Struct({ ...ErrnoCode.fields, message: Schema.String });
38
+
39
+ const LocatedCauseMessage = Schema.Struct({ filePath: Schema.String, message: Schema.String });
40
+
41
+ const FAILURE_VALUE_MAX_GRAPHEMES = 200;
42
+
43
+ const FormattedFailureValue = Schema.Unknown.pipe(
44
+ Schema.decodeTo(
45
+ Schema.String,
46
+ SchemaTransformation.transform({
47
+ decode: (failureValue) =>
48
+ cutToGraphemes(Formatter.format(failureValue), FAILURE_VALUE_MAX_GRAPHEMES),
49
+ encode: (formatted) => formatted,
50
+ }),
51
+ ),
52
+ );
53
+
54
+ const CauseMessage = Schema.Union([
55
+ LocatedCauseMessage,
56
+ Schema.Struct({ message: Schema.String }),
57
+ Schema.String,
58
+ FormattedFailureValue,
59
+ ]);
60
+
61
+ const isLocatedCauseMessage = Schema.is(LocatedCauseMessage);
62
+
63
+ export const THROWN_VALUE_WITHOUT_MESSAGE = "a thrown value that has no message";
64
+
65
+ const describeDecodedCause = (decoded: typeof CauseMessage.Type) => {
66
+ const message = Predicate.isString(decoded) ? decoded : decoded.message;
67
+ const description = message === "" ? THROWN_VALUE_WITHOUT_MESSAGE : message;
68
+ const hasLocation = !Predicate.isString(decoded) && isLocatedCauseMessage(decoded);
69
+ return hasLocation ? `${decoded.filePath} ${description}` : description;
70
+ };
71
+
72
+ const restatesItsCause = (reason: PlatformError.PlatformError["reason"]) =>
73
+ reason.description === String(reason.cause) ||
74
+ Option.exists(
75
+ Schema.decodeUnknownOption(Schema.Struct({ message: Schema.String }))(reason.cause),
76
+ (cause) => cause.message === reason.description,
77
+ );
78
+
79
+ const describePlatformError = (platformError: PlatformError.PlatformError) =>
80
+ Option.getOrElse(
81
+ Option.map(
82
+ Option.filter(
83
+ Schema.decodeUnknownOption(SystemErrorMessage)(platformError.reason.cause),
84
+ () =>
85
+ platformError.reason.description === undefined || restatesItsCause(platformError.reason),
86
+ ),
87
+ (systemError) => systemError.message,
88
+ ),
89
+ () => platformError.reason.description ?? platformError.message,
90
+ );
91
+
92
+ export const describeCauseMessage = (cause: unknown) =>
93
+ Option.match(Schema.decodeUnknownOption(Schema.instanceOf(PlatformError.PlatformError))(cause), {
94
+ onSome: describePlatformError,
95
+ onNone: () =>
96
+ Option.match(Schema.decodeUnknownOption(CauseMessage)(cause), {
97
+ onSome: describeDecodedCause,
98
+ onNone: () => "a failure value that cannot be formatted",
99
+ }),
100
+ });
101
+
102
+ export const UNTAGGED_FAILURE_TAG = "UntaggedFailure";
103
+
104
+ export class UntaggedFailure extends Schema.TaggedError<UntaggedFailure>()(UNTAGGED_FAILURE_TAG, {
105
+ ...TASK_ERROR_FIELDS,
106
+ failureMessage: Schema.String,
107
+ location: Schema.optional(Schema.String),
108
+ }) {
109
+ public override get message() {
110
+ return this.failureMessage;
111
+ }
112
+ }
113
+
114
+ export class CauseWithoutFailure extends Schema.TaggedError<CauseWithoutFailure>()(
115
+ "CauseWithoutFailure",
116
+ { ...TASK_ERROR_FIELDS, details: Schema.String },
117
+ ) {
118
+ public override get message() {
119
+ return "was interrupted without a failure";
120
+ }
121
+ }