(
claims: ReadonlyArray<PortClaim>,
boot: (ports: Record<string, number>) => Promise<{ teardown: () => Promise<void>; value: T }>,
options: { readonly maxAttempts?: number; readonly label?: string } = {},
)
| 245 | * returned `teardown` chains the caller's teardown then releases the block. |
| 246 | */ |
| 247 | export const claimAndBoot = async <T>( |
| 248 | claims: ReadonlyArray<PortClaim>, |
| 249 | boot: (ports: Record<string, number>) => Promise<{ teardown: () => Promise<void>; value: T }>, |
| 250 | options: { readonly maxAttempts?: number; readonly label?: string } = {}, |
| 251 | ): Promise<{ ports: Record<string, number>; teardown: () => Promise<void>; value: T }> => { |
| 252 | const maxAttempts = options.maxAttempts ?? 3; |
| 253 | const label = options.label ?? "boot"; |
| 254 | let lastError: unknown; |
| 255 | for (let attempt = 1; attempt <= maxAttempts; attempt++) { |
| 256 | const { ports, release } = await claimPorts(claims); |
| 257 | try { |
| 258 | const booted = await boot(ports); |
| 259 | return { |
| 260 | ports, |
| 261 | value: booted.value, |
| 262 | teardown: async () => { |
| 263 | await booted.teardown(); |
| 264 | await release(); |
| 265 | }, |
| 266 | }; |
| 267 | } catch (error) { |
| 268 | await release(); |
| 269 | lastError = error; |
| 270 | if (!isAddrInUse(error) || attempt === maxAttempts) throw error; |
| 271 | const collided = claims |
| 272 | .map((claim) => ports[claim.envVar]) |
| 273 | .filter((port): port is number => port !== undefined) |
| 274 | .join(", "); |
| 275 | console.warn( |
| 276 | `[e2e] ${label} hit EADDRINUSE on port(s) ${collided} (attempt ${attempt}/${maxAttempts}); re-claiming a fresh block and retrying`, |
| 277 | ); |
| 278 | } |
| 279 | } |
| 280 | throw lastError; |
| 281 | }; |
no test coverage detected