Files
pnpm/installing/deps-installer/test/install/injectLocalPackages.ts
Brandon Cheng 41dc031a67 test: use resolution-mode=highest in tests (#10989)
* fix: configure default resolution-mode to highest in pkg-manager/core

* test: update catalog tests for resolution-mode=highest

* test: fix `--fix-lockfile` test for new resolution-mode default

```
  ● fix broken lockfile with --fix-lockfile

    expect(received).toBeTruthy()

    Received: undefined

      55 |   const lockfile: LockfileFile = readYamlFileSync(WANTED_LOCKFILE)
      56 |   expect(Object.keys(lockfile.packages as PackageSnapshots)).toHaveLength(2)
    > 57 |   expect(lockfile.packages?.['@types/semver@5.3.31']).toBeTruthy()
         |                                                       ^
      58 |   expect(lockfile.packages?.['@types/semver@5.3.31']?.resolution).toEqual({
      59 |     integrity: 'sha512-WBv5F9HrWTyG800cB9M3veCVkFahqXN7KA7c3VUCYZm/xhNzzIFiXiq+rZmj75j7GvWelN3YNrLX7FjtqBvhMw==',
      60 |   })

      at Object.<anonymous> (test/install/fixLockfile.ts:57:55)
```

* test: fix lockfile conflict test

  ● a lockfile v6 with merge conflicts is autofixed

    expect(received).toHaveProperty(path, value)

    Expected path: "version"

    Expected value: "100.1.0"
    Received value: "101.0.0"

      1284 |
      1285 |   const lockfile = project.readLockfile()
    > 1286 |   expect(lockfile.importers?.['.'].dependencies?.['@pnpm.e2e/dep-of-pkg-with-1-dep']).toHaveProperty('version', '100.1.0')
           |                                                                                       ^
      1287 | })
      1288 |
      1289 | test('a lockfile with duplicate keys is fixed', async () => {

      at Object.<anonymous> (test/lockfile.ts:1286:87)

* test: fix deploy shared lockfile test

  ● deploy with a shared lockfile that has peer dependencies suffix in workspace package dependency paths

    expect(received).toMatchObject(expected)

    - Expected  - 6
    + Received  + 1

    @@ -1,11 +1,11 @@
      Object {
        "importers": Object {
          "packages/project-0": Object {
            "dependencies": Object {
              "project-1": Object {
    -           "version": "file:packages/project-1(is-negative@1.0.0)(project-2@file:packages/project-2(is-positive@1.0.0))",
    +           "version": "file:packages/project-1(is-negative@2.1.0)(project-2@file:packages/project-2(is-positive@1.0.0))",
              },
              "project-2": Object {
                "version": "file:packages/project-2(is-positive@1.0.0)",
              },
            },
    @@ -31,13 +31,8 @@
              "type": "directory",
            },
          },
        },
        "snapshots": Object {
    -     "project-1@file:packages/project-1(is-negative@1.0.0)(project-2@file:packages/project-2(is-positive@1.0.0))": Object {
    -       "dependencies": Object {
    -         "project-2": "file:packages/project-2(is-positive@1.0.0)",
    -       },
    -     },
          "project-2@file:packages/project-2(is-positive@1.0.0)": Object {},
        },
      }

      950 |     workspaceDir: process.cwd(),
      951 |   })
    > 952 |   expect(assertProject('.').readLockfile()).toMatchObject({
          |                                             ^
      953 |     importers: {
      954 |       'packages/project-0': {
      955 |         dependencies: {

      at Object.<anonymous> (test/shared-lockfile.test.ts:952:45)

* test: fix injectLocalPackages test
2026-03-21 23:21:04 +01:00

2015 lines
53 KiB
TypeScript

import fs from 'node:fs'
import path from 'node:path'
import { assertProject } from '@pnpm/assert-project'
import { type MutatedProject, mutateModules, type ProjectOptions } from '@pnpm/installing.deps-installer'
import { preparePackages } from '@pnpm/prepare'
import type { ProjectRootDir } from '@pnpm/types'
import { rimrafSync } from '@zkochan/rimraf'
import { writeJsonFileSync } from 'write-json-file'
import { testDefaults } from '../utils/index.js'
test('inject local packages', async () => {
const project1Manifest = {
name: 'project-1',
version: '1.0.0',
dependencies: {
'is-negative': '1.0.0',
},
devDependencies: {
'@pnpm.e2e/dep-of-pkg-with-1-dep': '100.0.0',
},
peerDependencies: {
'is-positive': '>=1.0.0',
},
}
const project2Manifest = {
name: 'project-2',
version: '1.0.0',
dependencies: {
'project-1': 'workspace:1.0.0',
},
devDependencies: {
'is-positive': '1.0.0',
},
dependenciesMeta: {
'project-1': {
injected: true,
},
},
}
const project3Manifest = {
name: 'project-3',
version: '1.0.0',
dependencies: {
'project-2': 'workspace:1.0.0',
},
devDependencies: {
'is-positive': '2.0.0',
},
dependenciesMeta: {
'project-2': {
injected: true,
},
},
}
const projects = preparePackages([
{
location: 'project-1',
package: project1Manifest,
},
{
location: 'project-2',
package: project2Manifest,
},
{
location: 'project-3',
package: project3Manifest,
},
])
const importers: MutatedProject[] = [
{
mutation: 'install',
rootDir: path.resolve('project-1') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('project-2') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('project-3') as ProjectRootDir,
},
]
const allProjects: ProjectOptions[] = [
{
buildIndex: 0,
manifest: project1Manifest,
rootDir: path.resolve('project-1') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: project2Manifest,
rootDir: path.resolve('project-2') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: project3Manifest,
rootDir: path.resolve('project-3') as ProjectRootDir,
},
]
await mutateModules(importers, testDefaults({
autoInstallPeers: false,
allProjects,
}))
projects['project-1'].has('is-negative')
projects['project-1'].has('@pnpm.e2e/dep-of-pkg-with-1-dep')
projects['project-1'].hasNot('is-positive')
projects['project-2'].has('is-positive')
projects['project-2'].has('project-1')
projects['project-3'].has('is-positive')
projects['project-3'].has('project-2')
expect(fs.readdirSync('node_modules/.pnpm')).toHaveLength(8)
const rootModules = assertProject(process.cwd())
{
const lockfile = rootModules.readLockfile()
expect(lockfile.importers['project-2'].dependenciesMeta).toEqual({
'project-1': {
injected: true,
},
})
expect(lockfile.packages['project-1@file:project-1']).toEqual({
resolution: {
directory: 'project-1',
type: 'directory',
},
peerDependencies: {
'is-positive': '>=1.0.0',
},
})
expect(lockfile.snapshots['project-1@file:project-1(is-positive@1.0.0)']).toEqual({
dependencies: {
'is-negative': '1.0.0',
'is-positive': '1.0.0',
},
})
expect(lockfile.packages['project-2@file:project-2']).toEqual({
resolution: {
directory: 'project-2',
type: 'directory',
},
})
expect(lockfile.snapshots['project-2@file:project-2(is-positive@2.0.0)']).toEqual({
dependencies: {
'project-1': 'file:project-1(is-positive@2.0.0)',
},
transitivePeerDependencies: ['is-positive'],
})
const modulesState = rootModules.readModulesManifest()
expect(modulesState?.injectedDeps?.['project-1'].length).toBe(2)
expect(modulesState?.injectedDeps?.['project-1'][0]).toContain(`node_modules${path.sep}.pnpm`)
expect(modulesState?.injectedDeps?.['project-1'][1]).toContain(`node_modules${path.sep}.pnpm`)
}
rimrafSync('node_modules')
rimrafSync('project-1/node_modules')
rimrafSync('project-2/node_modules')
rimrafSync('project-3/node_modules')
await mutateModules(importers, testDefaults({
autoInstallPeers: false,
allProjects,
frozenLockfile: true,
}))
projects['project-1'].has('is-negative')
projects['project-1'].has('@pnpm.e2e/dep-of-pkg-with-1-dep')
projects['project-1'].hasNot('is-positive')
projects['project-2'].has('is-positive')
projects['project-2'].has('project-1')
projects['project-3'].has('is-positive')
projects['project-3'].has('project-2')
expect(fs.readdirSync('node_modules/.pnpm')).toHaveLength(8)
// The injected project is updated when one of its dependencies needs to be updated
allProjects[0].manifest.dependencies!['is-negative'] = '2.0.0'
await mutateModules(importers, testDefaults({ autoInstallPeers: false, allProjects }))
{
const lockfile = rootModules.readLockfile()
expect(lockfile.importers['project-2'].dependenciesMeta).toEqual({
'project-1': {
injected: true,
},
})
expect(lockfile.packages['project-1@file:project-1']).toEqual({
resolution: {
directory: 'project-1',
type: 'directory',
},
peerDependencies: {
'is-positive': '>=1.0.0',
},
})
expect(lockfile.snapshots['project-1@file:project-1(is-positive@1.0.0)']).toEqual({
dependencies: {
'is-negative': '2.0.0',
'is-positive': '1.0.0',
},
})
const modulesState = rootModules.readModulesManifest()
expect(modulesState?.injectedDeps?.['project-1'].length).toBe(2)
expect(modulesState?.injectedDeps?.['project-1'][0]).toContain(`node_modules${path.sep}.pnpm`)
expect(modulesState?.injectedDeps?.['project-1'][1]).toContain(`node_modules${path.sep}.pnpm`)
}
})
test('inject local packages using the injectWorkspacePackages setting', async () => {
const project1Manifest = {
name: 'project-1',
version: '1.0.0',
dependencies: {
'is-negative': '1.0.0',
},
devDependencies: {
'@pnpm.e2e/dep-of-pkg-with-1-dep': '100.0.0',
},
peerDependencies: {
'is-positive': '>=1.0.0',
},
}
const project2Manifest = {
name: 'project-2',
version: '1.0.0',
dependencies: {
'project-1': 'workspace:1.0.0',
},
devDependencies: {
'is-positive': '1.0.0',
},
}
const project3Manifest = {
name: 'project-3',
version: '1.0.0',
dependencies: {
'project-2': 'workspace:1.0.0',
},
devDependencies: {
'is-positive': '2.0.0',
},
}
const projects = preparePackages([
{
location: 'project-1',
package: project1Manifest,
},
{
location: 'project-2',
package: project2Manifest,
},
{
location: 'project-3',
package: project3Manifest,
},
])
const importers: MutatedProject[] = [
{
mutation: 'install',
rootDir: path.resolve('project-1') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('project-2') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('project-3') as ProjectRootDir,
},
]
const allProjects: ProjectOptions[] = [
{
buildIndex: 0,
manifest: project1Manifest,
rootDir: path.resolve('project-1') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: project2Manifest,
rootDir: path.resolve('project-2') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: project3Manifest,
rootDir: path.resolve('project-3') as ProjectRootDir,
},
]
await mutateModules(importers, testDefaults({
autoInstallPeers: false,
allProjects,
injectWorkspacePackages: true,
}))
projects['project-1'].has('is-negative')
projects['project-1'].has('@pnpm.e2e/dep-of-pkg-with-1-dep')
projects['project-1'].hasNot('is-positive')
projects['project-2'].has('is-positive')
projects['project-2'].has('project-1')
projects['project-3'].has('is-positive')
projects['project-3'].has('project-2')
expect(fs.readdirSync('node_modules/.pnpm')).toHaveLength(8)
const rootModules = assertProject(process.cwd())
{
const lockfile = rootModules.readLockfile()
expect(lockfile.settings.injectWorkspacePackages).toBe(true)
expect(lockfile.importers['project-2'].dependenciesMeta).not.toEqual({
'project-1': {
injected: true,
},
})
expect(lockfile.packages['project-1@file:project-1']).toEqual({
resolution: {
directory: 'project-1',
type: 'directory',
},
peerDependencies: {
'is-positive': '>=1.0.0',
},
})
expect(lockfile.snapshots['project-1@file:project-1(is-positive@1.0.0)']).toEqual({
dependencies: {
'is-negative': '1.0.0',
'is-positive': '1.0.0',
},
})
expect(lockfile.packages['project-2@file:project-2']).toEqual({
resolution: {
directory: 'project-2',
type: 'directory',
},
})
expect(lockfile.snapshots['project-2@file:project-2(is-positive@2.0.0)']).toEqual({
dependencies: {
'project-1': 'file:project-1(is-positive@2.0.0)',
},
transitivePeerDependencies: ['is-positive'],
})
const modulesState = rootModules.readModulesManifest()
expect(modulesState?.injectedDeps?.['project-1'].length).toBe(2)
expect(modulesState?.injectedDeps?.['project-1'][0]).toContain(`node_modules${path.sep}.pnpm`)
expect(modulesState?.injectedDeps?.['project-1'][1]).toContain(`node_modules${path.sep}.pnpm`)
}
rimrafSync('node_modules')
rimrafSync('project-1/node_modules')
rimrafSync('project-2/node_modules')
rimrafSync('project-3/node_modules')
await mutateModules(importers, testDefaults({
autoInstallPeers: false,
allProjects,
frozenLockfile: true,
injectWorkspacePackages: true,
}))
projects['project-1'].has('is-negative')
projects['project-1'].has('@pnpm.e2e/dep-of-pkg-with-1-dep')
projects['project-1'].hasNot('is-positive')
projects['project-2'].has('is-positive')
projects['project-2'].has('project-1')
projects['project-3'].has('is-positive')
projects['project-3'].has('project-2')
expect(fs.readdirSync('node_modules/.pnpm')).toHaveLength(8)
// The injected project is updated when one of its dependencies needs to be updated
allProjects[0].manifest.dependencies!['is-negative'] = '2.0.0'
await mutateModules(importers, testDefaults({ autoInstallPeers: false, allProjects, injectWorkspacePackages: true }))
{
const lockfile = rootModules.readLockfile()
expect(lockfile.settings.injectWorkspacePackages).toBe(true)
expect(lockfile.importers['project-2'].dependenciesMeta).not.toEqual({
'project-1': {
injected: true,
},
})
expect(lockfile.packages['project-1@file:project-1']).toEqual({
resolution: {
directory: 'project-1',
type: 'directory',
},
peerDependencies: {
'is-positive': '>=1.0.0',
},
})
expect(lockfile.snapshots['project-1@file:project-1(is-positive@1.0.0)']).toEqual({
dependencies: {
'is-negative': '2.0.0',
'is-positive': '1.0.0',
},
})
const modulesState = rootModules.readModulesManifest()
expect(modulesState?.injectedDeps?.['project-1'].length).toBe(2)
expect(modulesState?.injectedDeps?.['project-1'][0]).toContain(`node_modules${path.sep}.pnpm`)
expect(modulesState?.injectedDeps?.['project-1'][1]).toContain(`node_modules${path.sep}.pnpm`)
}
})
test('inject local packages declared via file protocol', async () => {
const project1Manifest = {
name: 'project-1',
version: '1.0.0',
dependencies: {
'is-negative': '1.0.0',
},
devDependencies: {
'@pnpm.e2e/dep-of-pkg-with-1-dep': '100.0.0',
},
peerDependencies: {
'is-positive': '>=1.0.0',
},
}
const project2Manifest = {
name: 'project-2',
version: '1.0.0',
dependencies: {
'project-1': 'file:../project-1',
},
devDependencies: {
'is-positive': '1.0.0',
},
dependenciesMeta: {
'project-1': {
injected: true,
},
},
}
const project3Manifest = {
name: 'project-3',
version: '1.0.0',
dependencies: {
'project-2': 'file:../project-2',
},
devDependencies: {
'is-positive': '2.0.0',
},
dependenciesMeta: {
'project-2': {
injected: true,
},
},
}
const projects = preparePackages([
{
location: 'project-1',
package: project1Manifest,
},
{
location: 'project-2',
package: project2Manifest,
},
{
location: 'project-3',
package: project3Manifest,
},
])
const importers: MutatedProject[] = [
{
mutation: 'install',
rootDir: path.resolve('project-1') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('project-2') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('project-3') as ProjectRootDir,
},
]
const allProjects: ProjectOptions[] = [
{
buildIndex: 0,
manifest: project1Manifest,
rootDir: path.resolve('project-1') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: project2Manifest,
rootDir: path.resolve('project-2') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: project3Manifest,
rootDir: path.resolve('project-3') as ProjectRootDir,
},
]
await mutateModules(importers, testDefaults({
autoInstallPeers: false,
allProjects,
}))
projects['project-1'].has('is-negative')
projects['project-1'].has('@pnpm.e2e/dep-of-pkg-with-1-dep')
projects['project-1'].hasNot('is-positive')
projects['project-2'].has('is-positive')
projects['project-2'].has('project-1')
projects['project-3'].has('is-positive')
projects['project-3'].has('project-2')
expect(fs.readdirSync('node_modules/.pnpm')).toHaveLength(8)
const rootModules = assertProject(process.cwd())
{
const lockfile = rootModules.readLockfile()
expect(lockfile.importers['project-2'].dependenciesMeta).toEqual({
'project-1': {
injected: true,
},
})
expect(lockfile.snapshots['project-1@file:project-1(is-positive@1.0.0)']).toEqual({
dependencies: {
'is-negative': '1.0.0',
'is-positive': '1.0.0',
},
})
expect(lockfile.packages['project-1@file:project-1']).toEqual({
resolution: {
directory: 'project-1',
type: 'directory',
},
peerDependencies: {
'is-positive': '>=1.0.0',
},
})
expect(lockfile.snapshots['project-2@file:project-2(is-positive@2.0.0)']).toEqual({
dependencies: {
'project-1': 'file:project-1(is-positive@2.0.0)',
},
transitivePeerDependencies: ['is-positive'],
})
expect(lockfile.packages['project-2@file:project-2']).toEqual({
resolution: {
directory: 'project-2',
type: 'directory',
},
})
const modulesState = rootModules.readModulesManifest()
expect(modulesState?.injectedDeps?.['project-1'].length).toBe(2)
expect(modulesState?.injectedDeps?.['project-1'][0]).toContain(`node_modules${path.sep}.pnpm`)
expect(modulesState?.injectedDeps?.['project-1'][1]).toContain(`node_modules${path.sep}.pnpm`)
}
rimrafSync('node_modules')
rimrafSync('project-1/node_modules')
rimrafSync('project-2/node_modules')
rimrafSync('project-3/node_modules')
await mutateModules(importers, testDefaults({
autoInstallPeers: false,
allProjects,
frozenLockfile: true,
}))
projects['project-1'].has('is-negative')
projects['project-1'].has('@pnpm.e2e/dep-of-pkg-with-1-dep')
projects['project-1'].hasNot('is-positive')
projects['project-2'].has('is-positive')
projects['project-2'].has('project-1')
projects['project-3'].has('is-positive')
projects['project-3'].has('project-2')
expect(fs.readdirSync('node_modules/.pnpm')).toHaveLength(8)
// The injected project is updated when one of its dependencies needs to be updated
allProjects[0].manifest.dependencies!['is-negative'] = '2.0.0'
writeJsonFileSync('project-1/package.json', allProjects[0].manifest)
await mutateModules(importers, testDefaults({ autoInstallPeers: false, allProjects }))
{
const lockfile = rootModules.readLockfile()
expect(lockfile.importers['project-2'].dependenciesMeta).toEqual({
'project-1': {
injected: true,
},
})
expect(lockfile.snapshots['project-1@file:project-1(is-positive@1.0.0)']).toEqual({
dependencies: {
'is-negative': '2.0.0',
'is-positive': '1.0.0',
},
})
expect(lockfile.packages['project-1@file:project-1']).toEqual({
resolution: {
directory: 'project-1',
type: 'directory',
},
peerDependencies: {
'is-positive': '>=1.0.0',
},
})
const modulesState = rootModules.readModulesManifest()
expect(modulesState?.injectedDeps?.['project-1'].length).toBe(2)
expect(modulesState?.injectedDeps?.['project-1'][0]).toContain(`node_modules${path.sep}.pnpm`)
expect(modulesState?.injectedDeps?.['project-1'][1]).toContain(`node_modules${path.sep}.pnpm`)
}
})
test('inject local packages when the file protocol is used', async () => {
const project1Manifest = {
name: 'project-1',
version: '1.0.0',
dependencies: {
'is-negative': '1.0.0',
},
devDependencies: {
'@pnpm.e2e/dep-of-pkg-with-1-dep': '100.0.0',
},
peerDependencies: {
'is-positive': '>=1.0.0',
},
}
const project2Manifest = {
name: 'project-2',
version: '1.0.0',
dependencies: {
'project-1': 'file:../project-1',
},
devDependencies: {
'is-positive': '1.0.0',
},
}
const project3Manifest = {
name: 'project-3',
version: '1.0.0',
dependencies: {
'project-2': 'file:../project-2',
},
devDependencies: {
'is-positive': '2.0.0',
},
}
const projects = preparePackages([
{
location: 'project-1',
package: project1Manifest,
},
{
location: 'project-2',
package: project2Manifest,
},
{
location: 'project-3',
package: project3Manifest,
},
])
const importers: MutatedProject[] = [
{
mutation: 'install',
rootDir: path.resolve('project-1') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('project-2') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('project-3') as ProjectRootDir,
},
]
const allProjects: ProjectOptions[] = [
{
buildIndex: 0,
manifest: project1Manifest,
rootDir: path.resolve('project-1') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: project2Manifest,
rootDir: path.resolve('project-2') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: project3Manifest,
rootDir: path.resolve('project-3') as ProjectRootDir,
},
]
await mutateModules(importers, testDefaults({
autoInstallPeers: false,
allProjects,
}))
projects['project-1'].has('is-negative')
projects['project-1'].has('@pnpm.e2e/dep-of-pkg-with-1-dep')
projects['project-1'].hasNot('is-positive')
projects['project-2'].has('is-positive')
projects['project-2'].has('project-1')
projects['project-3'].has('is-positive')
projects['project-3'].has('project-2')
expect(fs.readdirSync('node_modules/.pnpm')).toHaveLength(8)
const rootModules = assertProject(process.cwd())
{
const lockfile = rootModules.readLockfile()
expect(lockfile.snapshots['project-1@file:project-1(is-positive@1.0.0)']).toEqual({
dependencies: {
'is-negative': '1.0.0',
'is-positive': '1.0.0',
},
})
expect(lockfile.packages['project-1@file:project-1']).toEqual({
resolution: {
directory: 'project-1',
type: 'directory',
},
peerDependencies: {
'is-positive': '>=1.0.0',
},
})
expect(lockfile.snapshots['project-2@file:project-2(is-positive@2.0.0)']).toEqual({
dependencies: {
'project-1': 'file:project-1(is-positive@2.0.0)',
},
transitivePeerDependencies: ['is-positive'],
})
expect(lockfile.packages['project-2@file:project-2']).toEqual({
resolution: {
directory: 'project-2',
type: 'directory',
},
})
const modulesState = rootModules.readModulesManifest()
expect(modulesState?.injectedDeps?.['project-1'].length).toBe(2)
expect(modulesState?.injectedDeps?.['project-1'][0]).toContain(`node_modules${path.sep}.pnpm`)
expect(modulesState?.injectedDeps?.['project-1'][1]).toContain(`node_modules${path.sep}.pnpm`)
}
rimrafSync('node_modules')
rimrafSync('project-1/node_modules')
rimrafSync('project-2/node_modules')
rimrafSync('project-3/node_modules')
await mutateModules(importers, testDefaults({
autoInstallPeers: false,
allProjects,
frozenLockfile: true,
}))
projects['project-1'].has('is-negative')
projects['project-1'].has('@pnpm.e2e/dep-of-pkg-with-1-dep')
projects['project-1'].hasNot('is-positive')
projects['project-2'].has('is-positive')
projects['project-2'].has('project-1')
projects['project-3'].has('is-positive')
projects['project-3'].has('project-2')
expect(fs.readdirSync('node_modules/.pnpm')).toHaveLength(8)
// The injected project is updated when one of its dependencies needs to be updated
allProjects[0].manifest.dependencies!['is-negative'] = '2.0.0'
writeJsonFileSync('project-1/package.json', allProjects[0].manifest)
await mutateModules(importers, testDefaults({
autoInstallPeers: false,
allProjects,
}))
{
const lockfile = rootModules.readLockfile()
expect(lockfile.snapshots['project-1@file:project-1(is-positive@1.0.0)']).toEqual({
dependencies: {
'is-negative': '2.0.0',
'is-positive': '1.0.0',
},
})
expect(lockfile.packages['project-1@file:project-1']).toEqual({
resolution: {
directory: 'project-1',
type: 'directory',
},
peerDependencies: {
'is-positive': '>=1.0.0',
},
})
const modulesState = rootModules.readModulesManifest()
expect(modulesState?.injectedDeps?.['project-1'].length).toBe(2)
expect(modulesState?.injectedDeps?.['project-1'][0]).toContain(`node_modules${path.sep}.pnpm`)
expect(modulesState?.injectedDeps?.['project-1'][1]).toContain(`node_modules${path.sep}.pnpm`)
}
})
test('inject local packages and relink them after build', async () => {
const project1Manifest = {
name: 'project-1',
version: '1.0.0',
dependencies: {
'is-negative': '1.0.0',
},
devDependencies: {
'@pnpm.e2e/dep-of-pkg-with-1-dep': '100.0.0',
},
peerDependencies: {
'is-positive': '1.0.0',
},
scripts: {
prepublishOnly: 'touch main.js',
},
}
const project2Manifest = {
name: 'project-2',
version: '1.0.0',
dependencies: {
'is-positive': '1.0.0',
'project-1': 'workspace:1.0.0',
},
dependenciesMeta: {
'project-1': {
injected: true,
},
},
}
const projects = preparePackages([
{
location: 'project-1',
package: project1Manifest,
},
{
location: 'project-2',
package: project2Manifest,
},
])
const importers: MutatedProject[] = [
{
mutation: 'install',
rootDir: path.resolve('project-1') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('project-2') as ProjectRootDir,
},
]
const allProjects = [
{
buildIndex: 0,
manifest: project1Manifest,
rootDir: path.resolve('project-1') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: project2Manifest,
rootDir: path.resolve('project-2') as ProjectRootDir,
},
]
await mutateModules(importers, testDefaults({
autoInstallPeers: false,
allProjects,
}))
projects['project-1'].has('is-negative')
projects['project-1'].has('@pnpm.e2e/dep-of-pkg-with-1-dep')
projects['project-1'].hasNot('is-positive')
projects['project-2'].has('is-positive')
projects['project-2'].has('project-1')
expect(fs.existsSync(path.resolve('project-2/node_modules/project-1/main.js'))).toBeTruthy()
const rootModules = assertProject(process.cwd())
const lockfile = rootModules.readLockfile()
expect(lockfile.importers['project-2'].dependenciesMeta).toEqual({
'project-1': {
injected: true,
},
})
expect(lockfile.snapshots['project-1@file:project-1(is-positive@1.0.0)']).toEqual({
dependencies: {
'is-negative': '1.0.0',
'is-positive': '1.0.0',
},
})
expect(lockfile.packages['project-1@file:project-1']).toEqual({
resolution: {
directory: 'project-1',
type: 'directory',
},
peerDependencies: {
'is-positive': '1.0.0',
},
})
rimrafSync('node_modules')
rimrafSync('project-1/main.js')
rimrafSync('project-1/node_modules')
rimrafSync('project-2/node_modules')
await mutateModules(importers, testDefaults({
autoInstallPeers: false,
allProjects,
frozenLockfile: true,
}))
projects['project-1'].has('is-negative')
projects['project-1'].has('@pnpm.e2e/dep-of-pkg-with-1-dep')
projects['project-1'].hasNot('is-positive')
projects['project-2'].has('is-positive')
projects['project-2'].has('project-1')
expect(fs.existsSync(path.resolve('project-2/node_modules/project-1/main.js'))).toBeTruthy()
})
test('inject local packages and relink them after build (file protocol is used)', async () => {
const project1Manifest = {
name: 'project-1',
version: '1.0.0',
dependencies: {
'is-negative': '1.0.0',
},
devDependencies: {
'@pnpm.e2e/dep-of-pkg-with-1-dep': '100.0.0',
},
peerDependencies: {
'is-positive': '1.0.0',
},
scripts: {
prepublishOnly: 'touch main.js',
},
}
const project2Manifest = {
name: 'project-2',
version: '1.0.0',
dependencies: {
'is-positive': '1.0.0',
'project-1': 'file:../project-1',
},
}
const projects = preparePackages([
{
location: 'project-1',
package: project1Manifest,
},
{
location: 'project-2',
package: project2Manifest,
},
])
const importers: MutatedProject[] = [
{
mutation: 'install',
rootDir: path.resolve('project-1') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('project-2') as ProjectRootDir,
},
]
const allProjects = [
{
buildIndex: 0,
manifest: project1Manifest,
rootDir: path.resolve('project-1') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: project2Manifest,
rootDir: path.resolve('project-2') as ProjectRootDir,
},
]
await mutateModules(importers, testDefaults({ autoInstallPeers: false, allProjects }))
projects['project-1'].has('is-negative')
projects['project-1'].has('@pnpm.e2e/dep-of-pkg-with-1-dep')
projects['project-1'].hasNot('is-positive')
projects['project-2'].has('is-positive')
projects['project-2'].has('project-1')
expect(fs.existsSync(path.resolve('project-2/node_modules/project-1/main.js'))).toBeTruthy()
const rootModules = assertProject(process.cwd())
const lockfile = rootModules.readLockfile()
expect(lockfile.snapshots['project-1@file:project-1(is-positive@1.0.0)']).toEqual({
dependencies: {
'is-negative': '1.0.0',
'is-positive': '1.0.0',
},
})
expect(lockfile.packages['project-1@file:project-1']).toEqual({
resolution: {
directory: 'project-1',
type: 'directory',
},
peerDependencies: {
'is-positive': '1.0.0',
},
})
rimrafSync('node_modules')
rimrafSync('project-1/main.js')
rimrafSync('project-1/node_modules')
rimrafSync('project-2/node_modules')
await mutateModules(importers, testDefaults({
autoInstallPeers: false,
allProjects,
frozenLockfile: true,
}))
projects['project-1'].has('is-negative')
projects['project-1'].has('@pnpm.e2e/dep-of-pkg-with-1-dep')
projects['project-1'].hasNot('is-positive')
projects['project-2'].has('is-positive')
projects['project-2'].has('project-1')
expect(fs.existsSync(path.resolve('project-2/node_modules/project-1/main.js'))).toBeTruthy()
})
test('inject local packages when node-linker is hoisted', async () => {
const project1Manifest = {
name: 'project-1',
version: '1.0.0',
dependencies: {
'is-negative': '1.0.0',
'@pnpm.e2e/dep-of-pkg-with-1-dep': '100.0.0',
},
peerDependencies: {
'is-positive': '>=1.0.0',
},
}
const project2Manifest = {
name: 'project-2',
version: '1.0.0',
dependencies: {
'project-1': 'workspace:1.0.0',
'@pnpm.e2e/dep-of-pkg-with-1-dep': '101.0.0',
},
devDependencies: {
'is-positive': '1.0.0',
},
dependenciesMeta: {
'project-1': {
injected: true,
},
},
}
const project3Manifest = {
name: 'project-3',
version: '1.0.0',
dependencies: {
'project-2': 'workspace:1.0.0',
},
devDependencies: {
'is-positive': '2.0.0',
},
dependenciesMeta: {
'project-2': {
injected: true,
},
},
}
const projects = preparePackages([
{
location: 'project-1',
package: project1Manifest,
},
{
location: 'project-2',
package: project2Manifest,
},
{
location: 'project-3',
package: project3Manifest,
},
])
const importers: MutatedProject[] = [
{
mutation: 'install',
rootDir: path.resolve('project-1') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('project-2') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('project-3') as ProjectRootDir,
},
]
const allProjects = [
{
buildIndex: 0,
manifest: project1Manifest,
rootDir: path.resolve('project-1') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: project2Manifest,
rootDir: path.resolve('project-2') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: project3Manifest,
rootDir: path.resolve('project-3') as ProjectRootDir,
},
]
await mutateModules(importers, testDefaults({
autoInstallPeers: false,
allProjects,
nodeLinker: 'hoisted',
}))
const rootModules = assertProject(process.cwd())
rootModules.has('is-negative')
rootModules.has('@pnpm.e2e/dep-of-pkg-with-1-dep')
rootModules.has('is-positive')
projects['project-2'].has('project-1')
projects['project-2'].has('project-1/node_modules/@pnpm.e2e/dep-of-pkg-with-1-dep')
projects['project-3'].has('project-1')
projects['project-3'].has('project-2')
projects['project-3'].has('is-positive')
{
const lockfile = rootModules.readLockfile()
expect(lockfile.importers['project-2'].dependenciesMeta).toEqual({
'project-1': {
injected: true,
},
})
expect(lockfile.snapshots['project-1@file:project-1(is-positive@1.0.0)']).toEqual({
dependencies: {
'@pnpm.e2e/dep-of-pkg-with-1-dep': '100.0.0',
'is-negative': '1.0.0',
'is-positive': '1.0.0',
},
})
expect(lockfile.packages['project-1@file:project-1']).toEqual({
resolution: {
directory: 'project-1',
type: 'directory',
},
peerDependencies: {
'is-positive': '>=1.0.0',
},
})
expect(lockfile.snapshots['project-2@file:project-2(is-positive@2.0.0)']).toEqual({
dependencies: {
'@pnpm.e2e/dep-of-pkg-with-1-dep': '101.0.0',
'project-1': 'file:project-1(is-positive@2.0.0)',
},
transitivePeerDependencies: ['is-positive'],
})
expect(lockfile.packages['project-2@file:project-2']).toEqual({
resolution: {
directory: 'project-2',
type: 'directory',
},
})
const modulesState = rootModules.readModulesManifest()
expect(modulesState?.injectedDeps?.['project-1'].length).toBe(2)
expect(modulesState?.injectedDeps?.['project-1'][0]).toEqual(path.join('project-2', 'node_modules', 'project-1'))
expect(modulesState?.injectedDeps?.['project-1'][1]).toEqual(path.join('project-3', 'node_modules', 'project-1'))
}
})
test('inject local packages when node-linker is hoisted and dependenciesMeta is set via a hook', async () => {
const project1Manifest = {
name: 'project-1',
version: '1.0.0',
dependencies: {
'is-negative': '1.0.0',
'@pnpm.e2e/dep-of-pkg-with-1-dep': '100.0.0',
},
peerDependencies: {
'is-positive': '>=1.0.0',
},
}
const project2Manifest = {
name: 'project-2',
version: '1.0.0',
dependencies: {
'project-1': 'workspace:1.0.0',
'@pnpm.e2e/dep-of-pkg-with-1-dep': '101.0.0',
},
devDependencies: {
'is-positive': '1.0.0',
},
}
const project3Manifest = {
name: 'project-3',
version: '1.0.0',
dependencies: {
'project-2': 'workspace:1.0.0',
},
devDependencies: {
'is-positive': '2.0.0',
},
}
const projects = preparePackages([
{
location: 'project-1',
package: project1Manifest,
},
{
location: 'project-2',
package: project2Manifest,
},
{
location: 'project-3',
package: project3Manifest,
},
])
const importers: MutatedProject[] = [
{
mutation: 'install',
rootDir: path.resolve('project-1') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('project-2') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('project-3') as ProjectRootDir,
},
]
const allProjects = [
{
buildIndex: 0,
manifest: project1Manifest,
rootDir: path.resolve('project-1') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: project2Manifest,
rootDir: path.resolve('project-2') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: project3Manifest,
rootDir: path.resolve('project-3') as ProjectRootDir,
},
]
await mutateModules(importers, testDefaults({
autoInstallPeers: false,
allProjects,
nodeLinker: 'hoisted',
hooks: {
readPackage: (manifest: any) => { // eslint-disable-line
if (manifest.name === 'project-2') {
manifest.dependenciesMeta = {
'project-1': {
injected: true,
},
}
}
if (manifest.name === 'project-3') {
manifest.dependenciesMeta = {
'project-2': {
injected: true,
},
}
}
return manifest
},
},
} as any)) // eslint-disable-line
const rootModules = assertProject(process.cwd())
rootModules.has('is-negative')
rootModules.has('@pnpm.e2e/dep-of-pkg-with-1-dep')
rootModules.has('is-positive')
projects['project-2'].has('project-1')
projects['project-2'].has('project-1/node_modules/@pnpm.e2e/dep-of-pkg-with-1-dep')
projects['project-3'].has('project-1')
projects['project-3'].has('project-2')
projects['project-3'].has('is-positive')
{
const lockfile = rootModules.readLockfile()
expect(lockfile.importers['project-2'].dependenciesMeta).toEqual({
'project-1': {
injected: true,
},
})
expect(lockfile.snapshots['project-1@file:project-1(is-positive@1.0.0)']).toEqual({
dependencies: {
'@pnpm.e2e/dep-of-pkg-with-1-dep': '100.0.0',
'is-negative': '1.0.0',
'is-positive': '1.0.0',
},
})
expect(lockfile.packages['project-1@file:project-1']).toEqual({
resolution: {
directory: 'project-1',
type: 'directory',
},
peerDependencies: {
'is-positive': '>=1.0.0',
},
})
expect(lockfile.snapshots['project-2@file:project-2(is-positive@2.0.0)']).toEqual({
dependencies: {
'@pnpm.e2e/dep-of-pkg-with-1-dep': '101.0.0',
'project-1': 'file:project-1(is-positive@2.0.0)',
},
transitivePeerDependencies: ['is-positive'],
})
expect(lockfile.packages['project-2@file:project-2']).toEqual({
resolution: {
directory: 'project-2',
type: 'directory',
},
})
const modulesState = rootModules.readModulesManifest()
expect(modulesState?.injectedDeps?.['project-1'].length).toBe(2)
expect(modulesState?.injectedDeps?.['project-1'][0]).toEqual(path.join('project-2', 'node_modules', 'project-1'))
expect(modulesState?.injectedDeps?.['project-1'][1]).toEqual(path.join('project-3', 'node_modules', 'project-1'))
}
})
test('peer dependency of injected project should be resolved correctly', async () => {
const project1Manifest = {
name: 'project-1',
version: '1.0.0',
dependencies: {},
}
const project2Manifest = {
name: 'project-2',
version: '1.0.0',
devDependencies: {
'project-1': 'workspace:1.0.0',
},
peerDependencies: {
'project-1': 'workspace:^1.0.0',
},
}
const project3Manifest = {
name: 'project-3',
version: '1.0.0',
dependencies: {
'project-1': 'workspace:1.0.0',
'project-2': 'workspace:1.0.0',
},
dependenciesMeta: {
'project-2': {
injected: true,
},
},
}
preparePackages([
{
location: 'project-1',
package: project1Manifest,
},
{
location: 'project-2',
package: project2Manifest,
},
{
location: 'project-3',
package: project3Manifest,
},
])
const importers: MutatedProject[] = [
{
mutation: 'install',
rootDir: path.resolve('project-1') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('project-2') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('project-3') as ProjectRootDir,
},
]
const allProjects = [
{
buildIndex: 0,
manifest: project1Manifest,
rootDir: path.resolve('project-1') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: project2Manifest,
rootDir: path.resolve('project-2') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: project3Manifest,
rootDir: path.resolve('project-3') as ProjectRootDir,
},
]
await mutateModules(importers, testDefaults({
autoInstallPeers: false,
allProjects,
nodeLinker: 'hoisted',
}))
const rootModules = assertProject(process.cwd())
const lockfile = rootModules.readLockfile()
expect(lockfile.snapshots?.['project-2@file:project-2(project-1@project-1)'].dependencies?.['project-1']).toBe('link:project-1')
})
// There was a bug related to this. The manifests in the workspacePackages object were modified
test('do not modify the manifest of the injected workspace project', async () => {
const project1Manifest = {
name: 'project-1',
version: '1.0.0',
dependencies: {
'is-positive': '1.0.0',
},
peerDependencies: {
'is-positive': '>=1.0.0',
},
}
const project2Manifest = {
name: 'project-2',
version: '1.0.0',
dependencies: {
'project-1': 'workspace:1.0.0',
},
devDependencies: {
'is-positive': '1.0.0',
},
dependenciesMeta: {
'project-1': {
injected: true,
},
},
}
preparePackages([
{
location: 'project-1',
package: project1Manifest,
},
{
location: 'project-2',
package: project2Manifest,
},
])
const importers: MutatedProject[] = [
{
mutation: 'install',
rootDir: path.resolve('project-1') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('project-2') as ProjectRootDir,
},
]
const allProjects = [
{
buildIndex: 0,
manifest: project1Manifest,
rootDir: path.resolve('project-1') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: project2Manifest,
rootDir: path.resolve('project-2') as ProjectRootDir,
},
]
const [project1] = (await mutateModules(importers, testDefaults({
autoInstallPeers: false,
allProjects,
}))).updatedProjects
expect(project1.manifest).toStrictEqual({
name: 'project-1',
version: '1.0.0',
dependencies: {
'is-positive': '1.0.0',
},
peerDependencies: {
'is-positive': '>=1.0.0',
},
})
})
test('injected package is kept up-to-date when it is hoisted to multiple places', async () => {
// We create a root project with is-positive in the dependencies, so that the local is-positive
// inside project-1 and project-2 will be nested into their node_modules
const rootProjectManifest = {
name: 'project-1',
version: '1.0.0',
dependencies: {
'is-positive': '2.0.0',
},
}
const project1Manifest = {
name: 'project-1',
version: '1.0.0',
dependencies: {
'is-positive': 'workspace:1.0.0',
},
dependenciesMeta: {
'is-positive': {
injected: true,
},
},
}
const project2Manifest = {
name: 'project-2',
version: '1.0.0',
dependencies: {
'is-positive': 'workspace:1.0.0',
},
dependenciesMeta: {
'is-positive': {
injected: true,
},
},
}
const project3Manifest = {
name: 'is-positive',
version: '1.0.0',
scripts: {
prepare: 'node -e "require(\'fs\').writeFileSync(\'prepare.txt\', \'prepare\', \'utf8\')"',
},
}
const projects = preparePackages([
{
location: '',
package: rootProjectManifest,
},
{
location: 'project-1',
package: project1Manifest,
},
{
location: 'project-2',
package: project2Manifest,
},
{
location: 'project-3',
package: project3Manifest,
},
])
const importers: MutatedProject[] = [
{
mutation: 'install',
rootDir: process.cwd() as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('project-1') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('project-2') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('project-3') as ProjectRootDir,
},
]
const allProjects = [
{
buildIndex: 0,
manifest: rootProjectManifest,
rootDir: process.cwd() as ProjectRootDir,
},
{
buildIndex: 0,
manifest: project1Manifest,
rootDir: path.resolve('project-1') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: project2Manifest,
rootDir: path.resolve('project-2') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: project3Manifest,
rootDir: path.resolve('project-3') as ProjectRootDir,
},
]
await mutateModules(importers, testDefaults({
allProjects,
dedupeInjectedDeps: false,
nodeLinker: 'hoisted',
}))
projects['project-1'].has('is-positive/prepare.txt')
projects['project-2'].has('is-positive/prepare.txt')
const rootModules = assertProject(process.cwd())
const modulesState = rootModules.readModulesManifest()
expect(modulesState?.injectedDeps?.['project-3'].length).toBe(2)
expect(modulesState?.injectedDeps?.['project-3'][0]).toEqual(path.join('project-1', 'node_modules', 'is-positive'))
expect(modulesState?.injectedDeps?.['project-3'][1]).toEqual(path.join('project-2', 'node_modules', 'is-positive'))
})
test('relink injected dependency on install by default', async () => {
const depManifest = {
name: 'dep',
version: '1.0.0',
}
const mainManifest = {
name: 'main',
version: '1.0.0',
dependencies: {
dep: 'workspace:1.0.0',
},
dependenciesMeta: {
dep: {
injected: true,
},
},
}
preparePackages([
{
location: 'dep',
package: depManifest,
},
{
location: 'main',
package: mainManifest,
},
])
fs.writeFileSync('dep/index.js', 'console.log("dep")')
const importers: MutatedProject[] = [
{
mutation: 'install',
rootDir: path.resolve('dep') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('main') as ProjectRootDir,
},
]
const allProjects: ProjectOptions[] = [
{
buildIndex: 0,
manifest: depManifest,
rootDir: path.resolve('dep') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: mainManifest,
rootDir: path.resolve('main') as ProjectRootDir,
},
]
await mutateModules(importers, testDefaults({
allProjects,
dedupeInjectedDeps: false,
packageImportMethod: 'hardlink',
fastUnpack: false,
}))
const indexJsPath = path.resolve('main/node_modules/dep/index.js')
const getInode = () => fs.statSync(indexJsPath).ino
const storeInode = getInode()
// rewriting index.js, to destroy the link
fs.unlinkSync(indexJsPath)
fs.writeFileSync(indexJsPath, 'console.log("dep updated")')
expect(storeInode).not.toEqual(getInode())
await mutateModules(importers, testDefaults({
allProjects,
dedupeInjectedDeps: false,
packageImportMethod: 'hardlink',
fastUnpack: false,
}))
expect(storeInode).toEqual(getInode())
})
test('do not relink injected dependency on install when disableRelinkLocalDirDeps is set to true', async () => {
const depManifest = {
name: 'dep',
version: '1.0.0',
}
const mainManifest = {
name: 'main',
version: '1.0.0',
dependencies: {
dep: 'workspace:1.0.0',
},
dependenciesMeta: {
dep: {
injected: true,
},
},
}
preparePackages([
{
location: 'dep',
package: depManifest,
},
{
location: 'main',
package: mainManifest,
},
])
fs.writeFileSync('dep/index.js', 'console.log("dep")')
const importers: MutatedProject[] = [
{
mutation: 'install',
rootDir: path.resolve('dep') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('main') as ProjectRootDir,
},
]
const allProjects: ProjectOptions[] = [
{
buildIndex: 0,
manifest: depManifest,
rootDir: path.resolve('dep') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: mainManifest,
rootDir: path.resolve('main') as ProjectRootDir,
},
]
await mutateModules(importers, testDefaults({
allProjects,
dedupeInjectedDeps: false,
packageImportMethod: 'hardlink',
fastUnpack: false,
}))
const pkgJsonPath = path.resolve('main/node_modules/dep/package.json')
const getInode = () => fs.statSync(pkgJsonPath).ino
const storeInode = getInode()
// rewriting index.js, to destroy the link
const pkgJsonContent = fs.readFileSync(pkgJsonPath, 'utf8')
fs.unlinkSync(pkgJsonPath)
fs.writeFileSync(pkgJsonPath, pkgJsonContent)
const newInode = getInode()
expect(storeInode).not.toEqual(newInode)
await mutateModules(importers, testDefaults({
allProjects,
dedupeInjectedDeps: false,
packageImportMethod: 'hardlink',
fastUnpack: false,
disableRelinkLocalDirDeps: true,
}))
expect(newInode).toEqual(getInode())
})
test('injected local packages are deduped', async () => {
const project1Manifest = {
name: 'project-1',
version: '1.0.0',
dependencies: {
'is-negative': '1.0.0',
},
devDependencies: {
'@pnpm.e2e/dep-of-pkg-with-1-dep': '100.0.0',
},
peerDependencies: {
'is-positive': '>=1.0.0',
},
}
const project2Manifest = {
name: 'project-2',
version: '1.0.0',
dependencies: {
'project-1': 'workspace:1.0.0',
},
devDependencies: {
'is-positive': '2.0.0',
},
dependenciesMeta: {
'project-1': {
injected: true,
},
},
}
const project3Manifest = {
name: 'project-3',
version: '1.0.0',
dependencies: {
'project-2': 'workspace:1.0.0',
},
devDependencies: {
'is-positive': '2.0.0',
},
dependenciesMeta: {
'project-2': {
injected: true,
},
},
}
const project4Manifest = {
name: 'project-4',
version: '1.0.0',
dependencies: {
'project-2': 'workspace:1.0.0',
},
devDependencies: {
'is-positive': '1.0.0',
},
dependenciesMeta: {
'project-2': {
injected: true,
},
},
}
const project5Manifest = {
name: 'project-5',
version: '1.0.0',
dependencies: {
'project-4': 'workspace:1.0.0',
},
devDependencies: {
'is-positive': '1.0.0',
},
dependenciesMeta: {
'project-4': {
injected: true,
},
},
}
const projects = preparePackages([
{
location: 'project-1',
package: project1Manifest,
},
{
location: 'project-2',
package: project2Manifest,
},
{
location: 'project-3',
package: project3Manifest,
},
{
location: 'project-4',
package: project4Manifest,
},
{
location: 'project-5',
package: project5Manifest,
},
])
const importers: MutatedProject[] = [
{
mutation: 'install',
rootDir: path.resolve('project-1') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('project-2') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('project-3') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('project-4') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('project-5') as ProjectRootDir,
},
]
const allProjects: ProjectOptions[] = [
{
buildIndex: 0,
manifest: project1Manifest,
rootDir: path.resolve('project-1') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: project2Manifest,
rootDir: path.resolve('project-2') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: project3Manifest,
rootDir: path.resolve('project-3') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: project4Manifest,
rootDir: path.resolve('project-4') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: project5Manifest,
rootDir: path.resolve('project-5') as ProjectRootDir,
},
]
await mutateModules(importers, testDefaults({
autoInstallPeers: true,
allProjects,
dedupeInjectedDeps: true,
}))
projects['project-1'].has('is-negative')
projects['project-1'].has('@pnpm.e2e/dep-of-pkg-with-1-dep')
projects['project-1'].has('is-positive')
projects['project-2'].has('is-positive')
projects['project-2'].has('project-1')
projects['project-3'].has('is-positive')
projects['project-3'].has('project-2')
expect(fs.readdirSync('node_modules/.pnpm')).toHaveLength(7)
const rootModules = assertProject(process.cwd())
{
const lockfile = rootModules.readLockfile()
expect(lockfile.importers['project-2'].dependenciesMeta).toEqual({
'project-1': {
injected: true,
},
})
expect(lockfile.packages['project-1@file:project-1(is-positive@1.0.0)']).toBeFalsy()
expect(lockfile.snapshots['project-2@file:project-2(is-positive@1.0.0)']).toEqual({
dependencies: {
'project-1': 'file:project-1(is-positive@1.0.0)',
},
transitivePeerDependencies: ['is-positive'],
})
expect(lockfile.packages['project-2@file:project-2']).toEqual({
resolution: {
directory: 'project-2',
type: 'directory',
},
})
const modulesState = rootModules.readModulesManifest()
expect(modulesState?.injectedDeps?.['project-1'].length).toBe(1)
expect(modulesState?.injectedDeps?.['project-1'][0]).toContain(`node_modules${path.sep}.pnpm`)
}
rimrafSync('node_modules')
rimrafSync('project-1/node_modules')
rimrafSync('project-2/node_modules')
rimrafSync('project-3/node_modules')
await mutateModules(importers, testDefaults({
autoInstallPeers: true,
allProjects,
dedupeInjectedDeps: true,
frozenLockfile: true,
}))
projects['project-1'].has('is-negative')
projects['project-1'].has('@pnpm.e2e/dep-of-pkg-with-1-dep')
projects['project-1'].has('is-positive')
projects['project-2'].has('is-positive')
projects['project-2'].has('project-1')
projects['project-3'].has('is-positive')
projects['project-3'].has('project-2')
expect(fs.readdirSync('node_modules/.pnpm')).toHaveLength(7)
// The injected project is updated when one of its dependencies needs to be updated
allProjects[0].manifest.dependencies!['is-negative'] = '2.0.0'
await mutateModules(importers, testDefaults({ autoInstallPeers: true, allProjects, dedupeInjectedDeps: true }))
{
const lockfile = rootModules.readLockfile()
expect(lockfile.importers['project-2'].dependenciesMeta).toEqual({
'project-1': {
injected: true,
},
})
expect(lockfile.packages['project-1@file:project-1(is-positive@1.0.0)']).toBeFalsy()
const modulesState = rootModules.readModulesManifest()
expect(modulesState?.injectedDeps?.['project-1'].length).toBe(1)
expect(modulesState?.injectedDeps?.['project-1'][0]).toContain(`node_modules${path.sep}.pnpm`)
}
})