refactor: dedupe path lowercase helpers
This commit is contained in:
@@ -3,6 +3,7 @@ import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { resolveChannelMediaMaxBytes } from "openclaw/plugin-sdk/media-runtime";
|
||||
import { lowercasePreservingWhitespace } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { resolveBlueBubblesAccount } from "./accounts.js";
|
||||
import { sendBlueBubblesAttachment } from "./attachments.js";
|
||||
import { basenameFromMediaSource, safeFileURLToPath } from "./local-file-access.js";
|
||||
@@ -73,9 +74,9 @@ function isPathInsideRoot(candidate: string, root: string): boolean {
|
||||
? normalizedRoot
|
||||
: normalizedRoot + path.sep;
|
||||
if (process.platform === "win32") {
|
||||
const candidateLower = normalizedCandidate.toLowerCase();
|
||||
const rootLower = normalizedRoot.toLowerCase();
|
||||
const rootWithSepLower = rootWithSep.toLowerCase();
|
||||
const candidateLower = lowercasePreservingWhitespace(normalizedCandidate);
|
||||
const rootLower = lowercasePreservingWhitespace(normalizedRoot);
|
||||
const rootWithSepLower = lowercasePreservingWhitespace(rootWithSep);
|
||||
return candidateLower === rootLower || candidateLower.startsWith(rootWithSepLower);
|
||||
}
|
||||
return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(rootWithSep);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { existsSync, readFileSync, readdirSync, realpathSync } from "node:fs";
|
||||
import type { Dirent } from "node:fs";
|
||||
import { delimiter, dirname, join } from "node:path";
|
||||
import { lowercasePreservingWhitespace } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { CLIENT_ID_KEYS, CLIENT_SECRET_KEYS } from "./oauth.shared.js";
|
||||
|
||||
type CredentialFs = {
|
||||
@@ -96,7 +97,9 @@ function resolveGeminiCliDirs(geminiPath: string, resolvedPath: string): string[
|
||||
for (const candidate of candidates) {
|
||||
for (const searchDir of resolveGeminiCliSearchDirs(candidate)) {
|
||||
const key =
|
||||
process.platform === "win32" ? searchDir.replace(/\\/g, "/").toLowerCase() : searchDir;
|
||||
process.platform === "win32"
|
||||
? lowercasePreservingWhitespace(searchDir.replace(/\\/g, "/"))
|
||||
: searchDir;
|
||||
if (seen.has(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,10 @@ import path from "node:path";
|
||||
import { Readable, Writable } from "node:stream";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import {
|
||||
lowercasePreservingWhitespace,
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
export type LobsterEnvelope =
|
||||
| {
|
||||
@@ -168,7 +172,7 @@ type PipelineRuntimeContext = {
|
||||
|
||||
function normalizeForCwdSandbox(p: string): string {
|
||||
const normalized = path.normalize(p);
|
||||
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
||||
return process.platform === "win32" ? lowercasePreservingWhitespace(normalized) : normalized;
|
||||
}
|
||||
|
||||
export function resolveLobsterCwd(cwdRaw: unknown): string {
|
||||
@@ -305,7 +309,7 @@ async function resolveWorkflowFile(candidate: string, cwd: string) {
|
||||
if (!fileStat.isFile()) {
|
||||
throw new Error("Workflow path is not a file");
|
||||
}
|
||||
const ext = path.extname(resolved).toLowerCase();
|
||||
const ext = normalizeLowercaseStringOrEmpty(path.extname(resolved));
|
||||
if (![".lobster", ".yaml", ".yml", ".json"].includes(ext)) {
|
||||
throw new Error("Workflow file must end in .lobster, .yaml, .yml, or .json");
|
||||
}
|
||||
|
||||
@@ -18,7 +18,10 @@ import {
|
||||
type MemoryLightDreamingConfig,
|
||||
type MemoryRemDreamingConfig,
|
||||
} from "openclaw/plugin-sdk/memory-core-host-status";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
lowercasePreservingWhitespace,
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import { writeDailyDreamingPhaseBlock } from "./dreaming-markdown.js";
|
||||
import { generateAndAppendDreamNarrative, type NarrativePhaseData } from "./dreaming-narrative.js";
|
||||
import {
|
||||
@@ -425,7 +428,7 @@ type SessionIngestionCollectionResult = {
|
||||
|
||||
function normalizeWorkspaceKey(workspaceDir: string): string {
|
||||
const resolved = path.resolve(workspaceDir).replace(/\\/g, "/");
|
||||
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
||||
return process.platform === "win32" ? lowercasePreservingWhitespace(resolved) : resolved;
|
||||
}
|
||||
|
||||
function resolveSessionIngestionStatePath(workspaceDir: string): string {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { lowercasePreservingWhitespace } from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
export async function pathExists(filePath: string): Promise<boolean> {
|
||||
try {
|
||||
@@ -12,5 +13,7 @@ export async function pathExists(filePath: string): Promise<boolean> {
|
||||
|
||||
export async function resolveArtifactKey(absolutePath: string): Promise<string> {
|
||||
const canonicalPath = await fs.realpath(absolutePath).catch(() => path.resolve(absolutePath));
|
||||
return process.platform === "win32" ? canonicalPath.toLowerCase() : canonicalPath;
|
||||
return process.platform === "win32"
|
||||
? lowercasePreservingWhitespace(canonicalPath)
|
||||
: canonicalPath;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
resolveAgentIdFromSessionKey,
|
||||
} from "../routing/session-key.js";
|
||||
import {
|
||||
lowercasePreservingWhitespace,
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
readStringValue,
|
||||
resolvePrimaryStringValue,
|
||||
@@ -293,7 +294,7 @@ function normalizePathForComparison(input: string): string {
|
||||
// Keep lexical path for non-existent directories.
|
||||
}
|
||||
if (process.platform === "win32") {
|
||||
return normalizeLowercaseStringOrEmpty(normalized);
|
||||
return lowercasePreservingWhitespace(normalized);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { detectMime } from "../media/mime.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
||||
import { lowercasePreservingWhitespace } from "../shared/string-coerce.js";
|
||||
import { resolveFileWithinRoot } from "./file-resolver.js";
|
||||
|
||||
export const A2UI_PATH = "/__openclaw__/a2ui";
|
||||
@@ -133,7 +133,7 @@ export function injectCanvasLiveReload(html: string): string {
|
||||
</script>
|
||||
`.trim();
|
||||
|
||||
const idx = normalizeLowercaseStringOrEmpty(html).lastIndexOf("</body>");
|
||||
const idx = lowercasePreservingWhitespace(html).lastIndexOf("</body>");
|
||||
if (idx >= 0) {
|
||||
return `${html.slice(0, idx)}\n${snippet}\n${html.slice(idx)}`;
|
||||
}
|
||||
@@ -181,7 +181,7 @@ export async function handleA2uiHttpRequest(
|
||||
}
|
||||
|
||||
try {
|
||||
const lower = normalizeLowercaseStringOrEmpty(result.realPath);
|
||||
const lower = lowercasePreservingWhitespace(result.realPath);
|
||||
const mime =
|
||||
lower.endsWith(".html") || lower.endsWith(".htm")
|
||||
? "text/html"
|
||||
|
||||
@@ -15,7 +15,7 @@ import { resolveStateDir } from "../config/paths.js";
|
||||
import { isTruthyEnvValue } from "../infra/env.js";
|
||||
import { detectMime } from "../media/mime.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
||||
import { lowercasePreservingWhitespace } from "../shared/string-coerce.js";
|
||||
import { ensureDir, resolveUserPath } from "../utils.js";
|
||||
import {
|
||||
CANVAS_HOST_PATH,
|
||||
@@ -394,7 +394,7 @@ export async function createCanvasHostHandler(
|
||||
await handle.close().catch(() => {});
|
||||
}
|
||||
|
||||
const lower = normalizeLowercaseStringOrEmpty(realPath);
|
||||
const lower = lowercasePreservingWhitespace(realPath);
|
||||
const mime =
|
||||
lower.endsWith(".html") || lower.endsWith(".htm")
|
||||
? "text/html"
|
||||
@@ -465,7 +465,7 @@ export async function startCanvasHost(opts: CanvasHostServerOpts): Promise<Canva
|
||||
|
||||
const bindHost = opts.listenHost?.trim() || "127.0.0.1";
|
||||
const server: Server = http.createServer((req, res) => {
|
||||
if (normalizeLowercaseStringOrEmpty(req.headers.upgrade ?? "") === "websocket") {
|
||||
if (lowercasePreservingWhitespace(String(req.headers.upgrade ?? "")) === "websocket") {
|
||||
return;
|
||||
}
|
||||
void (async () => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { asNullableRecord } from "../shared/record-coerce.js";
|
||||
import {
|
||||
lowercasePreservingWhitespace,
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalLowercaseString,
|
||||
} from "../shared/string-coerce.js";
|
||||
@@ -302,7 +303,7 @@ function resolveExecutionConfig(
|
||||
|
||||
function normalizePathForComparison(input: string): string {
|
||||
const normalized = path.resolve(input);
|
||||
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
||||
return process.platform === "win32" ? lowercasePreservingWhitespace(normalized) : normalized;
|
||||
}
|
||||
|
||||
function formatLocalIsoDay(epochMs: number): string {
|
||||
|
||||
@@ -29,6 +29,7 @@ export * from "../utils/reaction-level.js";
|
||||
export * from "../utils/with-timeout.js";
|
||||
export {
|
||||
hasNonEmptyString,
|
||||
lowercasePreservingWhitespace,
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeNullableString,
|
||||
normalizeOptionalLowercaseString,
|
||||
|
||||
@@ -22,6 +22,10 @@ export function normalizeLowercaseStringOrEmpty(value: unknown): string {
|
||||
return normalizeOptionalLowercaseString(value) ?? "";
|
||||
}
|
||||
|
||||
export function lowercasePreservingWhitespace(value: string): string {
|
||||
return value.toLowerCase();
|
||||
}
|
||||
|
||||
export function resolvePrimaryStringValue(value: unknown): string | undefined {
|
||||
if (typeof value === "string") {
|
||||
return normalizeOptionalString(value);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ServerResponse } from "node:http";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
||||
import { lowercasePreservingWhitespace } from "../shared/string-coerce.js";
|
||||
|
||||
export function createMockServerResponse(): ServerResponse & { body?: string } {
|
||||
const headers: Record<string, string> = {};
|
||||
@@ -14,10 +14,10 @@ export function createMockServerResponse(): ServerResponse & { body?: string } {
|
||||
headersSent: false,
|
||||
statusCode: 200,
|
||||
setHeader: (key: string, value: string) => {
|
||||
headers[normalizeLowercaseStringOrEmpty(key)] = value;
|
||||
headers[lowercasePreservingWhitespace(key)] = value;
|
||||
return res;
|
||||
},
|
||||
getHeader: (key: string) => headers[normalizeLowercaseStringOrEmpty(key)],
|
||||
getHeader: (key: string) => headers[lowercasePreservingWhitespace(key)],
|
||||
end: (body?: string) => {
|
||||
res.headersSent = true;
|
||||
res.body = body;
|
||||
|
||||
Reference in New Issue
Block a user