Unified · Split
| @@ -350,7 +350,10 @@ export const resolveSshTarget = Effect.fn("ssh/command.resolveSshTarget")(functi · 349 unmodified lines | ||
| 350 | 350 | Effect.logDebug("ssh.target.resolve.succeeded", sshTargetLogFields(target)), |
| 351 | 351 | ), |
| 352 | 352 | Effect.catch((cause) => |
| 353 | − Effect.logDebug("ssh.target.resolve.fallback", { alias: trimmedAlias, cause }).pipe( | |
| 353 | + Effect.logDebug("ssh.target.resolve.fallback", { | |
| 354 | + alias: trimmedAlias, | |
| 355 | + failureTag: cause._tag, | |
| 356 | + }).pipe( | |
| 354 | 357 | Effect.as({ |
| 355 | 358 | alias: trimmedAlias, |
| 356 | 359 | hostname: trimmedAlias, |
| @@ -5,12 +5,14 @@ import * as Duration from "effect/Duration"; · 4 unmodified lines | ||
| 5 | 5 | import * as Effect from "effect/Effect"; |
| 6 | 6 | import * as Fiber from "effect/Fiber"; |
| 7 | 7 | import * as Layer from "effect/Layer"; |
| 8 | +import * as Logger from "effect/Logger"; | |
| 8 | 9 | import * as PlatformError from "effect/PlatformError"; |
| 9 | 10 | import * as Result from "effect/Result"; |
| 10 | 11 | import * as Sink from "effect/Sink"; |
| 11 | 12 | import * as Stream from "effect/Stream"; |
| 12 | 13 | import * as TestClock from "effect/testing/TestClock"; |
| 13 | 14 | import * as HttpClient from "effect/unstable/http/HttpClient"; |
| 15 | +import * as HttpClientError from "effect/unstable/http/HttpClientError"; | |
| 14 | 16 | import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse"; |
| 15 | 17 | import * as ChildProcess from "effect/unstable/process/ChildProcess"; |
| 16 | 18 | import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"; |
| @@ -21,6 +23,7 @@ import { · 4 unmodified lines | ||
| 21 | 23 | SshHttpBridgeMissingUrlError, |
| 22 | 24 | SshHttpBridgeNonLoopbackUrlError, |
| 23 | 25 | SshPairingOutputParseError, |
| 26 | + SshReadinessProbeError, | |
| 24 | 27 | SshReadinessProbeTimeoutError, |
| 25 | 28 | SshReadinessTimeoutError, |
| 26 | 29 | } from "./errors.ts"; |
| @@ -88,6 +91,14 @@ function commandArgs(command: ChildProcess.Command): ReadonlyArray<string> { · 61 unmodified lines | ||
| 88 | 91 | return command._tag === "StandardCommand" ? command.args : []; |
| 89 | 92 | } |
| 90 | 93 | |
| 94 | +function flattenedLogText(value: unknown): string { | |
| 95 | + if (typeof value === "string") return value; | |
| 96 | + if (typeof value !== "object" || value === null) return String(value); | |
| 97 | + return Object.entries(value) | |
| 98 | + .flatMap(([key, nested]) => [key, flattenedLogText(nested)]) | |
| 99 | + .join("\n"); | |
| 100 | +} | |
| 101 | + | |
| 91 | 102 | describe("ssh tunnel scripts", () => { |
| 92 | 103 | it("builds the remote t3 runner with npx and npm fallbacks", () => { |
| 93 | 104 | const script = SshTunnel.buildRemoteT3RunnerScript({ nodeEngineRange: TEST_NODE_ENGINE_RANGE }); |
| @@ -255,13 +266,22 @@ describe("ssh tunnel scripts", () => { · 161 unmodified lines | ||
| 255 | 266 | assert.include(SshTunnel.REMOTE_PICK_PORT_SCRIPT, 'const filePath = process.argv[2] ?? "";'); |
| 256 | 267 | }); |
| 257 | 268 | |
| 258 | − it.effect("bounds each HTTP readiness probe so retries cannot hang on one request", () => | |
| 259 | − Effect.gen(function* () { | |
| 269 | + it.effect("bounds each HTTP readiness probe so retries cannot hang on one request", () => { | |
| 270 | + const baseUrl = | |
| 271 | + "http://ssh-user:ssh-password@127.0.0.1:41773/private/base?token=base-secret#base-fragment"; | |
| 272 | + const path = "/ready/private?credential=request-secret#request-fragment"; | |
| 273 | + const requestUrl = new URL(path, baseUrl).toString(); | |
| 274 | + const capturedLogs: Array<ReadonlyArray<unknown>> = []; | |
| 275 | + const logger = Logger.make(({ message }) => { | |
| 276 | + capturedLogs.push(Array.isArray(message) ? message : [message]); | |
| 277 | + }); | |
| 278 | + | |
| 279 | + return Effect.gen(function* () { | |
| 260 | 280 | const fiber = yield* Effect.forkChild( |
| 261 | 281 | Effect.result( |
| 262 | 282 | SshTunnel.waitForHttpReady({ |
| 263 | − baseUrl: "http://127.0.0.1:41773/?token=base-secret#fragment", | |
| 264 | − path: "/ready?credential=request-secret#fragment", | |
| 283 | + baseUrl, | |
| 284 | + path, | |
| 265 | 285 | timeoutMs: 1_000, |
| 266 | 286 | intervalMs: 100, |
| 267 | 287 | probeTimeoutMs: 250, |
| @@ -281,8 +301,8 @@ describe("ssh tunnel scripts", () => { · 13 unmodified lines | ||
| 281 | 301 | protocol: "http:", |
| 282 | 302 | hostname: "127.0.0.1", |
| 283 | 303 | port: "41773", |
| 284 | − urlLength: 50, | |
| 285 | − pathnameLength: 1, | |
| 304 | + urlLength: baseUrl.length, | |
| 305 | + pathnameLength: new TextEncoder().encode(new URL(baseUrl).pathname).length, | |
| 286 | 306 | hasQuery: true, |
| 287 | 307 | hasFragment: true, |
| 288 | 308 | }); |
| @@ -290,8 +310,8 @@ describe("ssh tunnel scripts", () => { · 1 unmodified line | ||
| 290 | 310 | protocol: "http:", |
| 291 | 311 | hostname: "127.0.0.1", |
| 292 | 312 | port: "41773", |
| 293 | − urlLength: 63, | |
| 294 | − pathnameLength: 6, | |
| 313 | + urlLength: requestUrl.length, | |
| 314 | + pathnameLength: new TextEncoder().encode(new URL(requestUrl).pathname).length, | |
| 295 | 315 | hasQuery: true, |
| 296 | 316 | hasFragment: true, |
| 297 | 317 | }); |
| @@ -311,13 +331,91 @@ describe("ssh tunnel scripts", () => { · 13 unmodified lines | ||
| 311 | 331 | const probeTimeout = timeoutError.cause as SshReadinessProbeTimeoutError; |
| 312 | 332 | assert.equal(probeTimeout.attempt, timeoutError.attempts); |
| 313 | 333 | assert.isFalse("cause" in probeTimeout); |
| 334 | + | |
| 335 | + const logText = flattenedLogText(capturedLogs); | |
| 336 | + assert.include(logText, "127.0.0.1"); | |
| 337 | + assert.notInclude(logText, "ssh-user"); | |
| 338 | + assert.notInclude(logText, "ssh-password"); | |
| 339 | + assert.notInclude(logText, "/private/base"); | |
| 340 | + assert.notInclude(logText, "base-secret"); | |
| 341 | + assert.notInclude(logText, "/ready/private"); | |
| 342 | + assert.notInclude(logText, "request-secret"); | |
| 343 | + assert.notInclude(logText, "base-fragment"); | |
| 344 | + assert.notInclude(logText, "request-fragment"); | |
| 314 | 345 | } |
| 315 | 346 | }).pipe( |
| 316 | 347 | Effect.provide( |
| 317 | − Layer.merge(TestClock.layer(), Layer.succeed(HttpClient.HttpClient, hangingHttpClient)), | |
| 348 | + Layer.mergeAll( | |
| 349 | + TestClock.layer(), | |
| 350 | + Layer.succeed(HttpClient.HttpClient, hangingHttpClient), | |
| 351 | + Logger.layer([logger], { mergeWithExisting: false }), | |
| 352 | + ), | |
| 318 | 353 | ), |
| 319 | − ), | |
| 320 | − ); | |
| 354 | + ); | |
| 355 | + }); | |
| 356 | + | |
| 357 | + it.effect("preserves the exact HTTP transport cause without logging request secrets", () => { | |
| 358 | + const baseUrl = | |
| 359 | + "http://transport-user:transport-password@127.0.0.1:41773/private?token=transport-secret#fragment"; | |
| 360 | + const transportCause = new Error("socket closed"); | |
| 361 | + const clientErrors: Array<HttpClientError.HttpClientError> = []; | |
| 362 | + const failingHttpClient = HttpClient.make((request) => { | |
| 363 | + const error = new HttpClientError.HttpClientError({ | |
| 364 | + reason: new HttpClientError.TransportError({ request, cause: transportCause }), | |
| 365 | + }); | |
| 366 | + clientErrors.push(error); | |
| 367 | + return Effect.fail(error); | |
| 368 | + }); | |
| 369 | + const capturedLogs: Array<ReadonlyArray<unknown>> = []; | |
| 370 | + const logger = Logger.make(({ message }) => { | |
| 371 | + capturedLogs.push(Array.isArray(message) ? message : [message]); | |
| 372 | + }); | |
| 373 | + | |
| 374 | + return Effect.gen(function* () { | |
| 375 | + const fiber = yield* Effect.forkChild( | |
| 376 | + Effect.result( | |
| 377 | + SshTunnel.waitForHttpReady({ | |
| 378 | + baseUrl, | |
| 379 | + timeoutMs: 300, | |
| 380 | + intervalMs: 100, | |
| 381 | + probeTimeoutMs: 250, | |
| 382 | + }), | |
| 383 | + ), | |
| 384 | + ); | |
| 385 | + yield* Effect.yieldNow; | |
| 386 | + yield* TestClock.adjust(Duration.millis(300)); | |
| 387 | + | |
| 388 | + const result = yield* Fiber.join(fiber); | |
| 389 | + assert.isTrue(Result.isFailure(result)); | |
| 390 | + if (Result.isFailure(result)) { | |
| 391 | + assert.instanceOf(result.failure, SshReadinessTimeoutError); | |
| 392 | + const timeoutError = result.failure as SshReadinessTimeoutError; | |
| 393 | + assert.instanceOf(timeoutError.cause, SshReadinessProbeError); | |
| 394 | + const probeError = timeoutError.cause as SshReadinessProbeError; | |
| 395 | + assert.strictEqual(probeError.cause, clientErrors.at(-1)); | |
| 396 | + assert.strictEqual( | |
| 397 | + (probeError.cause as HttpClientError.HttpClientError).reason.cause, | |
| 398 | + transportCause, | |
| 399 | + ); | |
| 400 | + | |
| 401 | + const logText = flattenedLogText(capturedLogs); | |
| 402 | + assert.include(logText, "HttpClientError"); | |
| 403 | + assert.include(logText, "TransportError"); | |
| 404 | + assert.notInclude(logText, "transport-user"); | |
| 405 | + assert.notInclude(logText, "transport-password"); | |
| 406 | + assert.notInclude(logText, "/private"); | |
| 407 | + assert.notInclude(logText, "transport-secret"); | |
| 408 | + } | |
| 409 | + }).pipe( | |
| 410 | + Effect.provide( | |
| 411 | + Layer.mergeAll( | |
| 412 | + TestClock.layer(), | |
| 413 | + Layer.succeed(HttpClient.HttpClient, failingHttpClient), | |
| 414 | + Logger.layer([logger], { mergeWithExisting: false }), | |
| 415 | + ), | |
| 416 | + ), | |
| 417 | + ); | |
| 418 | + }); | |
| 321 | 419 | |
| 322 | 420 | it.effect("preserves forwarded HTTP URL error messages", () => |
| 323 | 421 | Effect.gen(function* () { |
| @@ -363,6 +461,10 @@ describe("ssh tunnel scripts", () => { · 39 unmodified lines | ||
| 363 | 461 | cause: { type: "string" }, |
| 364 | 462 | }, |
| 365 | 463 | ); |
| 464 | + | |
| 465 | + const cyclicCause: { readonly _tag: string; cause?: unknown } = { _tag: "CyclicCause" }; | |
| 466 | + cyclicCause.cause = cyclicCause; | |
| 467 | + assert.include(flattenedLogText(SshTunnel.describeReadinessCause(cyclicCause)), "truncated"); | |
| 366 | 468 | }); |
| 367 | 469 | |
| 368 | 470 | it("preserves structured readiness attributes in diagnostic output", () => { |
| @@ -542,6 +644,10 @@ describe("ssh tunnel scripts", () => { · 173 unmodified lines | ||
| 542 | 644 | const spawnedCommands: Array<ReadonlyArray<string>> = []; |
| 543 | 645 | let tunnelKillCount = 0; |
| 544 | 646 | let stopCommandCount = 0; |
| 647 | + const capturedLogs: Array<ReadonlyArray<unknown>> = []; | |
| 648 | + const logger = Logger.make(({ message }) => { | |
| 649 | + capturedLogs.push(Array.isArray(message) ? message : [message]); | |
| 650 | + }); | |
| 545 | 651 | const spawner = ChildProcessSpawner.make((command) => |
| 546 | 652 | Effect.sync(() => { |
| 547 | 653 | const args = commandArgs(command); |
| @@ -568,6 +674,7 @@ describe("ssh tunnel scripts", () => { · 20 unmodified lines | ||
| 568 | 674 | Layer.succeed(NetService.NetService, testNetService), |
| 569 | 675 | SshAuth.disabledLayer, |
| 570 | 676 | SshTunnel.layer(), |
| 677 | + Logger.layer([logger], { mergeWithExisting: false }), | |
| 571 | 678 | ); |
| 572 | 679 | const target = { |
| 573 | 680 | alias: "devbox", |
| @@ -590,6 +697,10 @@ describe("ssh tunnel scripts", () => { · 16 unmodified lines | ||
| 590 | 697 | |
| 591 | 698 | assert.equal(spawnedCommands.filter((args) => args.includes("-N")).length, 2); |
| 592 | 699 | assert.equal(tunnelKillCount, 1); |
| 700 | + | |
| 701 | + const logText = flattenedLogText(capturedLogs); | |
| 702 | + assert.include(logText, "httpBaseUrlDiagnostics"); | |
| 703 | + assert.notInclude(logText, "httpBaseUrl\nhttp://"); | |
| 593 | 704 | }).pipe(Effect.provide(layer), Effect.scoped); |
| 594 | 705 | }); |
| 595 | 706 | }); |
| @@ -5,6 +5,8 @@ import type { · 4 unmodified lines | ||
| 5 | 5 | import * as NetService from "@t3tools/shared/Net"; |
| 6 | 6 | import { extractJsonObject, fromLenientJson } from "@t3tools/shared/schemaJson"; |
| 7 | 7 | import { satisfiesSemverRange } from "@t3tools/shared/semver"; |
| 8 | +import { getUrlDiagnostics } from "@t3tools/shared/urlDiagnostics"; | |
| 9 | +import * as Cause from "effect/Cause"; | |
| 8 | 10 | import * as Context from "effect/Context"; |
| 9 | 11 | import * as Deferred from "effect/Deferred"; |
| 10 | 12 | import * as Duration from "effect/Duration"; |
| @@ -243,7 +245,24 @@ function applyScriptPlaceholders( · 232 unmodified lines | ||
| 243 | 245 | return result; |
| 244 | 246 | } |
| 245 | 247 | |
| 246 | −export function describeReadinessCause(cause: unknown): unknown { | |
| 248 | +const SSH_FAILURE_DIAGNOSTIC_DEPTH = 6; | |
| 249 | + | |
| 250 | +function describeReadinessCauseAtDepth(cause: unknown, depth: number): unknown { | |
| 251 | + if (depth >= SSH_FAILURE_DIAGNOSTIC_DEPTH) return { truncated: true }; | |
| 252 | + const describeNested = (nested: unknown) => describeReadinessCauseAtDepth(nested, depth + 1); | |
| 253 | + if (Cause.isCause(cause)) { | |
| 254 | + return { | |
| 255 | + reasons: cause.reasons.map((reason) => { | |
| 256 | + if (Cause.isFailReason(reason)) { | |
| 257 | + return { _tag: reason._tag, error: describeNested(reason.error) }; | |
| 258 | + } | |
| 259 | + if (Cause.isDieReason(reason)) { | |
| 260 | + return { _tag: reason._tag, defect: describeNested(reason.defect) }; | |
| 261 | + } | |
| 262 | + return { _tag: reason._tag, fiberId: reason.fiberId }; | |
| 263 | + }), | |
| 264 | + }; | |
| 265 | + } | |
| 247 | 266 | if (isSshReadinessError(cause)) { |
| 248 | 267 | const { cause: nestedCause, ...attributes } = cause as SshReadinessError & { |
| 249 | 268 | readonly cause?: unknown; |
| @@ -251,13 +270,15 @@ export function describeReadinessCause(cause: unknown): unknown { · 1 unmodified line | ||
| 251 | 270 | return { |
| 252 | 271 | ...attributes, |
| 253 | 272 | message: cause.message, |
| 254 | − ...(nestedCause === undefined ? {} : { cause: describeReadinessCause(nestedCause) }), | |
| 273 | + ...(nestedCause === undefined ? {} : { cause: describeNested(nestedCause) }), | |
| 255 | 274 | }; |
| 256 | 275 | } |
| 257 | 276 | if (cause instanceof Error) { |
| 277 | + const tagged = cause as Error & { readonly _tag?: unknown; readonly reason?: unknown }; | |
| 258 | 278 | return { |
| 259 | − name: cause.name, | |
| 260 | − ...(cause.cause === undefined ? {} : { cause: describeReadinessCause(cause.cause) }), | |
| 279 | + ...(typeof tagged._tag === "string" ? { _tag: tagged._tag } : { name: cause.name }), | |
| 280 | + ...(tagged.reason === undefined ? {} : { reason: describeNested(tagged.reason) }), | |
| 281 | + ...(cause.cause === undefined ? {} : { cause: describeNested(cause.cause) }), | |
| 261 | 282 | }; |
| 262 | 283 | } |
| 263 | 284 | if (typeof cause !== "object" || cause === null) { |
| @@ -267,17 +288,23 @@ export function describeReadinessCause(cause: unknown): unknown { · 3 unmodified lines | ||
| 267 | 288 | const record = cause as Readonly<Record<string, unknown>>; |
| 268 | 289 | return { |
| 269 | 290 | ...(typeof record._tag === "string" ? { _tag: record._tag } : {}), |
| 270 | − ...(record.reason === undefined ? {} : { reason: describeReadinessCause(record.reason) }), | |
| 271 | − ...(record.cause === undefined ? {} : { cause: describeReadinessCause(record.cause) }), | |
| 291 | + ...(record.reason === undefined ? {} : { reason: describeNested(record.reason) }), | |
| 292 | + ...(record.cause === undefined ? {} : { cause: describeNested(record.cause) }), | |
| 272 | 293 | }; |
| 273 | 294 | } |
| 274 | 295 | |
| 275 | −function readinessUrlDiagnostics(url: URL, urlLength: number) { | |
| 296 | +export function describeReadinessCause(cause: unknown): unknown { | |
| 297 | + return describeReadinessCauseAtDepth(cause, 0); | |
| 298 | +} | |
| 299 | + | |
| 300 | +function readinessUrlDiagnostics(input: string) { | |
| 301 | + const url = new URL(input); | |
| 302 | + const diagnostics = getUrlDiagnostics(input); | |
| 276 | 303 | return { |
| 277 | − protocol: url.protocol, | |
| 278 | − hostname: url.hostname, | |
| 304 | + protocol: diagnostics.protocol ?? url.protocol, | |
| 305 | + hostname: diagnostics.hostname ?? url.hostname, | |
| 279 | 306 | ...(url.port === "" ? {} : { port: url.port }), |
| 280 | − urlLength, | |
| 307 | + urlLength: diagnostics.inputLength, | |
| 281 | 308 | pathnameLength: utf8ByteLength(url.pathname), |
| 282 | 309 | hasQuery: url.search !== "", |
| 283 | 310 | hasFragment: url.hash !== "", |
| @@ -900,8 +927,8 @@ export const waitForHttpReady = Effect.fn("ssh/tunnel.waitForHttpReady")(functio · 616 unmodified lines | ||
| 900 | 927 | const baseUrl = new URL(input.baseUrl); |
| 901 | 928 | const request = new URL(input.path ?? "/", baseUrl); |
| 902 | 929 | const requestUrl = request.toString(); |
| 903 | − const base = readinessUrlDiagnostics(baseUrl, utf8ByteLength(input.baseUrl)); | |
| 904 | − const requestDiagnostics = readinessUrlDiagnostics(request, utf8ByteLength(requestUrl)); | |
| 930 | + const base = readinessUrlDiagnostics(input.baseUrl); | |
| 931 | + const requestDiagnostics = readinessUrlDiagnostics(requestUrl); | |
| 905 | 932 | const client = yield* HttpClient.HttpClient; |
| 906 | 933 | const lastProbeFailure = yield* Ref.make<unknown>(null); |
| 907 | 934 | let attempt = 0; |
| @@ -1088,6 +1115,7 @@ const startSshTunnel = Effect.fn("ssh/tunnel.startSshTunnel")(function* (input: · 180 unmodified lines | ||
| 1088 | 1115 | const sshCommand = yield* resolveSshCommand; |
| 1089 | 1116 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; |
| 1090 | 1117 | const scope = yield* Scope.Scope; |
| 1118 | + const httpBaseUrlDiagnostics = getUrlDiagnostics(input.httpBaseUrl); | |
| 1091 | 1119 | yield* Effect.logDebug("ssh.tunnel.spawn.start", { |
| 1092 | 1120 | ...sshTargetLogFields(input.resolvedTarget), |
| 1093 | 1121 | command: sshCommand, |
| @@ -1095,7 +1123,7 @@ const startSshTunnel = Effect.fn("ssh/tunnel.startSshTunnel")(function* (input: · 1 unmodified line | ||
| 1095 | 1123 | localPort: input.localPort, |
| 1096 | 1124 | remotePort: input.remotePort, |
| 1097 | 1125 | remoteServerKind: input.remoteServerKind, |
| 1098 | − httpBaseUrl: input.httpBaseUrl, | |
| 1126 | + httpBaseUrlDiagnostics, | |
| 1099 | 1127 | }); |
| 1100 | 1128 | const child = yield* spawner |
| 1101 | 1129 | .spawn( |
| @@ -1128,7 +1156,7 @@ const startSshTunnel = Effect.fn("ssh/tunnel.startSshTunnel")(function* (input: · 26 unmodified lines | ||
| 1128 | 1156 | pid: child.pid, |
| 1129 | 1157 | localPort: input.localPort, |
| 1130 | 1158 | remotePort: input.remotePort, |
| 1131 | − httpBaseUrl: input.httpBaseUrl, | |
| 1159 | + httpBaseUrlDiagnostics, | |
| 1132 | 1160 | }); |
| 1133 | 1161 | const tunnelEntry: SshTunnelEntry = { |
| 1134 | 1162 | key: input.key, |
| @@ -1171,7 +1199,7 @@ const startSshTunnel = Effect.fn("ssh/tunnel.startSshTunnel")(function* (input: · 36 unmodified lines | ||
| 1171 | 1199 | pid: child.pid, |
| 1172 | 1200 | localPort: input.localPort, |
| 1173 | 1201 | remotePort: input.remotePort, |
| 1174 | − httpBaseUrl: input.httpBaseUrl, | |
| 1202 | + httpBaseUrlDiagnostics, | |
| 1175 | 1203 | exitCode, |
| 1176 | 1204 | stderrBytes: utf8ByteLength(stderr), |
| 1177 | 1205 | }).pipe(Effect.andThen(Effect.fail(error))); |
| @@ -1192,7 +1220,7 @@ const startSshTunnel = Effect.fn("ssh/tunnel.startSshTunnel")(function* (input: · 14 unmodified lines | ||
| 1192 | 1220 | pid: child.pid, |
| 1193 | 1221 | localPort: input.localPort, |
| 1194 | 1222 | remotePort: input.remotePort, |
| 1195 | − httpBaseUrl: input.httpBaseUrl, | |
| 1223 | + httpBaseUrlDiagnostics, | |
| 1196 | 1224 | }), |
| 1197 | 1225 | ), |
| 1198 | 1226 | Effect.tapError((cause) => |
| @@ -1220,19 +1248,19 @@ const startSshTunnel = Effect.fn("ssh/tunnel.startSshTunnel")(function* (input: · 21 unmodified lines | ||
| 1220 | 1248 | processRunning, |
| 1221 | 1249 | ...(Exit.isSuccess(processRunningExit) |
| 1222 | 1250 | ? {} |
| 1223 | − : { processRunningError: processRunningExit.cause }), | |
| 1251 | + : { processRunningFailure: describeReadinessCause(processRunningExit.cause) }), | |
| 1224 | 1252 | localPort: input.localPort, |
| 1225 | 1253 | localPortListening: localPortAvailable === null ? null : !localPortAvailable, |
| 1226 | 1254 | remotePort: input.remotePort, |
| 1227 | − httpBaseUrl: input.httpBaseUrl, | |
| 1255 | + httpBaseUrlDiagnostics, | |
| 1228 | 1256 | ...(Exit.isSuccess(localPortAvailableExit) |
| 1229 | 1257 | ? {} |
| 1230 | − : { localPortProbeError: localPortAvailableExit.cause }), | |
| 1258 | + : { localPortProbeFailure: describeReadinessCause(localPortAvailableExit.cause) }), | |
| 1231 | 1259 | ...(remoteLogTail === null ? {} : { remoteLogTailBytes: utf8ByteLength(remoteLogTail) }), |
| 1232 | 1260 | ...(Exit.isSuccess(remoteLogTailExit) |
| 1233 | 1261 | ? {} |
| 1234 | − : { remoteLogTailError: remoteLogTailExit.cause }), | |
| 1235 | − cause, | |
| 1262 | + : { remoteLogTailFailure: describeReadinessCause(remoteLogTailExit.cause) }), | |
| 1263 | + failure: describeReadinessCause(cause), | |
| 1236 | 1264 | }); |
| 1237 | 1265 | }), |
| 1238 | 1266 | ), |
| @@ -1370,7 +1398,7 @@ export const make = Effect.fn("ssh/tunnel.SshEnvironmentManager.make")(function* · 131 unmodified lines | ||
| 1370 | 1398 | ...sshTargetLogFields(input.target), |
| 1371 | 1399 | key: input.key, |
| 1372 | 1400 | promptCount: input.promptCount, |
| 1373 | − cause: input.error, | |
| 1401 | + failure: describeReadinessCause(input.error), | |
| 1374 | 1402 | }); |
| 1375 | 1403 | const promptService = yield* SshAuth.SshPasswordPrompt; |
| 1376 | 1404 | if (!promptService.isAvailable) { |
| @@ -1569,7 +1597,7 @@ export const make = Effect.fn("ssh/tunnel.SshEnvironmentManager.make")(function* · 192 unmodified lines | ||
| 1569 | 1597 | key, |
| 1570 | 1598 | localPort: entry.localPort, |
| 1571 | 1599 | remotePort: entry.remotePort, |
| 1572 | − cause: readinessExit.cause, | |
| 1600 | + failure: describeReadinessCause(readinessExit.cause), | |
| 1573 | 1601 | }); |
| 1574 | 1602 | yield* closeTunnelEntry(entry); |
| 1575 | 1603 | yield* cancelPendingTunnelEntry(key, resolvedTarget); |
| @@ -1597,7 +1625,7 @@ export const make = Effect.fn("ssh/tunnel.SshEnvironmentManager.make")(function* · 21 unmodified lines | ||
| 1597 | 1625 | Effect.logWarning("ssh.environment.tunnel.create.failed", { |
| 1598 | 1626 | ...sshTargetLogFields(resolvedTarget), |
| 1599 | 1627 | key, |
| 1600 | − cause, | |
| 1628 | + failure: describeReadinessCause(cause), | |
| 1601 | 1629 | }), |
| 1602 | 1630 | ), |
| 1603 | 1631 | Effect.onExit((exit) => |