@livestore/utils-dev 0.0.0-snapshot-d677008bdbdee9280bb55474e2e095d3d09a6d60 → 0.0.0-snapshot-61ffc7dbe7378bd127038444529800e35e2ebb4b

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 (47) hide show
  1. package/dist/.tsbuildinfo.json +1 -1
  2. package/dist/node/DockerComposeService/DockerComposeService.d.ts +1 -1
  3. package/dist/node/DockerComposeService/DockerComposeService.js +8 -8
  4. package/dist/node/DockerComposeService/DockerComposeService.js.map +1 -1
  5. package/dist/node/mod.d.ts +0 -2
  6. package/dist/node/mod.d.ts.map +1 -1
  7. package/dist/node/mod.js +0 -2
  8. package/dist/node/mod.js.map +1 -1
  9. package/dist/{node/WranglerDevServer → wrangler}/WranglerDevServer.d.ts +3 -5
  10. package/dist/wrangler/WranglerDevServer.d.ts.map +1 -0
  11. package/dist/wrangler/WranglerDevServer.js +90 -0
  12. package/dist/wrangler/WranglerDevServer.js.map +1 -0
  13. package/dist/wrangler/WranglerDevServer.test.d.ts.map +1 -0
  14. package/dist/wrangler/WranglerDevServer.test.js +77 -0
  15. package/dist/wrangler/WranglerDevServer.test.js.map +1 -0
  16. package/dist/wrangler/fixtures/cf-worker.d.ts.map +1 -0
  17. package/dist/wrangler/fixtures/cf-worker.js.map +1 -0
  18. package/dist/wrangler/mod.d.ts +2 -0
  19. package/dist/wrangler/mod.d.ts.map +1 -0
  20. package/dist/wrangler/mod.js +2 -0
  21. package/dist/wrangler/mod.js.map +1 -0
  22. package/package.json +6 -3
  23. package/src/node/DockerComposeService/DockerComposeService.ts +8 -8
  24. package/src/node/mod.ts +0 -7
  25. package/src/wrangler/WranglerDevServer.test.ts +133 -0
  26. package/src/wrangler/WranglerDevServer.ts +180 -0
  27. package/src/wrangler/mod.ts +6 -0
  28. package/dist/node/WranglerDevServer/WranglerDevServer.d.ts.map +0 -1
  29. package/dist/node/WranglerDevServer/WranglerDevServer.js +0 -150
  30. package/dist/node/WranglerDevServer/WranglerDevServer.js.map +0 -1
  31. package/dist/node/WranglerDevServer/WranglerDevServer.test.d.ts.map +0 -1
  32. package/dist/node/WranglerDevServer/WranglerDevServer.test.js +0 -179
  33. package/dist/node/WranglerDevServer/WranglerDevServer.test.js.map +0 -1
  34. package/dist/node/WranglerDevServer/fixtures/cf-worker.d.ts.map +0 -1
  35. package/dist/node/WranglerDevServer/fixtures/cf-worker.js.map +0 -1
  36. package/dist/node/WranglerDevServer/process-tree-manager.d.ts +0 -55
  37. package/dist/node/WranglerDevServer/process-tree-manager.d.ts.map +0 -1
  38. package/dist/node/WranglerDevServer/process-tree-manager.js +0 -178
  39. package/dist/node/WranglerDevServer/process-tree-manager.js.map +0 -1
  40. package/src/node/WranglerDevServer/WranglerDevServer.test.ts +0 -270
  41. package/src/node/WranglerDevServer/WranglerDevServer.ts +0 -302
  42. package/src/node/WranglerDevServer/process-tree-manager.ts +0 -263
  43. /package/dist/{node/WranglerDevServer → wrangler}/WranglerDevServer.test.d.ts +0 -0
  44. /package/dist/{node/WranglerDevServer → wrangler}/fixtures/cf-worker.d.ts +0 -0
  45. /package/dist/{node/WranglerDevServer → wrangler}/fixtures/cf-worker.js +0 -0
  46. /package/src/{node/WranglerDevServer → wrangler}/fixtures/cf-worker.ts +0 -0
  47. /package/src/{node/WranglerDevServer → wrangler}/fixtures/wrangler.toml +0 -0
