chore(deadcode): remove unused helper paths

This commit is contained in:
Vincent Koc
2026-06-22 03:34:49 +08:00
parent e21164933a
commit 9adf3d92bd
4 changed files with 2 additions and 298 deletions

View File

@@ -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({

View File

@@ -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,

View File

@@ -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<typeof vi.fn>,
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"));

View File

@@ -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<string, unknown> {
const trimmed = stdout.trim();
@@ -190,57 +185,6 @@ async function getTailscaleBinary(): Promise<string> {
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,