Skip to content

Commit

Permalink
fix(resolve-dependencies): a sequence of injected deps via the file p…
Browse files Browse the repository at this point in the history
…rotocol (#4415)
  • Loading branch information
zkochan committed Mar 5, 2022
1 parent 51793ca commit 4941f31
Show file tree
Hide file tree
Showing 3 changed files with 275 additions and 1 deletion.
50 changes: 50 additions & 0 deletions .changeset/selfish-boxes-fry.md
@@ -0,0 +1,50 @@
---
"@pnpm/resolve-dependencies": patch
"pnpm": patch
---

The location of an injected directory dependency should be correctly located, when there is a chain of local dependencies (declared via the `file:` protocol`).

The next scenario was not working prior to the fix. There are 3 projects in the same folder: foo, bar, qar.

`foo/package.json`:

```json
{
"name": "foo",
"dependencies": {
"bar": "file:../bar"
},
"dependenciesMeta": {
"bar": {
"injected": true
}
}
}
```

`bar/package.json`:

```json
{
"name": "bar",
"dependencies": {
"qar": "file:../qar"
},
"dependenciesMeta": {
"qar": {
"injected": true
}
}
}
```

`qar/package.json`:

```json
{
"name": "qar"
}
```

Related PR: [#4415](https://github.com/pnpm/pnpm/pull/4415).
219 changes: 219 additions & 0 deletions packages/core/test/install/injectLocalPackages.ts
Expand Up @@ -5,6 +5,7 @@ import { MutatedProject, mutateModules } from '@pnpm/core'
import { preparePackages } from '@pnpm/prepare'
import rimraf from '@zkochan/rimraf'
import pathExists from 'path-exists'
import { sync as writeJsonFile } from 'write-json-file'
import { testDefaults } from '../utils'

test('inject local packages', async () => {
Expand Down Expand Up @@ -224,6 +225,224 @@ test('inject local packages', async () => {
}
})

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: {
'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[] = [
{
buildIndex: 0,
manifest: project1Manifest,
mutation: 'install',
rootDir: path.resolve('project-1'),
},
{
buildIndex: 0,
manifest: project2Manifest,
mutation: 'install',
rootDir: path.resolve('project-2'),
},
{
buildIndex: 0,
manifest: project3Manifest,
mutation: 'install',
rootDir: path.resolve('project-3'),
},
]
const workspacePackages = {
'project-1': {
'1.0.0': {
dir: path.resolve('project-1'),
manifest: project1Manifest,
},
},
'project-2': {
'1.0.0': {
dir: path.resolve('project-2'),
manifest: project2Manifest,
},
},
'project-3': {
'1.0.0': {
dir: path.resolve('project-3'),
manifest: project2Manifest,
},
},
}
await mutateModules(importers, await testDefaults({
workspacePackages,
}))

await projects['project-1'].has('is-negative')
await projects['project-1'].has('dep-of-pkg-with-1-dep')
await projects['project-1'].hasNot('is-positive')

await projects['project-2'].has('is-positive')
await projects['project-2'].has('project-1')

await projects['project-3'].has('is-positive')
await projects['project-3'].has('project-2')

expect(fs.readdirSync('node_modules/.pnpm').length).toBe(8)

const rootModules = assertProject(process.cwd())
{
const lockfile = await rootModules.readLockfile()
expect(lockfile.importers['project-2'].dependenciesMeta).toEqual({
'project-1': {
injected: true,
},
})
expect(lockfile.packages['file:project-1_is-positive@1.0.0']).toEqual({
resolution: {
directory: 'project-1',
type: 'directory',
},
id: 'file:project-1',
name: 'project-1',
version: '1.0.0',
peerDependencies: {
'is-positive': '>=1.0.0',
},
dependencies: {
'is-negative': '1.0.0',
'is-positive': '1.0.0',
},
dev: false,
})
expect(lockfile.packages['file:project-2_is-positive@2.0.0']).toEqual({
resolution: {
directory: 'project-2',
type: 'directory',
},
id: 'file:project-2',
name: 'project-2',
version: '1.0.0',
dependencies: {
'project-1': 'file:project-1_is-positive@2.0.0',
},
transitivePeerDependencies: ['is-positive'],
dev: false,
})

const modulesState = await rootModules.readModulesManifest()
expect(modulesState?.injectedDeps?.['project-1'].length).toEqual(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`)
}

await rimraf('node_modules')
await rimraf('project-1/node_modules')
await rimraf('project-2/node_modules')
await rimraf('project-3/node_modules')

await mutateModules(importers, await testDefaults({
frozenLockfile: true,
workspacePackages,
}))

await projects['project-1'].has('is-negative')
await projects['project-1'].has('dep-of-pkg-with-1-dep')
await projects['project-1'].hasNot('is-positive')

await projects['project-2'].has('is-positive')
await projects['project-2'].has('project-1')

await projects['project-3'].has('is-positive')
await projects['project-3'].has('project-2')

expect(fs.readdirSync('node_modules/.pnpm').length).toBe(8)

// The injected project is updated when one of its dependencies needs to be updated
importers[0].manifest.dependencies!['is-negative'] = '2.0.0'
writeJsonFile('project-1/package.json', importers[0].manifest)
await mutateModules(importers, await testDefaults({ workspacePackages }))
{
const lockfile = await rootModules.readLockfile()
expect(lockfile.importers['project-2'].dependenciesMeta).toEqual({
'project-1': {
injected: true,
},
})
expect(lockfile.packages['file:project-1_is-positive@1.0.0']).toEqual({
resolution: {
directory: 'project-1',
type: 'directory',
},
id: 'file:project-1',
name: 'project-1',
version: '1.0.0',
peerDependencies: {
'is-positive': '>=1.0.0',
},
dependencies: {
'is-negative': '2.0.0',
'is-positive': '1.0.0',
},
dev: false,
})
const modulesState = await rootModules.readModulesManifest()
expect(modulesState?.injectedDeps?.['project-1'].length).toEqual(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',
Expand Down
7 changes: 6 additions & 1 deletion packages/resolve-dependencies/src/resolveDependencies.ts
Expand Up @@ -619,7 +619,12 @@ async function resolveDependency (
lockfileDir: ctx.lockfileDir,
preferredVersions: options.preferredVersions,
preferWorkspacePackages: ctx.preferWorkspacePackages,
projectDir: options.currentDepth > 0 ? ctx.lockfileDir : ctx.prefix,
projectDir: (
options.currentDepth > 0 &&
!wantedDependency.pref.startsWith('file:')
)
? ctx.lockfileDir
: ctx.prefix,
registry: wantedDependency.alias && pickRegistryForPackage(ctx.registries, wantedDependency.alias, wantedDependency.pref) || ctx.registries.default,
// Unfortunately, even when run with --lockfile-only, we need the *real* package.json
// so fetching of the tarball cannot be ever avoided. Related issue: https://github.com/pnpm/pnpm/issues/1176
Expand Down

0 comments on commit 4941f31

Please sign in to comment.