From 9adf3d92bd1e7bfb40dfb402e8631c67331e9275 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Mon, 22 Jun 2026 03:34:49 +0800 Subject: [PATCH] chore(deadcode): remove unused helper paths --- .../src/connect-error-details.test.ts | 15 -- .../src/connect-error-details.ts | 14 -- src/infra/tailscale.test.ts | 123 +-------------- src/infra/tailscale.ts | 148 +----------------- 4 files changed, 2 insertions(+), 298 deletions(-) diff --git a/packages/gateway-protocol/src/connect-error-details.test.ts b/packages/gateway-protocol/src/connect-error-details.test.ts index 3ac1fb266cec..690e89e6fe48 100644 --- a/packages/gateway-protocol/src/connect-error-details.test.ts +++ b/packages/gateway-protocol/src/connect-error-details.test.ts @@ -11,7 +11,6 @@ import { normalizePairingConnectRequestId, readConnectErrorDetailCode, readConnectErrorRecoveryAdvice, - readConnectPairingRequiredDetails, readConnectPairingRequiredMessage, readPairingConnectErrorDetails, resolveAuthConnectErrorDetailCode, @@ -140,20 +139,6 @@ describe("pairing connect details", () => { }); }); - it("reads pairing details as compact connect details", () => { - expect( - readConnectPairingRequiredDetails({ - code: "PAIRING_REQUIRED", - requestId: "req-123", - reason: "scope-upgrade", - remediationHint: "Review the requested scopes, then approve the pending upgrade.", - }), - ).toEqual({ - requestId: "req-123", - reason: "scope-upgrade", - }); - }); - it("formats upgrade rejections with the request id", () => { expect( formatConnectPairingRequiredMessage({ diff --git a/packages/gateway-protocol/src/connect-error-details.ts b/packages/gateway-protocol/src/connect-error-details.ts index 9cb4ce44fde3..6d02d8c151f8 100644 --- a/packages/gateway-protocol/src/connect-error-details.ts +++ b/packages/gateway-protocol/src/connect-error-details.ts @@ -440,20 +440,6 @@ export function readPairingConnectErrorDetails( }); } -/** Reads the compact pairing-required subset from untrusted connect details. */ -export function readConnectPairingRequiredDetails( - details: unknown, -): ConnectPairingRequiredDetails | null { - const pairing = readPairingConnectErrorDetails(details); - if (!pairing) { - return null; - } - return { - ...(pairing.requestId ? { requestId: pairing.requestId } : {}), - ...(pairing.reason ? { reason: pairing.reason } : {}), - }; -} - /** Parses legacy/string-only pairing-required messages into structured details. */ export function readConnectPairingRequiredMessage( message: string | null | undefined, diff --git a/src/infra/tailscale.test.ts b/src/infra/tailscale.test.ts index 64bb2f784364..cf078200b45c 100644 --- a/src/infra/tailscale.test.ts +++ b/src/infra/tailscale.test.ts @@ -1,32 +1,19 @@ -// Covers Tailscale install, whois, Serve, and Funnel helpers. +// Covers Tailscale whois, Serve, and Funnel helpers. import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { captureEnv } from "../test-utils/env.js"; import * as tailscale from "./tailscale.js"; const { - ensureGoInstalled, - ensureTailscaledInstalled, getTailnetHostname, getTestTailscaleBinaryOverride, readTailscaleWhoisIdentity, enableTailscaleServe, disableTailscaleServe, - ensureFunnel, hasTailscaleFunnelRouteForPort, tailscaleFunnelStatusCoversPort, } = tailscale; const tailscaleBin = "tailscale"; -function createRuntimeWithExitError() { - return { - error: vi.fn(), - log: vi.fn(), - exit: ((code: number) => { - throw new Error(`exit ${code}`); - }) as (code: number) => never, - }; -} - function expectExecCall( exec: ReturnType, callNumber: number, @@ -169,52 +156,6 @@ describe("tailscale helpers", () => { expect(getTestTailscaleBinaryOverride()).toBeNull(); }); - it.each([ - { - name: "ensureGoInstalled installs when missing and user agrees", - fn: ensureGoInstalled, - missingError: new Error("no go"), - installCommand: ["brew", ["install", "go"]] as const, - promptResult: true, - }, - { - name: "ensureTailscaledInstalled installs when missing and user agrees", - fn: ensureTailscaledInstalled, - missingError: new Error("missing"), - installCommand: ["brew", ["install", "tailscale"]] as const, - promptResult: true, - }, - ])("$name", async ({ fn, missingError, installCommand, promptResult }) => { - const exec = vi.fn().mockRejectedValueOnce(missingError).mockResolvedValue({}); - const prompt = vi.fn().mockResolvedValue(promptResult); - const runtime = createRuntimeWithExitError(); - await fn(exec as never, prompt, runtime); - expect(exec).toHaveBeenCalledWith(installCommand[0], installCommand[1]); - }); - - it.each([ - { - name: "ensureGoInstalled exits when missing and user declines install", - fn: ensureGoInstalled, - missingError: new Error("no go"), - errorMessage: "Go is required to build tailscaled from source. Aborting.", - }, - { - name: "ensureTailscaledInstalled exits when missing and user declines install", - fn: ensureTailscaledInstalled, - missingError: new Error("missing"), - errorMessage: "tailscaled is required for user-space funnel. Aborting.", - }, - ])("$name", async ({ fn, missingError, errorMessage }) => { - const exec = vi.fn().mockRejectedValueOnce(missingError); - const prompt = vi.fn().mockResolvedValue(false); - const runtime = createRuntimeWithExitError(); - - await expect(fn(exec as never, prompt, runtime)).rejects.toThrow("exit 1"); - expect(runtime.error).toHaveBeenCalledWith(errorMessage); - expect(exec).toHaveBeenCalledTimes(1); - }); - it("enableTailscaleServe attempts normal first, then sudo", async () => { const exec = vi .fn() @@ -291,68 +232,6 @@ describe("tailscale helpers", () => { }); }); - it("ensureFunnel uses fallback for enabling", async () => { - const exec = vi - .fn() - .mockResolvedValueOnce({ stdout: JSON.stringify({ BackendState: "Running" }) }) // status - .mockRejectedValueOnce(new Error("permission denied")) // enable normal - .mockResolvedValueOnce({ stdout: "" }); // enable sudo - - const runtime = { - error: vi.fn(), - log: vi.fn(), - exit: vi.fn() as unknown as (code: number) => never, - }; - const prompt = vi.fn(); - - await ensureFunnel(8080, exec as never, runtime, prompt); - - expect(exec).toHaveBeenCalledTimes(3); - expectExecCall(exec, 1, tailscaleBin, ["funnel", "status", "--json"]); - expectExecCall(exec, 2, tailscaleBin, ["funnel", "--yes", "--bg", "8080"], { - maxBuffer: 200_000, - timeoutMs: 15_000, - }); - expectExecCall(exec, 3, "sudo", ["-n", tailscaleBin, "funnel", "--yes", "--bg", "8080"], { - maxBuffer: 200_000, - timeoutMs: 15_000, - }); - }); - - it("ensureFunnel accepts noisy JSON status output", async () => { - const exec = vi - .fn() - .mockResolvedValueOnce({ - stdout: 'warning: stale state\n{"BackendState":"Running"}\n', - }) - .mockResolvedValueOnce({ stdout: "" }); - const runtime = createRuntimeWithExitError(); - const prompt = vi.fn(); - - await ensureFunnel(8080, exec as never, runtime, prompt); - - expect(exec).toHaveBeenCalledTimes(2); - expectExecCall(exec, 2, tailscaleBin, ["funnel", "--yes", "--bg", "8080"], { - maxBuffer: 200_000, - timeoutMs: 15_000, - }); - expect(prompt).not.toHaveBeenCalled(); - }); - - it("ensureFunnel treats malformed status output as a failure", async () => { - const exec = vi.fn().mockResolvedValueOnce({ stdout: "warning: stale state\n{not json}\n" }); - const runtime = createRuntimeWithExitError(); - const prompt = vi.fn(); - - await expect(ensureFunnel(8080, exec as never, runtime, prompt)).rejects.toThrow("exit 1"); - - expect(exec).toHaveBeenCalledTimes(1); - expect(prompt).not.toHaveBeenCalled(); - expect(runtime.error).toHaveBeenCalledWith( - "Failed to enable Tailscale Funnel. Is it allowed on your tailnet?", - ); - }); - it("enableTailscaleServe skips sudo on non-permission errors", async () => { const exec = vi.fn().mockRejectedValueOnce(new Error("boom")); diff --git a/src/infra/tailscale.ts b/src/infra/tailscale.ts index 69bf60859f70..605bf98b72bf 100644 --- a/src/infra/tailscale.ts +++ b/src/infra/tailscale.ts @@ -9,13 +9,8 @@ import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, } from "@openclaw/normalization-core/string-coerce"; -import { colorize, isRich, theme } from "../../packages/terminal-core/src/theme.js"; -import { formatCliCommand } from "../cli/command-format.js"; -import { promptYesNo } from "../cli/prompt.js"; -import { danger, info, logVerbose, shouldLogVerbose, warn } from "../globals.js"; +import { logVerbose } from "../globals.js"; import { runExec } from "../process/exec.js"; -import { defaultRuntime, type RuntimeEnv } from "../runtime.js"; -import { ensureBinary } from "./binaries.js"; function parsePossiblyNoisyJsonObject(stdout: string): Record { const trimmed = stdout.trim(); @@ -190,57 +185,6 @@ async function getTailscaleBinary(): Promise { return cachedTailscaleBinary ?? "tailscale"; } -export async function ensureGoInstalled( - exec: typeof runExec = runExec, - prompt: typeof promptYesNo = promptYesNo, - runtime: RuntimeEnv = defaultRuntime, -) { - // Ensure Go toolchain is present; offer Homebrew install if missing. - const hasGo = await exec("go", ["version"]).then( - () => true, - () => false, - ); - if (hasGo) { - return; - } - const install = await prompt( - "Go is not installed. Install via Homebrew (brew install go)?", - true, - ); - if (!install) { - runtime.error("Go is required to build tailscaled from source. Aborting."); - runtime.exit(1); - } - logVerbose("Installing Go via Homebrew…"); - await exec("brew", ["install", "go"]); -} - -export async function ensureTailscaledInstalled( - exec: typeof runExec = runExec, - prompt: typeof promptYesNo = promptYesNo, - runtime: RuntimeEnv = defaultRuntime, -) { - // Ensure tailscaled binary exists; install via Homebrew tailscale if missing. - const hasTailscaled = await exec("tailscaled", ["--version"]).then( - () => true, - () => false, - ); - if (hasTailscaled) { - return; - } - - const install = await prompt( - "tailscaled not found. Install via Homebrew (tailscale package)?", - true, - ); - if (!install) { - runtime.error("tailscaled is required for user-space funnel. Aborting."); - runtime.exit(1); - } - logVerbose("Installing tailscaled via Homebrew…"); - await exec("brew", ["install", "tailscale"]); -} - type ExecErrorDetails = { stdout?: unknown; stderr?: unknown; @@ -315,96 +259,6 @@ async function execWithSudoFallback( } } -export async function ensureFunnel( - port: number, - exec: typeof runExec = runExec, - runtime: RuntimeEnv = defaultRuntime, - prompt: typeof promptYesNo = promptYesNo, -) { - // Ensure Funnel is enabled and publish the webhook port. - try { - const tailscaleBin = await getTailscaleBinary(); - const statusOut = (await exec(tailscaleBin, ["funnel", "status", "--json"])).stdout.trim(); - const parsed = statusOut ? parsePossiblyNoisyJsonObject(statusOut) : {}; - if (!parsed || Object.keys(parsed).length === 0) { - runtime.error(danger("Tailscale Funnel is not enabled on this tailnet/device.")); - runtime.error( - info( - "Enable in admin console: https://login.tailscale.com/admin (see https://tailscale.com/kb/1223/funnel)", - ), - ); - runtime.error( - info( - "macOS user-space tailscaled docs: https://github.com/tailscale/tailscale/wiki/Tailscaled-on-macOS", - ), - ); - const proceed = await prompt("Attempt local setup with user-space tailscaled?", true); - if (!proceed) { - runtime.exit(1); - } - await ensureBinary("brew", exec, runtime); - await ensureGoInstalled(exec, prompt, runtime); - await ensureTailscaledInstalled(exec, prompt, runtime); - } - - logVerbose(`Enabling funnel on port ${port}…`); - // Attempt with fallback - const { stdout } = await execWithSudoFallback( - exec, - tailscaleBin, - ["funnel", "--yes", "--bg", `${port}`], - { - maxBuffer: 200_000, - timeoutMs: 15_000, - }, - ); - if (stdout.trim()) { - console.log(stdout.trim()); - } - } catch (err) { - const errOutput = err as { stdout?: unknown; stderr?: unknown }; - const stdout = typeof errOutput.stdout === "string" ? errOutput.stdout : ""; - const stderr = typeof errOutput.stderr === "string" ? errOutput.stderr : ""; - if (stdout.includes("Funnel is not enabled")) { - console.error(danger("Funnel is not enabled on this tailnet/device.")); - const linkMatch = stdout.match(/https?:\/\/\S+/); - if (linkMatch) { - console.error(info(`Enable it here: ${linkMatch[0]}`)); - } else { - console.error( - info( - "Enable in admin console: https://login.tailscale.com/admin (see https://tailscale.com/kb/1223/funnel)", - ), - ); - } - } - if (stderr.includes("client version") || stdout.includes("client version")) { - console.error( - warn( - "Tailscale client/server version mismatch detected; try updating tailscale/tailscaled.", - ), - ); - } - runtime.error("Failed to enable Tailscale Funnel. Is it allowed on your tailnet?"); - runtime.error( - info( - `Tip: Funnel is optional for OpenClaw. You can keep running the web gateway without it: \`${formatCliCommand("openclaw gateway")}\``, - ), - ); - if (shouldLogVerbose()) { - const rich = isRich(); - if (stdout.trim()) { - runtime.error(colorize(rich, theme.muted, `stdout: ${stdout.trim()}`)); - } - if (stderr.trim()) { - runtime.error(colorize(rich, theme.muted, `stderr: ${stderr.trim()}`)); - } - runtime.error(err as Error); - } - runtime.exit(1); - } -} - export async function enableTailscaleServe( port: number, exec: typeof runExec = runExec,