@sshkeda/task 0.0.0-stage → 1.0.1

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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Stephen Shkeda
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,132 @@
1
- # Temporary Holding Version
1
+ # Task
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Delete your bash scripts! Your agent will thank you.
4
+
5
+ Task is a task runner for Bun. It runs your builds, servers and CI, does independent work in parallel, waits only where one piece needs another, and stops everything at the end.
6
+
7
+ ## How it works
8
+
9
+ A task is one named piece of work, such as a build, a typecheck or a server. When a task needs another task, it waits for that task and gets its result. Each task runs once per run, however many tasks need it. You write tasks in TypeScript, but the commands they start can use any runtime, such as Node or Python.
10
+
11
+ Here's an example CI script. The end-to-end tests start your server, get its URL and test against it, while typecheck runs alongside:
12
+
13
+ ```ts
14
+ // scripts/ci.ts
15
+ import { Task } from "@sshkeda/task";
16
+ import { Effect } from "effect";
17
+
18
+ import { app } from "../src/app.ts";
19
+
20
+ export const server = Task.make(
21
+ "server",
22
+ Effect.gen(function* () {
23
+ const server = Bun.serve({ port: 0, fetch: app.fetch });
24
+ return yield* Task.serve(server.url.href);
25
+ }),
26
+ );
27
+ export const typecheck = Task.make("typecheck", Task.exec("bunx", ["tsc", "--noEmit"]));
28
+ export const e2e = Task.make(
29
+ "e2e",
30
+ Effect.gen(function* () {
31
+ const url = yield* server;
32
+ yield* Task.exec("bun", ["test", "e2e"], { env: { APP_URL: url } });
33
+ }),
34
+ );
35
+ export const ci = Task.make("ci", Task.allSettled([typecheck, e2e]));
36
+
37
+ if (import.meta.main) {
38
+ Task.main(ci);
39
+ }
40
+ ```
41
+
42
+ Run it with `bun scripts/ci.ts`:
43
+
44
+ ```
45
+ [ci] started
46
+ [typecheck] started
47
+ [e2e] started
48
+ [server] started
49
+ [server] serving after 0.0s
50
+ [e2e] bun test v1.4.4
51
+ [e2e]
52
+ [e2e] e2e/health.test.ts:
53
+ [e2e] (pass) the server is healthy [1.70ms]
54
+ [e2e]
55
+ [e2e] 1 pass
56
+ [e2e] 0 fail
57
+ [e2e] 1 expect() calls
58
+ [e2e] Ran 1 test across 1 file. [9.00ms]
59
+ [e2e] passed in 0.0s
60
+ [typecheck] passed in 0.5s
61
+ [ci] passed in 0.5s
62
+ [server] closed
63
+ passed: 4 tasks, 0.6s
64
+ wrote the trace to /Users/you/.sshkeda-task/traces/my-app/2026-10-29T06-57-42.654Z-48677.jsonl
65
+ ```
66
+
67
+ Every run writes an OpenTelemetry trace file, so you can see exactly what makes it slow.
68
+
69
+ ## What you get
70
+
71
+ - **Multithreaded parallelism.** Task bodies run on a pool of worker threads, so independent tasks run side by side instead of blocking a single thread.
72
+ - **Any runtime in one run.** Vitest on Node, `bun test` and a Python script can all run side by side.
73
+ - **Values passed straight between tasks.** A task hands its result, such as a server's URL, to the tasks that use it, with no temporary files or wait scripts in between.
74
+ - **Observable errors.** The last line names the command that failed and its error, so you don't have to dig through logs.
75
+ - **Timeouts per task.** A hung test fails the run after the limit you set instead of blocking CI forever.
76
+ - **Everything stops when the run ends.** Whether it passes, fails, times out or you press Ctrl-C, Task stops every task and the programs they started.
77
+
78
+ ## API
79
+
80
+ - `Task.make(name, body, { timeout?, cleanup? })` makes a task from an Effect. `cleanup` is how long the task may take to stop, and it defaults to 5 seconds.
81
+ - `Task.exec(command, args, { env?, cwd?, stdin?, secret? })` runs a command, returns its output and fails if it exits with an error. The command sees only `HOME`, `PATH` and `TMPDIR` from your environment, so pass anything else it needs in `env`. With `secret: true`, the output comes back as a `Redacted` value and stays out of logs.
82
+ - `return yield* Task.serve(value)` ends a task's body but keeps the task running, such as a dev server, and hands `value` to the tasks that use it.
83
+ - `Task.main(task)` runs a task and everything it uses, then exits.
84
+ - `Task.all(tasks)` runs tasks in parallel and stops at the first failure. `Task.allSettled(tasks)` lets every task finish, then reports each failure.
85
+
86
+ ## Get started
87
+
88
+ Task needs Bun 1.4.4 or newer.
89
+
90
+ 1. Install Task, Effect and Bun's types.
91
+
92
+ ```bash
93
+ bun add @sshkeda/task effect
94
+ bun add -d @types/bun
95
+ ```
96
+
97
+ 2. Add these options to `compilerOptions` in `tsconfig.json`.
98
+
99
+ ```json
100
+ "allowImportingTsExtensions": true,
101
+ "types": ["bun"]
102
+ ```
103
+
104
+ 3. Write your tasks in a TypeScript file, such as `scripts/ci.ts`.
105
+
106
+ ```ts
107
+ import { Task } from "@sshkeda/task";
108
+
109
+ export const test = Task.make("test", Task.exec("bun", ["test"]));
110
+
111
+ if (import.meta.main) {
112
+ Task.main(test);
113
+ }
114
+ ```
115
+
116
+ 4. Run the file.
117
+
118
+ ```bash
119
+ bun scripts/ci.ts
120
+ ```
121
+
122
+ ## Limits
123
+
124
+ - Task runs only on Bun. Your commands can use any runtime, but your task files run on Bun.
125
+ - Task bodies are Effect, so every step gets typed errors, retries and timeouts. If you haven't used Effect, your agent has.
126
+ - Task has no caching. Every run runs every task it reaches.
127
+
128
+ ## Development
129
+
130
+ To run the checks and tests, run `bun scripts/ci.ts`.
131
+
132
+ [MIT](LICENSE)
package/package.json CHANGED
@@ -1,6 +1,43 @@
1
1
  {
2
2
  "name": "@sshkeda/task",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.1",
4
+ "homepage": "https://github.com/sshkeda/task#readme",
5
+ "bugs": {
6
+ "url": "https://github.com/sshkeda/task/issues"
7
+ },
8
+ "license": "MIT",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/sshkeda/task.git"
12
+ },
13
+ "files": [
14
+ "src"
15
+ ],
16
+ "type": "module",
17
+ "exports": {
18
+ ".": "./src/index.ts",
19
+ "./errors": "./src/lib/errors/index.ts"
20
+ },
21
+ "publishConfig": {
22
+ "access": "public",
23
+ "registry": "https://registry.npmjs.org"
24
+ },
25
+ "dependencies": {
26
+ "@effect/platform-bun": "4.1.0",
27
+ "oxc-parser": "0.153.0"
28
+ },
29
+ "devDependencies": {
30
+ "@effect/tsgo": "0.55.0",
31
+ "@sshkeda/check": "^1.0.1",
32
+ "@sshkeda/task": "file:.",
33
+ "@types/bun": "1.4.3",
34
+ "effect": "4.1.0",
35
+ "typescript": "7.0.2"
36
+ },
37
+ "peerDependencies": {
38
+ "effect": "^4.1.0"
39
+ },
40
+ "engines": {
41
+ "bun": ">=1.4.4"
42
+ }
43
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { Served, Serving, Tasks } from "./shared/task-services.ts";
2
+ export * as Task from "./lib/task/index.ts";
@@ -0,0 +1,15 @@
1
+ export {
2
+ AwaitedTaskFailed,
3
+ CapturedCommandFailed,
4
+ CommandFailed,
5
+ CommandNotStarted,
6
+ CommandStandardInputFailed,
7
+ PrintableError,
8
+ ReportedErrors,
9
+ REPORTED_FAILURE_FIELDS,
10
+ SettledTasksFailed,
11
+ TaskError,
12
+ TaskNotMadeAtTopLevel,
13
+ TaskResultNotJson,
14
+ TaskWaitsOnItself,
15
+ } from "./internal/errors.ts";
@@ -0,0 +1,147 @@
1
+ import { Effect, Schema } from "effect";
2
+
3
+ import { TASK_ERROR_FIELDS, UNATTRIBUTED_FAILURE_FIELDS } from "../../../shared/error-fields.ts";
4
+ import { findConstructingLocation } from "../../../shared/find-user-locations-in-stack-traces.ts";
5
+ import { CauseWithoutFailure, describeCauseMessage } from "../../../shared/report-errors.ts";
6
+
7
+ const isCauseWithoutFailure = Schema.is(CauseWithoutFailure);
8
+
9
+ export class PrintableError extends Schema.Opaque<PrintableError>()(
10
+ Schema.Struct({
11
+ tag: Schema.String,
12
+ location: Schema.optional(Schema.String),
13
+ message: Schema.String,
14
+ }),
15
+ ) {}
16
+
17
+ export class ReportedErrors extends Schema.TaggedError<ReportedErrors>()("ReportedErrors", {
18
+ ...TASK_ERROR_FIELDS,
19
+ reportedErrors: Schema.NonEmptyArray(PrintableError),
20
+ }) {
21
+ public override get message() {
22
+ return this.reportedErrors.map((printableError) => printableError.message).join("\n");
23
+ }
24
+ }
25
+
26
+ export const REPORTED_FAILURE_FIELDS = {
27
+ taskName: Schema.String,
28
+ failures: Schema.NonEmptyArray(PrintableError),
29
+ details: Schema.optional(Schema.String),
30
+ };
31
+
32
+ export class TaskResultNotJson extends Schema.TaggedError<TaskResultNotJson>()(
33
+ "TaskResultNotJson",
34
+ {
35
+ ...TASK_ERROR_FIELDS,
36
+ taskName: Schema.String,
37
+ handedOver: Schema.Literals(["returned", "served"]),
38
+ valuePath: Schema.String,
39
+ valueDescription: Schema.String,
40
+ },
41
+ ) {
42
+ public override get message() {
43
+ return `${this.handedOver} ${this.valueDescription}${this.valuePath === "" ? "" : ` at ${this.valuePath}`}, which is not JSON`;
44
+ }
45
+ }
46
+
47
+ export class TaskError extends Schema.TaggedError<TaskError>()("TaskError", {
48
+ ...TASK_ERROR_FIELDS,
49
+ message: Schema.String,
50
+ location: Schema.UndefinedOr(Schema.String).pipe(
51
+ Schema.withConstructorDefault(Effect.sync(findConstructingLocation)),
52
+ ),
53
+ }) {}
54
+
55
+ export class TaskNotMadeAtTopLevel extends Schema.TaggedError<TaskNotMadeAtTopLevel>()(
56
+ "TaskNotMadeAtTopLevel",
57
+ { ...TASK_ERROR_FIELDS, taskName: Schema.String, requestedTaskName: Schema.String },
58
+ ) {
59
+ public override get message() {
60
+ return `the module scan found no top-level Task.make("${this.requestedTaskName}") call`;
61
+ }
62
+ }
63
+
64
+ export class TaskWaitsOnItself extends Schema.TaggedError<TaskWaitsOnItself>()(
65
+ "TaskWaitsOnItself",
66
+ { ...TASK_ERROR_FIELDS, taskName: Schema.String, cycle: Schema.Array(Schema.String) },
67
+ ) {
68
+ public override get message() {
69
+ return `waits on itself through ${this.cycle.join(" -> ")}`;
70
+ }
71
+ }
72
+
73
+ export class AwaitedTaskFailed extends Schema.TaggedError<AwaitedTaskFailed>()(
74
+ "AwaitedTaskFailed",
75
+ { ...TASK_ERROR_FIELDS, ...UNATTRIBUTED_FAILURE_FIELDS, awaitedTaskName: Schema.String },
76
+ ) {
77
+ public override get message() {
78
+ return `${this.awaitedTaskName} failed`;
79
+ }
80
+ }
81
+
82
+ export class SettledTasksFailed extends Schema.TaggedError<SettledTasksFailed>()(
83
+ "SettledTasksFailed",
84
+ {
85
+ ...TASK_ERROR_FIELDS,
86
+ ...UNATTRIBUTED_FAILURE_FIELDS,
87
+ settledTaskCount: Schema.Int,
88
+ failedTaskNames: Schema.Array(Schema.String),
89
+ },
90
+ ) {
91
+ public override get message() {
92
+ return `${this.failedTaskNames.length} of ${this.settledTaskCount} ${this.settledTaskCount === 1 ? "task" : "tasks"} failed: ${this.failedTaskNames.join(", ")}`;
93
+ }
94
+ }
95
+
96
+ export class CommandFailed extends Schema.TaggedError<CommandFailed>()("CommandFailed", {
97
+ ...TASK_ERROR_FIELDS,
98
+ command: Schema.String,
99
+ code: Schema.Int,
100
+ standardOutput: Schema.String,
101
+ standardError: Schema.String,
102
+ }) {
103
+ public override get message() {
104
+ return [
105
+ `${this.command} exited with code ${this.code}`,
106
+ this.standardOutput.trim(),
107
+ this.standardError.trim(),
108
+ ]
109
+ .filter((line) => line !== "")
110
+ .join("\n");
111
+ }
112
+ }
113
+
114
+ export class CapturedCommandFailed extends Schema.TaggedError<CapturedCommandFailed>()(
115
+ "CapturedCommandFailed",
116
+ {
117
+ ...TASK_ERROR_FIELDS,
118
+ command: Schema.String,
119
+ code: Schema.Int,
120
+ standardOutput: Schema.Redacted(Schema.String),
121
+ standardError: Schema.Redacted(Schema.String),
122
+ },
123
+ ) {
124
+ public override get message() {
125
+ return `${this.command} exited with code ${this.code}, and its captured output is not shown`;
126
+ }
127
+ }
128
+
129
+ export class CommandNotStarted extends Schema.TaggedError<CommandNotStarted>()(
130
+ "CommandNotStarted",
131
+ { ...TASK_ERROR_FIELDS, command: Schema.String, cause: Schema.Defect() },
132
+ ) {
133
+ public override get message() {
134
+ return `could not start ${this.command}: ${describeCauseMessage(this.cause)}`;
135
+ }
136
+ }
137
+
138
+ export class CommandStandardInputFailed extends Schema.TaggedError<CommandStandardInputFailed>()(
139
+ "CommandStandardInputFailed",
140
+ { ...TASK_ERROR_FIELDS, command: Schema.String, cause: Schema.Defect() },
141
+ ) {
142
+ public override get message() {
143
+ return isCauseWithoutFailure(this.cause)
144
+ ? `the standard input stream of ${this.command} ${this.cause.message}`
145
+ : `the standard input stream of ${this.command} failed with ${describeCauseMessage(this.cause)}`;
146
+ }
147
+ }
@@ -0,0 +1,17 @@
1
+ export {
2
+ forgetEndedProcessGroups,
3
+ killAllProcessGroups,
4
+ killTaskProcessGroups,
5
+ } from "./internal/kill-process-groups.ts";
6
+ export {
7
+ processGroupHasOnlyEndedMembers,
8
+ readProcessUnlessGone,
9
+ } from "./internal/list-processes.ts";
10
+ export { adoptSupervisorRunMarker, createRunMarker } from "./internal/mark-run-processes.ts";
11
+ export { signalRunProcesses } from "./internal/signal-run-processes.ts";
12
+ export {
13
+ findStartedGroupWitnessNow,
14
+ readLatestMemberStartTime,
15
+ recordLeaderExit,
16
+ startedProcessGroup,
17
+ } from "./internal/started-process-groups.ts";
@@ -0,0 +1,16 @@
1
+ const BYTE_ORDER_MARK = "\uFEFF";
2
+
3
+ export const commandWordsOf = (argumentWords: readonly string[]) =>
4
+ argumentWords
5
+ .map((argumentWord) =>
6
+ argumentWord.startsWith(BYTE_ORDER_MARK)
7
+ ? argumentWord.slice(BYTE_ORDER_MARK.length)
8
+ : argumentWord,
9
+ )
10
+ .filter((commandWord) => commandWord !== "");
11
+
12
+ export const programNameOf = (argumentWords: readonly string[], commandName: string) => {
13
+ const [programPath] = commandWordsOf(argumentWords.slice(0, 1));
14
+ const programName = programPath?.slice(programPath.lastIndexOf("/") + 1) ?? "";
15
+ return programName === "" ? commandName : programName;
16
+ };
@@ -0,0 +1,36 @@
1
+ import type { Effect } from "effect";
2
+
3
+ import type { listProcesses } from "./list-processes.ts";
4
+
5
+ export const findDescendantProcessIds = (
6
+ listedProcesses: Effect.Success<ReturnType<typeof listProcesses>>,
7
+ processIdsAtThisDepth: readonly number[],
8
+ collectedProcessIds: readonly number[] = [],
9
+ ): readonly number[] => {
10
+ if (processIdsAtThisDepth.length === 0) {
11
+ return collectedProcessIds;
12
+ }
13
+ const collectedThroughThisDepth = [...collectedProcessIds, ...processIdsAtThisDepth];
14
+ return findDescendantProcessIds(
15
+ listedProcesses,
16
+ [...listedProcesses]
17
+ .filter(
18
+ ([processId, listedProcess]) =>
19
+ processIdsAtThisDepth.includes(listedProcess.parentProcessId) &&
20
+ !collectedThroughThisDepth.includes(processId),
21
+ )
22
+ .map(([processId]) => processId),
23
+ collectedThroughThisDepth,
24
+ );
25
+ };
26
+
27
+ export const findSessionProcessIds = (
28
+ listedProcesses: Effect.Success<ReturnType<typeof listProcesses>>,
29
+ sessionId: number | null,
30
+ ) =>
31
+ [...listedProcesses]
32
+ .filter(
33
+ ([processId, listedProcess]) =>
34
+ listedProcess.sessionId === sessionId && processId !== process.pid,
35
+ )
36
+ .map(([processId]) => processId);