mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-06 10:02:30 +00:00
chore(deadcode): remove unused helper paths
This commit is contained in:
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"));
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user