@effect/platform-node 0.1.1 → 0.2.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 (61) hide show
  1. package/Command.d.ts +111 -0
  2. package/Command.d.ts.map +1 -0
  3. package/Command.js +103 -0
  4. package/Command.js.map +1 -0
  5. package/CommandExecutor.d.ts +44 -0
  6. package/CommandExecutor.d.ts.map +1 -0
  7. package/CommandExecutor.js +23 -0
  8. package/CommandExecutor.js.map +1 -0
  9. package/NodeContext.d.ts +16 -0
  10. package/NodeContext.d.ts.map +1 -0
  11. package/NodeContext.js +24 -0
  12. package/NodeContext.js.map +1 -0
  13. package/internal/commandExecutor.d.ts +2 -0
  14. package/internal/commandExecutor.d.ts.map +1 -0
  15. package/internal/commandExecutor.js +145 -0
  16. package/internal/commandExecutor.js.map +1 -0
  17. package/internal/error.d.ts +2 -0
  18. package/internal/error.d.ts.map +1 -0
  19. package/internal/error.js +44 -0
  20. package/internal/error.js.map +1 -0
  21. package/internal/fileSystem.js +28 -62
  22. package/internal/fileSystem.js.map +1 -1
  23. package/internal/path.js +16 -10
  24. package/internal/path.js.map +1 -1
  25. package/internal/runtime.js +2 -2
  26. package/internal/runtime.js.map +1 -1
  27. package/internal/sink.js +3 -3
  28. package/internal/sink.js.map +1 -1
  29. package/internal/stream.js +6 -1
  30. package/internal/stream.js.map +1 -1
  31. package/mjs/Command.mjs +85 -0
  32. package/mjs/Command.mjs.map +1 -0
  33. package/mjs/CommandExecutor.mjs +13 -0
  34. package/mjs/CommandExecutor.mjs.map +1 -0
  35. package/mjs/NodeContext.mjs +15 -0
  36. package/mjs/NodeContext.mjs.map +1 -0
  37. package/mjs/internal/commandExecutor.mjs +136 -0
  38. package/mjs/internal/commandExecutor.mjs.map +1 -0
  39. package/mjs/internal/error.mjs +37 -0
  40. package/mjs/internal/error.mjs.map +1 -0
  41. package/mjs/internal/fileSystem.mjs +28 -62
  42. package/mjs/internal/fileSystem.mjs.map +1 -1
  43. package/mjs/internal/path.mjs +16 -10
  44. package/mjs/internal/path.mjs.map +1 -1
  45. package/mjs/internal/runtime.mjs +2 -2
  46. package/mjs/internal/runtime.mjs.map +1 -1
  47. package/mjs/internal/sink.mjs +3 -3
  48. package/mjs/internal/sink.mjs.map +1 -1
  49. package/mjs/internal/stream.mjs +6 -1
  50. package/mjs/internal/stream.mjs.map +1 -1
  51. package/package.json +5 -5
  52. package/src/Command.ts +114 -0
  53. package/src/CommandExecutor.ts +49 -0
  54. package/src/NodeContext.ts +26 -0
  55. package/src/internal/commandExecutor.ts +202 -0
  56. package/src/internal/error.ts +51 -0
  57. package/src/internal/fileSystem.ts +28 -76
  58. package/src/internal/path.ts +8 -8
  59. package/src/internal/runtime.ts +2 -2
  60. package/src/internal/sink.ts +3 -3
  61. package/src/internal/stream.ts +6 -1
