* Get Coder CLI info. Caches result for the session. * Returns discriminated union: available | outdated | unavailable.
()
| 372 | * Returns discriminated union: available | outdated | unavailable. |
| 373 | */ |
| 374 | async getCoderInfo(): Promise<CoderInfo> { |
| 375 | if (this.cachedInfo) { |
| 376 | return this.cachedInfo; |
| 377 | } |
| 378 | |
| 379 | // Resolve the Coder binary path for better error messages (helps when multiple binaries are on PATH). |
| 380 | const binaryPath = await this.resolveCoderBinaryPath(); |
| 381 | |
| 382 | try { |
| 383 | const stdout = await this.runCoderJsonCommand(["version", "--output=json"], { |
| 384 | timeoutMs: 10_000, |
| 385 | }); |
| 386 | |
| 387 | // Parse JSON output |
| 388 | const data = JSON.parse(stdout) as { version?: string }; |
| 389 | const version = data.version; |
| 390 | |
| 391 | if (!version) { |
| 392 | this.cachedInfo = { |
| 393 | state: "unavailable", |
| 394 | reason: { kind: "error", message: "Version output missing from CLI" }, |
| 395 | }; |
| 396 | return this.cachedInfo; |
| 397 | } |
| 398 | |
| 399 | // Check minimum version |
| 400 | if (compareVersions(version, MIN_CODER_VERSION) < 0) { |
| 401 | log.debug(`Coder CLI version ${version} is below minimum ${MIN_CODER_VERSION}`); |
| 402 | this.cachedInfo = { |
| 403 | state: "outdated", |
| 404 | version, |
| 405 | minVersion: MIN_CODER_VERSION, |
| 406 | ...(binaryPath ? { binaryPath } : {}), |
| 407 | }; |
| 408 | return this.cachedInfo; |
| 409 | } |
| 410 | |
| 411 | let whoami: CoderWhoamiData | null = null; |
| 412 | try { |
| 413 | whoami = await this.getWhoamiData(); |
| 414 | } catch (error) { |
| 415 | // Treat whoami failures as a blocking issue for the Coder runtime. |
| 416 | // If the CLI isn't logged in, users will hit confusing failures later during provisioning. |
| 417 | const err = error as Partial<{ stderr: string; message: string }>; |
| 418 | const raw = (err.stderr?.trim() ? err.stderr : err.message) ?? ""; |
| 419 | const normalized = raw.toLowerCase(); |
| 420 | |
| 421 | const isNotLoggedIn = |
| 422 | normalized.includes("not logged in") || |
| 423 | normalized.includes("try logging in") || |
| 424 | normalized.includes("please login") || |
| 425 | normalized.includes("coder login"); |
| 426 | |
| 427 | const lastLine = |
| 428 | raw |
| 429 | .split(/\r?\n/) |
| 430 | .map((line) => line.trim()) |
| 431 | .filter(Boolean) |
no test coverage detected