mirror of
https://github.com/pnpm/pnpm.git
synced 2026-03-31 21:42:15 -04:00
354 lines
10 KiB
TypeScript
354 lines
10 KiB
TypeScript
import fs from 'fs'
|
|
import path from 'path'
|
|
import { LOCKFILE_VERSION, WANTED_LOCKFILE } from '@pnpm/constants'
|
|
import {
|
|
type PackageManifestLog,
|
|
type RootLog,
|
|
type StatsLog,
|
|
} from '@pnpm/core-loggers'
|
|
import { type LockfileObject } from '@pnpm/lockfile.fs'
|
|
import { prepareEmpty, preparePackages } from '@pnpm/prepare'
|
|
import { REGISTRY_MOCK_PORT } from '@pnpm/registry-mock'
|
|
import { fixtures } from '@pnpm/test-fixtures'
|
|
import { type ProjectRootDir, type PackageManifest } from '@pnpm/types'
|
|
import { sync as readYamlFile } from 'read-yaml-file'
|
|
import symlinkDir from 'symlink-dir'
|
|
import {
|
|
addDependenciesToPackage,
|
|
mutateModules,
|
|
mutateModulesInSingleProject,
|
|
} from '@pnpm/core'
|
|
import sinon from 'sinon'
|
|
import { writeJsonFileSync } from 'write-json-file'
|
|
import existsSymlink from 'exists-link'
|
|
import { testDefaults } from './utils/index.js'
|
|
|
|
const f = fixtures(import.meta.dirname)
|
|
|
|
test('uninstall package with no dependencies', async () => {
|
|
const project = prepareEmpty()
|
|
|
|
let { updatedManifest: manifest } = await addDependenciesToPackage({}, ['is-negative@2.1.0'], testDefaults({ save: true }))
|
|
|
|
const reporter = sinon.spy()
|
|
manifest = (await mutateModulesInSingleProject({
|
|
dependencyNames: ['is-negative'],
|
|
manifest,
|
|
mutation: 'uninstallSome',
|
|
rootDir: process.cwd() as ProjectRootDir,
|
|
}, testDefaults({ save: true, reporter }))).updatedProject.manifest
|
|
|
|
expect(reporter.calledWithMatch({
|
|
initial: {
|
|
dependencies: {
|
|
'is-negative': '2.1.0',
|
|
},
|
|
},
|
|
level: 'debug',
|
|
name: 'pnpm:package-manifest',
|
|
prefix: process.cwd(),
|
|
} as PackageManifestLog)).toBeTruthy()
|
|
expect(reporter.calledWithMatch({
|
|
level: 'debug',
|
|
name: 'pnpm:stats',
|
|
prefix: process.cwd(),
|
|
removed: 1,
|
|
} as StatsLog)).toBeTruthy()
|
|
expect(reporter.calledWithMatch({
|
|
level: 'debug',
|
|
name: 'pnpm:root',
|
|
removed: {
|
|
dependencyType: 'prod',
|
|
name: 'is-negative',
|
|
version: '2.1.0',
|
|
},
|
|
} as RootLog)).toBeTruthy()
|
|
expect(reporter.calledWithMatch({
|
|
level: 'debug',
|
|
name: 'pnpm:package-manifest',
|
|
updated: {
|
|
dependencies: {},
|
|
},
|
|
} as PackageManifestLog)).toBeTruthy()
|
|
|
|
// uninstall does not remove packages from store
|
|
// even if they become unreferenced
|
|
project.storeHas('is-negative', '2.1.0')
|
|
|
|
project.hasNot('is-negative')
|
|
|
|
expect(manifest.dependencies).toStrictEqual({})
|
|
})
|
|
|
|
test('uninstall a dependency that is not present in node_modules', async () => {
|
|
prepareEmpty()
|
|
|
|
const reporter = sinon.spy()
|
|
await mutateModulesInSingleProject({
|
|
dependencyNames: ['is-negative'],
|
|
manifest: {},
|
|
mutation: 'uninstallSome',
|
|
rootDir: process.cwd() as ProjectRootDir,
|
|
}, testDefaults({ reporter }))
|
|
|
|
expect(reporter.calledWithMatch({
|
|
level: 'debug',
|
|
name: 'pnpm:root',
|
|
removed: {
|
|
name: 'is-negative',
|
|
},
|
|
} as RootLog)).toBeFalsy()
|
|
})
|
|
|
|
test('uninstall scoped package', async () => {
|
|
const project = prepareEmpty()
|
|
let { updatedManifest: manifest } = await addDependenciesToPackage({}, ['@zkochan/logger@0.1.0'], testDefaults({ save: true }))
|
|
manifest = (await mutateModulesInSingleProject({
|
|
dependencyNames: ['@zkochan/logger'],
|
|
manifest,
|
|
mutation: 'uninstallSome',
|
|
rootDir: process.cwd() as ProjectRootDir,
|
|
}, testDefaults({ save: true }))).updatedProject.manifest
|
|
|
|
project.storeHas('@zkochan/logger', '0.1.0')
|
|
|
|
project.hasNot('@zkochan/logger')
|
|
|
|
expect(manifest.dependencies).toStrictEqual({})
|
|
})
|
|
|
|
test('uninstall tarball dependency', async () => {
|
|
const project = prepareEmpty()
|
|
const opts = testDefaults({ save: true })
|
|
|
|
let { updatedManifest: manifest } = await addDependenciesToPackage({}, [`http://localhost:${REGISTRY_MOCK_PORT}/is-array/-/is-array-1.0.1.tgz`], opts)
|
|
manifest = (await mutateModulesInSingleProject({
|
|
dependencyNames: ['is-array'],
|
|
manifest,
|
|
mutation: 'uninstallSome',
|
|
rootDir: process.cwd() as ProjectRootDir,
|
|
}, opts)).updatedProject.manifest
|
|
|
|
project.storeHas('is-array', '1.0.1')
|
|
project.hasNot('is-array')
|
|
|
|
expect(manifest.dependencies).toStrictEqual({})
|
|
})
|
|
|
|
test('uninstall package with dependencies and do not touch other deps', async () => {
|
|
const project = prepareEmpty()
|
|
let { updatedManifest: manifest } = await addDependenciesToPackage({}, ['is-negative@2.1.0', 'camelcase-keys@3.0.0'], testDefaults({ save: true }))
|
|
manifest = (await mutateModulesInSingleProject({
|
|
dependencyNames: ['camelcase-keys'],
|
|
manifest,
|
|
mutation: 'uninstallSome',
|
|
rootDir: process.cwd() as ProjectRootDir,
|
|
}, testDefaults({ pruneStore: true, save: true }))).updatedProject.manifest
|
|
|
|
project.storeHasNot('camelcase-keys', '3.0.0')
|
|
project.hasNot('camelcase-keys')
|
|
|
|
project.storeHasNot('camelcase', '3.0.0')
|
|
project.hasNot('camelcase')
|
|
|
|
project.storeHasNot('map-obj', '1.0.1')
|
|
project.hasNot('map-obj')
|
|
|
|
project.storeHas('is-negative', '2.1.0')
|
|
project.has('is-negative')
|
|
|
|
expect(manifest.dependencies).toStrictEqual({ 'is-negative': '2.1.0' })
|
|
|
|
const lockfile = project.readLockfile()
|
|
expect(lockfile.importers['.'].dependencies).toStrictEqual({
|
|
'is-negative': {
|
|
specifier: '2.1.0',
|
|
version: '2.1.0',
|
|
},
|
|
})
|
|
})
|
|
|
|
test('uninstall package with its bin files', async () => {
|
|
prepareEmpty()
|
|
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['@pnpm.e2e/sh-hello-world@1.0.1'], testDefaults({ fastUnpack: false, save: true }))
|
|
await mutateModulesInSingleProject({
|
|
dependencyNames: ['@pnpm.e2e/sh-hello-world'],
|
|
manifest,
|
|
mutation: 'uninstallSome',
|
|
rootDir: process.cwd() as ProjectRootDir,
|
|
}, testDefaults({ save: true }))
|
|
|
|
// check for both a symlink and a file because in some cases the file will be a proxied not symlinked
|
|
const stat = await existsSymlink(path.resolve('node_modules', '.bin', 'sh-hello-world'))
|
|
expect(stat).toBeFalsy()
|
|
|
|
expect(fs.existsSync(path.resolve('node_modules', '.bin', 'sh-hello-world'))).toBeFalsy()
|
|
expect(fs.existsSync(path.resolve('node_modules', '.bin', 'sh-hello-world.cmd'))).toBeFalsy()
|
|
expect(fs.existsSync(path.resolve('node_modules', '.bin', 'sh-hello-world.ps1'))).toBeFalsy()
|
|
})
|
|
|
|
test('relative link is uninstalled', async () => {
|
|
const project = prepareEmpty()
|
|
const opts = testDefaults({ manifest: {}, dir: process.cwd() })
|
|
|
|
const linkedPkgName = 'hello-world-js-bin'
|
|
const linkedPkgPath = path.resolve('..', linkedPkgName)
|
|
|
|
f.copy(linkedPkgName, linkedPkgPath)
|
|
symlinkDir.sync(linkedPkgPath, path.resolve('node_modules/@pnpm.e2e/hello-world-js-bin'))
|
|
project.has('@pnpm.e2e/hello-world-js-bin')
|
|
await mutateModulesInSingleProject({
|
|
dependencyNames: ['@pnpm.e2e/hello-world-js-bin'],
|
|
manifest: {},
|
|
mutation: 'uninstallSome',
|
|
rootDir: process.cwd() as ProjectRootDir,
|
|
}, opts)
|
|
|
|
project.hasNot('@pnpm.e2e/hello-world-js-bin')
|
|
})
|
|
|
|
test('pendingBuilds gets updated after uninstall', async () => {
|
|
const project = prepareEmpty()
|
|
|
|
const { updatedManifest: manifest } = await addDependenciesToPackage({},
|
|
['@pnpm.e2e/pre-and-postinstall-scripts-example', '@pnpm.e2e/with-postinstall-b'],
|
|
testDefaults({ fastUnpack: false, save: true, ignoreScripts: true })
|
|
)
|
|
|
|
const modules1 = project.readModulesManifest()
|
|
expect(modules1).toBeTruthy()
|
|
expect(modules1!.pendingBuilds).toHaveLength(2)
|
|
|
|
await mutateModulesInSingleProject({
|
|
dependencyNames: ['@pnpm.e2e/with-postinstall-b'],
|
|
manifest,
|
|
mutation: 'uninstallSome',
|
|
rootDir: process.cwd() as ProjectRootDir,
|
|
}, testDefaults({ save: true }))
|
|
|
|
const modules2 = project.readModulesManifest()
|
|
expect(modules2).toBeTruthy()
|
|
expect(modules2!.pendingBuilds).toHaveLength(1)
|
|
})
|
|
|
|
test('uninstalling a dependency from package that uses shared lockfile', async () => {
|
|
const pkgs: PackageManifest[] = [
|
|
{
|
|
name: 'project-1',
|
|
version: '1.0.0',
|
|
|
|
dependencies: {
|
|
'is-positive': '1.0.0',
|
|
'project-2': '1.0.0',
|
|
},
|
|
},
|
|
{
|
|
name: 'project-2',
|
|
version: '1.0.0',
|
|
|
|
dependencies: {
|
|
'is-negative': '1.0.0',
|
|
},
|
|
},
|
|
]
|
|
const projects = preparePackages(pkgs)
|
|
|
|
const store = path.resolve('.store')
|
|
|
|
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: pkgs[0],
|
|
rootDir: path.resolve('project-1') as ProjectRootDir,
|
|
},
|
|
{
|
|
buildIndex: 0,
|
|
manifest: pkgs[1],
|
|
rootDir: path.resolve('project-2') as ProjectRootDir,
|
|
},
|
|
],
|
|
store,
|
|
})
|
|
)
|
|
|
|
projects['project-1'].has('is-positive')
|
|
projects['project-2'].has('is-negative')
|
|
|
|
await mutateModulesInSingleProject({
|
|
dependencyNames: ['is-positive', 'project-2'],
|
|
manifest: pkgs[0],
|
|
mutation: 'uninstallSome',
|
|
rootDir: path.resolve('project-1') as ProjectRootDir,
|
|
}, testDefaults({
|
|
lockfileDir: process.cwd(),
|
|
store,
|
|
pruneLockfileImporters: false,
|
|
}))
|
|
|
|
projects['project-1'].hasNot('is-positive')
|
|
projects['project-2'].has('is-negative')
|
|
|
|
const lockfile = readYamlFile<LockfileObject>(WANTED_LOCKFILE)
|
|
|
|
expect(lockfile).toStrictEqual({
|
|
settings: {
|
|
autoInstallPeers: true,
|
|
excludeLinksFromLockfile: false,
|
|
},
|
|
importers: {
|
|
'project-1': {},
|
|
'project-2': {
|
|
dependencies: {
|
|
'is-negative': {
|
|
specifier: '1.0.0',
|
|
version: '1.0.0',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
lockfileVersion: LOCKFILE_VERSION,
|
|
packages: {
|
|
'is-negative@1.0.0': {
|
|
engines: {
|
|
node: '>=0.10.0',
|
|
},
|
|
resolution: {
|
|
integrity: 'sha512-1aKMsFUc7vYQGzt//8zhkjRWPoYkajY/I5MJEvrc0pDoHXrW7n5ri8DYxhy3rR+Dk0QFl7GjHHsZU1sppQrWtw==',
|
|
},
|
|
},
|
|
},
|
|
snapshots: {
|
|
'is-negative@1.0.0': {},
|
|
},
|
|
})
|
|
})
|
|
|
|
test('uninstall remove modules that is not in package.json', async () => {
|
|
const project = prepareEmpty()
|
|
|
|
writeJsonFileSync('node_modules/foo/package.json', { name: 'foo', version: '1.0.0' })
|
|
|
|
project.has('foo')
|
|
|
|
await mutateModulesInSingleProject({
|
|
dependencyNames: ['foo'],
|
|
manifest: {},
|
|
mutation: 'uninstallSome',
|
|
rootDir: process.cwd() as ProjectRootDir,
|
|
}, testDefaults())
|
|
|
|
project.hasNot('foo')
|
|
})
|