Skip to content

Commit 04bf67b

Browse files
cursoragentvirattt
andcommitted
fix(memory): handle Bun Windows mkdir EEXIST
Co-authored-by: Virat Singh <virattt@users.noreply.github.com>
1 parent 8819ad7 commit 04bf67b

4 files changed

Lines changed: 67 additions & 5 deletions

File tree

src/memory/database.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { mkdir } from 'node:fs/promises';
21
import { dirname } from 'node:path';
2+
import { ensureDirectory } from '../utils/ensure-directory.js';
33
import type {
44
MemoryChunk,
55
MemoryKeywordCandidate,
@@ -117,7 +117,7 @@ export class MemoryDatabase {
117117
private constructor(private readonly db: SqliteDatabase) {}
118118

119119
static async create(path: string): Promise<MemoryDatabase> {
120-
await mkdir(dirname(path), { recursive: true });
120+
await ensureDirectory(dirname(path));
121121
const db = await MemoryDatabase.openSqlite(path);
122122
const memoryDb = new MemoryDatabase(db);
123123
memoryDb.db.exec(CREATE_SCHEMA_SQL);

src/memory/store.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
1+
import { readdir, readFile, writeFile } from 'node:fs/promises';
22
import { dirname, join, normalize, relative } from 'node:path';
33
import type { MemoryReadOptions, MemoryReadResult, MemorySessionContext } from './types.js';
44
import { estimateTokens } from '../utils/tokens.js';
55
import { getDexterDir } from '../utils/paths.js';
6+
import { ensureDirectory } from '../utils/ensure-directory.js';
67

78
const MEMORY_DIRNAME = 'memory';
89
const LONG_TERM_FILE = 'MEMORY.md';
@@ -36,7 +37,7 @@ export class MemoryStore {
3637
}
3738

3839
async ensureDirectoryExists(): Promise<void> {
39-
await mkdir(this.getMemoryDir(), { recursive: true });
40+
await ensureDirectory(this.getMemoryDir());
4041
}
4142

4243
async readMemoryFile(path: string): Promise<string> {
@@ -50,7 +51,7 @@ export class MemoryStore {
5051

5152
async writeMemoryFile(path: string, content: string): Promise<void> {
5253
const resolved = this.resolveMemoryPath(path);
53-
await mkdir(dirname(resolved), { recursive: true });
54+
await ensureDirectory(dirname(resolved));
5455
await writeFile(resolved, content, 'utf-8');
5556
}
5657

src/utils/ensure-directory.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
2+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
3+
import { tmpdir } from 'node:os';
4+
import { join } from 'node:path';
5+
import { ensureDirectory } from './ensure-directory.js';
6+
7+
describe('ensureDirectory', () => {
8+
let rootDir: string;
9+
10+
beforeEach(() => {
11+
rootDir = mkdtempSync(join(tmpdir(), 'dexter-ensure-dir-'));
12+
});
13+
14+
afterEach(() => {
15+
rmSync(rootDir, { recursive: true, force: true });
16+
});
17+
18+
test('succeeds when the directory already exists', async () => {
19+
const dir = join(rootDir, 'memory');
20+
mkdirSync(dir, { recursive: true });
21+
22+
await expect(ensureDirectory(dir)).resolves.toBeUndefined();
23+
});
24+
25+
test('creates missing nested directories', async () => {
26+
const dir = join(rootDir, '.dexter', 'memory', 'nested');
27+
28+
await expect(ensureDirectory(dir)).resolves.toBeUndefined();
29+
});
30+
31+
test('throws when path exists as a file', async () => {
32+
const filePath = join(rootDir, 'not-a-dir');
33+
writeFileSync(filePath, 'x');
34+
35+
await expect(ensureDirectory(filePath)).rejects.toThrow();
36+
});
37+
});

src/utils/ensure-directory.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { mkdir, stat } from 'node:fs/promises';
2+
3+
type NodeErrorWithCode = Error & { code?: string };
4+
5+
/**
6+
* Work around Bun-on-Windows mkdir recursive EEXIST behavior by accepting
7+
* EEXIST only when the target already exists as a directory.
8+
*/
9+
export async function ensureDirectory(path: string): Promise<void> {
10+
try {
11+
await mkdir(path, { recursive: true });
12+
return;
13+
} catch (error) {
14+
const err = error as NodeErrorWithCode;
15+
if (err.code !== 'EEXIST') {
16+
throw error;
17+
}
18+
const existing = await stat(path).catch(() => null);
19+
if (existing?.isDirectory()) {
20+
return;
21+
}
22+
throw error;
23+
}
24+
}

0 commit comments

Comments
 (0)