@livestore/utils-dev 0.4.0-dev.9 → 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 (82) hide show
  1. package/dist/.tsbuildinfo +1 -0
  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} +18 -18
  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 +64 -80
  17. package/dist/node/FileLogger.js.map +1 -1
  18. package/dist/node/cmd-log.d.ts +21 -0
  19. package/dist/node/cmd-log.d.ts.map +1 -0
  20. package/dist/node/cmd-log.js +58 -0
  21. package/dist/node/cmd-log.js.map +1 -0
  22. package/dist/node/cmd.d.ts +20 -14
  23. package/dist/node/cmd.d.ts.map +1 -1
  24. package/dist/node/cmd.js +248 -33
  25. package/dist/node/cmd.js.map +1 -1
  26. package/dist/node/cmd.test.d.ts +2 -0
  27. package/dist/node/cmd.test.d.ts.map +1 -0
  28. package/dist/node/cmd.test.js +103 -0
  29. package/dist/node/cmd.test.js.map +1 -0
  30. package/dist/node/mod.d.ts +5 -5
  31. package/dist/node/mod.d.ts.map +1 -1
  32. package/dist/node/mod.js +114 -66
  33. package/dist/node/mod.js.map +1 -1
  34. package/dist/node/workspace.d.ts +22 -0
  35. package/dist/node/workspace.d.ts.map +1 -0
  36. package/dist/node/workspace.js +26 -0
  37. package/dist/node/workspace.js.map +1 -0
  38. package/dist/node-vitest/Vitest.d.ts +17 -8
  39. package/dist/node-vitest/Vitest.d.ts.map +1 -1
  40. package/dist/node-vitest/Vitest.js +16 -16
  41. package/dist/node-vitest/Vitest.js.map +1 -1
  42. package/dist/node-vitest/Vitest.test.d.ts +6 -0
  43. package/dist/node-vitest/Vitest.test.d.ts.map +1 -1
  44. package/dist/node-vitest/Vitest.test.js +25 -10
  45. package/dist/node-vitest/Vitest.test.js.map +1 -1
  46. package/dist/wrangler/WranglerDevServer.d.ts +32 -20
  47. package/dist/wrangler/WranglerDevServer.d.ts.map +1 -1
  48. package/dist/wrangler/WranglerDevServer.js +79 -60
  49. package/dist/wrangler/WranglerDevServer.js.map +1 -1
  50. package/dist/wrangler/WranglerDevServer.test.js +31 -31
  51. package/dist/wrangler/WranglerDevServer.test.js.map +1 -1
  52. package/dist/wrangler/mod.d.ts +1 -1
  53. package/dist/wrangler/mod.d.ts.map +1 -1
  54. package/dist/wrangler/mod.js +1 -1
  55. package/dist/wrangler/mod.js.map +1 -1
  56. package/package.json +56 -21
  57. package/src/node/{DockerComposeService/DockerComposeService.test.ts → DockerCompose/DockerCompose.test.ts} +23 -20
  58. package/src/node/DockerCompose/DockerCompose.ts +328 -0
  59. package/src/node/DockerCompose/mod.ts +1 -0
  60. package/src/node/DockerCompose/test-fixtures/docker-compose.yml +4 -0
  61. package/src/node/FileLogger.ts +91 -115
  62. package/src/node/cmd-log.ts +95 -0
  63. package/src/node/cmd.test.ts +136 -0
  64. package/src/node/cmd.ts +431 -82
  65. package/src/node/mod.ts +152 -96
  66. package/src/node/workspace.ts +45 -0
  67. package/src/node-vitest/Vitest.test.ts +34 -10
  68. package/src/node-vitest/Vitest.ts +60 -66
  69. package/src/wrangler/WranglerDevServer.test.ts +38 -40
  70. package/src/wrangler/WranglerDevServer.ts +165 -108
  71. package/src/wrangler/fixtures/wrangler.toml +1 -1
  72. package/src/wrangler/mod.ts +1 -6
  73. package/dist/.tsbuildinfo.json +0 -1
  74. package/dist/node/DockerComposeService/DockerComposeService.d.ts +0 -58
  75. package/dist/node/DockerComposeService/DockerComposeService.d.ts.map +0 -1
  76. package/dist/node/DockerComposeService/DockerComposeService.js +0 -144
  77. package/dist/node/DockerComposeService/DockerComposeService.js.map +0 -1
  78. package/dist/node/DockerComposeService/DockerComposeService.test.d.ts +0 -2
  79. package/dist/node/DockerComposeService/DockerComposeService.test.d.ts.map +0 -1
  80. package/dist/node/DockerComposeService/DockerComposeService.test.js.map +0 -1
  81. package/src/node/DockerComposeService/DockerComposeService.ts +0 -328
  82. package/src/node/DockerComposeService/test-fixtures/docker-compose.yml +0 -4
