From b1e29f70b9c490158ed6a60e2f6023f2f4ff3e14 Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Wed, 18 Mar 2026 15:04:15 +0100 Subject: [PATCH 1/3] fix: update lockfile for 0.12.0 platform package versions The release PR bumped platform package versions in package.json but did not regenerate the lockfile, causing `bun install --frozen-lockfile` to fail in CI. --- bun.lock | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/bun.lock b/bun.lock index b6b8d83f..b050c71b 100644 --- a/bun.lock +++ b/bun.lock @@ -19,9 +19,9 @@ "zod": "4.3.6", }, "optionalDependencies": { - "archgate-darwin-arm64": "0.11.2", - "archgate-linux-x64": "0.11.2", - "archgate-win32-x64": "0.11.2", + "archgate-darwin-arm64": "0.12.0", + "archgate-linux-x64": "0.12.0", + "archgate-win32-x64": "0.12.0", }, "peerDependencies": { "typescript": "^5", @@ -185,10 +185,6 @@ "ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="], - "archgate-darwin-arm64": ["archgate-darwin-arm64@0.11.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-6zLgBves0TL4MbK7QgGUEw9yLVCTuDTVix3KiKpf8z2NQBK545+NIfp9WNgi66b3KAZRIyTbQYGglJ2D49uqrA=="], - - "archgate-linux-x64": ["archgate-linux-x64@0.11.2", "", { "os": "linux", "cpu": "x64" }, "sha512-MjdvBaN5W5buokND94o3L+kzO4+djq3boMDxoo1sUzBF9jD+rAQ55A4nMU8TPItGvM5d3+mB5Etlw+jJxBzlfg=="], - "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], "array-ify": ["array-ify@1.0.0", "", {}, "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng=="], From c29fd9b12624309926014804b49274c0e2bdac91 Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Wed, 18 Mar 2026 15:04:42 +0100 Subject: [PATCH 2/3] fix: detect TLS/SSL errors in login and show actionable guidance Corporate proxies performing SSL inspection cause fetch() to fail with opaque certificate errors. This detects those errors and tells the user to set NODE_EXTRA_CA_CERTS instead of showing a raw error message. --- src/commands/login.ts | 9 +++++++ src/helpers/tls.ts | 40 +++++++++++++++++++++++++++++++ tests/helpers/tls.test.ts | 50 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 src/helpers/tls.ts create mode 100644 tests/helpers/tls.test.ts diff --git a/src/commands/login.ts b/src/commands/login.ts index 17056ffd..506a2b37 100644 --- a/src/commands/login.ts +++ b/src/commands/login.ts @@ -12,6 +12,7 @@ import { clearCredentials, } from "../helpers/auth"; import { logError, logInfo } from "../helpers/log"; +import { isTlsError, tlsHintMessage } from "../helpers/tls"; export function registerLoginCommand(program: Command) { const login = program @@ -32,6 +33,10 @@ export function registerLoginCommand(program: Command) { await runDeviceFlow(); } catch (err) { + if (isTlsError(err)) { + logError(tlsHintMessage()); + process.exit(1); + } logError(err instanceof Error ? err.message : String(err)); process.exit(1); } @@ -67,6 +72,10 @@ export function registerLoginCommand(program: Command) { await clearCredentials(); await runDeviceFlow(); } catch (err) { + if (isTlsError(err)) { + logError(tlsHintMessage()); + process.exit(1); + } logError(err instanceof Error ? err.message : String(err)); process.exit(1); } diff --git a/src/helpers/tls.ts b/src/helpers/tls.ts new file mode 100644 index 00000000..7ea9db5c --- /dev/null +++ b/src/helpers/tls.ts @@ -0,0 +1,40 @@ +/** + * tls.ts — Detect TLS/SSL interception errors common in corporate environments + * and provide actionable guidance to the user. + */ + +const TLS_ERROR_PATTERNS = [ + "self signed certificate", + "unable to get local issuer certificate", + "certificate has expired", + "unable to verify the first certificate", + "CERT_HAS_EXPIRED", + "DEPTH_ZERO_SELF_SIGNED_CERT", + "SELF_SIGNED_CERT_IN_CHAIN", + "UNABLE_TO_GET_ISSUER_CERT_LOCALLY", +]; + +/** + * Returns true when the error looks like a TLS certificate verification + * failure — typically caused by a corporate proxy performing SSL inspection. + */ +export function isTlsError(error: unknown): boolean { + const message = error instanceof Error ? error.message : String(error); + return TLS_ERROR_PATTERNS.some((pattern) => message.includes(pattern)); +} + +/** + * Human-readable hint explaining the TLS failure and how to fix it. + */ +export function tlsHintMessage(): string { + return [ + "TLS certificate verification failed.", + "This typically happens behind a corporate proxy that performs SSL inspection.", + "", + "To fix this, set the NODE_EXTRA_CA_CERTS environment variable to your corporate CA certificate:", + "", + ' export NODE_EXTRA_CA_CERTS="/path/to/corporate-ca.pem"', + "", + "Then retry the command.", + ].join("\n"); +} diff --git a/tests/helpers/tls.test.ts b/tests/helpers/tls.test.ts new file mode 100644 index 00000000..9ebdf326 --- /dev/null +++ b/tests/helpers/tls.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, test } from "bun:test"; + +import { isTlsError, tlsHintMessage } from "../../src/helpers/tls"; + +describe("isTlsError", () => { + test("detects 'self signed certificate in certificate chain'", () => { + const err = new Error("self signed certificate in certificate chain"); + expect(isTlsError(err)).toBe(true); + }); + + test("detects 'self signed certificate' (without chain suffix)", () => { + expect(isTlsError(new Error("self signed certificate"))).toBe(true); + }); + + test("detects SELF_SIGNED_CERT_IN_CHAIN code", () => { + expect(isTlsError(new Error("SELF_SIGNED_CERT_IN_CHAIN"))).toBe(true); + }); + + test("detects 'unable to verify the first certificate'", () => { + expect( + isTlsError(new Error("unable to verify the first certificate")) + ).toBe(true); + }); + + test("detects UNABLE_TO_GET_ISSUER_CERT_LOCALLY code", () => { + expect(isTlsError(new Error("UNABLE_TO_GET_ISSUER_CERT_LOCALLY"))).toBe( + true + ); + }); + + test("returns false for unrelated errors", () => { + expect(isTlsError(new Error("fetch failed"))).toBe(false); + expect(isTlsError(new Error("ECONNREFUSED"))).toBe(false); + }); + + test("handles non-Error values", () => { + expect(isTlsError("self signed certificate")).toBe(true); + expect(isTlsError(42)).toBe(false); + }); +}); + +describe("tlsHintMessage", () => { + test("mentions NODE_EXTRA_CA_CERTS", () => { + expect(tlsHintMessage()).toContain("NODE_EXTRA_CA_CERTS"); + }); + + test("mentions corporate proxy", () => { + expect(tlsHintMessage()).toContain("corporate proxy"); + }); +}); From 8c729405a0dbfef422daa87cf3cd83a9c7133a1c Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Wed, 18 Mar 2026 15:13:31 +0100 Subject: [PATCH 3/3] fix: regenerate lockfile after bumping platform package versions The release bump script updates optionalDependencies in package.json but was not running `bun install` afterward, leaving the lockfile stale and breaking `--frozen-lockfile` in CI. --- .simple-release.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.simple-release.js b/.simple-release.js index 23003237..2aae4012 100644 --- a/.simple-release.js +++ b/.simple-release.js @@ -1,3 +1,4 @@ +import { execSync } from "node:child_process"; import { readFileSync, writeFileSync } from "node:fs"; import { NpmProject } from "@simple-release/npm"; @@ -21,6 +22,7 @@ class ArchgateProject extends NpmProject { if (changed) { writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n"); + execSync("bun install", { stdio: "inherit" }); } }