diff --git a/packages/core/core/src/utils/__tests__/filepath-to-prop-path.test.ts b/packages/core/core/src/utils/__tests__/filepath-to-prop-path.test.ts index 7a853586cd..13d7c03b48 100644 --- a/packages/core/core/src/utils/__tests__/filepath-to-prop-path.test.ts +++ b/packages/core/core/src/utils/__tests__/filepath-to-prop-path.test.ts @@ -25,4 +25,21 @@ describe('filePathToPropPath', () => { expect(filePathToPropPath('./config/test.js', false)).toEqual(['config']); expect(filePathToPropPath('./config/test.key.js', false)).toEqual(['config', 'test']); }); + + describe('Separators', () => { + test('Win32 Separators', () => { + expect(filePathToPropPath('config\\test.js')).toEqual(['config', 'test']); + expect(filePathToPropPath('.\\config\\test.js')).toEqual(['config', 'test']); + }); + + test('Posix Separators', () => { + expect(filePathToPropPath('config/test.js')).toEqual(['config', 'test']); + expect(filePathToPropPath('./config/test.js')).toEqual(['config', 'test']); + }); + + test('Mixed Separators (win32 + posix)', () => { + expect(filePathToPropPath('src\\config/test.js')).toEqual(['src', 'config', 'test']); + expect(filePathToPropPath('.\\config/test.js')).toEqual(['config', 'test']); + }); + }); }); diff --git a/packages/core/core/src/utils/filepath-to-prop-path.ts b/packages/core/core/src/utils/filepath-to-prop-path.ts index d5d43949d6..b65cb2de4a 100644 --- a/packages/core/core/src/utils/filepath-to-prop-path.ts +++ b/packages/core/core/src/utils/filepath-to-prop-path.ts @@ -1,18 +1,37 @@ -import _ from 'lodash'; +import path from 'node:path'; +import fp from 'lodash/fp'; /** * Returns a path (as an array) from a file path */ -export const filePathToPropPath = (filePath: string, useFileNameAsKey = true) => { - const cleanPath = filePath.startsWith('./') ? filePath.slice(2) : filePath; +export const filePathToPropPath = ( + entryPath: string, + useFileNameAsKey: boolean = true +): string[] => { + const transform = fp.pipe( + // Remove the relative path prefixes: './' for posix (and some win32) and ".\" for win32 + removeRelativePrefix, + // Remove the path metadata and extensions + fp.replace(/(\.settings|\.json|\.js)/g, ''), + // Transform to lowercase + // Note: We're using fp.toLower instead of fp.lowercase as the latest removes special characters such as "/" + fp.toLower, + // Split the cleaned path by matching every possible separator (either "/" or "\" depending on the OS) + fp.split(new RegExp(`[\\${path.win32.sep}|${path.posix.sep}]`, 'g')), + // Make sure to remove leading '.' from the different path parts + fp.map(fp.trimCharsStart('.')), + // join + split in case some '.' characters are still present in different parts of the path + fp.join('.'), + fp.split('.'), + // Remove the last portion of the path array if the file name shouldn't be used as a key + useFileNameAsKey ? fp.identity : fp.slice(0, -1) + ); - const prop = cleanPath - .replace(/(\.settings|\.json|\.js)/g, '') - .toLowerCase() - .split('/') - .map((p) => _.trimStart(p, '.')) - .join('.') - .split('.'); - - return useFileNameAsKey === true ? prop : prop.slice(0, -1); + return transform(entryPath) as string[]; +}; + +const removeRelativePrefix = (filePath: string) => { + return filePath.startsWith(`.${path.win32.sep}`) || filePath.startsWith(`.${path.posix.sep}`) + ? filePath.slice(2) + : filePath; };