mirror of
https://github.com/pnpm/pnpm.git
synced 2026-06-29 18:35:18 -04:00
fix(installing.commands): key selectProjectByDir graph by project.rootDir (#12380)
* fix(installing.commands): key selectProjectByDir graph by project.rootDir
`selectProjectByDir` constructs a single-entry `ProjectsGraph` for the
non-workspace install path. It was using `searchedDir` (`opts.dir`) as
the key, but downstream `recursive()` builds `manifestsByPath` from the
projects array (keyed by `project.rootDir`) and then looks up entries
via `manifestsByPath[rootDir]` where `rootDir` is drawn from
`Object.keys(selectedProjectsGraph)`. When `opts.dir` and
`project.rootDir` differ in platform-normalized form (most often on
Windows due to drive-letter casing), the lookup falls through as
`undefined` and `pnpm add <pkg>` crashes with:
Cannot destructure property 'manifest' of 'manifestsByPath[rootDir]' as it is undefined
Pin the graph key to `project.rootDir` in both `installing/commands/src/installDeps.ts`
and `installing/commands/src/import/index.ts`, so the keys stay in sync
with `manifestsByPath`. Closes https://github.com/pnpm/pnpm/issues/12379
Written by an agent (Claude Code, claude-opus-4-7).
* docs: remove redundant comments
* test(installing.commands): cover project graph keying
* Revert "test(installing.commands): cover project graph keying"
This reverts commit 426fae9434.
* test(installing.commands): cover add with mismatched project dir
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
This commit is contained in:
6
.changeset/fix-windows-recursive-undefined-manifest.md
Normal file
6
.changeset/fix-windows-recursive-undefined-manifest.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@pnpm/installing.commands": patch
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
Fixed `Cannot destructure property 'manifest' of 'manifestsByPath[rootDir]' as it is undefined` regression introduced in 11.6.0 when running `pnpm add <pkg>` outside a workspace on Windows. `selectProjectByDir` was keying the resulting `ProjectsGraph` by `opts.dir` instead of `project.rootDir`, so downstream `manifestsByPath` lookups missed when the two paths normalized differently (typically drive-letter casing). [pnpm/pnpm#12379](https://github.com/pnpm/pnpm/issues/12379)
|
||||
@@ -337,7 +337,7 @@ function getAllVersionsFromYarnLockFile (
|
||||
function selectProjectByDir (projects: Project[], searchedDir: string): ProjectsGraph | undefined {
|
||||
const project = projects.find(({ rootDir }) => path.relative(rootDir, searchedDir) === '')
|
||||
if (project == null) return undefined
|
||||
return { [searchedDir]: { dependencies: [], package: project } }
|
||||
return { [project.rootDir]: { dependencies: [], package: project } }
|
||||
}
|
||||
|
||||
function getYarnLockfileType (
|
||||
|
||||
@@ -519,7 +519,7 @@ export async function installDeps (
|
||||
function selectProjectByDir (projects: Project[], searchedDir: string): ProjectsGraph | undefined {
|
||||
const project = projects.find(({ rootDir }) => path.relative(rootDir, searchedDir) === '')
|
||||
if (project == null) return undefined
|
||||
return { [searchedDir]: { dependencies: [], package: project } }
|
||||
return { [project.rootDir]: { dependencies: [], package: project } }
|
||||
}
|
||||
|
||||
async function recursiveInstallThenUpdateWorkspaceState (
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { PnpmError } from '@pnpm/error'
|
||||
import { add, remove } from '@pnpm/installing.commands'
|
||||
import { prepare, prepareEmpty, preparePackages } from '@pnpm/prepare'
|
||||
import { REGISTRY_MOCK_PORT } from '@pnpm/testing.registry-mock'
|
||||
import type { ProjectManifest } from '@pnpm/types'
|
||||
import type { Project, ProjectManifest, ProjectRootDir, ProjectRootDirRealPath } from '@pnpm/types'
|
||||
import { loadJsonFile } from 'load-json-file'
|
||||
import { temporaryDirectory } from 'tempy'
|
||||
|
||||
@@ -308,6 +308,41 @@ test('pnpm add automatically installs missing peer dependencies', async () => {
|
||||
expect(Object.keys(lockfile.packages)).toHaveLength(5)
|
||||
})
|
||||
|
||||
test('pnpm add handles matching workspace project when dir differs from project.rootDir', async () => {
|
||||
const rootProjectManifest: ProjectManifest = {
|
||||
name: 'project',
|
||||
version: '0.0.0',
|
||||
}
|
||||
const project = prepare(rootProjectManifest)
|
||||
const rootDir = project.dir() as ProjectRootDir
|
||||
const allProjects: Project[] = [
|
||||
{
|
||||
manifest: rootProjectManifest,
|
||||
rootDir,
|
||||
rootDirRealPath: fs.realpathSync(rootDir) as ProjectRootDirRealPath,
|
||||
writeProjectManifest: async (manifest) => project.writePackageJson(manifest),
|
||||
},
|
||||
]
|
||||
|
||||
await expect(add.handler({
|
||||
...DEFAULT_OPTIONS,
|
||||
allProjects,
|
||||
dir: `${rootDir}${path.sep}`,
|
||||
linkWorkspacePackages: false,
|
||||
lockfileDir: rootDir,
|
||||
rootProjectManifest,
|
||||
rootProjectManifestDir: rootDir,
|
||||
sharedWorkspaceLockfile: true,
|
||||
workspaceDir: rootDir,
|
||||
}, ['is-positive@1.0.0'])).resolves.toBeUndefined()
|
||||
|
||||
const manifest = await loadJsonFile<ProjectManifest>(path.resolve('package.json'))
|
||||
expect(manifest.dependencies).toStrictEqual({
|
||||
'is-positive': '1.0.0',
|
||||
})
|
||||
project.has('is-positive')
|
||||
})
|
||||
|
||||
test('add: fail when global bin directory is not found', async () => {
|
||||
prepareEmpty()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user