@@ -8,7 +8,12 @@ export const fromReadable = (evaluate, onError, {
8
8
  stream.once("error", err => {
9
9
  emit.fail(onError(err));
10
10
  });
11
- stream.once("end", () => {
11
+ // The 'close' event is emitted after a process has ended and the stdio
12
+ // streams of a child process have been closed. This is distinct from
13
+ // the 'exit' event, since multiple processes might share the same
14
+ // stdio streams. The 'close' event will always emit after 'exit' was
15
+ // already emitted, or 'error' if the child failed to spawn.
16
+ stream.once("close", () => {
12
17
  emit.end();
13
18
  });
14
19
  stream.on("readable", () => {
@@ -1 +1 @@
1
- {"version":3,"file":"stream.mjs","names":["Option","Effect","Stream","fromReadable","evaluate","onError","chunkSize","none","flatMap","_","repeatEffectOption","readChunk","unwrapScoped","map","stream","async","emit","once","err","fail","end","on","single","readable","acquireRelease","sync","removeAllListeners","closed","destroy","size","succeed","_tag","read","Number"],"sources":["../../src/internal/stream.ts"],"sourcesContent":[null],"mappings":"AAEA,OAAO,KAAKA,MAAM,MAAM,qBAAqB;AAC7C,OAAO,KAAKC,MAAM,MAAM,mBAAmB;AAG3C,OAAO,KAAKC,MAAM,MAAM,uBAAuB;AAG/C;AACA,OAAO,MAAMC,YAAY,GAAGA,CAC1BC,QAA2B,EAC3BC,OAA8B,EAC9B;EAAEC,SAAS,GAAGN,MAAM,CAACO,IAAI;AAAE,IAA0B,EAAE,KA+BrDL,MAAM,CAACM,OAAO,CAAEC,CAAC,IAAKP,MAAM,CAACQ,kBAAkB,CAACC,SAAS,CAAIF,CAAC,EAAEH,SAAS,CAAC,CAAC,CAAC,CAD5EJ,MAAM,CAACU,YAAY,CAnBnBX,MAAM,CAACY,GAAG,CAAEC,MAAM,IAChBZ,MAAM,CAACa,KAAK,CAAsBC,IAAI,IAAI;EACxCF,MAAM,CAACG,IAAI,CAAC,OAAO,EAAGC,GAAG,IAAI;IAC3BF,IAAI,CAACG,IAAI,CAACd,OAAO,CAACa,GAAG,CAAC,CAAC;EACzB,CAAC,CAAC;EAEFJ,MAAM,CAACG,IAAI,CAAC,KAAK,EAAE,MAAK;IACtBD,IAAI,CAACI,GAAG,EAAE;EACZ,CAAC,CAAC;EAEFN,MAAM,CAACO,EAAE,CAAC,UAAU,EAAE,MAAK;IACzBL,IAAI,CAACM,MAAM,CAACR,MAAM,CAAC;EACrB,CAAC,CAAC;EAEF,IAAIA,MAAM,CAACS,QAAQ,EAAE;IACnBP,IAAI,CAACM,MAAM,CAACR,MAAM,CAAC;;AAEvB,CAAC,EAAE,CAAC,CAAC,CACN,CA1BDb,MAAM,CAACuB,cAAc,CAACvB,MAAM,CAACwB,IAAI,CAACrB,QAAQ,CAAC,EAAGU,MAAM,IAClDb,MAAM,CAACwB,IAAI,CAAC,MAAK;EACfX,MAAM,CAACY,kBAAkB,EAAE;EAE3B,IAAI,CAACZ,MAAM,CAACa,MAAM,EAAE;IAClBb,MAAM,CAACc,OAAO,EAAE;;AAEpB,CAAC,CAAC,CAAC,GAsBN;AAEH,MAAMjB,SAAS,GAAGA,CAChBG,MAAgB,EAChBe,IAAyB,KAIvB5B,MAAM,CAACO,OAAO,CAAEC,CAAC,IAAMA,CAAC,GAAGR,MAAM,CAAC6B,OAAO,CAACrB,CAAC,CAAC,GAAGR,MAAM,CAACkB,IAAI,CAACnB,MAAM,CAACO,IAAI,EAAE,CAAE,CAAC,CAD3EN,MAAM,CAACwB,IAAI,CAAC,MAAOI,IAAI,CAACE,IAAI,KAAK,MAAM,GAAGjB,MAAM,CAACkB,IAAI,CAACC,MAAM,CAACJ,IAAI,CAAC,CAAC,GAAGf,MAAM,CAACkB,IAAI,EAAe,CAAC,CAElG"}
1
+ {"version":3,"file":"stream.mjs","names":["Option","Effect","Stream","fromReadable","evaluate","onError","chunkSize","none","flatMap","_","repeatEffectOption","readChunk","unwrapScoped","map","stream","async","emit","once","err","fail","end","on","single","readable","acquireRelease","sync","removeAllListeners","closed","destroy","size","succeed","_tag","read","Number"],"sources":["../../src/internal/stream.ts"],"sourcesContent":[null],"mappings":"AAEA,OAAO,KAAKA,MAAM,MAAM,qBAAqB;AAC7C,OAAO,KAAKC,MAAM,MAAM,mBAAmB;AAG3C,OAAO,KAAKC,MAAM,MAAM,uBAAuB;AAG/C;AACA,OAAO,MAAMC,YAAY,GAAGA,CAC1BC,QAA2B,EAC3BC,OAA8B,EAC9B;EAAEC,SAAS,GAAGN,MAAM,CAACO,IAAI;AAAE,IAA0B,EAAE,KAoCrDL,MAAM,CAACM,OAAO,CAAEC,CAAC,IAAKP,MAAM,CAACQ,kBAAkB,CAACC,SAAS,CAAIF,CAAC,EAAEH,SAAS,CAAC,CAAC,CAAC,CAD5EJ,MAAM,CAACU,YAAY,CAxBnBX,MAAM,CAACY,GAAG,CAAEC,MAAM,IAChBZ,MAAM,CAACa,KAAK,CAAsBC,IAAI,IAAI;EACxCF,MAAM,CAACG,IAAI,CAAC,OAAO,EAAGC,GAAG,IAAI;IAC3BF,IAAI,CAACG,IAAI,CAACd,OAAO,CAACa,GAAG,CAAC,CAAC;EACzB,CAAC,CAAC;EAEF;EACA;EACA;EACA;EACA;EACAJ,MAAM,CAACG,IAAI,CAAC,OAAO,EAAE,MAAK;IACxBD,IAAI,CAACI,GAAG,EAAE;EACZ,CAAC,CAAC;EAEFN,MAAM,CAACO,EAAE,CAAC,UAAU,EAAE,MAAK;IACzBL,IAAI,CAACM,MAAM,CAACR,MAAM,CAAC;EACrB,CAAC,CAAC;EAEF,IAAIA,MAAM,CAACS,QAAQ,EAAE;IACnBP,IAAI,CAACM,MAAM,CAACR,MAAM,CAAC;;AAEvB,CAAC,EAAE,CAAC,CAAC,CACN,CA/BDb,MAAM,CAACuB,cAAc,CAACvB,MAAM,CAACwB,IAAI,CAACrB,QAAQ,CAAC,EAAGU,MAAM,IAClDb,MAAM,CAACwB,IAAI,CAAC,MAAK;EACfX,MAAM,CAACY,kBAAkB,EAAE;EAE3B,IAAI,CAACZ,MAAM,CAACa,MAAM,EAAE;IAClBb,MAAM,CAACc,OAAO,EAAE;;AAEpB,CAAC,CAAC,CAAC,GA2BN;AAEH,MAAMjB,SAAS,GAAGA,CAChBG,MAAgB,EAChBe,IAAyB,KAIvB5B,MAAM,CAACO,OAAO,CAAEC,CAAC,IAAMA,CAAC,GAAGR,MAAM,CAAC6B,OAAO,CAACrB,CAAC,CAAC,GAAGR,MAAM,CAACkB,IAAI,CAACnB,MAAM,CAACO,IAAI,EAAE,CAAE,CAAC,CAD3EN,MAAM,CAACwB,IAAI,CAAC,MAAOI,IAAI,CAACE,IAAI,KAAK,MAAM,GAAGjB,MAAM,CAACkB,IAAI,CAACC,MAAM,CAACJ,IAAI,CAAC,CAAC,GAAGf,MAAM,CAACkB,IAAI,EAAe,CAAC,CAElG"}
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@effect/platform-node",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/effect-ts/platform.git"
8
8
  },
9
9
  "dependencies": {
10
- "@effect/data": "^0.12.8",
11
- "@effect/io": "^0.26.1",
12
- "@effect/stream": "^0.22.1",
13
- "@effect/platform": "^0.1.0"
10
+ "@effect/data": "^0.13.5",
11
+ "@effect/io": "^0.31.3",
12
+ "@effect/stream": "^0.25.1",
13
+ "@effect/platform": "^0.2.0"
14
14
  },
15
15
  "publishConfig": {
16
16
  "access": "public"
package/src/Command.ts ADDED
@@ -0,0 +1,114 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+
5
+ export type {
6
+ /**
7
+ * @since 1.0.0
8
+ * @category model
9
+ */
10
+ Command,
11
+ /**
12
+ * @since 1.0.0
13
+ * @category model
14
+ */
15
+ CommandInput,
16
+ /**
17
+ * @since 1.0.0
18
+ * @category model
19
+ */
20
+ CommandOutput,
21
+ /**
22
+ * @since 1.0.0
23
+ * @category model
24
+ */
25
+ PipedCommand,
26
+ /**
27
+ * @since 1.0.0
28
+ * @category model
29
+ */
30
+ StandardCommand
31
+ } from "@effect/platform/Command"
32
+
33
+ export {
34
+ /**
35
+ * @since 1.0.0
36
+ * @category combinators
37
+ */
38
+ env,
39
+ /**
40
+ * @since 1.0.0
41
+ * @category execution
42
+ */
43
+ exitCode,
44
+ /**
45
+ * @since 1.0.0
46
+ * @category combinators
47
+ */
48
+ feed,
49
+ /**
50
+ * @since 1.0.0
51
+ * @category combinators
52
+ */
53
+ flatten,
54
+ /**
55
+ * @since 1.0.0
56
+ * @category refinements
57
+ */
58
+ isCommand,
59
+ /**
60
+ * @since 1.0.0
61
+ * @category execution
62
+ */
63
+ lines,
64
+ /**
65
+ * @since 1.0.0
66
+ * @category constructors
67
+ */
68
+ make,
69
+ /**
70
+ * @since 1.0.0
71
+ * @category combinators
72
+ */
73
+ pipeTo,
74
+ /**
75
+ * @since 1.0.0
76
+ * @category execution
77
+ */
78
+ start,
79
+ /**
80
+ * @since 1.0.0
81
+ * @category combinators
82
+ */
83
+ stderr,
84
+ /**
85
+ * @since 1.0.0
86
+ * @category combinators
87
+ */
88
+ stdin,
89
+ /**
90
+ * @since 1.0.0
91
+ * @category combinators
92
+ */
93
+ stdout,
94
+ /**
95
+ * @since 1.0.0
96
+ * @category execution
97
+ */
98
+ stream,
99
+ /**
100
+ * @since 1.0.0
101
+ * @category execution
102
+ */
103
+ streamLines,
104
+ /**
105
+ * @since 1.0.0
106
+ * @category execution
107
+ */
108
+ string,
109
+ /**
110
+ * @since 1.0.0
111
+ * @category combinators
112
+ */
113
+ workingDirectory
114
+ } from "@effect/platform/Command"
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+ import type { Layer } from "@effect/io/Layer"
5
+ import * as internal from "@effect/platform-node/internal/commandExecutor"
6
+ import type { CommandExecutor } from "@effect/platform/CommandExecutor"
7
+ import type { FileSystem } from "@effect/platform/FileSystem"
8
+
9
+ export type {
10
+ /**
11
+ * @since 1.0.0
12
+ * @category models
13
+ */
14
+ ExitCode,
15
+ /**
16
+ * @since 1.0.0
17
+ * @category models
18
+ */
19
+ Process,
20
+ /**
21
+ * @since 1.0.0
22
+ * @category models
23
+ */
24
+ ProcessId,
25
+ /**
26
+ * @since 1.0.0
27
+ * @category symbols
28
+ */
29
+ ProcessTypeId,
30
+ /**
31
+ * @since 1.0.0
32
+ * @category models
33
+ */
34
+ Signal
35
+ } from "@effect/platform/CommandExecutor"
36
+
37
+ export {
38
+ /**
39
+ * @since 1.0.0
40
+ * @category tag
41
+ */
42
+ CommandExecutor
43
+ } from "@effect/platform/CommandExecutor"
44
+
45
+ /**
46
+ * @since 1.0.0
47
+ * @category layer
48
+ */
49
+ export const layer: Layer<FileSystem, never, CommandExecutor> = internal.layer
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+ import { pipe } from "@effect/data/Function"
5
+ import * as Layer from "@effect/io/Layer"
6
+ import * as CommandExecutor from "@effect/platform-node/CommandExecutor"
7
+ import * as Console from "@effect/platform-node/Console"
8
+ import * as FileSystem from "@effect/platform-node/FileSystem"
9
+ import * as Path from "@effect/platform-node/Path"
10
+
11
+ /**
12
+ * @since 1.0.0
13
+ * @category models
14
+ */
15
+ export type NodeContext = Console.Console | CommandExecutor.CommandExecutor | FileSystem.FileSystem | Path.Path
16
+
17
+ /**
18
+ * @since 1.0.0
19
+ * @category layer
20
+ */
21
+ export const layer: Layer.Layer<never, never, NodeContext> = pipe(
22
+ Console.layer,
23
+ Layer.merge(FileSystem.layer),
24
+ Layer.merge(Path.layer),
25
+ Layer.merge(Layer.provideMerge(FileSystem.layer, CommandExecutor.layer))
26
+ )
@@ -0,0 +1,202 @@
1
+ import { constUndefined, pipe } from "@effect/data/Function"
2
+ import * as Option from "@effect/data/Option"
3
+ import * as Effect from "@effect/io/Effect"
4
+ import * as Layer from "@effect/io/Layer"
5
+ import { handleErrnoException } from "@effect/platform-node/internal/error"
6
+ import { fromWritable } from "@effect/platform-node/internal/sink"
7
+ import { fromReadable } from "@effect/platform-node/internal/stream"
8
+ import * as Command from "@effect/platform/Command"
9
+ import * as CommandExecutor from "@effect/platform/CommandExecutor"
10
+ import type * as Error from "@effect/platform/Error"
11
+ import * as FileSystem from "@effect/platform/FileSystem"
12
+ import * as Sink from "@effect/stream/Sink"
13
+ import * as Stream from "@effect/stream/Stream"
14
+ import * as ChildProcess from "node:child_process"
15
+
16
+ const inputToStdioOption = (stdin: Option.Option<Command.Command.Input>): "pipe" | "inherit" =>
17
+ Option.match(stdin, { onNone: () => "inherit", onSome: () => "pipe" })
18
+
19
+ const outputToStdioOption = (output: Command.Command.Output): "pipe" | "inherit" =>
20
+ typeof output === "string" ? output : "pipe"
21
+
22
+ const toError = (err: unknown): Error => err instanceof globalThis.Error ? err : new globalThis.Error(String(err))
23
+
24
+ const toPlatformError = (
25
+ method: string,
26
+ error: NodeJS.ErrnoException,
27
+ command: Command.Command
28
+ ): Error.PlatformError => {
29
+ const flattened = Command.flatten(command).reduce((acc, curr) => {
30
+ const command = `${curr.command} ${curr.args.join(" ")}`
31
+ return acc.length === 0 ? command : `${acc} | ${command}`
32
+ }, "")
33
+ return handleErrnoException("Command", method)(error, [flattened])
34
+ }
35
+
36
+ const runCommand = (fileSystem: FileSystem.FileSystem) =>
37
+ (command: Command.Command): Effect.Effect<never, Error.PlatformError, CommandExecutor.Process> => {
38
+ switch (command._tag) {
39
+ case "StandardCommand": {
40
+ return pipe(
41
+ // Validate that the directory is accessible
42
+ Option.match(command.cwd, {
43
+ onNone: () => Effect.unit,
44
+ onSome: (dir) => fileSystem.access(dir)
45
+ }),
46
+ Effect.zipRight(Effect.sync(() => globalThis.process.env)),
47
+ Effect.flatMap((env) =>
48
+ Effect.asyncInterrupt<never, Error.PlatformError, CommandExecutor.Process>((resume) => {
49
+ const handle = ChildProcess.spawn(command.command, command.args, {
50
+ stdio: [
51
+ inputToStdioOption(command.stdin),
52
+ outputToStdioOption(command.stdout),
53
+ outputToStdioOption(command.stderr)
54
+ ],
55
+ cwd: Option.getOrElse(command.cwd, constUndefined),
56
+ env: { ...env, ...Object.fromEntries(command.env) }
57
+ })
58
+
59
+ // If starting the process throws an error, make sure to capture it
60
+ handle.on("error", (err) => {
61
+ handle.kill("SIGKILL")
62
+ resume(Effect.fail(toPlatformError("spawn", err, command)))
63
+ })
64
+
65
+ // If the process is assigned a process identifier, then we know it
66
+ // was spawned successfully
67
+ if (handle.pid) {
68
+ let stdin: Sink.Sink<never, Error.PlatformError, unknown, never, void> = Sink.drain()
69
+
70
+ if (handle.stdin !== null) {
71
+ stdin = fromWritable(
72
+ () => handle.stdin!,
73
+ (err) => toPlatformError("toWritable", toError(err), command)
74
+ )
75
+ }
76
+
77
+ const exitCode: CommandExecutor.Process["exitCode"] = Effect.asyncInterrupt((resume) => {
78
+ handle.on("exit", (code, signal) => {
79
+ if (code !== null) {
80
+ resume(Effect.succeed(CommandExecutor.ExitCode(code)))
81
+ } else {
82
+ // If code is `null`, then `signal` must be defined. See the NodeJS
83
+ // documentation for the `"exit"` event on a `child_process`.
84
+ // https://nodejs.org/api/child_process.html#child_process_event_exit
85
+ resume(
86
+ Effect.fail(
87
+ toPlatformError(
88
+ "exitCode",
89
+ new globalThis.Error(`Process interrupted due to receipt of signal: ${signal}`),
90
+ command
91
+ )
92
+ )
93
+ )
94
+ }
95
+ })
96
+ // Make sure to terminate the running process if the fiber is
97
+ // terminated
98
+ return Effect.sync(() => {
99
+ handle.kill("SIGKILL")
100
+ })
101
+ })
102
+
103
+ const isRunning = Effect.sync(() =>
104
+ handle.exitCode === null &&
105
+ handle.signalCode === null &&
106
+ !handle.killed
107
+ )
108
+
109
+ const kill: CommandExecutor.Process["kill"] = (signal = "SIGTERM") =>
110
+ Effect.asyncInterrupt((resume) => {
111
+ handle.kill(signal)
112
+ handle.on("exit", () => {
113
+ resume(Effect.unit)
114
+ })
115
+ // Make sure to terminate the running process if the fiber
116
+ // is terminated
117
+ return Effect.sync(() => {
118
+ handle.kill("SIGKILL")
119
+ })
120
+ })
121
+
122
+ resume(Effect.sync<CommandExecutor.Process>(() => {
123
+ const pid = CommandExecutor.ProcessId(handle.pid!)
124
+ const stderr = fromReadable<Error.PlatformError, Uint8Array>(
125
+ () => handle.stderr!,
126
+ (err) => toPlatformError("fromReadable(stderr)", toError(err), command)
127
+ )
128
+ let stdout: Stream.Stream<never, Error.PlatformError, Uint8Array> = fromReadable<
129
+ Error.PlatformError,
130
+ Uint8Array
131
+ >(
132
+ () => handle.stdout!,
133
+ (err) => toPlatformError("fromReadable(stdout)", toError(err), command)
134
+ )
135
+ // TODO: add Sink.isSink
136
+ if (typeof command.stdout !== "string") {
137
+ stdout = Stream.transduce(stdout, command.stdout)
138
+ }
139
+ return {
140
+ [CommandExecutor.ProcessTypeId]: CommandExecutor.ProcessTypeId,
141
+ pid,
142
+ exitCode,
143
+ isRunning,
144
+ kill,
145
+ stdin,
146
+ stderr,
147
+ stdout
148
+ }
149
+ }))
150
+ }
151
+ return Effect.async<never, never, void>((resume) => {
152
+ if (handle.pid) {
153
+ handle.kill("SIGTERM")
154
+ }
155
+ handle.on("exit", () => {
156
+ resume(Effect.unit)
157
+ })
158
+ })
159
+ })
160
+ ),
161
+ Effect.tap((process) =>
162
+ Option.match(command.stdin, {
163
+ onNone: () => Effect.unit,
164
+ onSome: (stdin) => Effect.forkDaemon(Stream.run(stdin, process.stdin))
165
+ })
166
+ )
167
+ )
168
+ }
169
+ case "PipedCommand": {
170
+ const flattened = Command.flatten(command)
171
+ if (flattened.length === 1) {
172
+ return pipe(flattened[0], runCommand(fileSystem))
173
+ }
174
+ const head = flattened[0]
175
+ const tail = flattened.slice(1)
176
+ const initial = tail.slice(0, tail.length - 1)
177
+ const last = tail[tail.length - 1]
178
+ const stream = initial.reduce(
179
+ (stdin, command) =>
180
+ pipe(
181
+ Command.stdin(command, stdin),
182
+ runCommand(fileSystem),
183
+ Stream.flatMap((process) => process.stdout)
184
+ ),
185
+ pipe(
186
+ runCommand(fileSystem)(head),
187
+ Stream.flatMap((process) => process.stdout)
188
+ )
189
+ )
190
+ return pipe(Command.stdin(last, stream), runCommand(fileSystem))
191
+ }
192
+ }
193
+ }
194
+
195
+ /** @internal */
196
+ export const layer: Layer.Layer<FileSystem.FileSystem, never, CommandExecutor.CommandExecutor> = Layer.effect(
197
+ CommandExecutor.CommandExecutor,
198
+ pipe(
199
+ FileSystem.FileSystem,
200
+ Effect.map((fileSystem) => CommandExecutor.makeExecutor(runCommand(fileSystem)))
201
+ )
202
+ )
@@ -0,0 +1,51 @@
1
+ import type { PlatformError, SystemErrorReason } from "@effect/platform/Error"
2
+ import { SystemError } from "@effect/platform/Error"
3
+ import type { PathLike } from "node:fs"
4
+
5
+ /** @internal */
6
+ export const handleErrnoException = (module: SystemError["module"], method: string) =>
7
+ (
8
+ err: NodeJS.ErrnoException,
9
+ [path]: [path: PathLike | number, ...args: Array<any>]
10
+ ): PlatformError => {
11
+ let reason: SystemErrorReason = "Unknown"
12
+
13
+ switch (err.code) {
14
+ case "ENOENT":
15
+ reason = "NotFound"
16
+ break
17
+
18
+ case "EACCES":
19
+ reason = "PermissionDenied"
20
+ break
21
+
22
+ case "EEXIST":
23
+ reason = "AlreadyExists"
24
+ break
25
+
26
+ case "EISDIR":
27
+ reason = "BadResource"
28
+ break
29
+
30
+ case "ENOTDIR":
31
+ reason = "BadResource"
32
+ break
33
+
34
+ case "EBUSY":
35
+ reason = "Busy"
36
+ break
37
+
38
+ case "ELOOP":
39
+ reason = "BadResource"
40
+ break
41
+ }
42
+
43
+ return SystemError({
44
+ reason,
45
+ module,
46
+ method,
47
+ pathOrDescriptor: path as string | number,
48
+ syscall: err.syscall,
49
+ message: err.message
50
+ })
51
+ }