@@ -1,270 +0,0 @@
1
- import { Effect, Exit, FetchHttpClient, Fiber, Layer, Scope } from '@livestore/utils/effect'
2
- import { getFreePort, PlatformNode } from '@livestore/utils/node'
3
- import { Vitest } from '@livestore/utils-dev/node-vitest'
4
- import { expect } from 'vitest'
5
- import {
6
- type StartWranglerDevServerArgs,
7
- WranglerDevServerError,
8
- WranglerDevServerService,
9
- } from './WranglerDevServer.ts'
10
-
11
- const testTimeout = 60_000
12
-
13
- const withTestCtx = Vitest.makeWithTestCtx({
14
- timeout: testTimeout,
15
- makeLayer: () => PlatformNode.NodeContext.layer,
16
- })
17
-
18
- const WranglerDevServerTest = (args: Partial<StartWranglerDevServerArgs> = {}) =>
19
- WranglerDevServerService.Default({
20
- cwd: `${import.meta.dirname}/fixtures`,
21
- ...args,
22
- }).pipe(Layer.provide(FetchHttpClient.layer))
23
-
24
- Vitest.describe('WranglerDevServer', { timeout: testTimeout }, () => {
25
- Vitest.describe('Basic Operations', () => {
26
- const withBasicTest = (args: Partial<StartWranglerDevServerArgs> = {}) =>
27
- Vitest.makeWithTestCtx({
28
- timeout: testTimeout,
29
- makeLayer: () => WranglerDevServerTest(args).pipe(Layer.provide(PlatformNode.NodeContext.layer)),
30
- })
31
-
32
- Vitest.scopedLive('should start wrangler dev server and return port', (test) =>
33
- Effect.gen(function* () {
34
- const server = yield* WranglerDevServerService
35
-
36
- expect(server.port).toBeGreaterThan(0)
37
- expect(server.url).toMatch(/http:\/\/localhost:\d+/)
38
- expect(typeof server.processId).toBe('number')
39
- expect(server.processId).toBeGreaterThan(0)
40
- }).pipe(withBasicTest()(test)),
41
- )
42
-
43
- Vitest.scopedLive('should use specified port when provided', (test) =>
44
- Effect.andThen(getFreePort, (port) =>
45
- Effect.gen(function* () {
46
- const server = yield* WranglerDevServerService
47
-
48
- expect(server.port).toBe(port)
49
- expect(server.url).toBe(`http://localhost:${port}`)
50
- }).pipe(withBasicTest({ preferredPort: port })(test)),
51
- ),
52
- )
53
- })
54
-
55
- Vitest.describe('Resource Management', () => {
56
- Vitest.scopedLive('should cleanup processes on scope close', (test) =>
57
- Effect.gen(function* () {
58
- let processId: number | undefined
59
-
60
- // Create a separate scope for the server
61
- const serverScope = yield* Scope.make()
62
-
63
- const server = yield* Effect.provide(
64
- WranglerDevServerService,
65
- WranglerDevServerTest().pipe(Layer.provide(PlatformNode.NodeContext.layer)),
66
- ).pipe(Scope.extend(serverScope))
67
-
68
- processId = server.processId
69
- expect(processId).toBeGreaterThan(0)
70
- expect(server.port).toBeGreaterThan(0)
71
- expect(server.url).toMatch(/http:\/\/localhost:\d+/)
72
-
73
- // Close scope to trigger cleanup
74
- yield* Scope.close(serverScope, Exit.succeed(void 0))
75
-
76
- // Wait for cleanup to complete
77
- yield* Effect.sleep('2 seconds')
78
-
79
- // Verify process is terminated
80
- const isRunning2 = yield* Effect.promise(() => {
81
- try {
82
- process.kill(processId!, 0)
83
- return Promise.resolve(true)
84
- } catch {
85
- return Promise.resolve(false)
86
- }
87
- })
88
- expect(isRunning2).toBe(false)
89
- }).pipe(withTestCtx(test)),
90
- )
91
-
92
- Vitest.scopedLive('should handle interruption with fast cleanup', (test) =>
93
- Effect.gen(function* () {
94
- let processId: number | undefined
95
-
96
- const fiber = yield* Effect.fork(
97
- Effect.provide(
98
- Effect.gen(function* () {
99
- const server = yield* WranglerDevServerService
100
- processId = server.processId
101
- yield* Effect.sleep('30 seconds') // Keep running
102
- return server
103
- }),
104
- WranglerDevServerTest().pipe(Layer.provide(PlatformNode.NodeContext.layer)),
105
- ),
106
- )
107
-
108
- // Wait for server to start
109
- yield* Effect.sleep('3 seconds')
110
-
111
- expect(processId).toBeGreaterThan(0)
112
-
113
- // Interrupt and measure cleanup time
114
- const start = Date.now()
115
- yield* Fiber.interrupt(fiber)
116
- const elapsed = Date.now() - start
117
-
118
- // Should use fast cleanup (500ms timeout) + some overhead
119
- expect(elapsed).toBeLessThan(1500) // Allow some overhead
120
-
121
- // Wait for cleanup to complete
122
- yield* Effect.sleep('1 second')
123
-
124
- // Verify process is terminated
125
- const isRunningAfter = yield* Effect.promise(() => {
126
- try {
127
- process.kill(processId!, 0)
128
- return Promise.resolve(true)
129
- } catch {
130
- return Promise.resolve(false)
131
- }
132
- })
133
- expect(isRunningAfter).toBe(false)
134
- }).pipe(withTestCtx(test)),
135
- )
136
- })
137
-
138
- Vitest.describe('Error Handling', () => {
139
- const withErrorTest = (args: Partial<StartWranglerDevServerArgs> = {}) =>
140
- Vitest.makeWithTestCtx({
141
- timeout: testTimeout,
142
- makeLayer: () => WranglerDevServerTest(args).pipe(Layer.provide(PlatformNode.NodeContext.layer)),
143
- })
144
-
145
- Vitest.scopedLive('should handle missing wrangler.toml but should timeout', (test) =>
146
- Effect.gen(function* () {
147
- const error = yield* WranglerDevServerService.pipe(
148
- Effect.provide(
149
- WranglerDevServerTest({
150
- cwd: '/tmp',
151
- wranglerConfigPath: '/dev/null',
152
- connectTimeout: '500 millis',
153
- }).pipe(Layer.provide(PlatformNode.NodeContext.layer)),
154
- ),
155
- Effect.flip,
156
- )
157
-
158
- expect(error).toBeInstanceOf(WranglerDevServerError)
159
- }).pipe(Vitest.withTestCtx(test)),
160
- )
161
-
162
- Vitest.scopedLive('should handle invalid working directory', (test) =>
163
- Effect.gen(function* () {
164
- const result = yield* WranglerDevServerService.pipe(
165
- Effect.provide(
166
- WranglerDevServerTest({
167
- cwd: '/completely/nonexistent/directory',
168
- }).pipe(Layer.provide(PlatformNode.NodeContext.layer)),
169
- ),
170
- Effect.either,
171
- )
172
-
173
- expect(result._tag).toBe('Left')
174
- if (result._tag === 'Left') {
175
- expect(result.left).toBeInstanceOf(WranglerDevServerError)
176
- }
177
- }).pipe(Vitest.withTestCtx(test)),
178
- )
179
-
180
- Vitest.scopedLive('should timeout if server fails to start', (test) =>
181
- Effect.gen(function* () {
182
- // Create a command that will never output "Ready on"
183
- const result = yield* WranglerDevServerService.pipe(
184
- // Override the timeout for this test to be shorter
185
- Effect.timeout('5 seconds'),
186
- Effect.either,
187
- )
188
-
189
- // This might succeed or fail depending on actual wrangler behavior
190
- // The main point is testing timeout functionality
191
- expect(['Left', 'Right']).toContain(result._tag)
192
- }).pipe(withErrorTest()(test)),
193
- )
194
- })
195
-
196
- Vitest.describe('Process Tree Cleanup', () => {
197
- const withCleanupTest = (args: Partial<StartWranglerDevServerArgs> = {}) =>
198
- Vitest.makeWithTestCtx({
199
- timeout: testTimeout,
200
- makeLayer: () => WranglerDevServerTest(args).pipe(Layer.provide(PlatformNode.NodeContext.layer)),
201
- })
202
-
203
- Vitest.scopedLive('should clean up child workerd processes', (test) =>
204
- Effect.gen(function* () {
205
- let processId: number | undefined
206
-
207
- const server = yield* WranglerDevServerService
208
- processId = server.processId
209
-
210
- // Wait for wrangler to spawn workerd children
211
- yield* Effect.sleep('3 seconds')
212
-
213
- // Find any child processes (workerd)
214
- const children = yield* Effect.promise(async () => {
215
- const { exec } = require('node:child_process')
216
- const { promisify } = require('node:util')
217
- const execAsync = promisify(exec)
218
-
219
- try {
220
- if (!processId) throw new Error('processId is undefined')
221
- const { stdout } = await execAsync(`ps -o pid,ppid -ax | grep -E "^\\s*[0-9]+\\s+${processId}\\s*$"`)
222
- return stdout
223
- .trim()
224
- .split('\n')
225
- .map((line: string) => {
226
- const match = line.trim().match(/^\s*(\d+)\s+\d+\s*$/)
227
- return match?.[1] ? Number.parseInt(match[1], 10) : null
228
- })
229
- .filter((pid: number | null): pid is number => pid !== null)
230
- } catch {
231
- return []
232
- }
233
- })
234
-
235
- console.log(`Found ${children.length} child processes:`, children)
236
-
237
- // The scope will close here and should clean up all processes
238
- }).pipe(withCleanupTest()(test)),
239
- )
240
- })
241
-
242
- Vitest.describe('Service Pattern', () => {
243
- const withServiceTest = (args: Partial<StartWranglerDevServerArgs> = {}) =>
244
- Vitest.makeWithTestCtx({
245
- timeout: testTimeout,
246
- makeLayer: () => WranglerDevServerTest(args).pipe(Layer.provide(PlatformNode.NodeContext.layer)),
247
- })
248
-
249
- Vitest.scopedLive('should work with service pattern', (test) =>
250
- Effect.gen(function* () {
251
- const server = yield* WranglerDevServerService
252
-
253
- expect(server.port).toBeGreaterThan(0)
254
- expect(server.url).toMatch(/http:\/\/localhost:\d+/)
255
- expect(server.processId).toBeGreaterThan(0)
256
- }).pipe(withServiceTest()(test)),
257
- )
258
-
259
- Vitest.scopedLive('should work with custom port via service', (test) =>
260
- Effect.andThen(getFreePort, (port) =>
261
- Effect.gen(function* () {
262
- const server = yield* WranglerDevServerService
263
-
264
- expect(server.port).toBe(port)
265
- expect(server.url).toBe(`http://localhost:${port}`)
266
- }).pipe(withServiceTest({ preferredPort: port })(test)),
267
- ),
268
- )
269
- })
270
- })
@@ -1,302 +0,0 @@
1
- import { error } from 'node:console'
2
- import * as path from 'node:path'
3
- import { IS_CI, shouldNeverHappen } from '@livestore/utils'
4
- import {
5
- Command,
6
- Duration,
7
- Effect,
8
- Exit,
9
- HttpClient,
10
- Option,
11
- type PlatformError,
12
- Schedule,
13
- Schema,
14
- Stream,
15
- } from '@livestore/utils/effect'
16
- import { getFreePort } from '@livestore/utils/node'
17
- import { cleanupOrphanedProcesses, killProcessTree } from './process-tree-manager.ts'
18
-
19
- /**
20
- * Error type for WranglerDevServer operations
21
- */
22
- export class WranglerDevServerError extends Schema.TaggedError<WranglerDevServerError>()('WranglerDevServerError', {
23
- cause: Schema.Unknown,
24
- message: Schema.String,
25
- port: Schema.Number,
26
- }) {}
27
-
28
- /**
29
- * WranglerDevServer instance interface
30
- */
31
- export interface WranglerDevServer {
32
- readonly port: number
33
- readonly url: string
34
- readonly processId: number
35
- }
36
-
37
- /**
38
- * Configuration for starting WranglerDevServer
39
- */
40
- export interface StartWranglerDevServerArgs {
41
- wranglerConfigPath?: string
42
- cwd: string
43
- /** The port to try first. The dev server may bind a different port if unavailable. */
44
- preferredPort?: number
45
- /** @default false */
46
- showLogs?: boolean
47
- inspectorPort?: number
48
- connectTimeout?: Duration.DurationInput
49
- }
50
-
51
- /**
52
- * WranglerDevServer as an Effect.Service.
53
- *
54
- * This service provides the WranglerDevServer properties and can be accessed
55
- * directly to get port, url, and processId.
56
- *
57
- * TODO: Allow for config to be passed in via code instead of `wrangler.toml` file
58
- * (would need to be placed in temporary file as wrangler only accepts files as config)
59
- */
60
- export class WranglerDevServerService extends Effect.Service<WranglerDevServerService>()('WranglerDevServerService', {
61
- scoped: (args: StartWranglerDevServerArgs) =>
62
- Effect.gen(function* () {
63
- const showLogs = args.showLogs ?? false
64
-
65
- // Clean up any orphaned processes before starting (defensive cleanup)
66
- yield* cleanupOrphanedProcesses(['wrangler', 'workerd']).pipe(
67
- Effect.tap((result) =>
68
- showLogs && (result.cleaned.length > 0 || result.failed.length > 0)
69
- ? Effect.logInfo(`Cleanup result: ${result.cleaned.length} cleaned, ${result.failed.length} failed`)
70
- : Effect.void,
71
- ),
72
- Effect.ignore, // Don't fail startup if cleanup fails
73
- )
74
-
75
- // Allocate preferred port (Wrangler may bind a different one if unavailable)
76
- const preferredPort =
77
- args.preferredPort ??
78
- (yield* getFreePort.pipe(
79
- Effect.mapError(
80
- (cause) => new WranglerDevServerError({ cause, message: 'Failed to get free port', port: -1 }),
81
- ),
82
- ))
83
-
84
- yield* Effect.annotateCurrentSpan({ preferredPort })
85
-
86
- // Resolve config path
87
- const configPath = path.resolve(args.wranglerConfigPath ?? path.join(args.cwd, 'wrangler.toml'))
88
-
89
- const inspectorPort = args.inspectorPort ?? (yield* getFreePort)
90
-
91
- // Start wrangler process using Effect Command
92
- const process = yield* Command.make(
93
- 'bunx',
94
- 'wrangler',
95
- 'dev',
96
- '--port',
97
- preferredPort.toString(),
98
- '--inspector-port',
99
- inspectorPort.toString(),
100
- '--config',
101
- configPath,
102
- ).pipe(
103
- Command.workingDirectory(args.cwd),
104
- Command.stdout('pipe'),
105
- Command.stderr('pipe'),
106
- Command.start,
107
- Effect.catchAllCause(
108
- (error) =>
109
- new WranglerDevServerError({
110
- cause: error,
111
- message: `Failed to start wrangler process in directory: ${args.cwd}`,
112
- port: preferredPort,
113
- }),
114
- ),
115
- Effect.withSpan('WranglerDevServerService:startProcess'),
116
- )
117
-
118
- if (showLogs) {
119
- yield* process.stderr.pipe(
120
- Stream.decodeText('utf8'),
121
- Stream.tapLogWithLabel('wrangler:stderr'),
122
- Stream.runDrain,
123
- Effect.forkScoped,
124
- )
125
- }
126
-
127
- const processId = process.pid
128
-
129
- // We need to keep the `stdout` stream open, as we drain it in the waitForReady function
130
- // Otherwise we'll get a EPIPE error
131
- const stdout = yield* Stream.broadcastDynamic(process.stdout, 100)
132
-
133
- // Register cleanup finalizer with intelligent timeout handling
134
- yield* Effect.addFinalizer((exit) =>
135
- Effect.gen(function* () {
136
- const isInterrupted = Exit.isInterrupted(exit)
137
- if (showLogs) {
138
- yield* Effect.logDebug(`Cleaning up wrangler process ${processId}, interrupted: ${isInterrupted}`)
139
- }
140
- // yield* Effect.logDebug(`Cleaning up wrangler process ${processId}, interrupted: ${isInterrupted}`)
141
-
142
- // Check if process is still running
143
- const isRunning = yield* process.isRunning
144
-
145
- if (isRunning) {
146
- // Use our enhanced process tree cleanup
147
- yield* killProcessTree(processId, {
148
- timeout: isInterrupted ? 500 : 3000, // Fast cleanup on interruption
149
- signals: ['SIGTERM', 'SIGKILL'],
150
- includeRoot: true,
151
- }).pipe(
152
- Effect.tap((result) =>
153
- showLogs
154
- ? Effect.logDebug(
155
- `Cleaned up ${result.killedPids.length} processes, ${result.failedPids.length} failed`,
156
- )
157
- : Effect.void,
158
- ),
159
- Effect.mapError(
160
- (error) =>
161
- new WranglerDevServerError({
162
- cause: error,
163
- message: `Failed to kill process tree for PID ${processId}`,
164
- port: 0,
165
- }),
166
- ),
167
- Effect.ignore, // Don't fail the finalizer if cleanup has issues
168
- )
169
-
170
- // Also kill the command process handle
171
- yield* process.kill()
172
- } else if (showLogs) {
173
- yield* Effect.logDebug(`Process ${processId} already terminated`)
174
- }
175
- }).pipe(
176
- Effect.withSpan('WranglerDevServerService:cleanupProcess'),
177
- Effect.timeout('5 seconds'), // Don't let cleanup hang forever
178
- Effect.ignoreLogged,
179
- ),
180
- )
181
-
182
- // Wait for server to be ready and parse the actual bound host:port from stdout
183
- const readyInfo = yield* waitForReady({ stdout, showLogs })
184
-
185
- const actualPort = readyInfo.port
186
- const actualHost = readyInfo.host
187
- const url = `http://${actualHost}:${actualPort}`
188
-
189
- // Use longer timeout in CI environments to account for slower startup times
190
- const defaultTimeout = Duration.seconds(IS_CI ? 30 : 5)
191
-
192
- yield* verifyHttpConnectivity({ url, showLogs, connectTimeout: args.connectTimeout ?? defaultTimeout })
193
-
194
- if (showLogs) {
195
- yield* Effect.logDebug(
196
- `Wrangler dev server ready and accepting connections on port ${actualPort} (preferred: ${preferredPort})`,
197
- )
198
- }
199
-
200
- return {
201
- port: actualPort,
202
- url,
203
- processId,
204
- } satisfies WranglerDevServer
205
- }).pipe(
206
- Effect.withSpan('WranglerDevServerService', {
207
- attributes: { preferredPort: args.preferredPort ?? 'auto', cwd: args.cwd },
208
- }),
209
- ),
210
- }) {}
211
-
212
- /**
213
- * Waits for Wrangler server to be ready by monitoring stdout for "Ready on" message
214
- */
215
- const waitForReady = ({
216
- stdout,
217
- showLogs,
218
- }: {
219
- stdout: Stream.Stream<Uint8Array, PlatformError.PlatformError, never>
220
- showLogs: boolean
221
- }): Effect.Effect<{ host: string; port: number }, WranglerDevServerError, never> =>
222
- stdout.pipe(
223
- Stream.decodeText('utf8'),
224
- Stream.splitLines,
225
- Stream.tap((line) => (showLogs ? Effect.logDebug(`[wrangler] ${line}`) : Effect.void)),
226
- // Find the first readiness line and try to parse the port from it
227
- Stream.filterMap<string, { host: string; port: number }>((line) => {
228
- if (line.includes('Ready on')) {
229
- // Expect: "Ready on http://<host>:<port>"
230
- const m = line.match(/https?:\/\/([^:\s]+):(\d+)/i)
231
- if (!m) return shouldNeverHappen('Could not parse host:port from Wrangler "Ready on" line', line)
232
- const host = m[1]! as string
233
- const port = Number.parseInt(m[2]!, 10)
234
- if (!Number.isFinite(port)) return shouldNeverHappen('Parsed non-finite port from Wrangler output', line)
235
- return Option.some({ host, port } as const)
236
- } else {
237
- return Option.none()
238
- }
239
- }),
240
- Stream.take(1),
241
- Stream.runHead,
242
- Effect.flatten,
243
- Effect.orElse(
244
- () =>
245
- new WranglerDevServerError({
246
- cause: 'WranglerReadyLineMissing',
247
- message: 'Wrangler server did not emit a "Ready on" line',
248
- port: 0,
249
- }),
250
- ),
251
- Effect.timeoutFail({
252
- duration: '30 seconds',
253
- onTimeout: () =>
254
- new WranglerDevServerError({
255
- cause: error,
256
- message: 'Wrangler server failed to start within timeout',
257
- port: 0,
258
- }),
259
- }),
260
- )
261
-
262
- /**
263
- * Verifies the server is actually accepting HTTP connections by making a test request
264
- */
265
- const verifyHttpConnectivity = ({
266
- url,
267
- showLogs,
268
- connectTimeout,
269
- }: {
270
- url: string
271
- showLogs: boolean
272
- connectTimeout: Duration.DurationInput
273
- }): Effect.Effect<void, WranglerDevServerError, HttpClient.HttpClient> =>
274
- Effect.gen(function* () {
275
- const client = yield* HttpClient.HttpClient
276
-
277
- if (showLogs) {
278
- yield* Effect.logDebug(`Verifying HTTP connectivity to ${url}`)
279
- }
280
-
281
- // Try to connect with retries using exponential backoff
282
- yield* client.get(url).pipe(
283
- Effect.retryOrElse(
284
- Schedule.exponential('50 millis', 2).pipe(
285
- Schedule.jittered,
286
- Schedule.intersect(Schedule.elapsed.pipe(Schedule.whileOutput(Duration.lessThanOrEqualTo(connectTimeout)))),
287
- Schedule.compose(Schedule.count),
288
- ),
289
- (error, attemptCount) =>
290
- Effect.fail(
291
- new WranglerDevServerError({
292
- cause: error,
293
- message: `Failed to establish HTTP connection to Wrangler server at ${url} after ${attemptCount} attempts (timeout: ${Duration.toMillis(connectTimeout)}ms)`,
294
- port: 0,
295
- }),
296
- ),
297
- ),
298
- Effect.tap(() => (showLogs ? Effect.logDebug(`HTTP connectivity verified for ${url}`) : Effect.void)),
299
- Effect.asVoid,
300
- Effect.withSpan('verifyHttpConnectivity'),
301
- )
302
- })