Skip to content

Commit 7244b7c

Browse files
committed
Remove empty hash directories
1 parent 96cce4a commit 7244b7c

File tree

4 files changed

+34
-4
lines changed

4 files changed

+34
-4
lines changed

packages/file-storage/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
This is the changelog for [`file-storage`](https://github.com/remix-run/remix/tree/v3/packages/file-storage). It follows [semantic versioning](https://semver.org/).
44

5+
## HEAD
6+
7+
- Remove hash directories when they are empty in `LocalFileStorage`
8+
59
## v0.8.0 (2025-07-21)
610

711
- Renamed package from `@mjackson/file-storage` to `@remix-run/file-storage`

packages/file-storage/src/lib/local-file-storage.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,29 @@ describe('LocalFileStorage', () => {
4545
assert.equal(await storage.get('hello'), null);
4646
});
4747

48+
it('removes empty hash directories after removing files', async () => {
49+
let storage = new LocalFileStorage(directory);
50+
let file = new File(['Test content'], 'test.txt', { type: 'text/plain' });
51+
52+
// Set a file
53+
await storage.set('test-key', file);
54+
55+
// Verify subdirectories exist
56+
let subdirs = fs.readdirSync(directory).filter((name) => {
57+
return fs.statSync(path.join(directory, name)).isDirectory();
58+
});
59+
assert.ok(subdirs.length > 0);
60+
61+
// Remove the file
62+
await storage.remove('test-key');
63+
64+
// Verify no subdirectories remain
65+
let subdirsAfter = fs.readdirSync(directory).filter((name) => {
66+
return fs.statSync(path.join(directory, name)).isDirectory();
67+
});
68+
assert.equal(subdirsAfter.length, 0);
69+
});
70+
4871
it('lists files with pagination', async () => {
4972
let storage = new LocalFileStorage(directory);
5073
let allKeys = ['a', 'b', 'c', 'd', 'e'];

packages/file-storage/src/lib/local-file-storage.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,16 @@ export class LocalFileStorage implements FileStorage {
127127
}
128128

129129
async remove(key: string): Promise<void> {
130-
let { filePath, metaPath } = await this.#getPaths(key);
130+
let { directory, filePath, metaPath } = await this.#getPaths(key);
131131

132132
try {
133133
await Promise.all([fsp.unlink(filePath), fsp.unlink(metaPath)]);
134+
135+
// Check if directory is empty and remove it if so
136+
let files = await fsp.readdir(directory);
137+
if (files.length === 0) {
138+
await fsp.rmdir(directory);
139+
}
134140
} catch (error) {
135141
if (!isNoEntityError(error)) {
136142
throw error;

packages/file-storage/src/lib/memory-file-storage.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ import type { FileStorage, ListOptions, ListResult } from './file-storage.ts';
22

33
/**
44
* A simple, in-memory implementation of the `FileStorage` interface.
5-
*
6-
* Note: Any files you put in storage will have their entire contents buffered in memory, so this is not suitable for large files
7-
* in production scenarios.
85
*/
96
export class MemoryFileStorage implements FileStorage {
107
#map = new Map<string, File>();

0 commit comments

Comments
 (0)