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