@@ -1,39 +1,42 @@
1
1
  import path from 'node:path'
2
+
3
+ import { expect } from 'vitest'
4
+
5
+ import { Vitest } from '@livestore/utils-dev/node-vitest'
2
6
  import { Duration, Effect, Layer, Stream } from '@livestore/utils/effect'
3
7
  import { PlatformNode } from '@livestore/utils/node'
4
- import { Vitest } from '@livestore/utils-dev/node-vitest'
5
- import { expect } from 'vitest'
6
- import { type DockerComposeArgs, DockerComposeService } from './DockerComposeService.ts'
8
+
9
+ import * as DockerCompose from './DockerCompose.ts'
7
10
 
8
11
  const testTimeout = 30_000
9
12
  const testFixturePath = path.join(import.meta.dirname, 'test-fixtures')
10
13
 
11
- const DockerComposeTest = (args: Partial<DockerComposeArgs> = {}) =>
12
- DockerComposeService.Default({
14
+ const DockerComposeTest = (args: Partial<DockerCompose.Options> = {}) =>
15
+ DockerCompose.layer({
13
16
  cwd: testFixturePath,
14
17
  ...args,
15
18
  })
16
19
 
17
- Vitest.describe('DockerComposeService', { timeout: testTimeout }, () => {
20
+ Vitest.describe('DockerCompose', { timeout: testTimeout }, () => {
18
21
  Vitest.describe('Basic Operations', () => {
19
- const withBasicTest = (args: Partial<DockerComposeArgs> = {}) =>
22
+ const withBasicTest = (args: Partial<DockerCompose.Options> = {}) =>
20
23
  Vitest.makeWithTestCtx({
21
24
  timeout: testTimeout,
22
- makeLayer: () => DockerComposeTest(args).pipe(Layer.provide(PlatformNode.NodeContext.layer)),
25
+ makeLayer: () => DockerComposeTest(args).pipe(Layer.provide(PlatformNode.NodeServices.layer)),
23
26
  })
24
27
 
25
- Vitest.scopedLive('can pull docker images', (test) =>
28
+ Vitest.live('can pull docker images', (test) =>
26
29
  Effect.gen(function* () {
27
- const dockerCompose = yield* DockerComposeService
30
+ const dockerCompose = yield* DockerCompose.DockerCompose
28
31
 
29
32
  // Test that pull operation works (should succeed for hello-world image)
30
33
  yield* dockerCompose.pull
31
34
  }).pipe(withBasicTest()(test)),
32
35
  )
33
36
 
34
- Vitest.scopedLive('can start and stop docker compose services', (test) =>
37
+ Vitest.live('can start and stop docker compose services', (test) =>
35
38
  Effect.gen(function* () {
36
- const dockerCompose = yield* DockerComposeService
39
+ const dockerCompose = yield* DockerCompose.DockerCompose
37
40
 
38
41
  // Start the service
39
42
  yield* dockerCompose.start({ detached: true })
@@ -43,9 +46,9 @@ Vitest.describe('DockerComposeService', { timeout: testTimeout }, () => {
43
46
  }).pipe(withBasicTest({ serviceName: 'hello-world' })(test)),
44
47
  )
45
48
 
46
- Vitest.scopedLive('can get logs from docker compose services', (test) =>
49
+ Vitest.live('can get logs from docker compose services', (test) =>
47
50
  Effect.gen(function* () {
48
- const dockerCompose = yield* DockerComposeService
51
+ const dockerCompose = yield* DockerCompose.DockerCompose
49
52
 
50
53
  // Start the service first
51
54
  yield* dockerCompose.start({ detached: true })
@@ -62,15 +65,15 @@ Vitest.describe('DockerComposeService', { timeout: testTimeout }, () => {
62
65
  })
63
66
 
64
67
  Vitest.describe('Health Check Operations', () => {
65
- const withHealthCheckTest = (args: Partial<DockerComposeArgs> = {}) =>
68
+ const withHealthCheckTest = (args: Partial<DockerCompose.Options> = {}) =>
66
69
  Vitest.makeWithTestCtx({
67
70
  timeout: testTimeout,
68
- makeLayer: () => DockerComposeTest(args).pipe(Layer.provide(PlatformNode.NodeContext.layer)),
71
+ makeLayer: () => DockerComposeTest(args).pipe(Layer.provide(PlatformNode.NodeServices.layer)),
69
72
  })
70
73
 
71
- Vitest.scopedLive('handles health check timeout gracefully', (test) =>
74
+ Vitest.live('handles health check timeout gracefully', (test) =>
72
75
  Effect.gen(function* () {
73
- const dockerCompose = yield* DockerComposeService
76
+ const dockerCompose = yield* DockerCompose.DockerCompose
74
77
 
75
78
  // Test starting with a health check that will timeout (invalid URL)
76
79
  const result = yield* dockerCompose
@@ -81,10 +84,10 @@ Vitest.describe('DockerComposeService', { timeout: testTimeout }, () => {
81
84
  timeout: Duration.seconds(2),
82
85
  },
83
86
  })
84
- .pipe(Effect.either)
87
+ .pipe(Effect.result)
85
88
 
86
89
  // Should fail due to health check timeout
87
- expect(result._tag).toBe('Left')
90
+ expect(result._tag).toBe('Failure')
88
91
  }).pipe(withHealthCheckTest({ serviceName: 'hello-world' })(test)),
89
92
  )
90
93
  })
@@ -0,0 +1,328 @@
1
+ import { objectToString, omitUndefineds } from '@livestore/utils'
2
+ import {
3
+ ChildProcess,
4
+ ChildProcessSpawner,
5
+ Context,
6
+ Duration,
7
+ Effect,
8
+ Fiber,
9
+ Layer,
10
+ type PlatformError,
11
+ Schedule,
12
+ Schema,
13
+ type Scope,
14
+ Stream,
15
+ } from '@livestore/utils/effect'
16
+
17
+ export class DockerComposeError extends Schema.TaggedError<DockerComposeError>(
18
+ '~@livestore/utils-dev/DockerComposeError',
19
+ )('DockerComposeError', {
20
+ cause: Schema.Defect(),
21
+ note: Schema.String,
22
+ /** Compose env of the failed invocation, if any — structured instead of string-formatted into `note`. */
23
+ env: Schema.optional(Schema.Record(Schema.String, Schema.String)),
24
+ }) {}
25
+
26
+ export interface Options {
27
+ readonly cwd: string
28
+ readonly serviceName?: string
29
+ /** Unique project name to isolate this compose instance. If not provided, a random one is generated. */
30
+ readonly projectName?: string
31
+ }
32
+
33
+ export interface StartOptions {
34
+ readonly detached?: boolean
35
+ readonly env?: Record<string, string>
36
+ readonly healthCheck?: {
37
+ readonly url: string
38
+ readonly timeout?: Duration.Duration
39
+ readonly interval?: Duration.Duration
40
+ }
41
+ }
42
+
43
+ export interface LogsOptions {
44
+ readonly follow?: boolean
45
+ readonly tail?: number
46
+ readonly since?: string
47
+ }
48
+
49
+ export interface Service {
50
+ readonly pull: Effect.Effect<void, DockerComposeError | PlatformError.PlatformError>
51
+ readonly start: (
52
+ options?: StartOptions,
53
+ ) => Effect.Effect<void, DockerComposeError | PlatformError.PlatformError, Scope.Scope>
54
+ readonly stop: Effect.Effect<void, DockerComposeError | PlatformError.PlatformError>
55
+ readonly down: (options?: {
56
+ readonly env?: Record<string, string>
57
+ readonly volumes?: boolean
58
+ readonly removeOrphans?: boolean
59
+ }) => Effect.Effect<void, DockerComposeError | PlatformError.PlatformError>
60
+ readonly logs: (
61
+ options?: LogsOptions,
62
+ ) => Stream.Stream<string, DockerComposeError | PlatformError.PlatformError, Scope.Scope>
63
+ /** The unique project name used to isolate this compose instance */
64
+ readonly projectName: string
65
+ }
66
+
67
+ const generateProjectName = (): string => `ls-test-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
68
+
69
+ export class DockerCompose extends Context.Service<DockerCompose, Service>()('@livestore/utils-dev/DockerCompose') {}
70
+
71
+ export const make = (args: Options) =>
72
+ Effect.gen(function* () {
73
+ const { cwd, serviceName } = args
74
+ const projectName = args.projectName ?? generateProjectName()
75
+
76
+ const commandExecutorContext = yield* Effect.context<ChildProcessSpawner.ChildProcessSpawner>()
77
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
78
+
79
+ const baseComposeArgs = ['-p', projectName]
80
+
81
+ const composeCommand = (args: ReadonlyArray<string>, options: ChildProcess.CommandOptions = {}) =>
82
+ ChildProcess.make('docker', ['compose', ...baseComposeArgs, ...args], { ...options, cwd })
83
+
84
+ const envOptions = (
85
+ env: Record<string, string> | undefined,
86
+ ): Pick<ChildProcess.CommandOptions, 'env' | 'extendEnv'> => (env === undefined ? {} : { env, extendEnv: true })
87
+
88
+ const pull = Effect.gen(function* () {
89
+ yield* Effect.log(`Pulling Docker Compose images in ${cwd}`)
90
+
91
+ const process = yield* spawner.spawn(composeCommand(['pull'], { stdout: 'pipe', stderr: 'pipe' }))
92
+
93
+ const stdoutFiber = yield* process.stdout.pipe(
94
+ Stream.decodeText({ encoding: 'utf8' }),
95
+ Stream.runFold(
96
+ () => '',
97
+ (acc, chunk) => acc + chunk,
98
+ ),
99
+ // Drain subprocess output immediately so the child cannot block while we wait for its exit code.
100
+ Effect.forkChild,
101
+ )
102
+
103
+ const stderrFiber = yield* process.stderr.pipe(
104
+ Stream.decodeText({ encoding: 'utf8' }),
105
+ Stream.runFold(
106
+ () => '',
107
+ (acc, chunk) => acc + chunk,
108
+ ),
109
+ // Drain subprocess output immediately so the child cannot block while we wait for its exit code.
110
+ Effect.forkChild,
111
+ )
112
+
113
+ const exitCode = yield* process.exitCode
114
+ const stdout = yield* Fiber.join(stdoutFiber)
115
+ const stderr = yield* Fiber.join(stderrFiber)
116
+
117
+ const exitCodeNumber = Number(exitCode)
118
+
119
+ if (exitCodeNumber !== 0) {
120
+ const stdoutLog = stdout.length > 0 ? stdout : '<empty stdout>'
121
+ const stderrLog = stderr.length > 0 ? stderr : '<empty stderr>'
122
+ const failureMessage = [
123
+ `Docker compose pull failed with exit code ${exitCodeNumber} in ${cwd}`,
124
+ `stdout:\n${stdoutLog}`,
125
+ `stderr:\n${stderrLog}`,
126
+ ].join('\n')
127
+
128
+ yield* Effect.logError(failureMessage)
129
+
130
+ return yield* new DockerComposeError({
131
+ cause: new Error(`Docker compose pull failed with exit code ${exitCodeNumber}`),
132
+ note: failureMessage,
133
+ })
134
+ }
135
+
136
+ yield* Effect.log(`Successfully pulled Docker Compose images`)
137
+ }).pipe(
138
+ Effect.retry({
139
+ schedule: Schedule.exponentialBackoff10Sec,
140
+ while: Schema.is(DockerComposeError),
141
+ }),
142
+ Effect.withSpan('pullDockerComposeImages'),
143
+ Effect.scoped,
144
+ )
145
+
146
+ const start = Effect.fn('startDockerCompose')(function* (options: StartOptions = {}) {
147
+ const { detached = true, healthCheck } = options
148
+
149
+ const startArgs = ['up']
150
+ if (detached === true) startArgs.push('-d')
151
+ if (serviceName !== undefined) startArgs.push(serviceName)
152
+
153
+ const process = yield* spawner
154
+ .spawn(composeCommand(startArgs, { ...envOptions(options.env), stderr: 'inherit', stdout: 'inherit' }))
155
+ .pipe(
156
+ Effect.mapError(
157
+ (cause) =>
158
+ new DockerComposeError({
159
+ cause,
160
+ note: `Failed to start Docker Compose services in ${cwd}`,
161
+ }),
162
+ ),
163
+ )
164
+
165
+ const exitCode = yield* process.exitCode
166
+
167
+ if (Number(exitCode) !== 0) {
168
+ return yield* new DockerComposeError({
169
+ cause: new Error(`Docker compose exited with code ${exitCode}`),
170
+ note: `Docker Compose failed to start with exit code ${exitCode}`,
171
+ env: options.env,
172
+ })
173
+ }
174
+
175
+ if (healthCheck !== undefined) {
176
+ yield* performHealthCheck(healthCheck).pipe(Effect.provide(commandExecutorContext))
177
+ }
178
+
179
+ yield* Effect.log(`Docker Compose services started successfully in ${cwd}`)
180
+ })
181
+
182
+ const stop = Effect.gen(function* () {
183
+ yield* Effect.log(`Stopping Docker Compose services in ${cwd}`)
184
+
185
+ const stopArgs = serviceName === undefined ? ['stop'] : ['stop', serviceName]
186
+ const exitCode = yield* spawner.exitCode(composeCommand(stopArgs))
187
+
188
+ if (Number(exitCode) !== 0) {
189
+ return yield* new DockerComposeError({
190
+ cause: new Error(`Docker compose stop exited with code ${exitCode}`),
191
+ note: `Failed to stop Docker Compose services`,
192
+ })
193
+ }
194
+
195
+ yield* Effect.log(`Docker Compose services stopped successfully`)
196
+ }).pipe(Effect.withSpan('stopDockerCompose'))
197
+
198
+ const logs = (options: LogsOptions = {}) =>
199
+ Effect.gen(function* () {
200
+ const { follow = false, tail, since } = options
201
+
202
+ const logsArgs = ['logs']
203
+ if (follow === true) logsArgs.push('-f')
204
+ if (tail !== undefined) logsArgs.push('--tail', tail.toString())
205
+ if (since !== undefined) logsArgs.push('--since', since)
206
+ if (serviceName !== undefined) logsArgs.push(serviceName)
207
+
208
+ const process = yield* spawner.spawn(composeCommand(logsArgs)).pipe(
209
+ Effect.mapError(
210
+ (cause) =>
211
+ new DockerComposeError({
212
+ cause,
213
+ note: `Failed to read Docker Compose logs in ${cwd}`,
214
+ }),
215
+ ),
216
+ )
217
+
218
+ return process.stdout.pipe(
219
+ Stream.decodeText({ encoding: 'utf8' }),
220
+ Stream.splitLines,
221
+ Stream.mapError(
222
+ (cause) =>
223
+ new DockerComposeError({
224
+ cause,
225
+ note: `Error reading Docker Compose logs in ${cwd}`,
226
+ }),
227
+ ),
228
+ )
229
+ }).pipe(Stream.unwrap)
230
+
231
+ const down = Effect.fn('downDockerCompose')(function* (options?: {
232
+ readonly env?: Record<string, string>
233
+ readonly volumes?: boolean
234
+ readonly removeOrphans?: boolean
235
+ }) {
236
+ yield* Effect.log(`Tearing down Docker Compose services in ${cwd}`)
237
+
238
+ const downArgs = ['down']
239
+ if (options?.volumes === true) downArgs.push('-v')
240
+ if (options?.removeOrphans === true) downArgs.push('--remove-orphans')
241
+ if (serviceName !== undefined) downArgs.push(serviceName)
242
+
243
+ const exitCode = yield* spawner.exitCode(composeCommand(downArgs, envOptions(options?.env)))
244
+
245
+ if (Number(exitCode) !== 0) {
246
+ return yield* new DockerComposeError({
247
+ cause: new Error(`Docker compose down exited with code ${exitCode}`),
248
+ note: `Failed to tear down Docker Compose services`,
249
+ })
250
+ }
251
+
252
+ yield* Effect.log(`Docker Compose services torn down successfully`)
253
+ })
254
+
255
+ // Register cleanup finalizer to ensure containers are removed when scope closes
256
+ yield* Effect.addFinalizer(() =>
257
+ down({ volumes: true, removeOrphans: true }).pipe(
258
+ Effect.tap(() => Effect.log(`Docker Compose cleanup completed for project ${projectName}`)),
259
+ Effect.catchCause((cause) =>
260
+ Effect.log('Docker Compose cleanup failed for project', projectName, objectToString(cause)),
261
+ ),
262
+ ),
263
+ )
264
+
265
+ return DockerCompose.of({ pull, start, stop, down, logs, projectName })
266
+ })
267
+
268
+ export const layer = (options: Options) => Layer.effect(DockerCompose, make(options))
269
+
270
+ const performHealthCheck = ({
271
+ url,
272
+ timeout = Duration.minutes(2),
273
+ interval = Duration.seconds(2),
274
+ }: {
275
+ url: string
276
+ timeout?: Duration.Duration
277
+ interval?: Duration.Duration
278
+ }): Effect.Effect<void, DockerComposeError, ChildProcessSpawner.ChildProcessSpawner | Scope.Scope> =>
279
+ Effect.gen(function* () {
280
+ yield* Effect.log(`Performing health check on ${url}`)
281
+
282
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
283
+
284
+ const checkHealth = spawner.exitCode(ChildProcess.make('curl', ['-f', '-s', url])).pipe(
285
+ Effect.map((code: number) => code === 0),
286
+ Effect.orElseSucceed(() => false),
287
+ )
288
+
289
+ const healthCheck = checkHealth.pipe(
290
+ Effect.repeat({
291
+ while: (healthy: boolean) => healthy === false,
292
+ schedule: Schedule.fixed(interval),
293
+ }),
294
+ Effect.timeout(timeout),
295
+ Effect.mapError(
296
+ () =>
297
+ new DockerComposeError({
298
+ cause: new Error('Health check timeout'),
299
+ note: `Health check failed for ${url} after ${Duration.toMillis(timeout)}ms`,
300
+ }),
301
+ ),
302
+ )
303
+
304
+ yield* healthCheck
305
+ yield* Effect.log(`Health check passed for ${url}`)
306
+ })
307
+
308
+ // Convenience function for scoped Docker Compose operations with automatic cleanup
309
+ export const startDockerComposeServicesScoped = (
310
+ args: Options & {
311
+ healthCheck?: StartOptions['healthCheck']
312
+ },
313
+ ): Effect.Effect<
314
+ void,
315
+ DockerComposeError | PlatformError.PlatformError,
316
+ DockerCompose | ChildProcessSpawner.ChildProcessSpawner | Scope.Scope
317
+ > =>
318
+ Effect.gen(function* () {
319
+ const dockerCompose = yield* DockerCompose
320
+
321
+ // Start the services
322
+ yield* dockerCompose.start({
323
+ ...omitUndefineds({ healthCheck: args.healthCheck !== undefined ? args.healthCheck : undefined }),
324
+ })
325
+
326
+ // Add cleanup finalizer to the current scope
327
+ yield* Effect.addFinalizer(() => dockerCompose.stop.pipe(Effect.orDie))
328
+ })
@@ -0,0 +1 @@
1
+ export * as DockerCompose from './DockerCompose.ts'
@@ -0,0 +1,4 @@
1
+ services:
2
+ hello-world:
3
+ image: hello-world
4
+ profiles: [manual] # Don't auto-start this service