mirror of
https://github.com/pnpm/pnpm.git
synced 2026-01-25 07:18:33 -05:00
333 lines
10 KiB
TypeScript
333 lines
10 KiB
TypeScript
import fs from 'fs'
|
|
import path from 'path'
|
|
import { addDependenciesToPackage, install, mutateModules, mutateModulesInSingleProject } from '@pnpm/core'
|
|
import { prepareEmpty, preparePackages } from '@pnpm/prepare'
|
|
import { addDistTag } from '@pnpm/registry-mock'
|
|
import { type ProjectRootDir } from '@pnpm/types'
|
|
import { sync as rimraf } from '@zkochan/rimraf'
|
|
import { sync as loadJsonFile } from 'load-json-file'
|
|
import { sync as readYamlFile } from 'read-yaml-file'
|
|
import symlinkDir from 'symlink-dir'
|
|
import { testDefaults } from '../utils'
|
|
|
|
test('installing with hoisted node-linker', async () => {
|
|
prepareEmpty()
|
|
|
|
const manifest = {
|
|
dependencies: {
|
|
send: '0.17.2',
|
|
'has-flag': '1.0.0',
|
|
ms: '1.0.0',
|
|
},
|
|
}
|
|
await install(manifest, testDefaults({
|
|
nodeLinker: 'hoisted',
|
|
}))
|
|
|
|
expect(fs.realpathSync('node_modules/send')).toEqual(path.resolve('node_modules/send'))
|
|
expect(fs.realpathSync('node_modules/has-flag')).toEqual(path.resolve('node_modules/has-flag'))
|
|
expect(fs.realpathSync('node_modules/ms')).toEqual(path.resolve('node_modules/ms'))
|
|
expect(fs.existsSync('node_modules/send/node_modules/ms')).toBeTruthy()
|
|
|
|
expect(readYamlFile<{ nodeLinker: string }>('node_modules/.modules.yaml').nodeLinker).toBe('hoisted')
|
|
|
|
// If a package from node_modules is removed, it should be re-added.
|
|
rimraf('node_modules/send')
|
|
await install(manifest, testDefaults({
|
|
nodeLinker: 'hoisted',
|
|
}))
|
|
expect(fs.realpathSync('node_modules/send')).toEqual(path.resolve('node_modules/send'))
|
|
})
|
|
|
|
test('installing with hoisted node-linker and no lockfile', async () => {
|
|
prepareEmpty()
|
|
|
|
await install({
|
|
dependencies: {
|
|
ms: '1.0.0',
|
|
},
|
|
}, testDefaults({
|
|
useLockfile: false,
|
|
nodeLinker: 'hoisted',
|
|
}))
|
|
|
|
expect(fs.realpathSync('node_modules/ms')).toEqual(path.resolve('node_modules/ms'))
|
|
expect(fs.existsSync('pnpm-lock.yaml')).toBeFalsy()
|
|
})
|
|
|
|
test('overwriting (is-positive@3.0.0 with is-positive@latest)', async () => {
|
|
const project = prepareEmpty()
|
|
const manifest = await addDependenciesToPackage(
|
|
{},
|
|
['is-positive@3.0.0'],
|
|
testDefaults({ nodeLinker: 'hoisted', save: true })
|
|
)
|
|
|
|
project.storeHas('is-positive', '3.0.0')
|
|
|
|
const updatedManifest = await addDependenciesToPackage(
|
|
manifest,
|
|
['is-positive@latest'],
|
|
testDefaults({ nodeLinker: 'hoisted', save: true })
|
|
)
|
|
|
|
project.storeHas('is-positive', '3.1.0')
|
|
expect(updatedManifest.dependencies?.['is-positive']).toBe('3.1.0')
|
|
expect(loadJsonFile<{ version: string }>('node_modules/is-positive/package.json').version).toBe('3.1.0')
|
|
})
|
|
|
|
test('overwriting existing files in node_modules', async () => {
|
|
prepareEmpty()
|
|
await symlinkDir(__dirname, path.resolve('node_modules/is-positive'))
|
|
|
|
const manifest = await addDependenciesToPackage(
|
|
{},
|
|
['is-positive@3.0.0'],
|
|
testDefaults({ nodeLinker: 'hoisted', save: true })
|
|
)
|
|
|
|
expect(manifest.dependencies?.['is-positive']).toBe('3.0.0')
|
|
expect(loadJsonFile<{ version: string }>('node_modules/is-positive/package.json').version).toBe('3.0.0')
|
|
})
|
|
|
|
test('preserve subdeps on update', async () => {
|
|
prepareEmpty()
|
|
|
|
await addDistTag({ package: '@pnpm.e2e/foobarqar', version: '1.0.0', distTag: 'latest' })
|
|
|
|
const manifest = await addDependenciesToPackage(
|
|
{},
|
|
['@pnpm.e2e/foobarqar@1.0.0', '@pnpm.e2e/bar@100.1.0'],
|
|
testDefaults({ nodeLinker: 'hoisted' })
|
|
)
|
|
|
|
await addDependenciesToPackage(
|
|
manifest,
|
|
['@pnpm.e2e/foobarqar@1.0.1'],
|
|
testDefaults({ nodeLinker: 'hoisted' })
|
|
)
|
|
|
|
expect(loadJsonFile<{ version: string }>('node_modules/@pnpm.e2e/bar/package.json').version).toBe('100.1.0')
|
|
expect(loadJsonFile<{ version: string }>('node_modules/@pnpm.e2e/foobarqar/package.json').version).toBe('1.0.1')
|
|
expect(loadJsonFile<{ version: string }>('node_modules/@pnpm.e2e/foobarqar/node_modules/@pnpm.e2e/bar/package.json').version).toBe('100.0.0')
|
|
})
|
|
|
|
test('adding a new dependency to one of the workspace projects', async () => {
|
|
prepareEmpty()
|
|
|
|
let [{ manifest }] = (await mutateModules([
|
|
{
|
|
mutation: 'install',
|
|
rootDir: path.resolve('project-1') as ProjectRootDir,
|
|
},
|
|
{
|
|
mutation: 'install',
|
|
rootDir: path.resolve('project-2') as ProjectRootDir,
|
|
},
|
|
], testDefaults({
|
|
allProjects: [
|
|
{
|
|
buildIndex: 0,
|
|
manifest: {
|
|
name: 'project-1',
|
|
version: '1.0.0',
|
|
|
|
dependencies: {
|
|
'@pnpm.e2e/bar': '100.0.0',
|
|
},
|
|
},
|
|
rootDir: path.resolve('project-1') as ProjectRootDir,
|
|
},
|
|
{
|
|
buildIndex: 1,
|
|
manifest: {
|
|
name: 'project-2',
|
|
version: '1.0.0',
|
|
|
|
dependencies: {
|
|
'@pnpm.e2e/foobarqar': '1.0.0',
|
|
},
|
|
},
|
|
rootDir: path.resolve('project-2') as ProjectRootDir,
|
|
},
|
|
],
|
|
nodeLinker: 'hoisted',
|
|
}))).updatedProjects
|
|
manifest = await addDependenciesToPackage(
|
|
manifest,
|
|
['is-negative@1.0.0'],
|
|
testDefaults({ nodeLinker: 'hoisted', prefix: path.resolve('project-1'), targetDependenciesField: 'devDependencies' })
|
|
)
|
|
|
|
expect(manifest.dependencies).toStrictEqual({ '@pnpm.e2e/bar': '100.0.0' })
|
|
expect(manifest.devDependencies).toStrictEqual({ 'is-negative': '1.0.0' })
|
|
expect(loadJsonFile<{ version: string }>('node_modules/@pnpm.e2e/bar/package.json').version).toBe('100.0.0')
|
|
expect(loadJsonFile<{ version: string }>('node_modules/is-negative/package.json').version).toBe('1.0.0')
|
|
})
|
|
|
|
test('installing the same package with alias and no alias', async () => {
|
|
await addDistTag({ package: '@pnpm.e2e/dep-of-pkg-with-1-dep', version: '100.0.0', distTag: 'latest' })
|
|
prepareEmpty()
|
|
|
|
await addDependenciesToPackage(
|
|
{},
|
|
['@pnpm.e2e/pkg-with-1-aliased-dep@100.0.0', '@pnpm.e2e/dep-of-pkg-with-1-dep@^100.0.0'],
|
|
testDefaults({ nodeLinker: 'hoisted' })
|
|
)
|
|
|
|
expect(loadJsonFile<{ version: string }>('node_modules/@pnpm.e2e/pkg-with-1-aliased-dep/package.json').version).toBe('100.0.0')
|
|
expect(loadJsonFile<{ version: string }>('node_modules/@pnpm.e2e/dep-of-pkg-with-1-dep/package.json').version).toBe('100.0.0')
|
|
expect(loadJsonFile<{ version: string }>('node_modules/dep/package.json').version).toBe('100.0.0')
|
|
})
|
|
|
|
test('run pre/postinstall scripts. bin files should be linked in a hoisted node_modules', async () => {
|
|
const project = prepareEmpty()
|
|
await addDependenciesToPackage({},
|
|
['@pnpm.e2e/pre-and-postinstall-scripts-example'],
|
|
testDefaults({ fastUnpack: false, nodeLinker: 'hoisted', targetDependenciesField: 'devDependencies' })
|
|
)
|
|
|
|
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-prepare.js')).toBeFalsy()
|
|
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeTruthy()
|
|
|
|
const generatedByPreinstall = project.requireModule('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall')
|
|
expect(typeof generatedByPreinstall).toBe('function')
|
|
|
|
const generatedByPostinstall = project.requireModule('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall')
|
|
expect(typeof generatedByPostinstall).toBe('function')
|
|
})
|
|
|
|
// Covers https://github.com/pnpm/pnpm/issues/4209
|
|
test('running install scripts in a workspace that has no root project', async () => {
|
|
prepareEmpty()
|
|
|
|
await mutateModulesInSingleProject({
|
|
manifest: {
|
|
name: 'project-1',
|
|
version: '1.0.0',
|
|
|
|
dependencies: {
|
|
'@pnpm.e2e/pre-and-postinstall-scripts-example': '1.0.0',
|
|
},
|
|
},
|
|
mutation: 'install',
|
|
rootDir: path.resolve('project-1') as ProjectRootDir,
|
|
}, testDefaults({ fastUnpack: false, nodeLinker: 'hoisted' }))
|
|
|
|
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeTruthy()
|
|
})
|
|
|
|
test('hoistingLimits should prevent packages to be hoisted', async () => {
|
|
prepareEmpty()
|
|
|
|
const hoistingLimits = new Map()
|
|
hoistingLimits.set('.@', new Set(['send']))
|
|
await install({
|
|
dependencies: {
|
|
send: '0.17.2',
|
|
},
|
|
}, testDefaults({
|
|
nodeLinker: 'hoisted',
|
|
hoistingLimits,
|
|
}))
|
|
|
|
expect(fs.existsSync('node_modules/ms')).toBeFalsy()
|
|
expect(fs.existsSync('node_modules/send/node_modules/ms')).toBeTruthy()
|
|
})
|
|
|
|
test('externalDependencies should prevent package from being hoisted to the root', async () => {
|
|
prepareEmpty()
|
|
|
|
const externalDependencies = new Set(['ms'])
|
|
await install({
|
|
dependencies: {
|
|
send: '0.17.2',
|
|
},
|
|
}, testDefaults({
|
|
nodeLinker: 'hoisted',
|
|
externalDependencies,
|
|
}))
|
|
|
|
expect(fs.existsSync('node_modules/ms')).toBeFalsy()
|
|
expect(fs.existsSync('node_modules/send/node_modules/ms')).toBeTruthy()
|
|
})
|
|
|
|
test('linking bins of local projects when node-linker is set to hoisted', async () => {
|
|
const project1Manifest = {
|
|
name: 'project-1',
|
|
version: '1.0.0',
|
|
|
|
dependencies: {
|
|
'project-2': 'workspace:*',
|
|
},
|
|
}
|
|
const project2Manifest = {
|
|
name: 'project-2',
|
|
version: '1.0.0',
|
|
bin: {
|
|
'project-2': 'index.js',
|
|
},
|
|
}
|
|
preparePackages([
|
|
project1Manifest,
|
|
project2Manifest,
|
|
])
|
|
fs.writeFileSync('project-2/index.js', '#!/usr/bin/env node\nconsole.log("hello")', 'utf8')
|
|
|
|
await mutateModules([
|
|
{
|
|
mutation: 'install',
|
|
rootDir: path.resolve('project-1') as ProjectRootDir,
|
|
},
|
|
{
|
|
mutation: 'install',
|
|
rootDir: path.resolve('project-2') as ProjectRootDir,
|
|
},
|
|
], testDefaults({
|
|
allProjects: [
|
|
{
|
|
buildIndex: 0,
|
|
manifest: project1Manifest,
|
|
rootDir: path.resolve('project-1') as ProjectRootDir,
|
|
},
|
|
{
|
|
buildIndex: 1,
|
|
manifest: project2Manifest,
|
|
rootDir: path.resolve('project-2') as ProjectRootDir,
|
|
},
|
|
],
|
|
nodeLinker: 'hoisted',
|
|
}))
|
|
|
|
expect(fs.existsSync('project-1/node_modules/.bin/project-2')).toBeTruthy()
|
|
})
|
|
|
|
test('peerDependencies should be installed when autoInstallPeers is set to true and nodeLinker is set to hoisted', async () => {
|
|
prepareEmpty()
|
|
await install({
|
|
dependencies: {
|
|
'react-dom': '18.2.0',
|
|
},
|
|
}, testDefaults({
|
|
nodeLinker: 'hoisted',
|
|
autoInstallPeers: true,
|
|
}))
|
|
|
|
expect(fs.existsSync('node_modules/react')).toBeTruthy()
|
|
})
|
|
|
|
// Covers https://github.com/pnpm/pnpm/issues/8854
|
|
test('installing with hoisted node-linker a package that is a peer dependency of itself', async () => {
|
|
const project = prepareEmpty()
|
|
|
|
await addDependenciesToPackage(
|
|
{},
|
|
['@pnpm.e2e/peer-of-itself@1.0.0'],
|
|
testDefaults({ nodeLinker: 'hoisted', save: true })
|
|
)
|
|
|
|
project.has('@pnpm.e2e/peer-of-itself')
|
|
const lockfile = project.readLockfile()
|
|
expect(lockfile.packages['@pnpm.e2e/peer-of-itself@1.0.0'].peerDependencies).toBeFalsy()
|
|
})
|