Skip to content

Commit 8e5c936

Browse files
shubh73claude
andcommitted
metro-file-map: Create fileMapCacheDirectory if it doesn't exist
If `fileMapCacheDirectory` points at a directory that doesn't exist yet, the file map cache is never written, so every restart crawls the project from scratch. The only sign is a warning at startup: ``` [metro-file-map] Cache write error : Error: ENOENT: no such file or directory, open '/path/to/project/node_modules/.cache/metro-file-map/metro-file-map-<hash>-<hash>' ``` The default, `os.tmpdir()`, always exists, so this only affects a custom directory - but a project-local one like `node_modules/.cache/metro-file-map` won't exist after any fresh install. The transform cache doesn't have this problem, because `FileStore` creates its directory when a write fails with `ENOENT`. This change does the same in `DiskCacheManager`: on `ENOENT`, create the directory (recursively) and retry the write once. Writes into an existing directory are unchanged. Changelog: ``` - **[Fix]**: Create `fileMapCacheDirectory` if it doesn't exist, rather than failing to persist the file map cache ``` Test plan: New test in `DiskCacheManager-test.js` where the first write fails with `ENOENT`, asserting that the directory is created and the snapshot written without an error. It fails before this change and passes after. Also checked end-to-end with `createFileMap` and `fileMapCacheDirectory` set to a missing `<project>/node_modules/.cache/metro-file-map`: before, the warning above and no directory; after, no warning and the cache file is written. ``` yarn jest packages/metro-file-map packages/metro/src/node-haste packages/metro/src/DeltaBundler yarn flow check yarn eslint packages/metro-file-map yarn verify-api-snapshots ``` Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 17fa699 commit 8e5c936

2 files changed

Lines changed: 44 additions & 1 deletion

File tree

‎packages/metro-file-map/src/cache/DiskCacheManager.js‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,19 @@ export class DiskCacheManager implements CacheManager {
115115
}
116116
const data = getSnapshot();
117117
this.#hasUnwrittenChanges = false;
118-
await fsPromises.writeFile(this.#cachePath, serialize(data));
118+
const serialized = serialize(data);
119+
try {
120+
await fsPromises.writeFile(this.#cachePath, serialized);
121+
} catch (e) {
122+
if (e?.code !== 'ENOENT') {
123+
throw e;
124+
}
125+
// The cache directory doesn't exist yet - create it and retry.
126+
await fsPromises.mkdir(path.dirname(this.#cachePath), {
127+
recursive: true,
128+
});
129+
await fsPromises.writeFile(this.#cachePath, serialized);
130+
}
119131
debug('Written cache to %s', this.#cachePath);
120132
})
121133
.catch(onWriteError);

‎packages/metro-file-map/src/cache/__tests__/DiskCacheManager-test.js‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ import EventEmitter from 'node:events';
2121
import * as path from 'node:path';
2222
import {serialize} from 'node:v8';
2323

24+
const mockMkdir = jest.fn();
2425
const mockReadFile = jest.fn();
2526
const mockWriteFile = jest.fn();
2627

2728
jest.mock('node:fs', () => ({
2829
promises: {
30+
mkdir: (...args) => mockMkdir(...args),
2931
readFile: (...args) => mockReadFile(...args),
3032
writeFile: (...args) => mockWriteFile(...args),
3133
},
@@ -189,6 +191,35 @@ describe('cacheManager', () => {
189191
);
190192
});
191193

194+
test('creates the cache directory if it does not exist', async () => {
195+
const cacheManager = new DiskCacheManager({buildParameters}, defaultConfig);
196+
const getSnapshot = jest.fn(
197+
() =>
198+
({
199+
clocks: new Map([['foo', 'bar']]),
200+
fileSystemData: new Map(),
201+
plugins: new Map(),
202+
}) as CacheData,
203+
);
204+
mockWriteFile.mockRejectedValueOnce({code: 'ENOENT'});
205+
const onWriteError = jest.fn();
206+
await cacheManager.write(getSnapshot, {
207+
changedSinceCacheRead: true,
208+
eventSource: {onChange: () => () => {}},
209+
onWriteError,
210+
});
211+
expect(mockMkdir).toHaveBeenCalledWith(
212+
path.dirname(cacheManager.getCacheFilePath()),
213+
{recursive: true},
214+
);
215+
expect(mockWriteFile).toHaveBeenCalledTimes(2);
216+
expect(mockWriteFile).toHaveBeenLastCalledWith(
217+
cacheManager.getCacheFilePath(),
218+
serialize(getSnapshot()),
219+
);
220+
expect(onWriteError).not.toHaveBeenCalled();
221+
});
222+
192223
test('does not write when there have been no changes', async () => {
193224
const cacheManager = new DiskCacheManager({buildParameters}, defaultConfig);
194225
const getSnapshot = jest.fn(

0 commit comments

Comments
 (0)