diff --git a/.changeset/fix-directory-fetcher-absolute-path.md b/.changeset/fix-directory-fetcher-absolute-path.md
new file mode 100644
index 0000000000..692362ba09
--- /dev/null
+++ b/.changeset/fix-directory-fetcher-absolute-path.md
@@ -0,0 +1,6 @@
+---
+"@pnpm/fetching.directory-fetcher": patch
+"pnpm": patch
+---
+
+Fix installing a directory dependency (`file:
`) from an absolute path on a different drive on Windows. The directory fetcher was joining the stored directory onto `lockfileDir`, which on Windows concatenates an absolute cross-drive path literally (`path.join('D:\\...', 'C:\\Users\\...')` → `'D:\\...\\C:\\Users\\...'`). Use `path.resolve` so absolute paths are respected. This surfaced as an ENOENT during `pnpm setup` in CI when `PNPM_HOME` and the OS temp directory were on different drives.
diff --git a/fetching/directory-fetcher/src/index.ts b/fetching/directory-fetcher/src/index.ts
index bf4b0f161d..29fe4521a8 100644
--- a/fetching/directory-fetcher/src/index.ts
+++ b/fetching/directory-fetcher/src/index.ts
@@ -24,7 +24,10 @@ export function createDirectoryFetcher (
const fetchFromDir = opts?.includeOnlyPackageFiles ? fetchPackageFilesFromDir : fetchAllFilesFromDir.bind(null, readFileStat)
const directoryFetcher: DirectoryFetcher = (cafs, resolution, opts) => {
- const dir = path.join(opts.lockfileDir, resolution.directory)
+ // Use path.resolve so absolute directories (e.g. cross-drive Windows paths
+ // stored by `file:` deps) are respected instead of being concatenated
+ // onto lockfileDir.
+ const dir = path.resolve(opts.lockfileDir, resolution.directory)
return fetchFromDir(dir)
}
diff --git a/fetching/directory-fetcher/test/index.ts b/fetching/directory-fetcher/test/index.ts
index 087006de72..9c213babd6 100644
--- a/fetching/directory-fetcher/test/index.ts
+++ b/fetching/directory-fetcher/test/index.ts
@@ -111,6 +111,26 @@ test('fetch does not fail on package with broken symlink', async () => {
expect(debug).toHaveBeenCalledWith({ brokenSymlink: path.resolve('not-exists') })
})
+test('fetch respects absolute directory regardless of lockfileDir', async () => {
+ const absDir = f.find('simple-pkg')
+ const fetcher = createDirectoryFetcher({ includeOnlyPackageFiles: true })
+
+ // lockfileDir is unrelated to the directory being fetched. When the
+ // stored directory is absolute (e.g. cross-drive `file:` deps on Windows)
+ // the fetcher must use the absolute path as-is rather than joining it
+ // onto lockfileDir.
+ // eslint-disable-next-line
+ const fetchResult = await fetcher.directory({} as any, {
+ directory: absDir,
+ type: 'directory',
+ }, {
+ lockfileDir: f.find('no-manifest'),
+ })
+
+ expect(fetchResult.local).toBe(true)
+ expect(fetchResult.filesMap.get('package.json')).toBe(path.join(absDir, 'package.json'))
+})
+
describe('fetch resolves symlinked files to their real locations', () => {
const indexJsPath = path.join(f.find('no-manifest'), 'index.js')
const srcPath = f.find('simple-pkg')