diff --git a/packages/metro-file-map/src/crawlers/__tests__/watchman-test.js b/packages/metro-file-map/src/crawlers/__tests__/watchman-test.js index 1e42d86fdb..5a1f78289f 100644 --- a/packages/metro-file-map/src/crawlers/__tests__/watchman-test.js +++ b/packages/metro-file-map/src/crawlers/__tests__/watchman-test.js @@ -13,7 +13,8 @@ import {AbortController} from 'node-abort-controller'; const path = require('path'); jest.mock('fb-watchman', () => { - const normalizePathSep = require('../../lib/normalizePathSep').default; + const normalizePathSeparatorsToSystem = + require('../../lib/normalizePathSeparatorsToSystem').default; const Client = jest.fn(); const endedClients = new WeakSet(); Client.prototype.command = jest.fn(function (args, callback) { @@ -23,7 +24,9 @@ jest.mock('fb-watchman', () => { callback(new Error('Client has ended')); return; } - const path = args[1] ? normalizePathSep(args[1]) : undefined; + const path = args[1] + ? normalizePathSeparatorsToSystem(args[1]) + : undefined; const response = mockResponse[args[0]][path]; callback(null, response.next ? response.next().value : response); }); diff --git a/packages/metro-file-map/src/crawlers/watchman/index.js b/packages/metro-file-map/src/crawlers/watchman/index.js index d8ea31acd0..8a98a8e088 100644 --- a/packages/metro-file-map/src/crawlers/watchman/index.js +++ b/packages/metro-file-map/src/crawlers/watchman/index.js @@ -21,7 +21,7 @@ import type {WatchmanQueryResponse, WatchmanWatchResponse} from 'fb-watchman'; import H from '../../constants'; import * as fastPath from '../../lib/fast_path'; -import normalizePathSep from '../../lib/normalizePathSep'; +import normalizePathSeparatorsToSystem from '../../lib/normalizePathSeparatorsToSystem'; import {planQuery} from './planQuery'; import invariant from 'invariant'; import * as path from 'path'; @@ -291,7 +291,7 @@ module.exports = async function watchmanCrawl({ perfLogger?.point('watchmanCrawl/processResults_start'); for (const [watchRoot, response] of results) { - const fsRoot = normalizePathSep(watchRoot); + const fsRoot = normalizePathSeparatorsToSystem(watchRoot); const relativeFsRoot = fastPath.relative(rootDir, fsRoot); newClocks.set( relativeFsRoot, @@ -302,7 +302,8 @@ module.exports = async function watchmanCrawl({ ); for (const fileData of response.files) { - const filePath = fsRoot + path.sep + normalizePathSep(fileData.name); + const filePath = + fsRoot + path.sep + normalizePathSeparatorsToSystem(fileData.name); const relativeFilePath = fastPath.relative(rootDir, filePath); const existingFileData = previousState.files.get(relativeFilePath); diff --git a/packages/metro-file-map/src/index.js b/packages/metro-file-map/src/index.js index ba40ebe382..06fdff8d39 100644 --- a/packages/metro-file-map/src/index.js +++ b/packages/metro-file-map/src/index.js @@ -45,7 +45,7 @@ import checkWatchmanCapabilities from './lib/checkWatchmanCapabilities'; import deepCloneRawModuleMap from './lib/deepCloneRawModuleMap'; import * as fastPath from './lib/fast_path'; import getPlatformExtension from './lib/getPlatformExtension'; -import normalizePathSep from './lib/normalizePathSep'; +import normalizePathSeparatorsToSystem from './lib/normalizePathSeparatorsToSystem'; import TreeFS from './lib/TreeFS'; import HasteModuleMap from './ModuleMap'; import {Watcher} from './Watcher'; @@ -866,7 +866,7 @@ export default class HasteMap extends EventEmitter { if (this._options.mocksPattern) { const absoluteFilePath = path.join( this._options.rootDir, - normalizePathSep(relativeFilePath), + normalizePathSeparatorsToSystem(relativeFilePath), ); if ( this._options.mocksPattern && @@ -951,7 +951,10 @@ export default class HasteMap extends EventEmitter { return; } - const absoluteFilePath = path.join(root, normalizePathSep(filePath)); + const absoluteFilePath = path.join( + root, + normalizePathSeparatorsToSystem(filePath), + ); // Ignore files (including symlinks) whose path matches ignorePattern // (we don't ignore node_modules in watch mode) diff --git a/packages/metro-file-map/src/lib/__tests__/normalizePathSep-test.js b/packages/metro-file-map/src/lib/__tests__/normalizePathSeparatorsToSystem-test.js similarity index 52% rename from packages/metro-file-map/src/lib/__tests__/normalizePathSep-test.js rename to packages/metro-file-map/src/lib/__tests__/normalizePathSeparatorsToSystem-test.js index a079fb1be9..3cd0b87af3 100644 --- a/packages/metro-file-map/src/lib/__tests__/normalizePathSep-test.js +++ b/packages/metro-file-map/src/lib/__tests__/normalizePathSeparatorsToSystem-test.js @@ -10,18 +10,24 @@ 'use strict'; -describe('normalizePathSep', () => { +describe('normalizePathSeparatorsToSystem', () => { it('does nothing on posix', () => { jest.resetModules(); jest.mock('path', () => jest.requireActual('path').posix); - const normalizePathSep = require('../normalizePathSep').default; - expect(normalizePathSep('foo/bar/baz.js')).toEqual('foo/bar/baz.js'); + const normalizePathSeparatorsToSystem = + require('../normalizePathSeparatorsToSystem').default; + expect(normalizePathSeparatorsToSystem('foo/bar/baz.js')).toEqual( + 'foo/bar/baz.js', + ); }); it('replace slashes on windows', () => { jest.resetModules(); jest.mock('path', () => jest.requireActual('path').win32); - const normalizePathSep = require('../normalizePathSep').default; - expect(normalizePathSep('foo/bar/baz.js')).toEqual('foo\\bar\\baz.js'); + const normalizePathSeparatorsToSystem = + require('../normalizePathSeparatorsToSystem').default; + expect(normalizePathSeparatorsToSystem('foo/bar/baz.js')).toEqual( + 'foo\\bar\\baz.js', + ); }); }); diff --git a/packages/metro-file-map/src/lib/__tests__/rootRelativeCacheKeys-test.js b/packages/metro-file-map/src/lib/__tests__/rootRelativeCacheKeys-test.js index d217449616..efe0def796 100644 --- a/packages/metro-file-map/src/lib/__tests__/rootRelativeCacheKeys-test.js +++ b/packages/metro-file-map/src/lib/__tests__/rootRelativeCacheKeys-test.js @@ -10,6 +10,8 @@ import type {BuildParameters} from '../../flow-types'; +import typeof PathModule from 'path'; + import rootRelativeCacheKeys from '../rootRelativeCacheKeys'; const buildParameters: BuildParameters = { @@ -124,3 +126,31 @@ it('returns a distinct cache key for any change', () => { seen.set(configHash, i); } }); + +describe('cross-platform cache keys', () => { + afterEach(() => { + jest.unmock('path'); + }); + + it('returns the same cache key for Windows and POSIX path parameters', () => { + let mockPathModule; + jest.mock('path', () => mockPathModule); + + jest.resetModules(); + mockPathModule = jest.requireActual('path').posix; + const configHashPosix = require('../rootRelativeCacheKeys').default({ + ...buildParameters, + rootDir: '/root', + roots: ['/root/a', '/b/c'], + }).relativeConfigHash; + + jest.resetModules(); + mockPathModule = jest.requireActual('path').win32; + const configHashWin32 = require('../rootRelativeCacheKeys').default({ + ...buildParameters, + rootDir: 'c:\\root', + roots: ['c:\\root\\a', 'c:\\b\\c'], + }).relativeConfigHash; + expect(configHashWin32).toEqual(configHashPosix); + }); +}); diff --git a/packages/metro-file-map/src/lib/normalizePathSeparatorsToPosix.js b/packages/metro-file-map/src/lib/normalizePathSeparatorsToPosix.js new file mode 100644 index 0000000000..be55409b77 --- /dev/null +++ b/packages/metro-file-map/src/lib/normalizePathSeparatorsToPosix.js @@ -0,0 +1,21 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow strict + */ + +import * as path from 'path'; + +let normalizePathSeparatorsToPosix: (string: string) => string; +if (path.sep === '/') { + normalizePathSeparatorsToPosix = (filePath: string): string => filePath; +} else { + normalizePathSeparatorsToPosix = (filePath: string): string => + filePath.replace(/\\/g, '/'); +} + +export default normalizePathSeparatorsToPosix; diff --git a/packages/metro-file-map/src/lib/normalizePathSep.js b/packages/metro-file-map/src/lib/normalizePathSeparatorsToSystem.js similarity index 56% rename from packages/metro-file-map/src/lib/normalizePathSep.js rename to packages/metro-file-map/src/lib/normalizePathSeparatorsToSystem.js index 7fc03e4000..bdee4fb75f 100644 --- a/packages/metro-file-map/src/lib/normalizePathSep.js +++ b/packages/metro-file-map/src/lib/normalizePathSeparatorsToSystem.js @@ -10,12 +10,12 @@ import * as path from 'path'; -let normalizePathSep: (string: string) => string; +let normalizePathSeparatorsToSystem: (string: string) => string; if (path.sep === '/') { - normalizePathSep = (filePath: string): string => filePath; + normalizePathSeparatorsToSystem = (filePath: string): string => filePath; } else { - normalizePathSep = (filePath: string): string => + normalizePathSeparatorsToSystem = (filePath: string): string => filePath.replace(/\//g, path.sep); } -export default normalizePathSep; +export default normalizePathSeparatorsToSystem; diff --git a/packages/metro-file-map/src/lib/rootRelativeCacheKeys.js b/packages/metro-file-map/src/lib/rootRelativeCacheKeys.js index 5eda3bc36d..72ed9d860f 100644 --- a/packages/metro-file-map/src/lib/rootRelativeCacheKeys.js +++ b/packages/metro-file-map/src/lib/rootRelativeCacheKeys.js @@ -12,6 +12,7 @@ import type {BuildParameters} from '../flow-types'; import * as fastPath from './fast_path'; +import normalizePathSeparatorsToPosix from './normalizePathSeparatorsToPosix'; import {createHash} from 'crypto'; function moduleCacheKey(modulePath: ?string) { @@ -37,7 +38,9 @@ export default function rootRelativeCacheKeys( relativeConfigHash: string, } { const {rootDir, ...otherParameters} = buildParameters; - const rootDirHash = createHash('md5').update(rootDir).digest('hex'); + const rootDirHash = createHash('md5') + .update(normalizePathSeparatorsToPosix(rootDir)) + .digest('hex'); const cacheComponents = Object.keys(otherParameters) .sort() @@ -45,7 +48,7 @@ export default function rootRelativeCacheKeys( switch (key) { case 'roots': return buildParameters[key].map(root => - fastPath.relative(rootDir, root), + normalizePathSeparatorsToPosix(fastPath.relative(rootDir, root)), ); case 'cacheBreaker': case 'extensions':