Skip to content

Commit 4d160a7

Browse files
author
cacaview
committed
fix: Windows path handling - replace hardcoded forward slashes with Node.js path module
This commit fixes Windows compatibility issues caused by hardcoded forward slash (/) path separators. The changes ensure proper path handling across both Windows and Unix-like systems. Changes: - src/services/embedding.ts: Use path.join() for cache directory path - src/services/sqlite/connection-manager.ts: Use path.dirname() instead of lastIndexOf('/') - src/services/tags.ts: Use path.sep and path.normalize() for cross-platform path handling - src/web/app.js: Normalize Windows backslashes before splitting paths - tests/windows-path.test.ts: Add comprehensive tests for Windows path handling All existing tests pass (13 tests), and new tests verify correct behavior on both Windows and Unix paths.
1 parent b43a88d commit 4d160a7

5 files changed

Lines changed: 104 additions & 6 deletions

File tree

src/services/embedding.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { pipeline, env } from "@xenova/transformers";
22
import { CONFIG } from "../config.js";
33
import { log } from "./logger.js";
4+
import { join } from "node:path";
45

56
env.allowLocalModels = true;
67
env.allowRemoteModels = true;
7-
env.cacheDir = CONFIG.storagePath + "/.cache";
8+
env.cacheDir = join(CONFIG.storagePath, ".cache");
89

910
const TIMEOUT_MS = 30000;
1011
const GLOBAL_EMBEDDING_KEY = Symbol.for("opencode-mem.embedding.instance");

src/services/sqlite/connection-manager.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Database } from "bun:sqlite";
22
import * as sqliteVec from "sqlite-vec";
33
import { existsSync, mkdirSync } from "node:fs";
4+
import { dirname } from "node:path";
45
import { log } from "../logger.js";
56
import { CONFIG } from "../../config.js";
67

@@ -98,14 +99,14 @@ export class ConnectionManager {
9899
}
99100
}
100101

101-
getConnection(dbPath: string): Database {
102+
getConnection(dbPath: string): Database {
102103
if (this.connections.has(dbPath)) {
103104
return this.connections.get(dbPath)!;
104105
}
105106

106107
this.configureSqlite();
107108

108-
const dir = dbPath.substring(0, dbPath.lastIndexOf("/"));
109+
const dir = dirname(dbPath);
109110
if (!existsSync(dir)) {
110111
mkdirSync(dir, { recursive: true });
111112
}

src/services/tags.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { createHash } from "node:crypto";
22
import { execSync } from "node:child_process";
33
import { CONFIG } from "../config.js";
4+
import { sep, normalize } from "node:path";
45

56
function sha256(input: string): string {
67
return createHash("sha256").update(input).digest("hex").slice(0, 16);
@@ -47,7 +48,9 @@ export function getGitRepoUrl(directory: string): string | null {
4748
}
4849

4950
export function getProjectName(directory: string): string {
50-
const parts = directory.split("/").filter((p) => p);
51+
// Normalize path to handle both Unix and Windows separators
52+
const normalized = normalize(directory);
53+
const parts = normalized.split(sep).filter((p) => p);
5154
return parts[parts.length - 1] || directory;
5255
}
5356

src/web/app.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,9 @@ function renderMemoryCard(memory) {
171171
? `<span class="similarity-score">${memory.similarity}%</span>`
172172
: "";
173173

174-
let displayInfo = memory.displayName || memory.id;
174+
let displayInfo = memory.displayName || memory.id;
175175
if (memory.projectPath) {
176-
const pathParts = memory.projectPath.split("/");
176+
const pathParts = memory.projectPath.replace(/\\/g, "/").split("/").filter((p) => p);
177177
displayInfo = pathParts[pathParts.length - 1] || memory.projectPath;
178178
}
179179

tests/windows-path.test.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { describe, it, expect } from "bun:test";
2+
import { getProjectName } from "../src/services/tags.js";
3+
import { dirname } from "node:path";
4+
import { join } from "node:path";
5+
import * as fs from "node:fs";
6+
7+
describe("Windows Path Handling", () => {
8+
describe("getProjectName", () => {
9+
it("should handle Unix-style paths correctly", () => {
10+
const result = getProjectName("/home/user/projects/my-project");
11+
expect(result).toBe("my-project");
12+
});
13+
14+
it("should handle Windows-style paths correctly", () => {
15+
const result = getProjectName("C:\\Users\\user\\projects\\my-project");
16+
expect(result).toBe("my-project");
17+
});
18+
19+
it("should handle Windows-style paths with forward slashes", () => {
20+
const result = getProjectName("C:/Users/user/projects/my-project");
21+
expect(result).toBe("my-project");
22+
});
23+
24+
it("should handle relative Windows paths", () => {
25+
const result = getProjectName("..\\projects\\my-project");
26+
expect(result).toBe("my-project");
27+
});
28+
29+
it("should return the input if no path separators found", () => {
30+
const result = getProjectName("my-project");
31+
expect(result).toBe("my-project");
32+
});
33+
});
34+
35+
describe("path.join for cache directory", () => {
36+
it("should join paths correctly on Windows", () => {
37+
const storagePath = "C:\\Users\\user\\.opencode-mem";
38+
const cacheDir = join(storagePath, ".cache");
39+
expect(cacheDir).toContain(".cache");
40+
expect(cacheDir).not.toContain("//");
41+
});
42+
43+
it("should join paths correctly on Unix", () => {
44+
const storagePath = "/home/user/.opencode-mem";
45+
const cacheDir = join(storagePath, ".cache");
46+
expect(cacheDir).toContain(".cache");
47+
expect(cacheDir).not.toContain("//");
48+
});
49+
});
50+
51+
describe("dirname for database path", () => {
52+
it("should extract directory correctly from Windows path", () => {
53+
const dbPath = "C:\\Users\\user\\.opencode-mem\\shards\\project.db";
54+
const dir = dirname(dbPath);
55+
expect(dir).toBe("C:\\Users\\user\\.opencode-mem\\shards");
56+
});
57+
58+
it("should extract directory correctly from Unix path", () => {
59+
const dbPath = "/home/user/.opencode-mem/shards/project.db";
60+
const dir = dirname(dbPath);
61+
expect(dir).toBe("/home/user/.opencode-mem/shards");
62+
});
63+
64+
it("should handle paths with mixed separators", () => {
65+
const dbPath = "C:\\Users\\user/.opencode-mem\\shards/project.db";
66+
const dir = dirname(dbPath);
67+
expect(dir).toContain("shards");
68+
});
69+
});
70+
71+
describe("Web UI path normalization", () => {
72+
it("should normalize Windows backslashes to forward slashes", () => {
73+
const projectPath = "C:\\Users\\user\\projects\\my-project";
74+
const normalized = projectPath.replace(/\\/g, "/");
75+
const parts = normalized.split("/").filter((p) => p);
76+
expect(parts[parts.length - 1]).toBe("my-project");
77+
});
78+
79+
it("should handle Unix paths without modification", () => {
80+
const projectPath = "/home/user/projects/my-project";
81+
const normalized = projectPath.replace(/\\/g, "/");
82+
const parts = normalized.split("/").filter((p) => p);
83+
expect(parts[parts.length - 1]).toBe("my-project");
84+
});
85+
86+
it("should handle mixed path separators", () => {
87+
const projectPath = "C:\\Users/user/projects\\my-project";
88+
const normalized = projectPath.replace(/\\/g, "/");
89+
const parts = normalized.split("/").filter((p) => p);
90+
expect(parts[parts.length - 1]).toBe("my-project");
91+
});
92+
});
93+
});

0 commit comments

Comments
 (0)