diff --git a/.changeset/quiet-icons-wait.md b/.changeset/quiet-icons-wait.md new file mode 100644 index 00000000000..ca659a1004b --- /dev/null +++ b/.changeset/quiet-icons-wait.md @@ -0,0 +1,6 @@ +--- +"@pnpm/find-workspace-dir": patch +"pnpm": patch +--- + +Resolve native workspace path for case-insensitive file systems [#4904](https://github.com/pnpm/pnpm/issues/4904). diff --git a/packages/find-workspace-dir/src/index.ts b/packages/find-workspace-dir/src/index.ts index 1e33d9276f3..a6e7e4b2b01 100644 --- a/packages/find-workspace-dir/src/index.ts +++ b/packages/find-workspace-dir/src/index.ts @@ -1,3 +1,4 @@ +import fs from 'fs' import path from 'path' import PnpmError from '@pnpm/error' import findUp from 'find-up' @@ -9,9 +10,21 @@ export default async function findWorkspaceDir (cwd: string) { const workspaceManifestDirEnvVar = process.env[WORKSPACE_DIR_ENV_VAR] ?? process.env[WORKSPACE_DIR_ENV_VAR.toLowerCase()] const workspaceManifestLocation = workspaceManifestDirEnvVar ? path.join(workspaceManifestDirEnvVar, 'pnpm-workspace.yaml') - : await findUp([WORKSPACE_MANIFEST_FILENAME, 'pnpm-workspace.yml'], { cwd }) + : await findUp([WORKSPACE_MANIFEST_FILENAME, 'pnpm-workspace.yml'], { cwd: await getRealPath(cwd) }) if (workspaceManifestLocation?.endsWith('.yml')) { throw new PnpmError('BAD_WORKSPACE_MANIFEST_NAME', `The workspace manifest file should be named "pnpm-workspace.yaml". File found: ${workspaceManifestLocation}`) } return workspaceManifestLocation && path.dirname(workspaceManifestLocation) } + +async function getRealPath (path: string) { + return new Promise((resolve) => { + // We need to resolve the real native path for case-insensitive file systems. + // For example, we can access file as C:\Code\Project as well as c:\code\projects + // Without this we can face a problem when try to install packages with -w flag, + // when root dir is using c:\code\projects but packages were found by C:\Code\Project + fs.realpath.native(path, function (err, resolvedPath) { + resolve(err !== null ? path : resolvedPath) + }) + }) +} diff --git a/packages/find-workspace-dir/test/index.ts b/packages/find-workspace-dir/test/index.ts index c651ed1d409..cf66ce243bd 100644 --- a/packages/find-workspace-dir/test/index.ts +++ b/packages/find-workspace-dir/test/index.ts @@ -1,16 +1,36 @@ /// import path from 'path' +import fs from 'fs' import findWorkspaceDir from '@pnpm/find-workspace-dir' const NPM_CONFIG_WORKSPACE_DIR_ENV_VAR = 'NPM_CONFIG_WORKSPACE_DIR' const FAKE_PATH = 'FAKE_PATH' +function isFileSystemCaseSensitive () { + try { + fs.realpathSync.native(process.cwd().toUpperCase()) + return false + } catch (_) { + return true + } +} + +// We don't need to validate case-sensitive systems +// because it is not possible to reach process.cwd() with wrong case there. +const testOnCaseInSensitiveSystems = isFileSystemCaseSensitive() ? test.skip : test + test('finds actual workspace dir', async () => { const workspaceDir = await findWorkspaceDir(process.cwd()) expect(workspaceDir).toBe(path.resolve(__dirname, '..', '..', '..')) }) +testOnCaseInSensitiveSystems('finds workspace dir with wrong case from cwd', async () => { + const workspaceDir = await findWorkspaceDir(process.cwd().toUpperCase()) + + expect(workspaceDir).toBe(path.resolve(__dirname, '..', '..', '..')) +}) + test('finds overriden workspace dir', async () => { const oldValue = process.env[NPM_CONFIG_WORKSPACE_DIR_ENV_VAR] process.env[NPM_CONFIG_WORKSPACE_DIR_ENV_VAR] = FAKE_PATH