diff --git a/.changeset/fix-windows-recursive-undefined-manifest.md b/.changeset/fix-windows-recursive-undefined-manifest.md new file mode 100644 index 0000000000..f1bbb5b4b3 --- /dev/null +++ b/.changeset/fix-windows-recursive-undefined-manifest.md @@ -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 ` 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) diff --git a/installing/commands/src/import/index.ts b/installing/commands/src/import/index.ts index 27cb169b9d..111a2a2b73 100644 --- a/installing/commands/src/import/index.ts +++ b/installing/commands/src/import/index.ts @@ -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 ( diff --git a/installing/commands/src/installDeps.ts b/installing/commands/src/installDeps.ts index 43c2ffc30c..c4649ddfe5 100644 --- a/installing/commands/src/installDeps.ts +++ b/installing/commands/src/installDeps.ts @@ -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 ( diff --git a/installing/commands/test/add.ts b/installing/commands/test/add.ts index b034b87b2d..67fd39def3 100644 --- a/installing/commands/test/add.ts +++ b/installing/commands/test/add.ts @@ -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(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()