@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,44 @@
1
+ import { Schema } from "effect";
2
+
3
+ import { TASK_ERROR_FIELDS } from "./error-fields.ts";
4
+
5
+ export class CommandPipelineRefused extends Schema.TaggedError<CommandPipelineRefused>()(
6
+ "CommandPipelineRefused",
7
+ { ...TASK_ERROR_FIELDS, programNames: Schema.String },
8
+ ) {
9
+ public override get message() {
10
+ return `task's process service refused the pipeline ${this.programNames}`;
11
+ }
12
+ }
13
+
14
+ export class CommandOptionsRefused extends Schema.TaggedError<CommandOptionsRefused>()(
15
+ "CommandOptionsRefused",
16
+ {
17
+ ...TASK_ERROR_FIELDS,
18
+ programName: Schema.String,
19
+ optionNames: Schema.NonEmptyArray(Schema.String),
20
+ },
21
+ ) {
22
+ public override get message() {
23
+ return `task's process service refused ${this.optionNames.join(", ")} for ${this.programName}`;
24
+ }
25
+ }
26
+
27
+ export class RedactedValueWithNulRefused extends Schema.TaggedError<RedactedValueWithNulRefused>()(
28
+ "RedactedValueWithNulRefused",
29
+ {
30
+ ...TASK_ERROR_FIELDS,
31
+ programName: Schema.String,
32
+ valueNames: Schema.NonEmptyArray(Schema.String),
33
+ },
34
+ ) {
35
+ public override get message() {
36
+ return `task's process service refused ${this.valueNames.join(", ")} for ${this.programName}, a Redacted value holding a NUL character`;
37
+ }
38
+ }
39
+
40
+ export const CommandRefusal = Schema.Union([
41
+ CommandPipelineRefused,
42
+ CommandOptionsRefused,
43
+ RedactedValueWithNulRefused,
44
+ ]);
@@ -0,0 +1,12 @@
1
+ import { Effect, Fiber } from "effect";
2
+ import type { Scope } from "effect";
3
+
4
+ export const completeOnceSent =
5
+ (scope: Scope.Scope) =>
6
+ <Success, Failure, Services>(discardedCall: Effect.Effect<Success, Failure, Services>) =>
7
+ discardedCall.pipe(
8
+ Effect.forkIn(scope, { startImmediately: true }),
9
+ Effect.flatMap((callFiber) =>
10
+ callFiber.pollUnsafe() === undefined ? Effect.void : Fiber.join(callFiber),
11
+ ),
12
+ );
@@ -0,0 +1,12 @@
1
+ const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
2
+
3
+ export const cutToGraphemes = (text: string, maxGraphemes: number) => {
4
+ const graphemes: string[] = [];
5
+ for (const { segment } of graphemeSegmenter.segment(text)) {
6
+ graphemes.push(segment);
7
+ if (graphemes.length > maxGraphemes) {
8
+ return `${graphemes.slice(0, maxGraphemes - 1).join("")}…`;
9
+ }
10
+ }
11
+ return text;
12
+ };
@@ -0,0 +1,74 @@
1
+ import { Schema } from "effect";
2
+
3
+ import { TASK_ERROR_FIELDS } from "./error-fields.ts";
4
+
5
+ export class StateDirectoryPathBlank extends Schema.TaggedError<StateDirectoryPathBlank>()(
6
+ "StateDirectoryPathBlank",
7
+ { ...TASK_ERROR_FIELDS, variableName: Schema.String, value: Schema.String },
8
+ ) {
9
+ public override get message() {
10
+ return `${this.variableName} is "${this.value}", which is blank`;
11
+ }
12
+ }
13
+
14
+ export class HomeDirectoryPathMissing extends Schema.TaggedError<HomeDirectoryPathMissing>()(
15
+ "HomeDirectoryPathMissing",
16
+ {
17
+ ...TASK_ERROR_FIELDS,
18
+ stateDirectoryVariableName: Schema.String,
19
+ homeValue: Schema.NullOr(Schema.String),
20
+ },
21
+ ) {
22
+ public override get message() {
23
+ return this.homeValue === null
24
+ ? `${this.stateDirectoryVariableName} and HOME are both unset`
25
+ : `${this.stateDirectoryVariableName} is unset and HOME is "${this.homeValue}", which is blank`;
26
+ }
27
+ }
28
+
29
+ export class WorkerCountInvalid extends Schema.TaggedError<WorkerCountInvalid>()(
30
+ "WorkerCountInvalid",
31
+ { ...TASK_ERROR_FIELDS, variableName: Schema.String, value: Schema.String },
32
+ ) {
33
+ public override get message() {
34
+ return `${this.variableName} is "${this.value}", not a whole decimal number above 0`;
35
+ }
36
+ }
37
+
38
+ export class SetupLimitInvalid extends Schema.TaggedError<SetupLimitInvalid>()(
39
+ "SetupLimitInvalid",
40
+ { ...TASK_ERROR_FIELDS, variableName: Schema.String, value: Schema.String },
41
+ ) {
42
+ public override get message() {
43
+ return `${this.variableName} is "${this.value}", not a duration above 0 such as "90 seconds"`;
44
+ }
45
+ }
46
+
47
+ export class WorkerCountAboveMaximum extends Schema.TaggedError<WorkerCountAboveMaximum>()(
48
+ "WorkerCountAboveMaximum",
49
+ {
50
+ ...TASK_ERROR_FIELDS,
51
+ variableName: Schema.String,
52
+ value: Schema.String,
53
+ maximum: Schema.Int,
54
+ },
55
+ ) {
56
+ public override get message() {
57
+ return `${this.variableName} is "${this.value}", above the maximum of ${this.maximum} worker threads`;
58
+ }
59
+ }
60
+
61
+ export class BunVersionUnsupported extends Schema.TaggedError<BunVersionUnsupported>()(
62
+ "BunVersionUnsupported",
63
+ {
64
+ ...TASK_ERROR_FIELDS,
65
+ bunVersion: Schema.String,
66
+ supportedRange: Schema.String,
67
+ taskPackageName: Schema.String,
68
+ taskVersion: Schema.String,
69
+ },
70
+ ) {
71
+ public override get message() {
72
+ return `${this.taskPackageName} ${this.taskVersion} needs Bun ${this.supportedRange}, but Bun ${this.bunVersion} is running`;
73
+ }
74
+ }
@@ -0,0 +1,13 @@
1
+ import { Effect, Schema } from "effect";
2
+
3
+ import { TASK_PACKAGE } from "./task-package.ts";
4
+
5
+ export const UNATTRIBUTED_FAILURE_FIELDS = {
6
+ taskName: Schema.Null.pipe(Schema.withConstructorDefault(Effect.succeed(null))),
7
+ };
8
+
9
+ export const TASK_ERROR_FIELDS = {
10
+ raisedBy: Schema.Literal(TASK_PACKAGE.name).pipe(
11
+ Schema.withConstructorDefault(Effect.succeed(TASK_PACKAGE.name)),
12
+ ),
13
+ };
@@ -0,0 +1,32 @@
1
+ export const SCRIPT_LOADERS = ["js", "jsx", "ts", "tsx"] as const;
2
+
3
+ const LOADER_OF_SCRIPT_EXTENSION = new Map<string, (typeof SCRIPT_LOADERS)[number]>([
4
+ [".ts", "ts"],
5
+ [".mts", "ts"],
6
+ [".cts", "ts"],
7
+ [".tsx", "tsx"],
8
+ [".js", "js"],
9
+ [".mjs", "js"],
10
+ [".cjs", "js"],
11
+ [".jsx", "jsx"],
12
+ ]);
13
+
14
+ export const isDataLoader = (loader: string | null) =>
15
+ loader !== null && !SCRIPT_LOADERS.some((scriptLoader) => scriptLoader === loader);
16
+
17
+ export const findBunLoaderForScript = (
18
+ filePath: string,
19
+ configuredLoaderOfExtension: ReadonlyMap<string, string>,
20
+ ) => {
21
+ const extension = /\.[^./]+$/u.exec(filePath)?.[0];
22
+ if (extension === undefined) {
23
+ return null;
24
+ }
25
+ const configuredLoader = configuredLoaderOfExtension.get(extension);
26
+ if (configuredLoader === undefined) {
27
+ const loader = LOADER_OF_SCRIPT_EXTENSION.get(extension);
28
+ return loader === undefined ? null : { loader, isConfigured: false };
29
+ }
30
+ const loader = SCRIPT_LOADERS.find((scriptLoader) => scriptLoader === configuredLoader);
31
+ return loader === undefined ? null : { loader, isConfigured: true };
32
+ };
@@ -0,0 +1,35 @@
1
+ const NODE_MODULES_DIRECTORY_NAME = "node_modules";
2
+
3
+ const SCOPED_PACKAGE_NAME_SEGMENT_COUNT = 2;
4
+
5
+ const UNSCOPED_PACKAGE_NAME_SEGMENT_COUNT = 1;
6
+
7
+ export const makeNodeModulesPackageFinder =
8
+ (projectDirectoryPath: string | null) =>
9
+ (filePath: string): string | null => {
10
+ const segments = filePath.split("/");
11
+ const isInsideTheProject =
12
+ projectDirectoryPath !== null && filePath.startsWith(`${projectDirectoryPath}/`);
13
+ const firstSegmentIndexBelowProject = isInsideTheProject
14
+ ? projectDirectoryPath.split("/").length
15
+ : 0;
16
+ const nodeModulesIndex = segments.lastIndexOf(NODE_MODULES_DIRECTORY_NAME);
17
+ const packageNameSegmentCount =
18
+ segments[nodeModulesIndex + 1]?.startsWith("@") === true
19
+ ? SCOPED_PACKAGE_NAME_SEGMENT_COUNT
20
+ : UNSCOPED_PACKAGE_NAME_SEGMENT_COUNT;
21
+ const hasPackageNameAfterNodeModulesBelowProject =
22
+ nodeModulesIndex >= firstSegmentIndexBelowProject &&
23
+ nodeModulesIndex + packageNameSegmentCount < segments.length;
24
+ if (!hasPackageNameAfterNodeModulesBelowProject) {
25
+ return null;
26
+ }
27
+ const packageDirectoryPath = segments
28
+ .slice(0, nodeModulesIndex + 1 + packageNameSegmentCount)
29
+ .join("/");
30
+ const holdsTheProject =
31
+ projectDirectoryPath !== null &&
32
+ (projectDirectoryPath === packageDirectoryPath ||
33
+ projectDirectoryPath.startsWith(`${packageDirectoryPath}/`));
34
+ return holdsTheProject ? null : packageDirectoryPath;
35
+ };
@@ -0,0 +1,64 @@
1
+ import { Ref } from "effect";
2
+
3
+ import { makeNodeModulesPackageFinder } from "./find-node-modules-package-directory.ts";
4
+
5
+ const TASK_SOURCE_DIRECTORY_PATH = Bun.fileURLToPath(new URL("../", import.meta.url));
6
+
7
+ const EFFECT_PACKAGE_JSON_PATH = Bun.isStandaloneExecutable
8
+ ? null
9
+ : Bun.fileURLToPath(import.meta.resolve("effect/package.json"));
10
+ const EFFECT_PACKAGE_DIRECTORY_PATH =
11
+ EFFECT_PACKAGE_JSON_PATH === null
12
+ ? null
13
+ : EFFECT_PACKAGE_JSON_PATH.slice(0, EFFECT_PACKAGE_JSON_PATH.lastIndexOf("/") + 1);
14
+
15
+ const STACK_FRAME_LOCATION =
16
+ /^\s*at (?:.* \()?(?<filePath>\/.+?):(?<lineAndColumn>\d+(?::\d+)?)\)?$/u;
17
+
18
+ const parseStackFrames = (stack: string) =>
19
+ stack.split("\n").flatMap((stackLine) => {
20
+ const [, filePath, lineAndColumn] = STACK_FRAME_LOCATION.exec(stackLine) ?? [];
21
+ const isFrameWithoutLocation = filePath === undefined || lineAndColumn === undefined;
22
+ return isFrameWithoutLocation ? [] : [{ filePath, lineAndColumn }];
23
+ });
24
+
25
+ export const readStackAbove = (frameAboveConstruction: (...parameters: never) => unknown) => {
26
+ const stackHolder: { stack?: string } = {};
27
+ const previousStackTraceLimit = Error.stackTraceLimit;
28
+ Error.stackTraceLimit = Number.POSITIVE_INFINITY;
29
+ try {
30
+ Error.captureStackTrace(stackHolder, frameAboveConstruction);
31
+ } finally {
32
+ Error.stackTraceLimit = previousStackTraceLimit;
33
+ }
34
+ return stackHolder.stack;
35
+ };
36
+
37
+ const findNodeModulesPackageDirectoryPathRef = Ref.makeUnsafe(makeNodeModulesPackageFinder(null));
38
+
39
+ export const setProjectDirectoryPath = (directoryPath: string) =>
40
+ Ref.set(findNodeModulesPackageDirectoryPathRef, makeNodeModulesPackageFinder(directoryPath));
41
+
42
+ const isInsideInstalledPackage = (filePath: string) =>
43
+ Ref.getUnsafe(findNodeModulesPackageDirectoryPathRef)(filePath) !== null;
44
+
45
+ const isUserFrame = (frame: { readonly filePath: string }) =>
46
+ !frame.filePath.startsWith(TASK_SOURCE_DIRECTORY_PATH) &&
47
+ !isInsideInstalledPackage(frame.filePath) &&
48
+ (EFFECT_PACKAGE_DIRECTORY_PATH === null ||
49
+ !frame.filePath.startsWith(EFFECT_PACKAGE_DIRECTORY_PATH));
50
+
51
+ export const findUserLocationInStack = (stack: string): string | undefined => {
52
+ const userFrame = parseStackFrames(stack).find((frame) => isUserFrame(frame));
53
+ return userFrame === undefined ? undefined : `${userFrame.filePath}:${userFrame.lineAndColumn}`;
54
+ };
55
+
56
+ export const findConstructingLocation = (): string | undefined => {
57
+ const stack = readStackAbove(findConstructingLocation);
58
+ return stack === undefined ? undefined : findUserLocationInStack(stack);
59
+ };
60
+
61
+ export const findLocationInFile = (stack: string, filePath: string): string | undefined => {
62
+ const frameInFile = parseStackFrames(stack).find((frame) => frame.filePath === filePath);
63
+ return frameInFile === undefined ? undefined : `${filePath}:${frameInFile.lineAndColumn}`;
64
+ };
@@ -0,0 +1,53 @@
1
+ import { Cause, String as Strings } from "effect";
2
+
3
+ import type { PrintableError } from "../lib/errors/index.ts";
4
+ import { cutToGraphemes } from "./cut-to-graphemes.ts";
5
+ import { reportCause } from "./report-cause.ts";
6
+
7
+ export const relativeToWorkingDirectory = (location: string) => {
8
+ const workingDirectoryPrefix = `${process.cwd()}/`;
9
+ return location.startsWith(workingDirectoryPrefix)
10
+ ? location.slice(workingDirectoryPrefix.length)
11
+ : location;
12
+ };
13
+
14
+ const indentContinuation = (continuationLine: string) =>
15
+ continuationLine === "" ? "" : ` ${continuationLine}`;
16
+
17
+ const formatErrorLines = (printable: PrintableError) => {
18
+ const [firstLine, ...continuationLines] = Strings.split(printable.message, "\n");
19
+ const locationPrefix =
20
+ printable.location === undefined ? "" : `${relativeToWorkingDirectory(printable.location)}: `;
21
+ return [
22
+ `${locationPrefix}error ${printable.tag}: ${firstLine}`,
23
+ ...continuationLines.map((continuationLine) => indentContinuation(continuationLine)),
24
+ ];
25
+ };
26
+
27
+ const omitTaskNameLocation = (printable: PrintableError, taskName: string | null) => {
28
+ const { location, ...printableWithoutLocation } = printable;
29
+ const isLocationRedundant = location === undefined || location === taskName;
30
+ return isLocationRedundant ? printableWithoutLocation : { ...printableWithoutLocation, location };
31
+ };
32
+
33
+ export const formatCauseLines = <Failure>(cause: Cause.Cause<Failure>, taskName: string | null) =>
34
+ reportCause(cause)
35
+ .failures.flatMap((reported) => formatErrorLines(omitTaskNameLocation(reported, taskName)))
36
+ .join("\n");
37
+
38
+ export const formatFailureLines = (failure: Error, taskName: string | null) =>
39
+ formatCauseLines(Cause.fail(failure), taskName);
40
+
41
+ const FAILURE_REASON_LINE_MAX_LENGTH = 200;
42
+
43
+ export const FAILURE_REASON_NOT_RECORDED_LINE = "TaskRunFailed: no reason recorded";
44
+
45
+ export const formatFailureReasonLine = (reason: PrintableError) =>
46
+ cutToGraphemes(
47
+ `${reason.tag}: ${reason.message
48
+ .split("\n")
49
+ .map((messageLine) => messageLine.trim())
50
+ .filter((messageLine) => messageLine !== "")
51
+ .join(" | ")}`,
52
+ FAILURE_REASON_LINE_MAX_LENGTH,
53
+ );
@@ -0,0 +1,21 @@
1
+ import { DateTime } from "effect";
2
+
3
+ const MILLISECONDS_PER_SECOND = 1000;
4
+
5
+ export const formatSeconds = (elapsedMilliseconds: number) =>
6
+ `${(elapsedMilliseconds / MILLISECONDS_PER_SECOND).toFixed(1)}s`;
7
+
8
+ export const formatIsoTimestamp = (millisecondsSinceEpoch: number) =>
9
+ DateTime.formatIso(DateTime.makeUnsafe(millisecondsSinceEpoch));
10
+
11
+ export const formatTaskPassed = (elapsedMilliseconds: number) =>
12
+ `passed in ${formatSeconds(elapsedMilliseconds)}`;
13
+
14
+ export const formatTaskServing = (elapsedMilliseconds: number) =>
15
+ `serving after ${formatSeconds(elapsedMilliseconds)}`;
16
+
17
+ export const prefixLines = (taskName: string, text: string) =>
18
+ text
19
+ .split("\n")
20
+ .map((line) => `[${taskName}] ${line}`)
21
+ .join("\n");
@@ -0,0 +1,24 @@
1
+ import { Effect } from "effect";
2
+
3
+ const TASK_LOGS_RELEASED = 0;
4
+ const TASK_LOGS_HELD = 1;
5
+
6
+ export const makeTaskLogHold = () =>
7
+ new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
8
+
9
+ export const holdTaskLogsDuring =
10
+ (taskLogHold: Int32Array) =>
11
+ <Success, Failure, Services>(effect: Effect.Effect<Success, Failure, Services>) =>
12
+ Effect.sync(() => Atomics.store(taskLogHold, 0, TASK_LOGS_HELD)).pipe(
13
+ Effect.andThen(effect),
14
+ Effect.ensuring(
15
+ Effect.sync(() => {
16
+ Atomics.store(taskLogHold, 0, TASK_LOGS_RELEASED);
17
+ Atomics.notify(taskLogHold, 0);
18
+ }),
19
+ ),
20
+ );
21
+
22
+ export const waitWhileTaskLogsAreHeld = (taskLogHold: Int32Array) => {
23
+ Atomics.wait(taskLogHold, 0, TASK_LOGS_HELD);
24
+ };
@@ -0,0 +1,28 @@
1
+ import { Effect, Schema } from "effect";
2
+
3
+ import { TASK_ERROR_FIELDS } from "./error-fields.ts";
4
+
5
+ export class InternalInvariantBroken extends Schema.TaggedError<InternalInvariantBroken>()(
6
+ "InternalInvariantBroken",
7
+ {
8
+ ...TASK_ERROR_FIELDS,
9
+ area: Schema.String,
10
+ invariant: Schema.String,
11
+ cause: Schema.optional(Schema.Defect()),
12
+ },
13
+ ) {
14
+ public override get message() {
15
+ return `${this.area} broke the internal invariant that ${this.invariant}`;
16
+ }
17
+ }
18
+
19
+ export const dieOnInvariantFailure =
20
+ (area: string, invariant: string) =>
21
+ <Success, Failure, Services>(effect: Effect.Effect<Success, Failure, Services>) =>
22
+ effect.pipe(
23
+ Effect.matchEffect({
24
+ onFailure: (error) =>
25
+ Effect.die(new InternalInvariantBroken({ area, invariant, cause: error })),
26
+ onSuccess: (success) => Effect.succeed(success),
27
+ }),
28
+ );
@@ -0,0 +1,29 @@
1
+ import { workerData } from "node:worker_threads";
2
+
3
+ import { Context, Effect, Fiber, Ref, Schema } from "effect";
4
+
5
+ import { WorkerData } from "./rpc.ts";
6
+ import { TASK_PACKAGE } from "./task-package.ts";
7
+
8
+ const isTaskWorkerData = Schema.is(WorkerData);
9
+
10
+ export const isInsideTaskWorkerThread = () => isTaskWorkerData(workerData);
11
+
12
+ const taskModuleImportsInProgress = Ref.makeUnsafe(0);
13
+
14
+ export const markImportingTaskModule = <Success, Failure, Services>(
15
+ importTaskModule: Effect.Effect<Success, Failure, Services>,
16
+ ) =>
17
+ Effect.acquireUseRelease(
18
+ Ref.update(taskModuleImportsInProgress, (inProgress) => inProgress + 1),
19
+ () => importTaskModule,
20
+ () => Ref.update(taskModuleImportsInProgress, (inProgress) => inProgress - 1),
21
+ );
22
+
23
+ export const isImportingTaskModule = () => Ref.getUnsafe(taskModuleImportsInProgress) > 0;
24
+
25
+ export const InsideTaskBody = Context.Reference<boolean>(`${TASK_PACKAGE.name}/InsideTaskBody`, {
26
+ defaultValue: () => false,
27
+ });
28
+
29
+ export const isInsideTaskBody = () => Fiber.getCurrent()?.getRef(InsideTaskBody) === true;
@@ -0,0 +1,49 @@
1
+ import { Schema } from "effect";
2
+
3
+ import { TASK_ERROR_FIELDS } from "./error-fields.ts";
4
+ import { describeCauseMessage } from "./report-errors.ts";
5
+
6
+ export class CodeFileUnreadable extends Schema.TaggedError<CodeFileUnreadable>()(
7
+ "CodeFileUnreadable",
8
+ { ...TASK_ERROR_FIELDS, filePath: Schema.String, cause: Schema.Defect() },
9
+ ) {
10
+ public override get message() {
11
+ return `could not read it because ${describeCauseMessage(this.cause)}`;
12
+ }
13
+ }
14
+
15
+ export class BunfigUnparsable extends Schema.TaggedError<BunfigUnparsable>()("BunfigUnparsable", {
16
+ ...TASK_ERROR_FIELDS,
17
+ filePath: Schema.String,
18
+ cause: Schema.Defect(),
19
+ }) {
20
+ public override get message() {
21
+ return `could not read the loaders it configures because ${describeCauseMessage(this.cause)}`;
22
+ }
23
+ }
24
+
25
+ export class ImportResolutionThrew extends Schema.TaggedError<ImportResolutionThrew>()(
26
+ "ImportResolutionThrew",
27
+ {
28
+ ...TASK_ERROR_FIELDS,
29
+ importingFilePath: Schema.String,
30
+ specifier: Schema.String,
31
+ cause: Schema.Defect(),
32
+ },
33
+ ) {
34
+ public get location() {
35
+ return this.importingFilePath;
36
+ }
37
+ public override get message() {
38
+ return `resolving the import "${this.specifier}" threw ${describeCauseMessage(this.cause)}`;
39
+ }
40
+ }
41
+
42
+ export class RewrittenModuleUnloadable extends Schema.TaggedError<RewrittenModuleUnloadable>()(
43
+ "RewrittenModuleUnloadable",
44
+ { ...TASK_ERROR_FIELDS, filePath: Schema.String },
45
+ ) {
46
+ public override get message() {
47
+ return "is not a script among the rewritten worker imports";
48
+ }
49
+ }
@@ -0,0 +1,42 @@
1
+ import { ConfigProvider, Effect } from "effect";
2
+ import type { Exit } from "effect";
3
+
4
+ export const nameConfigKey = (configPath: readonly PropertyKey[]) =>
5
+ configPath.map(String).join(".");
6
+
7
+ export const nameEnvironmentConfigVariable = (configPath: readonly PropertyKey[]) =>
8
+ configPath.map(String).join("_");
9
+
10
+ const runtimeConfigProviders = new WeakSet<ConfigProvider.ConfigProvider>();
11
+
12
+ export const markRuntimeConfigProvider = (provider: ConfigProvider.ConfigProvider) => {
13
+ runtimeConfigProviders.add(provider);
14
+ return provider;
15
+ };
16
+
17
+ export const observeConfigLoads =
18
+ (
19
+ onLoad: (load: {
20
+ readonly configPath: readonly PropertyKey[];
21
+ readonly readsRuntimeConfigProvider: boolean;
22
+ readonly loaded: Exit.Exit<ConfigProvider.Node | undefined, ConfigProvider.SourceError>;
23
+ }) => void,
24
+ ) =>
25
+ <Success, Failure, Services>(effect: Effect.Effect<Success, Failure, Services>) =>
26
+ Effect.gen(function* () {
27
+ const provider = yield* ConfigProvider.ConfigProvider;
28
+ const readsRuntimeConfigProvider = runtimeConfigProviders.has(provider);
29
+ const observed = ConfigProvider.make((configPath) =>
30
+ provider.load(configPath).pipe(
31
+ Effect.onExit((loaded) =>
32
+ Effect.sync(() => {
33
+ onLoad({ configPath, readsRuntimeConfigProvider, loaded });
34
+ }),
35
+ ),
36
+ ),
37
+ );
38
+ if (readsRuntimeConfigProvider) {
39
+ markRuntimeConfigProvider(observed);
40
+ }
41
+ return yield* Effect.provideService(effect, ConfigProvider.ConfigProvider, observed);
42
+ });
@@ -0,0 +1,7 @@
1
+ import { TASK_PACKAGE } from "./task-package.ts";
2
+
3
+ export const packageNameParts = TASK_PACKAGE.name.replace(/^@/u, "").split("/");
4
+
5
+ export const TASK_STATE_DIRECTORY_NAME = `.${packageNameParts.join("-")}`;
6
+
7
+ export const TASK_TRACE_LABEL_PREFIX = packageNameParts.join("_");
@@ -0,0 +1,48 @@
1
+ import { Deferred, Effect, Queue, Runtime, Schema } from "effect";
2
+ import type { Cause, LogLevel } from "effect";
3
+
4
+ import { TASK_ERROR_FIELDS, UNATTRIBUTED_FAILURE_FIELDS } from "./error-fields.ts";
5
+ import { prefixLines } from "./format-task-lines.ts";
6
+ import { isInsideTaskWorkerThread } from "./mark-task-worker-thread-contexts.ts";
7
+ import type { StandardErrorLine } from "./rpc.ts";
8
+
9
+ export class StandardErrorWriteFailed extends Schema.TaggedError<StandardErrorWriteFailed>()(
10
+ "StandardErrorWriteFailed",
11
+ {
12
+ ...TASK_ERROR_FIELDS,
13
+ ...UNATTRIBUTED_FAILURE_FIELDS,
14
+ errnoCode: Schema.NullOr(Schema.String),
15
+ reason: Schema.String,
16
+ },
17
+ ) {
18
+ public override get message() {
19
+ return `could not write to standard error (${this.reason}), so task stopped the run`;
20
+ }
21
+
22
+ public override get [Runtime.errorReported]() {
23
+ return false;
24
+ }
25
+ }
26
+
27
+ export const standardErrorWriteFailedDeferred = Deferred.makeUnsafe<StandardErrorWriteFailed>();
28
+
29
+ export const standardErrorClosedDeferred = Deferred.makeUnsafe<void>();
30
+
31
+ export const standardErrorLinesDeferred =
32
+ Deferred.makeUnsafe<Queue.Queue<typeof StandardErrorLine.Type, Cause.Done>>();
33
+
34
+ export const standardErrorLinesToTheMainThreadDeferred =
35
+ Deferred.makeUnsafe<(logLevel: LogLevel.LogLevel, text: string) => Effect.Effect<void>>();
36
+
37
+ export const printLine = (logLevel: LogLevel.LogLevel, text: string) =>
38
+ isInsideTaskWorkerThread()
39
+ ? Deferred.await(standardErrorLinesToTheMainThreadDeferred).pipe(
40
+ Effect.flatMap((sendLine) => sendLine(logLevel, text)),
41
+ )
42
+ : Deferred.await(standardErrorLinesDeferred).pipe(
43
+ Effect.flatMap((lines) => Queue.offer(lines, { logLevel, text: `${text}\n` })),
44
+ Effect.asVoid,
45
+ );
46
+
47
+ export const printTaskLines = (logLevel: LogLevel.LogLevel, taskName: string, text: string) =>
48
+ printLine(logLevel, prefixLines(taskName, text));