From 761e12008d82138476bcbfd527607446824478dd Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 7 Apr 2026 15:36:33 +0100 Subject: [PATCH] refactor: dedupe infra lowercase helpers --- src/cli/devices-cli.ts | 3 ++- src/cli/logs-cli.ts | 3 ++- src/infra/diagnostic-flags.ts | 4 ++-- src/infra/heartbeat-runner.ts | 7 +++++-- src/infra/runtime-status.ts | 8 +++++++- src/infra/system-events.ts | 10 ++-------- src/infra/system-presence.ts | 21 +++++++++------------ src/terminal/health-style.ts | 3 ++- 8 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/cli/devices-cli.ts b/src/cli/devices-cli.ts index 59f1dc14447..5e146c4d165 100644 --- a/src/cli/devices-cli.ts +++ b/src/cli/devices-cli.ts @@ -9,6 +9,7 @@ import { } from "../infra/device-pairing.js"; import { formatTimeAgo } from "../infra/format-time/format-relative.ts"; import { defaultRuntime } from "../runtime.js"; +import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js"; import { getTerminalTableWidth, renderTable } from "../terminal/table.js"; import { theme } from "../terminal/theme.js"; import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js"; @@ -100,7 +101,7 @@ function normalizeErrorMessage(error: unknown): string { } function shouldUseLocalPairingFallback(opts: DevicesRpcOpts, error: unknown): boolean { - const message = normalizeErrorMessage(error).toLowerCase(); + const message = normalizeLowercaseStringOrEmpty(normalizeErrorMessage(error)); if (!message.includes("pairing required")) { return false; } diff --git a/src/cli/logs-cli.ts b/src/cli/logs-cli.ts index 70e9ca4f198..8a630c0f67a 100644 --- a/src/cli/logs-cli.ts +++ b/src/cli/logs-cli.ts @@ -6,6 +6,7 @@ import { formatErrorMessage } from "../infra/errors.js"; import { readConfiguredLogTail } from "../logging/log-tail.js"; import { parseLogLine } from "../logging/parse-log-line.js"; import { formatTimestamp, isValidTimeZone } from "../logging/timestamps.js"; +import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js"; import { formatDocsLink } from "../terminal/links.js"; import { clearActiveProgressLine } from "../terminal/progress-line.js"; import { createSafeStreamWriter } from "../terminal/stream-writer.js"; @@ -94,7 +95,7 @@ function normalizeErrorMessage(error: unknown): string { } function shouldUseLocalLogsFallback(opts: LogsCliOptions, error: unknown): boolean { - const message = normalizeErrorMessage(error).toLowerCase(); + const message = normalizeLowercaseStringOrEmpty(normalizeErrorMessage(error)); if (!message.includes("pairing required")) { return false; } diff --git a/src/infra/diagnostic-flags.ts b/src/infra/diagnostic-flags.ts index 7df89593008..9fe0ce4d51f 100644 --- a/src/infra/diagnostic-flags.ts +++ b/src/infra/diagnostic-flags.ts @@ -8,10 +8,10 @@ function parseEnvFlags(raw?: string): string[] { return []; } const trimmed = raw.trim(); - if (!trimmed) { + const lowered = normalizeLowercaseStringOrEmpty(trimmed); + if (!lowered) { return []; } - const lowered = trimmed.toLowerCase(); if (["0", "false", "off", "none"].includes(lowered)) { return []; } diff --git a/src/infra/heartbeat-runner.ts b/src/infra/heartbeat-runner.ts index 3939caaaea9..11390af43a4 100644 --- a/src/infra/heartbeat-runner.ts +++ b/src/infra/heartbeat-runner.ts @@ -49,7 +49,10 @@ import { toAgentStoreSessionKey, } from "../routing/session-key.js"; import { defaultRuntime, type RuntimeEnv } from "../runtime.js"; -import { normalizeOptionalString } from "../shared/string-coerce.js"; +import { + normalizeLowercaseStringOrEmpty, + normalizeOptionalString, +} from "../shared/string-coerce.js"; import { escapeRegExp } from "../utils.js"; import { formatErrorMessage, hasErrnoCode } from "./errors.js"; import { isWithinActiveHours } from "./heartbeat-active-hours.js"; @@ -257,7 +260,7 @@ function resolveHeartbeatSession( }; } - const normalized = trimmed.toLowerCase(); + const normalized = normalizeLowercaseStringOrEmpty(trimmed); if (normalized === "main" || normalized === "global") { return { sessionKey: mainSessionKey, diff --git a/src/infra/runtime-status.ts b/src/infra/runtime-status.ts index e826c5cd32e..fcb28ecf815 100644 --- a/src/infra/runtime-status.ts +++ b/src/infra/runtime-status.ts @@ -1,3 +1,5 @@ +import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js"; + type RuntimeStatusFormatInput = { status?: string; pid?: number; @@ -17,7 +19,11 @@ export function formatRuntimeStatusWithDetails({ fullDetails.push(`pid ${pid}`); } const normalizedState = state?.trim(); - if (normalizedState && normalizedState.toLowerCase() !== runtimeStatus.toLowerCase()) { + if ( + normalizedState && + normalizeLowercaseStringOrEmpty(normalizedState) !== + normalizeLowercaseStringOrEmpty(runtimeStatus) + ) { fullDetails.push(`state ${normalizedState}`); } for (const detail of details) { diff --git a/src/infra/system-events.ts b/src/infra/system-events.ts index 1e73e80cb0c..28411e42f1e 100644 --- a/src/infra/system-events.ts +++ b/src/infra/system-events.ts @@ -3,6 +3,7 @@ // events ephemeral. Events are session-scoped and require an explicit key. import { resolveGlobalMap } from "../shared/global-singleton.js"; +import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js"; import { mergeDeliveryContext, normalizeDeliveryContext, @@ -45,14 +46,7 @@ function requireSessionKey(key?: string | null): string { } function normalizeContextKey(key?: string | null): string | null { - if (!key) { - return null; - } - const trimmed = key.trim(); - if (!trimmed) { - return null; - } - return trimmed.toLowerCase(); + return normalizeOptionalLowercaseString(key) ?? null; } function getSessionQueue(sessionKey: string): SessionQueue | undefined { diff --git a/src/infra/system-presence.ts b/src/infra/system-presence.ts index e8b4c394f28..7cd7d40de94 100644 --- a/src/infra/system-presence.ts +++ b/src/infra/system-presence.ts @@ -1,5 +1,9 @@ import { spawnSync } from "node:child_process"; import os from "node:os"; +import { + normalizeLowercaseStringOrEmpty, + normalizeOptionalLowercaseString, +} from "../shared/string-coerce.js"; import { resolveRuntimeServiceVersion } from "../version.js"; import { pickBestEffortPrimaryLanIPv4 } from "./network-discovery-display.js"; @@ -34,14 +38,7 @@ const TTL_MS = 5 * 60 * 1000; // 5 minutes const MAX_ENTRIES = 200; function normalizePresenceKey(key: string | undefined): string | undefined { - if (!key) { - return undefined; - } - const trimmed = key.trim(); - if (!trimmed) { - return undefined; - } - return trimmed.toLowerCase(); + return normalizeOptionalLowercaseString(key); } function resolvePrimaryIPv4(): string | undefined { @@ -107,7 +104,7 @@ function initSelfPresence() { text, ts: Date.now(), }; - const key = host.toLowerCase(); + const key = normalizeLowercaseStringOrEmpty(host); entries.set(key, selfEntry); } @@ -122,7 +119,7 @@ function ensureSelfPresence() { function touchSelfPresence() { const host = os.hostname(); - const key = host.toLowerCase(); + const key = normalizeLowercaseStringOrEmpty(host); const existing = entries.get(key); if (existing) { entries.set(key, { ...existing, ts: Date.now() }); @@ -200,7 +197,7 @@ export function updateSystemPresence(payload: SystemPresencePayload): SystemPres normalizePresenceKey(parsed.host) || parsed.ip || parsed.text.slice(0, 64) || - os.hostname().toLowerCase(); + normalizeLowercaseStringOrEmpty(os.hostname()); const hadExisting = entries.has(key); const existing = entries.get(key) ?? ({} as SystemPresence); const merged: SystemPresence = { @@ -247,7 +244,7 @@ export function updateSystemPresence(payload: SystemPresencePayload): SystemPres export function upsertPresence(key: string, presence: Partial) { ensureSelfPresence(); - const normalizedKey = normalizePresenceKey(key) ?? os.hostname().toLowerCase(); + const normalizedKey = normalizePresenceKey(key) ?? normalizeLowercaseStringOrEmpty(os.hostname()); const existing = entries.get(normalizedKey) ?? ({} as SystemPresence); const roles = mergeStringList(existing.roles, presence.roles); const scopes = mergeStringList(existing.scopes, presence.scopes); diff --git a/src/terminal/health-style.ts b/src/terminal/health-style.ts index 67bcfa62ccb..b6c18c3d160 100644 --- a/src/terminal/health-style.ts +++ b/src/terminal/health-style.ts @@ -1,3 +1,4 @@ +import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js"; import { theme } from "./theme.js"; export function styleHealthChannelLine(line: string, rich: boolean): string { @@ -12,7 +13,7 @@ export function styleHealthChannelLine(line: string, rich: boolean): string { const label = line.slice(0, colon + 1); const detail = line.slice(colon + 1).trimStart(); - const normalized = detail.toLowerCase(); + const normalized = normalizeLowercaseStringOrEmpty(detail); const applyPrefix = (prefix: string, color: (value: string) => string) => `${label} ${color(detail.slice(0, prefix.length))}${detail.slice(prefix.length)}`;