@livestore/utils-dev 0.4.0 → 0.5.0-dev.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 (76) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/node/DockerCompose/DockerCompose.d.ts +52 -0
  3. package/dist/node/DockerCompose/DockerCompose.d.ts.map +1 -0
  4. package/dist/node/DockerCompose/DockerCompose.js +158 -0
  5. package/dist/node/DockerCompose/DockerCompose.js.map +1 -0
  6. package/dist/node/DockerCompose/DockerCompose.test.d.ts +2 -0
  7. package/dist/node/DockerCompose/DockerCompose.test.d.ts.map +1 -0
  8. package/dist/node/{DockerComposeService/DockerComposeService.test.js → DockerCompose/DockerCompose.test.js} +16 -16
  9. package/dist/node/DockerCompose/DockerCompose.test.js.map +1 -0
  10. package/dist/node/DockerCompose/mod.d.ts +2 -0
  11. package/dist/node/DockerCompose/mod.d.ts.map +1 -0
  12. package/dist/node/DockerCompose/mod.js +2 -0
  13. package/dist/node/DockerCompose/mod.js.map +1 -0
  14. package/dist/node/FileLogger.d.ts +15 -8
  15. package/dist/node/FileLogger.d.ts.map +1 -1
  16. package/dist/node/FileLogger.js +63 -79
  17. package/dist/node/FileLogger.js.map +1 -1
  18. package/dist/node/cmd-log.d.ts.map +1 -1
  19. package/dist/node/cmd-log.js +19 -8
  20. package/dist/node/cmd-log.js.map +1 -1
  21. package/dist/node/cmd.d.ts +10 -12
  22. package/dist/node/cmd.d.ts.map +1 -1
  23. package/dist/node/cmd.js +73 -36
  24. package/dist/node/cmd.js.map +1 -1
  25. package/dist/node/cmd.test.js +17 -17
  26. package/dist/node/cmd.test.js.map +1 -1
  27. package/dist/node/mod.d.ts +4 -5
  28. package/dist/node/mod.d.ts.map +1 -1
  29. package/dist/node/mod.js +58 -55
  30. package/dist/node/mod.js.map +1 -1
  31. package/dist/node/workspace.d.ts +2 -2
  32. package/dist/node/workspace.d.ts.map +1 -1
  33. package/dist/node/workspace.js +7 -7
  34. package/dist/node/workspace.js.map +1 -1
  35. package/dist/node-vitest/Vitest.d.ts +17 -8
  36. package/dist/node-vitest/Vitest.d.ts.map +1 -1
  37. package/dist/node-vitest/Vitest.js +5 -5
  38. package/dist/node-vitest/Vitest.js.map +1 -1
  39. package/dist/node-vitest/Vitest.test.d.ts +3 -5
  40. package/dist/node-vitest/Vitest.test.d.ts.map +1 -1
  41. package/dist/node-vitest/Vitest.test.js +14 -3
  42. package/dist/node-vitest/Vitest.test.js.map +1 -1
  43. package/dist/wrangler/WranglerDevServer.d.ts +15 -20
  44. package/dist/wrangler/WranglerDevServer.d.ts.map +1 -1
  45. package/dist/wrangler/WranglerDevServer.js +77 -71
  46. package/dist/wrangler/WranglerDevServer.js.map +1 -1
  47. package/dist/wrangler/WranglerDevServer.test.js +28 -28
  48. package/dist/wrangler/WranglerDevServer.test.js.map +1 -1
  49. package/dist/wrangler/mod.d.ts +1 -1
  50. package/dist/wrangler/mod.d.ts.map +1 -1
  51. package/dist/wrangler/mod.js +1 -1
  52. package/dist/wrangler/mod.js.map +1 -1
  53. package/package.json +19 -37
  54. package/src/node/{DockerComposeService/DockerComposeService.test.ts → DockerCompose/DockerCompose.test.ts} +18 -18
  55. package/src/node/DockerCompose/DockerCompose.ts +328 -0
  56. package/src/node/DockerCompose/mod.ts +1 -0
  57. package/src/node/FileLogger.ts +89 -114
  58. package/src/node/cmd-log.ts +21 -10
  59. package/src/node/cmd.test.ts +19 -17
  60. package/src/node/cmd.ts +117 -86
  61. package/src/node/mod.ts +112 -104
  62. package/src/node/workspace.ts +9 -10
  63. package/src/node-vitest/Vitest.test.ts +22 -3
  64. package/src/node-vitest/Vitest.ts +32 -42
  65. package/src/wrangler/WranglerDevServer.test.ts +33 -37
  66. package/src/wrangler/WranglerDevServer.ts +145 -129
  67. package/src/wrangler/mod.ts +1 -6
  68. package/dist/node/DockerComposeService/DockerComposeService.d.ts +0 -63
  69. package/dist/node/DockerComposeService/DockerComposeService.d.ts.map +0 -1
  70. package/dist/node/DockerComposeService/DockerComposeService.js +0 -149
  71. package/dist/node/DockerComposeService/DockerComposeService.js.map +0 -1
  72. package/dist/node/DockerComposeService/DockerComposeService.test.d.ts +0 -2
  73. package/dist/node/DockerComposeService/DockerComposeService.test.d.ts.map +0 -1
  74. package/dist/node/DockerComposeService/DockerComposeService.test.js.map +0 -1
  75. package/src/node/DockerComposeService/DockerComposeService.ts +0 -343
  76. /package/src/node/{DockerComposeService → DockerCompose}/test-fixtures/docker-compose.yml +0 -0
