fix(openclaw): normalize Windows skill-loader URLs before fileURLToPath (#5679)
This commit is contained in:
@@ -4,10 +4,12 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import {
|
||||
safePath,
|
||||
normalizeModuleUrlToPath,
|
||||
loadSkill,
|
||||
loadTriagePrompt,
|
||||
loadCompactTriagePrompt,
|
||||
} from "./skill-loader.ts";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// safePath — path containment
|
||||
@@ -74,6 +76,31 @@ describe("loadSkill path traversal", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizeModuleUrlToPath", () => {
|
||||
it("normalizes raw Windows paths before fileURLToPath conversion", () => {
|
||||
const rawWindowsMetaUrl = "C:\\Users\\example\\openclaw\\index.ts";
|
||||
const result = normalizeModuleUrlToPath(rawWindowsMetaUrl);
|
||||
// Assert the decoded property directly rather than reconstructing via the function body
|
||||
expect(typeof result).toBe("string");
|
||||
expect(result).not.toContain("%5C");
|
||||
});
|
||||
|
||||
it("leaves already-correct file URLs unchanged", () => {
|
||||
const fileMetaUrl = "file:///C:/Users/example/openclaw/index.ts";
|
||||
const expected = fileURLToPath(fileMetaUrl);
|
||||
|
||||
expect(normalizeModuleUrlToPath(fileMetaUrl)).toBe(expected);
|
||||
});
|
||||
|
||||
it.skipIf(process.platform === "win32")(
|
||||
"passes POSIX absolute paths through unchanged",
|
||||
() => {
|
||||
const posixPath = "/home/user/openclaw/index.ts";
|
||||
expect(normalizeModuleUrlToPath(posixPath)).toBe(posixPath);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("loadCompactTriagePrompt", () => {
|
||||
it("keeps the core triage instructions without inlining the full skill body", () => {
|
||||
const prompt = loadCompactTriagePrompt();
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import * as path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import type { SkillsConfig, CategoryConfig } from "./types.ts";
|
||||
import { readText, exists } from "./fs-safe.ts";
|
||||
|
||||
@@ -84,6 +84,14 @@ function parseSkillFile(content: string): ParsedSkill {
|
||||
};
|
||||
}
|
||||
|
||||
/** @internal — exported for testing only */
|
||||
export function normalizeModuleUrlToPath(moduleUrl: string): string {
|
||||
const normalizedUrl = moduleUrl.startsWith("file:")
|
||||
? moduleUrl
|
||||
: pathToFileURL(moduleUrl).toString();
|
||||
return fileURLToPath(normalizedUrl);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Skill Loader
|
||||
// ============================================================================
|
||||
@@ -96,7 +104,7 @@ function resolveSkillsDir(): string {
|
||||
|
||||
// Strategy 1: import.meta.url (works in native ESM)
|
||||
try {
|
||||
const metaDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const metaDir = path.dirname(normalizeModuleUrlToPath(import.meta.url));
|
||||
candidates.push(path.join(metaDir, "skills"));
|
||||
candidates.push(path.join(metaDir, "..", "skills"));
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user