@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,44 +1,40 @@
1
1
  import { expect } from 'vitest'
2
2
 
3
3
  import { Vitest } from '@livestore/utils-dev/node-vitest'
4
- import { Effect, FetchHttpClient, Layer } from '@livestore/utils/effect'
4
+ import { Effect, FetchHttpClient, Layer, Result } from '@livestore/utils/effect'
5
5
  import { getFreePort, PlatformNode } from '@livestore/utils/node'
6
6
 
7
- import {
8
- type StartWranglerDevServerArgs,
9
- WranglerDevServerError,
10
- WranglerDevServerService,
11
- } from './WranglerDevServer.ts'
7
+ import * as WranglerDevServer from './WranglerDevServer.ts'
12
8
 
13
9
  const testTimeout = 60_000
14
10
 
15
- const WranglerDevServerTest = (args: Partial<StartWranglerDevServerArgs> = {}) =>
16
- WranglerDevServerService.Default({
11
+ const WranglerDevServerTest = (args: Partial<WranglerDevServer.Options> = {}) =>
12
+ WranglerDevServer.layer({
17
13
  cwd: `${import.meta.dirname}/fixtures`,
18
14
  ...args,
19
15
  }).pipe(Layer.provide(FetchHttpClient.layer))
20
16
 
21
17
  Vitest.describe('WranglerDevServer', { timeout: testTimeout }, () => {
22
18
  Vitest.describe('Basic Operations', () => {
23
- const withBasicTest = (args: Partial<StartWranglerDevServerArgs> = {}) =>
19
+ const withBasicTest = (args: Partial<WranglerDevServer.Options> = {}) =>
24
20
  Vitest.makeWithTestCtx({
25
21
  timeout: testTimeout,
26
- makeLayer: () => WranglerDevServerTest(args).pipe(Layer.provide(PlatformNode.NodeContext.layer)),
22
+ makeLayer: () => WranglerDevServerTest(args).pipe(Layer.provide(PlatformNode.NodeServices.layer)),
27
23
  })
28
24
 
29
- Vitest.scopedLive('should start wrangler dev server and return port', (test) =>
25
+ Vitest.live('should start wrangler dev server and return port', (test) =>
30
26
  Effect.gen(function* () {
31
- const server = yield* WranglerDevServerService
27
+ const server = yield* WranglerDevServer.WranglerDevServer
32
28
 
33
29
  expect(server.port).toBeGreaterThan(0)
34
30
  expect(server.url).toMatch(/http:\/\/127.0.0.1:\d+/)
35
31
  }).pipe(withBasicTest()(test)),
36
32
  )
37
33
 
38
- Vitest.scopedLive('should use specified port when provided', (test) =>
34
+ Vitest.live('should use specified port when provided', (test) =>
39
35
  Effect.andThen(getFreePort, (port) =>
40
36
  Effect.gen(function* () {
41
- const server = yield* WranglerDevServerService
37
+ const server = yield* WranglerDevServer.WranglerDevServer
42
38
 
43
39
  expect(server.port).toBe(port)
44
40
  expect(server.url).toBe(`http://127.0.0.1:${port}`)
@@ -48,83 +44,83 @@ Vitest.describe('WranglerDevServer', { timeout: testTimeout }, () => {
48
44
  })
49
45
 
50
46
  Vitest.describe('Error Handling', () => {
51
- const withErrorTest = (args: Partial<StartWranglerDevServerArgs> = {}) =>
47
+ const withErrorTest = (args: Partial<WranglerDevServer.Options> = {}) =>
52
48
  Vitest.makeWithTestCtx({
53
49
  timeout: testTimeout,
54
- makeLayer: () => WranglerDevServerTest(args).pipe(Layer.provide(PlatformNode.NodeContext.layer)),
50
+ makeLayer: () => WranglerDevServerTest(args).pipe(Layer.provide(PlatformNode.NodeServices.layer)),
55
51
  })
56
52
 
57
- Vitest.scopedLive('should handle missing wrangler.toml but should timeout', (test) =>
53
+ Vitest.live('should handle missing wrangler.toml but should timeout', (test) =>
58
54
  Effect.gen(function* () {
59
- const error = yield* WranglerDevServerService.pipe(
55
+ const error = yield* WranglerDevServer.WranglerDevServer.pipe(
60
56
  Effect.provide(
61
57
  WranglerDevServerTest({
62
58
  cwd: '/tmp',
63
59
  wranglerConfigPath: '/dev/null',
64
60
  readiness: { connectTimeout: '500 millis' },
65
- }).pipe(Layer.provide(PlatformNode.NodeContext.layer)),
61
+ }).pipe(Layer.provide(PlatformNode.NodeServices.layer)),
66
62
  ),
67
63
  Effect.flip,
68
64
  )
69
65
 
70
- expect(error).toBeInstanceOf(WranglerDevServerError)
66
+ expect(error).toBeInstanceOf(WranglerDevServer.WranglerDevServerError)
71
67
  }).pipe(Vitest.withTestCtx(test)),
72
68
  )
73
69
 
74
- Vitest.scopedLive('should handle invalid working directory', (test) =>
70
+ Vitest.live('should handle invalid working directory', (test) =>
75
71
  Effect.gen(function* () {
76
- const result = yield* WranglerDevServerService.pipe(
72
+ const result = yield* WranglerDevServer.WranglerDevServer.pipe(
77
73
  Effect.provide(
78
74
  WranglerDevServerTest({
79
75
  cwd: '/completely/nonexistent/directory',
80
- }).pipe(Layer.provide(PlatformNode.NodeContext.layer)),
76
+ }).pipe(Layer.provide(PlatformNode.NodeServices.layer)),
81
77
  ),
82
- Effect.either,
78
+ Effect.result,
83
79
  )
84
80
 
85
- expect(result._tag).toBe('Left')
86
- if (result._tag === 'Left') {
87
- expect(result.left).toBeInstanceOf(WranglerDevServerError)
81
+ expect(result._tag).toBe('Failure')
82
+ if (result._tag === 'Failure') {
83
+ expect(result.failure).toBeInstanceOf(WranglerDevServer.WranglerDevServerError)
88
84
  }
89
85
  }).pipe(Vitest.withTestCtx(test)),
90
86
  )
91
87
 
92
- Vitest.scopedLive('should timeout if server fails to start', (test) =>
88
+ Vitest.live('should timeout if server fails to start', (test) =>
93
89
  Effect.gen(function* () {
94
90
  // Create a command that will never output "Ready on"
95
- const result = yield* WranglerDevServerService.pipe(
91
+ const result = yield* WranglerDevServer.WranglerDevServer.pipe(
96
92
  // Override the timeout for this test to be shorter
97
93
  Effect.timeout('5 seconds'),
98
- Effect.either,
94
+ Effect.result,
99
95
  )
100
96
 
101
97
  // This might succeed or fail depending on actual wrangler behavior
102
98
  // The main point is testing timeout functionality
103
- expect(['Left', 'Right']).toContain(result._tag)
99
+ expect(['Failure', 'Success']).toContain(result._tag)
104
100
  }).pipe(withErrorTest()(test)),
105
101
  )
106
102
  })
107
103
 
108
104
  Vitest.describe('Service Pattern', () => {
109
- const withServiceTest = (args: Partial<StartWranglerDevServerArgs> = {}) =>
105
+ const withServiceTest = (args: Partial<WranglerDevServer.Options> = {}) =>
110
106
  Vitest.makeWithTestCtx({
111
107
  timeout: testTimeout,
112
- makeLayer: () => WranglerDevServerTest(args).pipe(Layer.provide(PlatformNode.NodeContext.layer)),
108
+ makeLayer: () => WranglerDevServerTest(args).pipe(Layer.provide(PlatformNode.NodeServices.layer)),
113
109
  })
114
110
 
115
- Vitest.scopedLive('should work with service pattern', (test) =>
111
+ Vitest.live('should work with service pattern', (test) =>
116
112
  Effect.gen(function* () {
117
- const server = yield* WranglerDevServerService
113
+ const server = yield* WranglerDevServer.WranglerDevServer
118
114
 
119
115
  expect(server.port).toBeGreaterThan(0)
120
116
  expect(server.url).toMatch(/http:\/\/127.0.0.1:\d+/)
121
117
  }).pipe(withServiceTest()(test)),
122
118
  )
123
119
 
124
- Vitest.scopedLive('should work with custom port via service', (test) =>
120
+ Vitest.live('should work with custom port via service', (test) =>
125
121
  Effect.andThen(getFreePort, (port) =>
126
122
  Effect.gen(function* () {
127
- const server = yield* WranglerDevServerService
123
+ const server = yield* WranglerDevServer.WranglerDevServer
128
124
 
129
125
  expect(server.port).toBe(port)
130
126
  expect(server.url).toBe(`http://127.0.0.1:${port}`)
@@ -1,24 +1,37 @@
1
1
  import * as path from 'node:path'
2
2
 
3
3
  import * as Toml from '@iarna/toml'
4
+ import * as wrangler from 'wrangler'
5
+
4
6
  import { IS_CI } from '@livestore/utils'
5
- import { Cause, Duration, Effect, FileSystem, HttpClient, Schedule, Schema } from '@livestore/utils/effect'
7
+ import {
8
+ Cause,
9
+ Context,
10
+ Duration,
11
+ Effect,
12
+ FileSystem,
13
+ HttpClient,
14
+ Layer,
15
+ Schedule,
16
+ Schema,
17
+ } from '@livestore/utils/effect'
6
18
  import { getFreePort } from '@livestore/utils/node'
7
- import * as wrangler from 'wrangler'
8
19
 
9
20
  /**
10
21
  * Error type for WranglerDevServer operations
11
22
  */
12
- export class WranglerDevServerError extends Schema.TaggedError<WranglerDevServerError>('~@livestore/utils-dev/WranglerDevServerError')('WranglerDevServerError', {
13
- cause: Schema.Unknown,
23
+ export class WranglerDevServerError extends Schema.TaggedError<WranglerDevServerError>(
24
+ '~@livestore/utils-dev/WranglerDevServerError',
25
+ )('WranglerDevServerError', {
26
+ cause: Schema.Defect(),
14
27
  message: Schema.String,
15
- port: Schema.Number,
28
+ port: Schema.Finite,
16
29
  }) {}
17
30
 
18
31
  /**
19
32
  * WranglerDevServer instance interface
20
33
  */
21
- export interface WranglerDevServer {
34
+ export interface Service {
22
35
  readonly port: number
23
36
  readonly url: string
24
37
  // readonly processId: number
@@ -34,14 +47,14 @@ export interface WranglerDevServer {
34
47
  */
35
48
  export interface WranglerReadinessOptions {
36
49
  /** Max time to wait for wrangler to report ready before retrying. */
37
- startupTimeout?: Duration.DurationInput
50
+ startupTimeout?: Duration.Input
38
51
  /** Max time for the HTTP connectivity check after wrangler reports ready. */
39
- connectTimeout?: Duration.DurationInput
52
+ connectTimeout?: Duration.Input
40
53
  /** Retry policy for startup attempts (applies when startupTimeout elapses or wrangler throws). */
41
54
  retrySchedule?: Schedule.Schedule<unknown>
42
55
  }
43
56
 
44
- export interface StartWranglerDevServerArgs {
57
+ export interface Options {
45
58
  /** Path to wrangler.toml (defaults to cwd/wrangler.toml). */
46
59
  wranglerConfigPath?: string
47
60
  /** Working directory wrangler should use. */
@@ -57,7 +70,7 @@ export interface StartWranglerDevServerArgs {
57
70
  }
58
71
 
59
72
  /**
60
- * WranglerDevServer as an Effect.Service.
73
+ * WranglerDevServer as an Effect service.
61
74
  *
62
75
  * This service provides the WranglerDevServer properties and can be accessed
63
76
  * directly to get port and url.
@@ -65,118 +78,127 @@ export interface StartWranglerDevServerArgs {
65
78
  * TODO: Allow for config to be passed in via code instead of `wrangler.toml` file
66
79
  * (would need to be placed in temporary file as wrangler only accepts files as config)
67
80
  */
68
- export class WranglerDevServerService extends Effect.Service<WranglerDevServerService>()('WranglerDevServerService', {
69
- scoped: (args: StartWranglerDevServerArgs) =>
70
- Effect.gen(function* () {
71
- const showLogs = args.showLogs ?? false
72
-
73
- // Allocate preferred port (Wrangler may bind a different one if unavailable)
74
- const preferredPort =
75
- args.preferredPort ??
76
- (yield* getFreePort.pipe(
77
- Effect.mapError(
78
- (cause) => new WranglerDevServerError({ cause, message: 'Failed to get free port', port: -1 }),
79
- ),
80
- ))
81
-
82
- yield* Effect.annotateCurrentSpan({ preferredPort })
83
-
84
- const configPath = path.resolve(args.wranglerConfigPath ?? path.join(args.cwd, 'wrangler.toml'))
85
-
86
- const fs = yield* FileSystem.FileSystem
87
- const configContent = yield* fs.readFileString(configPath)
88
- const parsedConfig = yield* Effect.try(() => Toml.parse(configContent)).pipe(
89
- Effect.andThen(Schema.decodeUnknown(Schema.Struct({ main: Schema.String }))),
90
- Effect.mapError(
91
- (error) => new WranglerDevServerError({ cause: error, message: 'Failed to parse wrangler config', port: -1 }),
92
- ),
93
- )
94
- const resolvedMainPath = yield* Effect.try(() => path.resolve(args.cwd, parsedConfig.main))
95
-
96
- const readiness = args.readiness ?? {}
97
- const startupTimeout = readiness.startupTimeout ?? Duration.seconds(IS_CI === true ? 30 : 10)
98
- const devServer = yield* Effect.promise(() =>
99
- wrangler.unstable_dev(resolvedMainPath, {
100
- config: configPath,
101
- port: preferredPort,
102
- inspectorPort: args.inspectorPort ?? 0,
103
- persistTo: path.join(args.cwd, '.wrangler/state'),
104
- logLevel: showLogs === true ? 'debug' : 'none',
105
- experimental: {
106
- disableExperimentalWarning: true,
107
- },
108
- }),
109
- ).pipe(
110
- Effect.timeout(startupTimeout),
111
- Effect.mapError(
112
- (cause) =>
113
- new WranglerDevServerError({
114
- cause,
115
- message: `Failed to start wrangler dev server within ${Duration.format(startupTimeout)}`,
116
- port: preferredPort,
117
- }),
118
- ),
119
- Effect.tapError((error) =>
120
- Effect.logError('Wrangler dev server failed to start', {
121
- message: error.message,
122
- preferredPort,
123
- cwd: args.cwd,
124
- }),
125
- ),
126
- Effect.retry(readiness.retrySchedule ?? Schedule.recurs(1)),
127
- )
81
+ export class WranglerDevServer extends Context.Service<WranglerDevServer, Service>()(
82
+ '@livestore/utils-dev/WranglerDevServer',
83
+ ) {}
128
84
 
129
- yield* Effect.addFinalizer(
130
- Effect.fn(
131
- function* (exit) {
132
- if (exit._tag === 'Failure' && Cause.isInterruptedOnly(exit.cause) === false) {
133
- yield* Effect.logError('Closing wrangler dev server on failure', exit.cause)
134
- }
135
-
136
- yield* Effect.tryPromise(async () => {
137
- await devServer.stop()
138
- // TODO investigate whether we need to wait until exit (see workers-sdk repo/talk to Cloudflare team)
139
- // await devServer.waitUntilExit()
140
- })
141
- },
142
- Effect.timeout('5 seconds'),
143
- Effect.orDie,
144
- Effect.tapCauseLogPretty,
145
- Effect.withSpan('WranglerDevServerService:stopDevServer'),
146
- ),
147
- )
148
-
149
- const actualPort = devServer.port
150
- const actualHost = devServer.address
151
- const url = `http://${actualHost}:${actualPort}`
85
+ export const make = (args: Options) =>
86
+ Effect.gen(function* () {
87
+ const showLogs = args.showLogs ?? false
152
88
 
153
- // Use longer timeout in CI environments to account for slower HTTP readiness
154
- const defaultConnectivityTimeout = Duration.seconds(IS_CI === true ? 30 : 5)
155
- const connectivityTimeout = readiness.connectTimeout ?? defaultConnectivityTimeout
89
+ // Allocate preferred port (Wrangler may bind a different one if unavailable)
90
+ const preferredPort =
91
+ args.preferredPort ??
92
+ (yield* getFreePort.pipe(
93
+ Effect.mapError((cause) => new WranglerDevServerError({ cause, message: 'Failed to get free port', port: -1 })),
94
+ ))
156
95
 
157
- yield* verifyHttpConnectivity({ url, showLogs, connectTimeout: connectivityTimeout })
96
+ yield* Effect.annotateCurrentSpan({ preferredPort })
158
97
 
159
- if (showLogs === true) {
160
- yield* Effect.logDebug(
161
- `Wrangler dev server ready and accepting connections on port ${actualPort} (preferred: ${preferredPort})`,
162
- )
163
- }
98
+ const configPath = path.resolve(args.wranglerConfigPath ?? path.join(args.cwd, 'wrangler.toml'))
164
99
 
165
- return {
166
- port: actualPort,
167
- url,
168
- } satisfies WranglerDevServer
100
+ const fs = yield* FileSystem.FileSystem
101
+ const configContent = yield* fs.readFileString(configPath)
102
+ const parsedConfig = yield* Effect.try({
103
+ try: () => Toml.parse(configContent),
104
+ catch: (cause) => new Cause.UnknownError(cause),
169
105
  }).pipe(
170
- Effect.mapError((error) =>
171
- error instanceof WranglerDevServerError
172
- ? error
173
- : new WranglerDevServerError({ cause: error, message: 'Failed to start wrangler dev server', port: -1 }),
106
+ Effect.andThen(Schema.decodeUnknownEffect(Schema.Struct({ main: Schema.String }))),
107
+ Effect.mapError(
108
+ (error) => new WranglerDevServerError({ cause: error, message: 'Failed to parse wrangler config', port: -1 }),
174
109
  ),
175
- Effect.withSpan('WranglerDevServerService', {
176
- attributes: { preferredPort: args.preferredPort ?? 'auto', cwd: args.cwd },
110
+ )
111
+ const resolvedMainPath = yield* Effect.try({
112
+ try: () => path.resolve(args.cwd, parsedConfig.main),
113
+ catch: (cause) => new Cause.UnknownError(cause),
114
+ })
115
+
116
+ const readiness = args.readiness ?? {}
117
+ const startupTimeout = readiness.startupTimeout ?? Duration.seconds(IS_CI === true ? 30 : 10)
118
+ const startupTimeoutDuration = Duration.fromInputUnsafe(startupTimeout)
119
+ const devServer = yield* Effect.promise(() =>
120
+ wrangler.unstable_dev(resolvedMainPath, {
121
+ config: configPath,
122
+ port: preferredPort,
123
+ inspectorPort: args.inspectorPort ?? 0,
124
+ persistTo: path.join(args.cwd, '.wrangler/state'),
125
+ logLevel: showLogs === true ? 'debug' : 'none',
126
+ experimental: {
127
+ disableExperimentalWarning: true,
128
+ },
177
129
  }),
130
+ ).pipe(
131
+ Effect.timeout(startupTimeout),
132
+ Effect.mapError(
133
+ (cause) =>
134
+ new WranglerDevServerError({
135
+ cause,
136
+ message: `Failed to start wrangler dev server within ${Duration.format(startupTimeoutDuration)}`,
137
+ port: preferredPort,
138
+ }),
139
+ ),
140
+ Effect.tapError((error) =>
141
+ Effect.logError('Wrangler dev server failed to start', {
142
+ message: error.message,
143
+ preferredPort,
144
+ cwd: args.cwd,
145
+ }),
146
+ ),
147
+ Effect.retry(readiness.retrySchedule ?? Schedule.recurs(1)),
148
+ )
149
+
150
+ yield* Effect.addFinalizer(
151
+ Effect.fn(
152
+ function* (exit) {
153
+ if (exit._tag === 'Failure' && Cause.hasInterruptsOnly(exit.cause) === false) {
154
+ yield* Effect.logError('Closing wrangler dev server on failure', exit.cause)
155
+ }
156
+
157
+ yield* Effect.tryPromise(async () => {
158
+ await devServer.stop()
159
+ // TODO investigate whether we need to wait until exit (see workers-sdk repo/talk to Cloudflare team)
160
+ // await devServer.waitUntilExit()
161
+ })
162
+ },
163
+ Effect.timeout('5 seconds'),
164
+ Effect.orDie,
165
+ Effect.tapCauseLogPretty,
166
+ Effect.withSpan('WranglerDevServer:stopDevServer'),
167
+ ),
168
+ )
169
+
170
+ const actualPort = devServer.port
171
+ const actualHost = devServer.address
172
+ const url = `http://${actualHost}:${actualPort}`
173
+
174
+ // Use longer timeout in CI environments to account for slower HTTP readiness
175
+ const defaultConnectivityTimeout = Duration.seconds(IS_CI === true ? 30 : 5)
176
+ const connectivityTimeout = readiness.connectTimeout ?? defaultConnectivityTimeout
177
+
178
+ yield* verifyHttpConnectivity({ url, showLogs, connectTimeout: connectivityTimeout })
179
+
180
+ if (showLogs === true) {
181
+ yield* Effect.logDebug(
182
+ `Wrangler dev server ready and accepting connections on port ${actualPort} (preferred: ${preferredPort})`,
183
+ )
184
+ }
185
+
186
+ return WranglerDevServer.of({
187
+ port: actualPort,
188
+ url,
189
+ })
190
+ }).pipe(
191
+ Effect.mapError((error) =>
192
+ error instanceof WranglerDevServerError
193
+ ? error
194
+ : new WranglerDevServerError({ cause: error, message: 'Failed to start wrangler dev server', port: -1 }),
178
195
  ),
179
- }) {}
196
+ Effect.withSpan('WranglerDevServer', {
197
+ attributes: { preferredPort: args.preferredPort ?? 'auto', cwd: args.cwd },
198
+ }),
199
+ )
200
+
201
+ export const layer = (options: Options) => Layer.effect(WranglerDevServer, make(options))
180
202
 
181
203
  /**
182
204
  * Verifies the server is actually accepting HTTP connections by making a test request
@@ -188,7 +210,7 @@ const verifyHttpConnectivity = ({
188
210
  }: {
189
211
  url: string
190
212
  showLogs: boolean
191
- connectTimeout: Duration.DurationInput
213
+ connectTimeout: Duration.Input
192
214
  }): Effect.Effect<void, WranglerDevServerError, HttpClient.HttpClient> =>
193
215
  Effect.gen(function* () {
194
216
  const client = yield* HttpClient.HttpClient
@@ -197,22 +219,16 @@ const verifyHttpConnectivity = ({
197
219
  yield* Effect.logDebug(`Verifying HTTP connectivity to ${url}`)
198
220
  }
199
221
 
200
- // Try to connect with retries using exponential backoff
201
222
  yield* client.get(url).pipe(
202
- Effect.retryOrElse(
203
- Schedule.exponential('50 millis', 2).pipe(
204
- Schedule.jittered,
205
- Schedule.intersect(Schedule.elapsed.pipe(Schedule.whileOutput(Duration.lessThanOrEqualTo(connectTimeout)))),
206
- Schedule.compose(Schedule.count),
207
- ),
208
- (error, attemptCount) =>
209
- Effect.fail(
210
- new WranglerDevServerError({
211
- cause: error,
212
- message: `Failed to establish HTTP connection to Wrangler server at ${url} after ${attemptCount} attempts (timeout: ${Duration.toMillis(connectTimeout)}ms)`,
213
- port: 0,
214
- }),
215
- ),
223
+ Effect.retry(Schedule.exponential('50 millis', 2).pipe(Schedule.jittered)),
224
+ Effect.timeout(connectTimeout),
225
+ Effect.mapError(
226
+ (error) =>
227
+ new WranglerDevServerError({
228
+ cause: error,
229
+ message: `Failed to establish HTTP connection to Wrangler server at ${url} (timeout: ${Duration.toMillis(Duration.fromInputUnsafe(connectTimeout))}ms)`,
230
+ port: 0,
231
+ }),
216
232
  ),
217
233
  Effect.tap(() => (showLogs === true ? Effect.logDebug(`HTTP connectivity verified for ${url}`) : Effect.void)),
218
234
  Effect.asVoid,
@@ -1,6 +1 @@
1
- export {
2
- type StartWranglerDevServerArgs,
3
- type WranglerDevServer,
4
- WranglerDevServerError,
5
- WranglerDevServerService,
6
- } from './WranglerDevServer.ts'
1
+ export * as WranglerDevServer from './WranglerDevServer.ts'
@@ -1,63 +0,0 @@
1
- import { type CommandExecutor, Duration, Effect, type PlatformError, Schema, type Scope, Stream } from '@livestore/utils/effect';
2
- declare const DockerComposeError_base: Schema.TaggedErrorClass<DockerComposeError, "DockerComposeError", {
3
- readonly _tag: Schema.tag<"DockerComposeError">;
4
- } & {
5
- cause: typeof Schema.Defect;
6
- note: typeof Schema.String;
7
- }>;
8
- export declare class DockerComposeError extends DockerComposeError_base {
9
- }
10
- export interface DockerComposeArgs {
11
- readonly cwd: string;
12
- readonly serviceName?: string;
13
- /** Unique project name to isolate this compose instance. If not provided, a random one is generated. */
14
- readonly projectName?: string;
15
- }
16
- export interface StartOptions {
17
- readonly detached?: boolean;
18
- readonly env?: Record<string, string>;
19
- readonly healthCheck?: {
20
- readonly url: string;
21
- readonly timeout?: Duration.Duration;
22
- readonly interval?: Duration.Duration;
23
- };
24
- }
25
- export interface LogsOptions {
26
- readonly follow?: boolean;
27
- readonly tail?: number;
28
- readonly since?: string;
29
- }
30
- export interface DockerComposeOperations {
31
- readonly pull: Effect.Effect<void, DockerComposeError | PlatformError.PlatformError>;
32
- readonly start: (options?: StartOptions) => Effect.Effect<void, DockerComposeError | PlatformError.PlatformError, Scope.Scope>;
33
- readonly stop: Effect.Effect<void, DockerComposeError | PlatformError.PlatformError>;
34
- readonly down: (options?: {
35
- readonly env?: Record<string, string>;
36
- readonly volumes?: boolean;
37
- readonly removeOrphans?: boolean;
38
- }) => Effect.Effect<void, DockerComposeError | PlatformError.PlatformError>;
39
- readonly logs: (options?: LogsOptions) => Stream.Stream<string, DockerComposeError | PlatformError.PlatformError, Scope.Scope>;
40
- /** The unique project name used to isolate this compose instance */
41
- readonly projectName: string;
42
- }
43
- declare const DockerComposeService_base: Effect.Service.Class<DockerComposeService, "DockerComposeService", {
44
- readonly scoped: (args: DockerComposeArgs) => Effect.Effect<{
45
- pull: Effect.Effect<undefined, PlatformError.PlatformError | DockerComposeError, never>;
46
- start: (options?: StartOptions | undefined) => Effect.Effect<void, PlatformError.PlatformError | DockerComposeError, Scope.Scope>;
47
- stop: Effect.Effect<void, PlatformError.PlatformError | DockerComposeError, never>;
48
- down: (options?: {
49
- readonly env?: Record<string, string>;
50
- readonly volumes?: boolean;
51
- readonly removeOrphans?: boolean;
52
- } | undefined) => Effect.Effect<void, PlatformError.PlatformError | DockerComposeError, never>;
53
- logs: (options?: LogsOptions) => Stream.Stream<string, DockerComposeError, never>;
54
- projectName: string;
55
- }, never, Scope.Scope | CommandExecutor.CommandExecutor>;
56
- }>;
57
- export declare class DockerComposeService extends DockerComposeService_base {
58
- }
59
- export declare const startDockerComposeServicesScoped: (args: DockerComposeArgs & {
60
- healthCheck?: StartOptions["healthCheck"];
61
- }) => Effect.Effect<void, DockerComposeError | PlatformError.PlatformError, DockerComposeService | CommandExecutor.CommandExecutor | Scope.Scope>;
62
- export {};
63
- //# sourceMappingURL=DockerComposeService.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"DockerComposeService.d.ts","sourceRoot":"","sources":["../../../src/node/DockerComposeService/DockerComposeService.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,eAAe,EACpB,QAAQ,EACR,MAAM,EAEN,KAAK,aAAa,EAElB,MAAM,EACN,KAAK,KAAK,EACV,MAAM,EACP,MAAM,yBAAyB,CAAA;;;;;;;AAEhC,qBAAa,kBAAmB,SAAQ,uBAGtC;CAAG;AAEL,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,wGAAwG;IACxG,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAC9B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAC3B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACrC,QAAQ,CAAC,WAAW,CAAC,EAAE;QACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;QACpB,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAA;QACpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAA;KACtC,CAAA;CACF;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,kBAAkB,GAAG,aAAa,CAAC,aAAa,CAAC,CAAA;IACpF,QAAQ,CAAC,KAAK,EAAE,CACd,OAAO,CAAC,EAAE,YAAY,KACnB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,kBAAkB,GAAG,aAAa,CAAC,aAAa,EAAE,KAAK,CAAC,KAAK,CAAC,CAAA;IACvF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,kBAAkB,GAAG,aAAa,CAAC,aAAa,CAAC,CAAA;IACpF,QAAQ,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,EAAE;QACxB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QACrC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;QAC1B,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAA;KACjC,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,kBAAkB,GAAG,aAAa,CAAC,aAAa,CAAC,CAAA;IAC3E,QAAQ,CAAC,IAAI,EAAE,CACb,OAAO,CAAC,EAAE,WAAW,KAClB,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,kBAAkB,GAAG,aAAa,CAAC,aAAa,EAAE,KAAK,CAAC,KAAK,CAAC,CAAA;IACzF,oEAAoE;IACpE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAC7B;;4BAKgB,iBAAiB;;;;;2BAiLX,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;+BAClB,OAAO;qCACD,OAAO;;yBAvCX,WAAW;;;;AA7IxC,qBAAa,oBAAqB,SAAQ,yBA6NxC;CAAG;AAwCL,eAAO,MAAM,gCAAgC,GAC3C,MAAM,iBAAiB,GAAG;IACxB,WAAW,CAAC,EAAE,YAAY,CAAC,aAAa,CAAC,CAAA;CAC1C,KACA,MAAM,CAAC,MAAM,CACd,IAAI,EACJ,kBAAkB,GAAG,aAAa,CAAC,aAAa,EAChD,oBAAoB,GAAG,eAAe,CAAC,eAAe,GAAG,KAAK,CAAC,KAAK,CAYlE,CAAA"}