@@ -1,37 +1,39 @@
1
1
  import fs from 'node:fs'
2
2
  import path from 'node:path'
3
- import { CommandExecutor, Duration, Effect, Layer } from '@livestore/utils/effect'
4
- import { PlatformNode } from '@livestore/utils/node'
5
- import { Vitest } from '@livestore/utils-dev/node-vitest'
3
+
6
4
  import { expect } from 'vitest'
7
5
 
6
+ import { Vitest } from '@livestore/utils-dev/node-vitest'
7
+ import { ChildProcessSpawner, Duration, Effect, Layer } from '@livestore/utils/effect'
8
+ import { PlatformNode } from '@livestore/utils/node'
9
+
8
10
  import { cmd } from './cmd.ts'
9
11
  import { CurrentWorkingDirectory } from './workspace.ts'
10
12
 
11
13
  const withNode = Vitest.makeWithTestCtx({
12
- makeLayer: () => Layer.mergeAll(PlatformNode.NodeContext.layer, CurrentWorkingDirectory.live),
14
+ makeLayer: () => Layer.mergeAll(PlatformNode.NodeServices.layer, CurrentWorkingDirectory.live),
13
15
  timeout: 20_000,
14
16
  })
15
17
 
16
18
  Vitest.describe('cmd helper', () => {
17
19
  const ansiRegex = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, 'g')
18
20
 
19
- Vitest.scopedLive('runs tokenized string without shell', (test) =>
21
+ Vitest.live('runs tokenized string without shell', (test) =>
20
22
  Effect.gen(function* () {
21
23
  const exit = yield* cmd('printf ok')
22
- expect(exit).toBe(CommandExecutor.ExitCode(0))
24
+ expect(exit).toBe(ChildProcessSpawner.ExitCode(0))
23
25
  }).pipe(withNode(test)),
24
26
  )
25
27
 
26
- Vitest.scopedLive('runs array input', (test) =>
28
+ Vitest.live('runs array input', (test) =>
27
29
  Effect.gen(function* () {
28
30
  const exit = yield* cmd(['printf', 'ok'])
29
- expect(exit).toBe(CommandExecutor.ExitCode(0))
31
+ expect(exit).toBe(ChildProcessSpawner.ExitCode(0))
30
32
  }).pipe(withNode(test)),
31
33
  )
32
34
 
33
35
  /** TODO(#1020): Investigate CI timeout flakiness for cmd tests that spawn many child processes */
34
- Vitest.scopedLive(
36
+ Vitest.live(
35
37
  'supports logging with archive + retention',
36
38
  (test) =>
37
39
  Effect.gen(function* () {
@@ -40,7 +42,7 @@ Vitest.describe('cmd helper', () => {
40
42
 
41
43
  // first run
42
44
  const exit1 = yield* cmd('printf first', { logDir: logsDir })
43
- expect(exit1).toBe(CommandExecutor.ExitCode(0))
45
+ expect(exit1).toBe(ChildProcessSpawner.ExitCode(0))
44
46
  const current = path.join(logsDir, 'dev.log')
45
47
  expect(fs.existsSync(current)).toBe(true)
46
48
  const firstLog = fs.readFileSync(current, 'utf8')
@@ -48,13 +50,13 @@ Vitest.describe('cmd helper', () => {
48
50
  expect(firstStdoutLines.length).toBeGreaterThan(0)
49
51
  for (const line of firstStdoutLines) {
50
52
  expect(line).toContain('[stdout] first')
51
- expect(line).toContain('INFO')
53
+ expect(line).toMatch(/Info/i)
52
54
  expect(line).toContain('printf first')
53
55
  }
54
56
 
55
57
  // second run — archives previous
56
58
  const exit2 = yield* cmd('printf second', { logDir: logsDir })
57
- expect(exit2).toBe(CommandExecutor.ExitCode(0))
59
+ expect(exit2).toBe(ChildProcessSpawner.ExitCode(0))
58
60
  const archiveDir = path.join(logsDir, 'archive')
59
61
  const archives = fs.readdirSync(archiveDir).filter((f) => f.endsWith('.log'))
60
62
  expect(archives.length).toBe(1)
@@ -71,7 +73,7 @@ Vitest.describe('cmd helper', () => {
71
73
  expect(secondStdoutLines.length).toBeGreaterThan(0)
72
74
  for (const line of secondStdoutLines) {
73
75
  expect(line).toContain('[stdout] second')
74
- expect(line).toContain('INFO')
76
+ expect(line).toMatch(/Info/i)
75
77
  }
76
78
 
77
79
  // generate many archives to exercise retention (keep 50)
@@ -85,7 +87,7 @@ Vitest.describe('cmd helper', () => {
85
87
  { timeout: 30_000, retry: 3 },
86
88
  )
87
89
 
88
- Vitest.scopedLive('streams stdout and stderr with logger formatting', (test) =>
90
+ Vitest.live('streams stdout and stderr with logger formatting', (test) =>
89
91
  Effect.gen(function* () {
90
92
  const workspace = process.env.WORKSPACE_ROOT!
91
93
  const logsDir = path.join(workspace, 'tmp', 'cmd-tests', `format-${Date.now()}`)
@@ -93,7 +95,7 @@ Vitest.describe('cmd helper', () => {
93
95
  const exit = yield* cmd(['node', '-e', "console.log('out'); console.error('err')"], {
94
96
  logDir: logsDir,
95
97
  })
96
- expect(exit).toBe(CommandExecutor.ExitCode(0))
98
+ expect(exit).toBe(ChildProcessSpawner.ExitCode(0))
97
99
 
98
100
  const current = path.join(logsDir, 'dev.log')
99
101
  const logContent = fs.readFileSync(current, 'utf8')
@@ -110,13 +112,13 @@ Vitest.describe('cmd helper', () => {
110
112
  for (const line of relevantLines) {
111
113
  const stripped = line.replace(ansiRegex, '')
112
114
  expect(stripped.startsWith('[')).toBe(true)
113
- expect(stripped).toMatch(/(INFO|WARN)/)
115
+ expect(stripped).toMatch(/(Info|Warn)/i)
114
116
  expect(stripped).toMatch(/\[(stdout|stderr)]/)
115
117
  }
116
118
  }).pipe(withNode(test)),
117
119
  )
118
120
 
119
- Vitest.scopedLive('cleans up logged child process when interrupted', (test) =>
121
+ Vitest.live('cleans up logged child process when interrupted', (test) =>
120
122
  Effect.gen(function* () {
121
123
  const workspace = process.env.WORKSPACE_ROOT!
122
124
  const logsDir = path.join(workspace, 'tmp', 'cmd-tests', `timeout-${Date.now()}`)
package/src/node/cmd.ts CHANGED
@@ -1,29 +1,24 @@
1
- import fs from 'node:fs'
2
-
3
- import { isNotUndefined } from '@livestore/utils'
4
1
  import {
5
2
  Cause,
6
- Command,
7
- type CommandExecutor,
3
+ ChildProcess,
4
+ ChildProcessSpawner,
8
5
  Effect,
9
6
  Fiber,
10
- FiberId,
11
- FiberRefs,
12
- HashMap,
13
- identity,
14
- List,
15
- LogLevel,
7
+ Logger,
16
8
  type PlatformError,
9
+ Predicate,
10
+ References,
17
11
  Schema,
18
12
  Stream,
19
13
  } from '@livestore/utils/effect'
14
+ import { NodeFileSystem } from '@livestore/utils/node'
20
15
 
21
16
  import { applyLoggingToCommand } from './cmd-log.ts'
22
17
  import * as FileLogger from './FileLogger.ts'
23
18
  import { CurrentWorkingDirectory } from './workspace.ts'
24
19
 
25
20
  // Branded zero value so we can compare exit codes without touching internals.
26
- const SUCCESS_EXIT_CODE: CommandExecutor.ExitCode = 0 as CommandExecutor.ExitCode
21
+ const SUCCESS_EXIT_CODE: ChildProcessSpawner.ExitCode = ChildProcessSpawner.ExitCode(0)
27
22
 
28
23
  export const cmd: (
29
24
  commandInput: string | (string | undefined)[],
@@ -43,14 +38,14 @@ export const cmd: (
43
38
  logRetention?: number
44
39
  },
45
40
  ) => Effect.Effect<
46
- CommandExecutor.ExitCode,
41
+ ChildProcessSpawner.ExitCode,
47
42
  PlatformError.PlatformError | CmdError,
48
- CommandExecutor.CommandExecutor | CurrentWorkingDirectory
43
+ ChildProcessSpawner.ChildProcessSpawner | CurrentWorkingDirectory
49
44
  > = Effect.fn('cmd')(function* (commandInput, options) {
50
45
  const cwd = yield* CurrentWorkingDirectory
51
46
 
52
47
  const asArray = Array.isArray(commandInput)
53
- const parts = asArray === true ? commandInput.filter(isNotUndefined) : undefined
48
+ const parts = asArray === true ? commandInput.filter(Predicate.isNotUndefined) : undefined
54
49
  const [command, ...args] = asArray === true ? (parts as string[]) : commandInput.split(' ')
55
50
 
56
51
  const debugEnvStr = Object.entries(options?.env ?? {})
@@ -89,7 +84,7 @@ export const cmd: (
89
84
  useShell,
90
85
  } as const
91
86
 
92
- const exitCode = yield* isNotUndefined(logPath) === true
87
+ const exitCode = yield* Predicate.isNotUndefined(logPath) === true
93
88
  ? Effect.gen(function* () {
94
89
  yield* Effect.sync(() => console.log(`Logging output to ${logPath}`))
95
90
  return yield* runWithLogging({ ...baseArgs, logPath, threadName: commandDebugStr })
@@ -116,37 +111,42 @@ export const cmdText: (
116
111
  runInShell?: boolean
117
112
  env?: Record<string, string | undefined>
118
113
  },
119
- ) => Effect.Effect<string, PlatformError.PlatformError, CommandExecutor.CommandExecutor | CurrentWorkingDirectory> =
120
- Effect.fn('cmdText')(function* (commandInput, options) {
121
- const cwd = yield* CurrentWorkingDirectory
122
- const [command, ...args] =
123
- Array.isArray(commandInput) === true ? commandInput.filter(isNotUndefined) : commandInput.split(' ')
124
- const debugEnvStr = Object.entries(options?.env ?? {})
125
- .map(([key, value]) => `${key}='${String(value)}' `)
126
- .join('')
127
-
128
- const commandDebugStr = debugEnvStr + [command, ...args].join(' ')
129
- const subshellStr = options?.runInShell === true ? ' (in subshell)' : ''
130
-
131
- yield* Effect.logDebug(`Running '${commandDebugStr}' in '${cwd}'${subshellStr}`)
132
- yield* Effect.annotateCurrentSpan({ 'span.label': commandDebugStr, command, cwd })
133
-
134
- return yield* Command.make(command!, ...args).pipe(
135
- // inherit = Stream stderr to process.stderr, pipe = Stream stderr to process.stdout
136
- Command.stderr(options?.stderr ?? 'inherit'),
137
- Command.workingDirectory(cwd),
138
- options?.runInShell === true ? Command.runInShell(true) : identity,
139
- Command.env(options?.env ?? {}),
140
- Command.string,
141
- )
142
- })
114
+ ) => Effect.Effect<
115
+ string,
116
+ PlatformError.PlatformError,
117
+ ChildProcessSpawner.ChildProcessSpawner | CurrentWorkingDirectory
118
+ > = Effect.fn('cmdText')(function* (commandInput, options) {
119
+ const cwd = yield* CurrentWorkingDirectory
120
+ const commandParts =
121
+ Array.isArray(commandInput) === true ? commandInput.filter(Predicate.isNotUndefined) : commandInput.split(' ')
122
+ const [command, ...args] = commandParts
123
+ const debugEnvStr = Object.entries(options?.env ?? {})
124
+ .map(([key, value]) => `${key}='${String(value)}' `)
125
+ .join('')
126
+
127
+ const commandDebugStr = debugEnvStr + [command, ...args].join(' ')
128
+ const subshellStr = options?.runInShell === true ? ' (in subshell)' : ''
129
+
130
+ yield* Effect.logDebug(`Running '${commandDebugStr}' in '${cwd}'${subshellStr}`)
131
+ yield* Effect.annotateCurrentSpan({ 'span.label': commandDebugStr, command, cwd })
132
+
133
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
134
+ return yield* spawner.string(
135
+ buildCommand(commandParts, {
136
+ cwd,
137
+ env: options?.env ?? {},
138
+ stderr: options?.stderr ?? 'inherit',
139
+ shell: options?.runInShell === true,
140
+ }),
141
+ )
142
+ })
143
143
 
144
144
  export class CmdError extends Schema.TaggedError<CmdError>('~@livestore/utils-dev/CmdError')('CmdError', {
145
145
  command: Schema.String,
146
146
  args: Schema.Array(Schema.String),
147
147
  cwd: Schema.String,
148
- env: Schema.Record({ key: Schema.String, value: Schema.String.pipe(Schema.UndefinedOr) }),
149
- stderr: Schema.Literal('inherit', 'pipe'),
148
+ env: Schema.Record(Schema.String, Schema.String.pipe(Schema.UndefinedOr)),
149
+ stderr: Schema.Literals(['inherit', 'pipe']),
150
150
  }) {}
151
151
 
152
152
  type TRunBaseArgs = {
@@ -159,15 +159,19 @@ type TRunBaseArgs = {
159
159
  }
160
160
 
161
161
  const runWithoutLogging = ({ commandInput, cwd, env, stdoutMode, stderrMode, useShell }: TRunBaseArgs) =>
162
- buildCommand(commandInput, useShell).pipe(
163
- Command.stdin('inherit'),
164
- Command.stdout(stdoutMode),
165
- Command.stderr(stderrMode),
166
- Command.workingDirectory(cwd),
167
- useShell === true ? Command.runInShell(true) : identity,
168
- Command.env(env),
169
- Command.exitCode,
170
- )
162
+ Effect.gen(function* () {
163
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
164
+ return yield* spawner.exitCode(
165
+ buildCommand(commandInput, {
166
+ cwd,
167
+ env,
168
+ stdin: 'inherit',
169
+ stdout: stdoutMode,
170
+ stderr: stderrMode,
171
+ shell: useShell,
172
+ }),
173
+ )
174
+ })
171
175
 
172
176
  type TRunWithLoggingArgs = TRunBaseArgs & {
173
177
  readonly logPath: string
@@ -192,46 +196,54 @@ const runWithLogging = ({
192
196
  Effect.gen(function* () {
193
197
  const envWithColor = env.FORCE_COLOR === undefined ? { ...env, FORCE_COLOR: '1' } : env
194
198
 
195
- const logFile = yield* Effect.acquireRelease(
196
- Effect.sync(() => fs.openSync(logPath, 'a', 0o666)),
197
- (fd) => Effect.sync(() => fs.closeSync(fd)),
198
- )
199
-
200
- const prettyLogger = FileLogger.prettyLoggerTty({
199
+ const prettyLoggerOptions = {
201
200
  colors: true,
202
201
  stderr: false,
203
- formatDate: (date) => `${FileLogger.defaultDateFormat(date)} ${threadName}`,
204
- })
202
+ formatDate: FileLogger.formatLogTime,
203
+ } as const
204
+
205
+ const logStringToFile = yield* Logger.make<unknown, string>(({ message }) => {
206
+ if (typeof message === 'string') return message
207
+ return String(message)
208
+ }).pipe(Logger.toFile(logPath, { flag: 'a' }), Effect.provide(NodeFileSystem.layer))
209
+
210
+ const FileLoggerLive = Logger.layer([logStringToFile])
205
211
 
206
212
  const appendLog = ({ channel, content }: { channel: 'stdout' | 'stderr'; content: string }) =>
207
- Effect.sync(() => {
208
- const formatted = prettyLogger.log({
209
- fiberId: FiberId.none,
210
- logLevel: channel === 'stdout' ? LogLevel.Info : LogLevel.Warning,
213
+ Effect.gen(function* () {
214
+ const date = new Date()
215
+ const formatted = FileLogger.formatPrettyLog({
216
+ logLevel: channel === 'stdout' ? 'Info' : 'Warn',
211
217
  message: [`[${channel}]${content.length > 0 ? ` ${content}` : ''}`],
212
218
  cause: Cause.empty,
213
- context: FiberRefs.empty(),
214
- spans: List.empty(),
215
- annotations: HashMap.empty(),
216
- date: new Date(),
219
+ date,
220
+ fiberId: 0,
221
+ spans: [[threadName, date.getTime()]],
222
+ annotations: { thread: threadName },
223
+ options: prettyLoggerOptions,
217
224
  })
218
- fs.writeSync(logFile, formatted)
225
+
226
+ yield* Effect.log(formatted.endsWith('\n') === true ? formatted.slice(0, -1) : formatted).pipe(
227
+ Effect.provideService(References.MinimumLogLevel, 'All'),
228
+ Effect.provide(FileLoggerLive),
229
+ )
219
230
  })
220
231
 
221
- const command = buildCommand(commandInput, useShell).pipe(
222
- Command.stdin('inherit'),
223
- Command.stdout('pipe'),
224
- Command.stderr('pipe'),
225
- Command.workingDirectory(cwd),
226
- useShell === true ? Command.runInShell(true) : identity,
227
- Command.env(envWithColor),
228
- )
232
+ const command = buildCommand(commandInput, {
233
+ cwd,
234
+ env: envWithColor,
235
+ stdin: 'inherit',
236
+ stdout: 'pipe',
237
+ stderr: 'pipe',
238
+ shell: useShell,
239
+ })
229
240
 
230
241
  // Acquire/start the command and make sure we kill it on interruption.
231
- const runningProcess = yield* Effect.acquireRelease(command.pipe(Command.start), (proc) =>
242
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
243
+ const runningProcess = yield* Effect.acquireRelease(spawner.spawn(command), (proc) =>
232
244
  proc.isRunning.pipe(
233
245
  Effect.flatMap((running) =>
234
- running === true ? proc.kill().pipe(Effect.catchAll(() => Effect.void)) : Effect.void,
246
+ running === true ? proc.kill().pipe(Effect.catch(() => Effect.void)) : Effect.void,
235
247
  ),
236
248
  Effect.ignore,
237
249
  ),
@@ -249,20 +261,20 @@ const runWithLogging = ({
249
261
  })
250
262
 
251
263
  const stdoutFiber = yield* runningProcess.stdout.pipe(
252
- Stream.decodeText('utf8'),
264
+ Stream.decodeText({ encoding: 'utf8' }),
253
265
  Stream.runForEach((chunk) => stdoutHandler.onChunk(chunk)),
254
266
  Effect.forkScoped,
255
267
  )
256
268
 
257
269
  const stderrFiber = yield* runningProcess.stderr.pipe(
258
- Stream.decodeText('utf8'),
270
+ Stream.decodeText({ encoding: 'utf8' }),
259
271
  Stream.runForEach((chunk) => stderrHandler.onChunk(chunk)),
260
272
  Effect.forkScoped,
261
273
  )
262
274
 
263
275
  // Dump any buffered data and finish both stream fibers before we return.
264
276
  const flushOutputs = Effect.gen(function* () {
265
- const stillRunning = yield* runningProcess.isRunning.pipe(Effect.catchAll(() => Effect.succeed(false)))
277
+ const stillRunning = yield* runningProcess.isRunning.pipe(Effect.orElseSucceed(() => false))
266
278
  if (stillRunning === true) {
267
279
  yield* Effect.ignore(runningProcess.kill())
268
280
  }
@@ -278,18 +290,37 @@ const runWithLogging = ({
278
290
  }),
279
291
  )
280
292
 
281
- const buildCommand = (input: string | string[], useShell: boolean) => {
293
+ type TBuildCommandOptions = {
294
+ readonly cwd: string
295
+ readonly env: Record<string, string | undefined>
296
+ readonly stdin?: ChildProcess.CommandInput
297
+ readonly stdout?: ChildProcess.CommandOutput
298
+ readonly stderr?: ChildProcess.CommandOutput
299
+ readonly shell: boolean
300
+ }
301
+
302
+ const buildCommand = (input: string | string[], options: TBuildCommandOptions): ChildProcess.Command => {
303
+ const commandOptions = {
304
+ cwd: options.cwd,
305
+ env: options.env,
306
+ extendEnv: true,
307
+ shell: options.shell,
308
+ ...(options.stdin !== undefined ? { stdin: options.stdin } : {}),
309
+ ...(options.stdout !== undefined ? { stdout: options.stdout } : {}),
310
+ ...(options.stderr !== undefined ? { stderr: options.stderr } : {}),
311
+ } as const
312
+
282
313
  if (Array.isArray(input) === true) {
283
314
  const [c, ...a] = input
284
- return Command.make(c!, ...a)
315
+ return ChildProcess.make(c ?? '', a, commandOptions)
285
316
  }
286
317
 
287
- if (useShell === true) {
288
- return Command.make(input)
318
+ if (options.shell === true) {
319
+ return ChildProcess.make(input, commandOptions)
289
320
  }
290
321
 
291
322
  const [c, ...a] = input.split(' ')
292
- return Command.make(c!, ...a)
323
+ return ChildProcess.make(c ?? '', a, commandOptions)
293
324
  }
294
325
 
295
326
  type TLineTerminator = 'newline' | 'carriage-return' | 'none'