mirror of
https://github.com/pnpm/pnpm.git
synced 2026-03-27 11:31:45 -04:00
When lockfile-include-tarball-url is explicitly set to false, tarball URLs are now always excluded from the lockfile. Previously, packages hosted under non-standard tarball URLs would still have their tarball field written to the lockfile even when the setting was false, causing flaky and inconsistent behavior across environments. The fix makes the option tri-state internally: - true: always include tarball URLs - false: never include tarball URLs - undefined (not set): use the existing heuristic that includes tarball URLs only for packages with non-standard registry URLs close #6667
1632 lines
54 KiB
TypeScript
1632 lines
54 KiB
TypeScript
import fs from 'fs'
|
|
import path from 'path'
|
|
import { LOCKFILE_VERSION, WANTED_LOCKFILE } from '@pnpm/constants'
|
|
import { type RootLog } from '@pnpm/core-loggers'
|
|
import { type PnpmError } from '@pnpm/error'
|
|
import { fixtures } from '@pnpm/test-fixtures'
|
|
import { type LockfileObject, type TarballResolution } from '@pnpm/lockfile.fs'
|
|
import { type LockfileFile } from '@pnpm/lockfile.types'
|
|
import { tempDir, prepareEmpty, preparePackages } from '@pnpm/prepare'
|
|
import { readPackageJsonFromDir } from '@pnpm/read-package-json'
|
|
import { addDistTag, getIntegrity, REGISTRY_MOCK_PORT } from '@pnpm/registry-mock'
|
|
import { type DepPath, type ProjectManifest, type ProjectRootDir } from '@pnpm/types'
|
|
import { jest } from '@jest/globals'
|
|
import { sync as readYamlFile } from 'read-yaml-file'
|
|
import {
|
|
addDependenciesToPackage,
|
|
install,
|
|
mutateModules,
|
|
mutateModulesInSingleProject,
|
|
type MutatedProject,
|
|
type ProjectOptions,
|
|
} from '@pnpm/core'
|
|
import { sync as rimraf } from '@zkochan/rimraf'
|
|
import { loadJsonFileSync } from 'load-json-file'
|
|
import nock from 'nock'
|
|
import sinon from 'sinon'
|
|
import { sync as writeYamlFile } from 'write-yaml-file'
|
|
import { testDefaults } from './utils/index.js'
|
|
|
|
afterEach(() => {
|
|
nock.abortPendingRequests()
|
|
nock.cleanAll()
|
|
})
|
|
|
|
const f = fixtures(import.meta.dirname)
|
|
|
|
const LOCKFILE_WARN_LOG = {
|
|
level: 'warn',
|
|
message: `A ${WANTED_LOCKFILE} file exists. The current configuration prohibits to read or write a lockfile`,
|
|
name: 'pnpm',
|
|
}
|
|
|
|
test('lockfile has correct format', async () => {
|
|
await addDistTag({ package: '@pnpm.e2e/pkg-with-1-dep', version: '100.0.0', distTag: 'latest' })
|
|
// Mock the HEAD request that isRepoPublic() in @pnpm/git-resolver makes to check if the repo is public.
|
|
// Without this, transient network failures cause the resolver to fall back to git+https:// instead of
|
|
// resolving via the codeload tarball URL.
|
|
const githubNock = nock('https://github.com', { allowUnmocked: true })
|
|
.head('/kevva/is-negative')
|
|
.reply(200)
|
|
const project = prepareEmpty()
|
|
|
|
await addDependenciesToPackage({},
|
|
[
|
|
'@pnpm.e2e/pkg-with-1-dep',
|
|
'@rstacruz/tap-spec@4.1.1',
|
|
'kevva/is-negative#1d7e288222b53a0cab90a331f1865220ec29560c',
|
|
], testDefaults({ fastUnpack: false, save: true }))
|
|
|
|
const modules = project.readModulesManifest()
|
|
expect(modules).toBeTruthy()
|
|
expect(modules!.pendingBuilds).toHaveLength(0)
|
|
|
|
const lockfile = project.readLockfile()
|
|
const id = '@pnpm.e2e/pkg-with-1-dep@100.0.0'
|
|
|
|
expect(lockfile.lockfileVersion).toBe(LOCKFILE_VERSION)
|
|
|
|
expect(lockfile.importers?.['.'].dependencies).toBeTruthy()
|
|
expect(lockfile.importers?.['.'].dependencies?.['@pnpm.e2e/pkg-with-1-dep'].version).toBe('100.0.0')
|
|
expect(lockfile.importers?.['.'].dependencies).toHaveProperty(['@rstacruz/tap-spec'])
|
|
expect(lockfile.importers?.['.'].dependencies?.['is-negative'].version).toContain('/') // has not shortened tarball from the non-standard registry
|
|
|
|
expect(lockfile.packages).toBeTruthy() // has packages field
|
|
expect(lockfile.packages).toHaveProperty([id])
|
|
expect(lockfile.snapshots[id].dependencies).toBeTruthy()
|
|
expect(lockfile.snapshots[id].dependencies).toHaveProperty(['@pnpm.e2e/dep-of-pkg-with-1-dep'])
|
|
expect(lockfile.packages[id].resolution).toBeTruthy()
|
|
expect((lockfile.packages[id].resolution as { integrity: string }).integrity).toBeTruthy()
|
|
expect((lockfile.packages[id].resolution as TarballResolution).tarball).toBeFalsy()
|
|
|
|
expect(lockfile.packages).toHaveProperty(['is-negative@https://codeload.github.com/kevva/is-negative/tar.gz/1d7e288222b53a0cab90a331f1865220ec29560c'])
|
|
githubNock.done()
|
|
})
|
|
|
|
test('lockfile has dev deps even when installing for prod only', async () => {
|
|
const project = prepareEmpty()
|
|
|
|
await install({
|
|
devDependencies: {
|
|
'is-negative': '2.1.0',
|
|
},
|
|
}, testDefaults({ production: true }))
|
|
|
|
const lockfile = project.readLockfile()
|
|
const id = 'is-negative@2.1.0'
|
|
|
|
expect(lockfile.importers['.'].devDependencies).toBeTruthy()
|
|
|
|
expect(lockfile.importers['.'].devDependencies?.['is-negative'].version).toBe('2.1.0')
|
|
|
|
expect(lockfile.packages[id]).toBeTruthy()
|
|
})
|
|
|
|
test('lockfile with scoped package', async () => {
|
|
prepareEmpty()
|
|
|
|
writeYamlFile(WANTED_LOCKFILE, {
|
|
importers: {
|
|
'.': {
|
|
dependencies: {
|
|
'@types/semver': {
|
|
specifier: '^5.3.31',
|
|
version: '5.3.31',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
lockfileVersion: LOCKFILE_VERSION,
|
|
packages: {
|
|
'@types/semver@5.3.31': {
|
|
resolution: {
|
|
integrity: 'sha512-WBv5F9HrWTyG800cB9M3veCVkFahqXN7KA7c3VUCYZm/xhNzzIFiXiq+rZmj75j7GvWelN3YNrLX7FjtqBvhMw==',
|
|
},
|
|
},
|
|
},
|
|
snapshots: {
|
|
'@types/semver@5.3.31': {},
|
|
},
|
|
}, { lineWidth: 1000 })
|
|
|
|
await install({
|
|
dependencies: {
|
|
'@types/semver': '^5.3.31',
|
|
},
|
|
}, testDefaults({ frozenLockfile: true }))
|
|
})
|
|
|
|
test("lockfile doesn't lock subdependencies that don't satisfy the new specs", async () => {
|
|
const project = prepareEmpty()
|
|
|
|
// depends on react-onclickoutside@5.9.0
|
|
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['react-datetime@2.8.8'], testDefaults({
|
|
autoInstallPeers: false,
|
|
fastUnpack: false,
|
|
save: true,
|
|
strictPeerDependencies: false,
|
|
}))
|
|
|
|
// depends on react-onclickoutside@0.3.4
|
|
await addDependenciesToPackage(manifest, ['react-datetime@1.3.0'], testDefaults({
|
|
autoInstallPeers: false,
|
|
save: true,
|
|
strictPeerDependencies: false,
|
|
}))
|
|
|
|
expect(
|
|
project.requireModule('.pnpm/react-datetime@1.3.0/node_modules/react-onclickoutside/package.json').version
|
|
).toBe('0.3.4') // react-datetime@1.3.0 has react-onclickoutside@0.3.4 in its node_modules
|
|
|
|
const lockfile = project.readLockfile()
|
|
|
|
expect(Object.keys(lockfile.importers!['.'].dependencies!)).toHaveLength(1) // resolutions not duplicated
|
|
})
|
|
|
|
test('a lockfile created even when there are no deps in package.json', async () => {
|
|
const project = prepareEmpty()
|
|
|
|
await install({}, testDefaults())
|
|
|
|
expect(project.readLockfile()).toBeTruthy()
|
|
expect(fs.existsSync('node_modules')).toBeFalsy()
|
|
})
|
|
|
|
test('current lockfile removed when no deps in package.json', async () => {
|
|
const project = prepareEmpty()
|
|
|
|
writeYamlFile(WANTED_LOCKFILE, {
|
|
dependencies: {
|
|
'is-negative': {
|
|
specifier: '2.1.0',
|
|
version: '2.1.0',
|
|
},
|
|
},
|
|
lockfileVersion: LOCKFILE_VERSION,
|
|
packages: {
|
|
'is-negative@2.1.0': {
|
|
resolution: {
|
|
tarball: `http://localhost:${REGISTRY_MOCK_PORT}/is-negative/-/is-negative-2.1.0.tgz`,
|
|
},
|
|
},
|
|
},
|
|
}, { lineWidth: 1000 })
|
|
|
|
await install({}, testDefaults())
|
|
|
|
expect(project.readLockfile()).toBeTruthy()
|
|
expect(fs.existsSync('node_modules')).toBeFalsy()
|
|
})
|
|
|
|
test('lockfile is fixed when it does not match package.json', async () => {
|
|
const project = prepareEmpty()
|
|
|
|
writeYamlFile(WANTED_LOCKFILE, {
|
|
dependencies: {
|
|
'@types/semver': {
|
|
specifier: '5.3.31',
|
|
version: '5.3.31',
|
|
},
|
|
'is-negative': {
|
|
specifier: '^2.1.0',
|
|
version: '2.1.0',
|
|
},
|
|
'is-positive': {
|
|
specifier: '^3.1.0',
|
|
version: '3.1.0',
|
|
},
|
|
},
|
|
lockfileVersion: LOCKFILE_VERSION,
|
|
packages: {
|
|
'@types/semver@5.3.31': {
|
|
resolution: {
|
|
integrity: 'sha512-WBv5F9HrWTyG800cB9M3veCVkFahqXN7KA7c3VUCYZm/xhNzzIFiXiq+rZmj75j7GvWelN3YNrLX7FjtqBvhMw==',
|
|
},
|
|
},
|
|
'is-negative@2.1.0': {
|
|
resolution: {
|
|
tarball: `http://localhost:${REGISTRY_MOCK_PORT}/is-negative/-/is-negative-2.1.0.tgz`,
|
|
},
|
|
},
|
|
'is-positive@3.1.0': {
|
|
resolution: {
|
|
integrity: 'sha512-8ND1j3y9/HP94TOvGzr69/FgbkX2ruOldhLEsTWwcJVfo4oRjwemJmJxt7RJkKYH8tz7vYBP9JcKQY8CLuJ90Q==',
|
|
},
|
|
},
|
|
},
|
|
}, { lineWidth: 1000 })
|
|
|
|
const reporter = sinon.spy()
|
|
await install({
|
|
devDependencies: {
|
|
'is-negative': '^2.1.0',
|
|
},
|
|
optionalDependencies: {
|
|
'is-positive': '^3.1.0',
|
|
},
|
|
}, testDefaults({ reporter }))
|
|
|
|
const progress = sinon.match({
|
|
name: 'pnpm:progress',
|
|
status: 'resolving',
|
|
})
|
|
expect(reporter.withArgs(progress).callCount).toBe(0)
|
|
|
|
const lockfile = project.readLockfile()
|
|
|
|
expect(lockfile.importers?.['.'].devDependencies?.['is-negative'].version).toBe('2.1.0')
|
|
expect(lockfile.importers?.['.'].optionalDependencies?.['is-positive'].version).toBe('3.1.0')
|
|
expect(lockfile.importers?.['.'].dependencies).toBeFalsy()
|
|
expect(lockfile.packages).not.toHaveProperty(['@types/semver@5.3.31'])
|
|
})
|
|
|
|
test(`doing named installation when ${WANTED_LOCKFILE} exists already`, async () => {
|
|
const project = prepareEmpty()
|
|
|
|
writeYamlFile(WANTED_LOCKFILE, {
|
|
dependencies: {
|
|
'@types/semver': {
|
|
specifier: '5.3.31',
|
|
version: '5.3.31',
|
|
},
|
|
'is-negative': {
|
|
specifier: '^2.1.0',
|
|
version: '2.1.0',
|
|
},
|
|
'is-positive': {
|
|
specifier: '^3.1.0',
|
|
version: '3.1.0',
|
|
},
|
|
},
|
|
lockfileVersion: LOCKFILE_VERSION,
|
|
packages: {
|
|
'@types/semver@5.3.31': {
|
|
resolution: {
|
|
integrity: 'sha512-WBv5F9HrWTyG800cB9M3veCVkFahqXN7KA7c3VUCYZm/xhNzzIFiXiq+rZmj75j7GvWelN3YNrLX7FjtqBvhMw==',
|
|
},
|
|
},
|
|
'is-negative@2.1.0': {
|
|
resolution: {
|
|
tarball: `http://localhost:${REGISTRY_MOCK_PORT}/is-negative/-/is-negative-2.1.0.tgz`,
|
|
},
|
|
},
|
|
'is-positive@3.1.0': {
|
|
resolution: {
|
|
integrity: 'sha512-8ND1j3y9/HP94TOvGzr69/FgbkX2ruOldhLEsTWwcJVfo4oRjwemJmJxt7RJkKYH8tz7vYBP9JcKQY8CLuJ90Q==',
|
|
},
|
|
},
|
|
},
|
|
}, { lineWidth: 1000 })
|
|
|
|
const reporter = sinon.spy()
|
|
|
|
const { updatedManifest: manifest } = await addDependenciesToPackage({
|
|
dependencies: {
|
|
'@types/semver': '5.3.31',
|
|
'is-negative': '^2.1.0',
|
|
'is-positive': '^3.1.0',
|
|
},
|
|
}, ['is-positive'], testDefaults({ reporter }))
|
|
await install(manifest, testDefaults({ reporter }))
|
|
|
|
expect(reporter.calledWithMatch(LOCKFILE_WARN_LOG)).toBeFalsy()
|
|
|
|
project.has('is-negative')
|
|
})
|
|
|
|
test(`respects ${WANTED_LOCKFILE} for top dependencies`, async () => {
|
|
const project = prepareEmpty()
|
|
const reporter = sinon.spy()
|
|
// const fooProgress = sinon.match({
|
|
// name: 'pnpm:progress',
|
|
// status: 'resolving',
|
|
// manifest: {
|
|
// name: 'foo',
|
|
// },
|
|
// })
|
|
|
|
const pkgs = ['@pnpm.e2e/foo', '@pnpm.e2e/bar', '@pnpm.e2e/qar']
|
|
await Promise.all(pkgs.map(async (pkgName) => addDistTag({ package: pkgName, version: '100.0.0', distTag: 'latest' })))
|
|
|
|
let { updatedManifest: manifest } = await addDependenciesToPackage({}, ['@pnpm.e2e/foo'], testDefaults({ save: true, reporter }))
|
|
// t.equal(reporter.withArgs(fooProgress).callCount, 1, 'reported foo once')
|
|
manifest = (await addDependenciesToPackage(manifest, ['@pnpm.e2e/bar'], testDefaults({ targetDependenciesField: 'optionalDependencies' }))).updatedManifest
|
|
manifest = (await addDependenciesToPackage(manifest, ['@pnpm.e2e/qar'], testDefaults({ addDependenciesToPackage: 'devDependencies' }))).updatedManifest
|
|
manifest = (await addDependenciesToPackage(manifest, ['@pnpm.e2e/foobar'], testDefaults({ save: true }))).updatedManifest
|
|
|
|
expect((await readPackageJsonFromDir(path.resolve('node_modules', '@pnpm.e2e/foo'))).version).toBe('100.0.0')
|
|
expect((await readPackageJsonFromDir(path.resolve('node_modules', '@pnpm.e2e/bar'))).version).toBe('100.0.0')
|
|
expect((await readPackageJsonFromDir(path.resolve('node_modules', '@pnpm.e2e/qar'))).version).toBe('100.0.0')
|
|
expect((await readPackageJsonFromDir(path.resolve('node_modules/.pnpm/@pnpm.e2e+foobar@100.0.0/node_modules/@pnpm.e2e/foo'))).version).toBe('100.0.0')
|
|
expect((await readPackageJsonFromDir(path.resolve('node_modules/.pnpm/@pnpm.e2e+foobar@100.0.0/node_modules/@pnpm.e2e/bar'))).version).toBe('100.0.0')
|
|
|
|
await Promise.all(pkgs.map(async (pkgName) => addDistTag({ package: pkgName, version: '100.1.0', distTag: 'latest' })))
|
|
|
|
rimraf('node_modules')
|
|
rimraf(path.join('..', '.store'))
|
|
|
|
reporter.resetHistory()
|
|
|
|
// shouldn't care about what the registry in npmrc is
|
|
// the one in lockfile should be used
|
|
await install(manifest, testDefaults({
|
|
rawConfig: {
|
|
registry: 'https://registry.npmjs.org',
|
|
},
|
|
registry: 'https://registry.npmjs.org',
|
|
reporter,
|
|
}))
|
|
|
|
// t.equal(reporter.withArgs(fooProgress).callCount, 0, 'not reported foo')
|
|
|
|
project.storeHasNot('@pnpm.e2e/foo', '100.1.0')
|
|
expect((await readPackageJsonFromDir(path.resolve('node_modules', '@pnpm.e2e/foo'))).version).toBe('100.0.0')
|
|
expect((await readPackageJsonFromDir(path.resolve('node_modules', '@pnpm.e2e/bar'))).version).toBe('100.0.0')
|
|
expect((await readPackageJsonFromDir(path.resolve('node_modules', '@pnpm.e2e/qar'))).version).toBe('100.0.0')
|
|
expect((await readPackageJsonFromDir(path.resolve('node_modules/.pnpm/@pnpm.e2e+foobar@100.0.0/node_modules/@pnpm.e2e/foo'))).version).toBe('100.0.0')
|
|
expect((await readPackageJsonFromDir(path.resolve('node_modules/.pnpm/@pnpm.e2e+foobar@100.0.0/node_modules/@pnpm.e2e/bar'))).version).toBe('100.0.0')
|
|
})
|
|
|
|
test(`subdeps are updated on repeat install if outer ${WANTED_LOCKFILE} does not match the inner one`, async () => {
|
|
const project = prepareEmpty()
|
|
|
|
await addDistTag({ package: '@pnpm.e2e/pkg-with-1-dep', version: '100.0.0', distTag: 'latest' })
|
|
await addDistTag({ package: '@pnpm.e2e/dep-of-pkg-with-1-dep', version: '100.0.0', distTag: 'latest' })
|
|
|
|
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['@pnpm.e2e/pkg-with-1-dep'], testDefaults())
|
|
|
|
project.storeHas('@pnpm.e2e/dep-of-pkg-with-1-dep', '100.0.0')
|
|
|
|
const lockfile = project.readLockfile()
|
|
|
|
expect(lockfile.packages).toHaveProperty(['@pnpm.e2e/dep-of-pkg-with-1-dep@100.0.0'])
|
|
|
|
delete lockfile.packages['@pnpm.e2e/dep-of-pkg-with-1-dep@100.0.0']
|
|
|
|
lockfile.packages['@pnpm.e2e/dep-of-pkg-with-1-dep@100.1.0'] = {
|
|
resolution: {
|
|
integrity: getIntegrity('@pnpm.e2e/dep-of-pkg-with-1-dep', '100.1.0'),
|
|
},
|
|
}
|
|
|
|
lockfile.snapshots['@pnpm.e2e/pkg-with-1-dep@100.0.0'].dependencies!['@pnpm.e2e/dep-of-pkg-with-1-dep'] = '100.1.0'
|
|
|
|
writeYamlFile(WANTED_LOCKFILE, lockfile, { lineWidth: 1000 })
|
|
|
|
await install(manifest, testDefaults())
|
|
|
|
project.storeHas('@pnpm.e2e/dep-of-pkg-with-1-dep', '100.1.0')
|
|
})
|
|
|
|
test("recreates lockfile if it doesn't match the dependencies in package.json", async () => {
|
|
const project = prepareEmpty()
|
|
|
|
let { updatedManifest: manifest } = await addDependenciesToPackage({}, ['is-negative@1.0.0'], testDefaults({ pinnedVersion: 'patch', targetDependenciesField: 'dependencies' }))
|
|
manifest = (await addDependenciesToPackage(manifest, ['is-positive@1.0.0'], testDefaults({ pinnedVersion: 'patch', targetDependenciesField: 'devDependencies' }))).updatedManifest
|
|
manifest = (await addDependenciesToPackage(manifest, ['map-obj@1.0.0'], testDefaults({ pinnedVersion: 'patch', targetDependenciesField: 'optionalDependencies' }))).updatedManifest
|
|
|
|
const lockfile1 = project.readLockfile()
|
|
expect(lockfile1.importers['.'].dependencies?.['is-negative'].version).toBe('1.0.0')
|
|
expect(lockfile1.importers['.'].dependencies?.['is-negative'].specifier).toBe('1.0.0')
|
|
|
|
manifest.dependencies!['is-negative'] = '^2.1.0'
|
|
manifest.devDependencies!['is-positive'] = '^2.0.0'
|
|
manifest.optionalDependencies!['map-obj'] = '1.0.1'
|
|
|
|
await install(manifest, testDefaults())
|
|
|
|
const lockfile = project.readLockfile()
|
|
const importer = lockfile.importers!['.']!
|
|
|
|
expect(importer.dependencies!['is-negative'].version).toBe('2.1.0')
|
|
expect(importer.dependencies!['is-negative'].specifier).toBe('^2.1.0')
|
|
|
|
expect(importer.devDependencies!['is-positive'].version).toBe('2.0.0')
|
|
expect(importer.devDependencies!['is-positive'].specifier).toBe('^2.0.0')
|
|
|
|
expect(importer.optionalDependencies!['map-obj'].version).toBe('1.0.1')
|
|
expect(importer.optionalDependencies!['map-obj'].specifier).toBe('1.0.1')
|
|
})
|
|
|
|
test('repeat install with lockfile should not mutate lockfile when dependency has version specified with v prefix', async () => {
|
|
const project = prepareEmpty()
|
|
|
|
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['highmaps-release@5.0.11'], testDefaults())
|
|
|
|
const lockfile1 = project.readLockfile()
|
|
|
|
expect(lockfile1.importers['.'].dependencies?.['highmaps-release'].version).toBe('5.0.11')
|
|
|
|
rimraf('node_modules')
|
|
|
|
await install(manifest, testDefaults())
|
|
|
|
const lockfile2 = project.readLockfile()
|
|
|
|
expect(lockfile1).toStrictEqual(lockfile2) // lockfile hasn't been changed
|
|
})
|
|
|
|
test('package is not marked optional if it is also a subdep of a regular dependency', async () => {
|
|
const project = prepareEmpty()
|
|
|
|
await addDistTag({ package: '@pnpm.e2e/pkg-with-1-dep', version: '100.0.0', distTag: 'latest' })
|
|
await addDistTag({ package: '@pnpm.e2e/dep-of-pkg-with-1-dep', version: '100.0.0', distTag: 'latest' })
|
|
|
|
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['@pnpm.e2e/pkg-with-1-dep'], testDefaults())
|
|
await addDependenciesToPackage(manifest, ['@pnpm.e2e/dep-of-pkg-with-1-dep'], testDefaults({ targetDependenciesField: 'optionalDependencies' }))
|
|
|
|
const lockfile = project.readLockfile()
|
|
|
|
expect(lockfile.snapshots['@pnpm.e2e/dep-of-pkg-with-1-dep@100.0.0'].optional).toBeFalsy()
|
|
})
|
|
|
|
test('scoped module from different registry', async () => {
|
|
const project = prepareEmpty()
|
|
|
|
const registries = {
|
|
default: 'https://registry.npmjs.org/',
|
|
'@zkochan': `http://localhost:${REGISTRY_MOCK_PORT}`,
|
|
'@foo': `http://localhost:${REGISTRY_MOCK_PORT}`,
|
|
}
|
|
await addDependenciesToPackage({}, ['@zkochan/foo', '@foo/has-dep-from-same-scope', 'is-positive'], testDefaults({ registries }, { registries }))
|
|
|
|
project.has('@zkochan/foo')
|
|
|
|
const lockfile = project.readLockfile()
|
|
|
|
expect(lockfile).toStrictEqual({
|
|
settings: {
|
|
autoInstallPeers: true,
|
|
excludeLinksFromLockfile: false,
|
|
},
|
|
importers: {
|
|
'.': {
|
|
dependencies: {
|
|
'@foo/has-dep-from-same-scope': {
|
|
specifier: '^1.0.0',
|
|
version: '1.0.0',
|
|
},
|
|
'@zkochan/foo': {
|
|
specifier: '^1.0.0',
|
|
version: '1.0.0',
|
|
},
|
|
'is-positive': {
|
|
specifier: '^3.1.0',
|
|
version: '3.1.0',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
lockfileVersion: LOCKFILE_VERSION,
|
|
packages: {
|
|
'@foo/has-dep-from-same-scope@1.0.0': {
|
|
resolution: {
|
|
integrity: getIntegrity('@foo/has-dep-from-same-scope', '1.0.0'),
|
|
},
|
|
},
|
|
'@foo/no-deps@1.0.0': {
|
|
resolution: {
|
|
integrity: getIntegrity('@foo/no-deps', '1.0.0'),
|
|
},
|
|
},
|
|
'@zkochan/foo@1.0.0': {
|
|
resolution: {
|
|
integrity: 'sha512-IFvrYpq7E6BqKex7A7czIFnFncPiUVdhSzGhAOWpp8RlkXns4y/9ZdynxaA/e0VkihRxQkihE2pTyvxjfe/wBg==',
|
|
},
|
|
},
|
|
'is-negative@1.0.0': {
|
|
engines: {
|
|
node: '>=0.10.0',
|
|
},
|
|
resolution: {
|
|
integrity: 'sha512-1aKMsFUc7vYQGzt//8zhkjRWPoYkajY/I5MJEvrc0pDoHXrW7n5ri8DYxhy3rR+Dk0QFl7GjHHsZU1sppQrWtw==',
|
|
},
|
|
},
|
|
'is-positive@3.1.0': {
|
|
engines: {
|
|
node: '>=0.10.0',
|
|
},
|
|
resolution: {
|
|
integrity: 'sha512-8ND1j3y9/HP94TOvGzr69/FgbkX2ruOldhLEsTWwcJVfo4oRjwemJmJxt7RJkKYH8tz7vYBP9JcKQY8CLuJ90Q==',
|
|
},
|
|
},
|
|
},
|
|
snapshots: {
|
|
'@foo/has-dep-from-same-scope@1.0.0': {
|
|
dependencies: {
|
|
'@foo/no-deps': '1.0.0',
|
|
'is-negative': '1.0.0',
|
|
},
|
|
},
|
|
'@foo/no-deps@1.0.0': {},
|
|
'@zkochan/foo@1.0.0': {},
|
|
'is-negative@1.0.0': {},
|
|
'is-positive@3.1.0': {},
|
|
},
|
|
})
|
|
})
|
|
|
|
test('repeat install with no inner lockfile should not rewrite packages in node_modules', async () => {
|
|
const project = prepareEmpty()
|
|
|
|
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['is-negative@1.0.0'], testDefaults())
|
|
|
|
rimraf('node_modules/.pnpm/lock.yaml')
|
|
|
|
await install(manifest, testDefaults())
|
|
|
|
project.has('is-negative')
|
|
})
|
|
|
|
test('packages are placed in devDependencies even if they are present as non-dev as well', async () => {
|
|
const project = prepareEmpty()
|
|
|
|
await addDistTag({ package: '@pnpm.e2e/pkg-with-1-dep', version: '100.0.0', distTag: 'latest' })
|
|
await addDistTag({ package: '@pnpm.e2e/dep-of-pkg-with-1-dep', version: '100.1.0', distTag: 'latest' })
|
|
|
|
const reporter = sinon.spy()
|
|
await install({
|
|
devDependencies: {
|
|
'@pnpm.e2e/dep-of-pkg-with-1-dep': '^100.1.0',
|
|
'@pnpm.e2e/pkg-with-1-dep': '^100.0.0',
|
|
},
|
|
}, testDefaults({ reporter }))
|
|
|
|
const importer = project.readLockfile().importers!['.']!
|
|
|
|
expect(importer.devDependencies).toHaveProperty(['@pnpm.e2e/dep-of-pkg-with-1-dep'])
|
|
expect(importer.devDependencies).toHaveProperty(['@pnpm.e2e/pkg-with-1-dep'])
|
|
|
|
expect(reporter.calledWithMatch({
|
|
added: {
|
|
dependencyType: 'dev',
|
|
name: '@pnpm.e2e/dep-of-pkg-with-1-dep',
|
|
version: '100.1.0',
|
|
},
|
|
level: 'debug',
|
|
name: 'pnpm:root',
|
|
} as RootLog)).toBeTruthy()
|
|
expect(reporter.calledWithMatch({
|
|
added: {
|
|
dependencyType: 'dev',
|
|
name: '@pnpm.e2e/pkg-with-1-dep',
|
|
version: '100.0.0',
|
|
},
|
|
level: 'debug',
|
|
name: 'pnpm:root',
|
|
} as RootLog)).toBeTruthy()
|
|
})
|
|
|
|
// This testcase verifies that pnpm is not failing when trying to preserve dependencies.
|
|
// Only when a dependency is a range dependency, should pnpm try to compare versions of deps with semver.satisfies().
|
|
test('updating package that has a github-hosted dependency', async () => {
|
|
prepareEmpty()
|
|
|
|
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['@pnpm.e2e/has-github-dep@1'], testDefaults())
|
|
await addDependenciesToPackage(manifest, ['@pnpm.e2e/has-github-dep@latest'], testDefaults())
|
|
})
|
|
|
|
test('updating package that has deps with peers', async () => {
|
|
prepareEmpty()
|
|
|
|
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['@pnpm.e2e/abc-grand-parent-with-c@0'], testDefaults())
|
|
await addDependenciesToPackage(manifest, ['@pnpm.e2e/abc-grand-parent-with-c@1'], testDefaults())
|
|
})
|
|
|
|
test('pendingBuilds gets updated if install removes packages', async () => {
|
|
const project = prepareEmpty()
|
|
|
|
await install({
|
|
dependencies: {
|
|
'@pnpm.e2e/pre-and-postinstall-scripts-example': '*',
|
|
'@pnpm.e2e/with-postinstall-b': '*',
|
|
},
|
|
}, testDefaults({ fastUnpack: false, ignoreScripts: true }))
|
|
const modules1 = project.readModulesManifest()
|
|
|
|
await install({
|
|
dependencies: {
|
|
'@pnpm.e2e/pre-and-postinstall-scripts-example': '*',
|
|
},
|
|
}, testDefaults({ fastUnpack: false, ignoreScripts: true }))
|
|
const modules2 = project.readModulesManifest()
|
|
|
|
expect(modules1).toBeTruthy()
|
|
expect(modules2).toBeTruthy()
|
|
expect(modules1!.pendingBuilds.length > modules2!.pendingBuilds.length).toBeTruthy()
|
|
})
|
|
|
|
test('optional properties are correctly updated on named install', async () => {
|
|
const project = prepareEmpty()
|
|
|
|
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['inflight@1.0.6'], testDefaults({ targetDependenciesField: 'optionalDependencies' }))
|
|
await addDependenciesToPackage(manifest, ['foo@npm:inflight@1.0.6'], testDefaults({}))
|
|
|
|
const lockfile = project.readLockfile()
|
|
expect(Object.values(lockfile.snapshots).filter((dep) => typeof dep.optional !== 'undefined')).toStrictEqual([])
|
|
})
|
|
|
|
test('no lockfile', async () => {
|
|
const project = prepareEmpty()
|
|
const reporter = sinon.spy()
|
|
|
|
await addDependenciesToPackage({}, ['is-positive'], testDefaults({ useLockfile: false, reporter }))
|
|
|
|
expect(reporter.calledWithMatch(LOCKFILE_WARN_LOG)).toBeFalsy()
|
|
|
|
project.has('is-positive')
|
|
|
|
expect(project.readLockfile()).toBeFalsy()
|
|
})
|
|
|
|
test('lockfile is ignored when lockfile = false', async () => {
|
|
const project = prepareEmpty()
|
|
|
|
writeYamlFile(WANTED_LOCKFILE, {
|
|
dependencies: {
|
|
'is-negative': {
|
|
specifier: '2.1.0',
|
|
version: '2.1.0',
|
|
},
|
|
},
|
|
lockfileVersion: LOCKFILE_VERSION,
|
|
packages: {
|
|
'is-negative@2.1.0': {
|
|
resolution: {
|
|
integrity: 'sha1-uZnX2TX0P1IHsBsA094ghS9Mp10=', // Invalid integrity
|
|
tarball: `http://localhost:${REGISTRY_MOCK_PORT}/is-negative/-/is-negative-2.1.0.tgz`,
|
|
},
|
|
},
|
|
},
|
|
}, { lineWidth: 1000 })
|
|
|
|
const reporter = sinon.spy()
|
|
|
|
await install({
|
|
dependencies: {
|
|
'is-negative': '2.1.0',
|
|
},
|
|
}, testDefaults({ useLockfile: false, reporter }))
|
|
|
|
expect(reporter.calledWithMatch(LOCKFILE_WARN_LOG)).toBeTruthy()
|
|
|
|
project.has('is-negative')
|
|
|
|
expect(project.readLockfile()).toBeTruthy()
|
|
})
|
|
|
|
test(`don't update ${WANTED_LOCKFILE} during uninstall when useLockfile: false`, async () => {
|
|
const project = prepareEmpty()
|
|
|
|
let manifest!: ProjectManifest
|
|
{
|
|
const reporter = sinon.spy()
|
|
|
|
manifest = (await addDependenciesToPackage({}, ['is-positive'], testDefaults({ reporter }))).updatedManifest
|
|
|
|
expect(reporter.calledWithMatch(LOCKFILE_WARN_LOG)).toBeFalsy()
|
|
}
|
|
|
|
{
|
|
const reporter = sinon.spy()
|
|
|
|
await mutateModulesInSingleProject({
|
|
dependencyNames: ['is-positive'],
|
|
manifest,
|
|
mutation: 'uninstallSome',
|
|
rootDir: process.cwd() as ProjectRootDir,
|
|
}, testDefaults({ useLockfile: false, reporter }))
|
|
|
|
expect(reporter.calledWithMatch(LOCKFILE_WARN_LOG)).toBeTruthy()
|
|
}
|
|
|
|
project.hasNot('is-positive')
|
|
|
|
expect(project.readLockfile()).toBeTruthy()
|
|
})
|
|
|
|
test('fail when installing with useLockfile: false and lockfileOnly: true', async () => {
|
|
prepareEmpty()
|
|
|
|
try {
|
|
await install({}, testDefaults({ useLockfile: false, lockfileOnly: true }))
|
|
throw new Error('installation should have failed')
|
|
} catch (err: any) { // eslint-disable-line
|
|
expect(err.message).toBe(`Cannot generate a ${WANTED_LOCKFILE} because lockfile is set to false`)
|
|
}
|
|
})
|
|
|
|
test("don't remove packages during named install when useLockfile: false", async () => {
|
|
const project = prepareEmpty()
|
|
|
|
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['is-positive'], testDefaults({ useLockfile: false }))
|
|
await addDependenciesToPackage(manifest, ['is-negative'], testDefaults({ useLockfile: false }))
|
|
|
|
project.has('is-positive')
|
|
project.has('is-negative')
|
|
})
|
|
|
|
test('save tarball URL when it is non-standard', async () => {
|
|
const project = prepareEmpty()
|
|
|
|
await addDependenciesToPackage({}, ['esprima-fb@3001.1.0-dev-harmony-fb'], testDefaults({ fastUnpack: false }))
|
|
|
|
const lockfile = project.readLockfile()
|
|
|
|
expect((lockfile.packages['esprima-fb@3001.1.0-dev-harmony-fb'].resolution as TarballResolution).tarball).toBe(`http://localhost:${REGISTRY_MOCK_PORT}/esprima-fb/-/esprima-fb-3001.0001.0000-dev-harmony-fb.tgz`)
|
|
})
|
|
|
|
test('packages installed via tarball URL from the default registry are normalized', async () => {
|
|
const project = prepareEmpty()
|
|
|
|
await addDependenciesToPackage({}, [
|
|
`http://localhost:${REGISTRY_MOCK_PORT}/@pnpm.e2e/pkg-with-tarball-dep-from-registry/-/pkg-with-tarball-dep-from-registry-1.0.0.tgz`,
|
|
'https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz',
|
|
], testDefaults())
|
|
|
|
const lockfile = project.readLockfile()
|
|
|
|
expect(lockfile).toStrictEqual({
|
|
settings: {
|
|
autoInstallPeers: true,
|
|
excludeLinksFromLockfile: false,
|
|
},
|
|
importers: {
|
|
'.': {
|
|
dependencies: {
|
|
'is-positive': {
|
|
specifier: 'https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz',
|
|
version: 'https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz',
|
|
},
|
|
'@pnpm.e2e/pkg-with-tarball-dep-from-registry': {
|
|
specifier: `http://localhost:${REGISTRY_MOCK_PORT}/@pnpm.e2e/pkg-with-tarball-dep-from-registry/-/pkg-with-tarball-dep-from-registry-1.0.0.tgz`,
|
|
version: '1.0.0',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
lockfileVersion: LOCKFILE_VERSION,
|
|
packages: {
|
|
'@pnpm.e2e/dep-of-pkg-with-1-dep@100.0.0': {
|
|
resolution: {
|
|
integrity: getIntegrity('@pnpm.e2e/dep-of-pkg-with-1-dep', '100.0.0'),
|
|
},
|
|
},
|
|
'@pnpm.e2e/pkg-with-tarball-dep-from-registry@1.0.0': {
|
|
resolution: {
|
|
integrity: getIntegrity('@pnpm.e2e/pkg-with-tarball-dep-from-registry', '1.0.0'),
|
|
},
|
|
},
|
|
'is-positive@https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz': {
|
|
engines: { node: '>=0.10.0' },
|
|
resolution: {
|
|
integrity: 'sha512-xxzPGZ4P2uN6rROUa5N9Z7zTX6ERuE0hs6GUOc/cKBLF2NqKc16UwqHMt3tFg4CO6EBTE5UecUasg+3jZx3Ckg==',
|
|
tarball: 'https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz',
|
|
},
|
|
version: '1.0.0',
|
|
},
|
|
},
|
|
snapshots: {
|
|
'@pnpm.e2e/dep-of-pkg-with-1-dep@100.0.0': {},
|
|
'@pnpm.e2e/pkg-with-tarball-dep-from-registry@1.0.0': {
|
|
dependencies: {
|
|
'@pnpm.e2e/dep-of-pkg-with-1-dep': '100.0.0',
|
|
},
|
|
},
|
|
'is-positive@https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz': {},
|
|
},
|
|
})
|
|
})
|
|
|
|
test('lockfile file has correct format when lockfile directory does not equal the prefix directory', async () => {
|
|
await addDistTag({ package: '@pnpm.e2e/pkg-with-1-dep', version: '100.0.0', distTag: 'latest' })
|
|
// Mock the HEAD request that isRepoPublic() in @pnpm/git-resolver makes to check if the repo is public.
|
|
// Without this, transient network failures cause the resolver to fall back to git+https:// instead of
|
|
// resolving via the codeload tarball URL.
|
|
const githubNock = nock('https://github.com', { allowUnmocked: true })
|
|
.head('/kevva/is-negative')
|
|
.reply(200)
|
|
prepareEmpty()
|
|
|
|
const storeDir = path.resolve('..', '.store')
|
|
|
|
const { updatedManifest: manifest } = await addDependenciesToPackage(
|
|
{},
|
|
[
|
|
'@pnpm.e2e/pkg-with-1-dep',
|
|
'@zkochan/foo@1.0.0',
|
|
'kevva/is-negative#1d7e288222b53a0cab90a331f1865220ec29560c',
|
|
],
|
|
testDefaults({ save: true, lockfileDir: path.resolve('..'), storeDir })
|
|
)
|
|
|
|
expect(!fs.existsSync('node_modules/.modules.yaml')).toBeTruthy()
|
|
|
|
process.chdir('..')
|
|
|
|
const modules = readYamlFile<any>(path.resolve('node_modules', '.modules.yaml')) // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
expect(modules).toBeTruthy()
|
|
expect(modules.pendingBuilds).toHaveLength(0)
|
|
|
|
{
|
|
const lockfile: LockfileFile = readYamlFile(WANTED_LOCKFILE)
|
|
const id = '@pnpm.e2e/pkg-with-1-dep@100.0.0'
|
|
|
|
expect(lockfile.lockfileVersion).toBe(LOCKFILE_VERSION)
|
|
|
|
expect(lockfile.importers).toBeTruthy()
|
|
expect(lockfile.importers?.project).toBeTruthy()
|
|
expect(lockfile.importers?.project).toBeTruthy()
|
|
expect(lockfile.importers?.project.dependencies).toBeTruthy()
|
|
expect(lockfile.importers?.project.dependencies!['@pnpm.e2e/pkg-with-1-dep'].version).toBe('100.0.0')
|
|
expect(lockfile.importers?.project.dependencies!['@zkochan/foo']).toBeTruthy()
|
|
expect(lockfile.importers?.project.dependencies!['is-negative'].version).toContain('/')
|
|
|
|
expect(lockfile.snapshots![id].dependencies).toHaveProperty(['@pnpm.e2e/dep-of-pkg-with-1-dep'])
|
|
expect(lockfile.packages![id].resolution).toHaveProperty(['integrity'])
|
|
expect(lockfile.packages![id].resolution).not.toHaveProperty(['tarball'])
|
|
|
|
expect(lockfile.packages).toHaveProperty(['is-negative@https://codeload.github.com/kevva/is-negative/tar.gz/1d7e288222b53a0cab90a331f1865220ec29560c'])
|
|
}
|
|
|
|
fs.mkdirSync('project-2')
|
|
|
|
process.chdir('project-2')
|
|
|
|
await addDependenciesToPackage(manifest, ['is-positive'], testDefaults({
|
|
save: true,
|
|
lockfileDir: path.resolve('..'),
|
|
storeDir,
|
|
pruneLockfileImporters: false,
|
|
}))
|
|
|
|
{
|
|
const lockfile = readYamlFile<LockfileFile>(path.join('..', WANTED_LOCKFILE))
|
|
|
|
expect(lockfile.importers).toHaveProperty(['project-2'])
|
|
|
|
// previous entries are not removed
|
|
const id = '@pnpm.e2e/pkg-with-1-dep@100.0.0'
|
|
|
|
expect(lockfile.importers?.project.dependencies!['@pnpm.e2e/pkg-with-1-dep'].version).toBe('100.0.0')
|
|
expect(lockfile.importers?.project.dependencies).toHaveProperty(['@zkochan/foo'])
|
|
expect(lockfile.importers?.project.dependencies!['is-negative'].version).toContain('/')
|
|
|
|
expect(lockfile.snapshots).toHaveProperty([id])
|
|
expect(lockfile.snapshots![id].dependencies).toBeTruthy()
|
|
expect(lockfile.snapshots![id].dependencies).toHaveProperty(['@pnpm.e2e/dep-of-pkg-with-1-dep'])
|
|
expect(lockfile.packages![id].resolution).toHaveProperty(['integrity'])
|
|
expect(lockfile.packages![id].resolution).not.toHaveProperty(['tarball'])
|
|
|
|
expect(lockfile.packages).toHaveProperty(['is-negative@https://codeload.github.com/kevva/is-negative/tar.gz/1d7e288222b53a0cab90a331f1865220ec29560c'])
|
|
}
|
|
githubNock.done()
|
|
})
|
|
|
|
test(`doing named installation when shared ${WANTED_LOCKFILE} exists already`, async () => {
|
|
const pkg1 = {
|
|
name: 'pkg1',
|
|
version: '1.0.0',
|
|
|
|
dependencies: {
|
|
'is-negative': '^2.1.0',
|
|
},
|
|
}
|
|
let pkg2: ProjectManifest = {
|
|
name: 'pkg2',
|
|
version: '1.0.0',
|
|
|
|
dependencies: {
|
|
'is-positive': '^3.1.0',
|
|
},
|
|
}
|
|
const projects = preparePackages([
|
|
pkg1,
|
|
pkg2,
|
|
])
|
|
|
|
writeYamlFile(WANTED_LOCKFILE, {
|
|
importers: {
|
|
pkg1: {
|
|
dependencies: {
|
|
'is-negative': {
|
|
specifier: '^2.1.0',
|
|
version: '2.1.0',
|
|
},
|
|
},
|
|
},
|
|
pkg2: {
|
|
dependencies: {
|
|
'is-positive': {
|
|
specifier: '^3.1.0',
|
|
version: '3.1.0',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
lockfileVersion: LOCKFILE_VERSION,
|
|
packages: {
|
|
'is-negative@2.1.0': {
|
|
resolution: {
|
|
tarball: `http://localhost:${REGISTRY_MOCK_PORT}/is-negative/-/is-negative-2.1.0.tgz`,
|
|
},
|
|
},
|
|
'is-positive@3.1.0': {
|
|
resolution: {
|
|
integrity: 'sha512-8ND1j3y9/HP94TOvGzr69/FgbkX2ruOldhLEsTWwcJVfo4oRjwemJmJxt7RJkKYH8tz7vYBP9JcKQY8CLuJ90Q==',
|
|
},
|
|
},
|
|
},
|
|
}, { lineWidth: 1000 })
|
|
|
|
pkg2 = (await addDependenciesToPackage(
|
|
pkg2,
|
|
['is-positive'],
|
|
testDefaults({
|
|
dir: path.resolve('pkg2'),
|
|
lockfileDir: process.cwd(),
|
|
})
|
|
)).updatedManifest
|
|
|
|
const currentLockfile = readYamlFile<LockfileFile>(path.resolve('node_modules/.pnpm/lock.yaml'))
|
|
|
|
expect(Object.keys(currentLockfile.importers ?? {})).toStrictEqual(['pkg2'])
|
|
|
|
await mutateModules(
|
|
[
|
|
{
|
|
mutation: 'install',
|
|
rootDir: path.resolve('pkg1') as ProjectRootDir,
|
|
},
|
|
{
|
|
mutation: 'install',
|
|
rootDir: path.resolve('pkg2') as ProjectRootDir,
|
|
},
|
|
],
|
|
testDefaults({
|
|
allProjects: [
|
|
{
|
|
buildIndex: 0,
|
|
manifest: pkg1,
|
|
rootDir: path.resolve('pkg1') as ProjectRootDir,
|
|
},
|
|
{
|
|
buildIndex: 0,
|
|
manifest: pkg2,
|
|
rootDir: path.resolve('pkg2') as ProjectRootDir,
|
|
},
|
|
],
|
|
})
|
|
)
|
|
|
|
projects['pkg1'].has('is-negative')
|
|
projects['pkg2'].has('is-positive')
|
|
})
|
|
|
|
// Covers https://github.com/pnpm/pnpm/issues/1200
|
|
test(`use current ${WANTED_LOCKFILE} as initial wanted one, when wanted was removed`, async () => {
|
|
const project = prepareEmpty()
|
|
|
|
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['lodash@4.17.11', 'underscore@1.9.0'], testDefaults())
|
|
|
|
rimraf(WANTED_LOCKFILE)
|
|
|
|
await addDependenciesToPackage(manifest, ['underscore@1.9.1'], testDefaults())
|
|
|
|
project.has('lodash')
|
|
project.has('underscore')
|
|
})
|
|
|
|
// Covers https://github.com/pnpm/pnpm/issues/1876
|
|
test('existing dependencies are preserved when updating a lockfile to a newer format', async () => {
|
|
await addDistTag({ package: '@pnpm.e2e/pkg-with-1-dep', version: '100.0.0', distTag: 'latest' })
|
|
const project = prepareEmpty()
|
|
|
|
await addDistTag({ package: '@pnpm.e2e/dep-of-pkg-with-1-dep', version: '100.0.0', distTag: 'latest' })
|
|
|
|
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['@pnpm.e2e/pkg-with-1-dep'], testDefaults())
|
|
|
|
const initialLockfile = project.readLockfile()
|
|
writeYamlFile(WANTED_LOCKFILE, { ...initialLockfile, lockfileVersion: '6.0' }, { lineWidth: 1000 })
|
|
|
|
await addDistTag({ package: '@pnpm.e2e/dep-of-pkg-with-1-dep', version: '100.1.0', distTag: 'latest' })
|
|
|
|
await mutateModulesInSingleProject({
|
|
manifest,
|
|
mutation: 'install',
|
|
rootDir: process.cwd() as ProjectRootDir,
|
|
}, testDefaults())
|
|
|
|
const updatedLockfile = project.readLockfile()
|
|
|
|
expect(initialLockfile.packages).toStrictEqual(updatedLockfile.packages)
|
|
})
|
|
|
|
test('broken lockfile is fixed even if it seems like up to date at first. Unless frozenLockfile option is set to true', async () => {
|
|
const project = prepareEmpty()
|
|
await addDistTag({ package: '@pnpm.e2e/pkg-with-1-dep', version: '100.0.0', distTag: 'latest' })
|
|
await addDistTag({ package: '@pnpm.e2e/dep-of-pkg-with-1-dep', version: '100.0.0', distTag: 'latest' })
|
|
|
|
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['@pnpm.e2e/pkg-with-1-dep'], testDefaults({ lockfileOnly: true }))
|
|
{
|
|
const lockfile = project.readLockfile()
|
|
expect(lockfile.packages).toHaveProperty(['@pnpm.e2e/dep-of-pkg-with-1-dep@100.0.0'])
|
|
delete lockfile.packages['@pnpm.e2e/dep-of-pkg-with-1-dep@100.0.0']
|
|
delete lockfile.snapshots['@pnpm.e2e/dep-of-pkg-with-1-dep@100.0.0']
|
|
writeYamlFile(WANTED_LOCKFILE, lockfile, { lineWidth: 1000 })
|
|
}
|
|
|
|
let err!: PnpmError
|
|
try {
|
|
await mutateModulesInSingleProject({
|
|
manifest,
|
|
mutation: 'install',
|
|
rootDir: process.cwd() as ProjectRootDir,
|
|
}, testDefaults({ frozenLockfile: true }))
|
|
} catch (_err: any) { // eslint-disable-line
|
|
err = _err
|
|
}
|
|
expect(err.code).toBe('ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY')
|
|
|
|
await mutateModulesInSingleProject({
|
|
manifest,
|
|
mutation: 'install',
|
|
rootDir: process.cwd() as ProjectRootDir,
|
|
}, testDefaults({ preferFrozenLockfile: true }))
|
|
|
|
project.has('@pnpm.e2e/pkg-with-1-dep')
|
|
const lockfile = project.readLockfile()
|
|
expect(lockfile.packages).toHaveProperty(['@pnpm.e2e/dep-of-pkg-with-1-dep@100.0.0'])
|
|
})
|
|
|
|
const REGISTRY_MIRROR_DIR = path.join(import.meta.dirname, './registry-mirror')
|
|
|
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
const isPositiveMeta = loadJsonFileSync<any>(path.join(REGISTRY_MIRROR_DIR, 'is-positive.json'))
|
|
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
const tarballPath = f.find('is-positive-3.1.0.tgz')
|
|
|
|
test('tarball domain differs from registry domain', async () => {
|
|
nock('https://registry.example.com', { allowUnmocked: true })
|
|
.get('/is-positive')
|
|
.reply(200, isPositiveMeta)
|
|
|
|
nock('https://registry.npmjs.org', { allowUnmocked: true })
|
|
.get('/is-positive/-/is-positive-3.1.0.tgz')
|
|
.replyWithFile(200, tarballPath)
|
|
|
|
const project = prepareEmpty()
|
|
|
|
await addDependenciesToPackage({},
|
|
[
|
|
'is-positive',
|
|
], testDefaults({
|
|
fastUnpack: false,
|
|
lockfileOnly: true,
|
|
save: true,
|
|
}, {
|
|
registries: {
|
|
default: 'https://registry.example.com',
|
|
},
|
|
})
|
|
)
|
|
|
|
const lockfile = project.readLockfile()
|
|
|
|
expect(lockfile).toStrictEqual({
|
|
settings: {
|
|
autoInstallPeers: true,
|
|
excludeLinksFromLockfile: false,
|
|
},
|
|
importers: {
|
|
'.': {
|
|
dependencies: {
|
|
'is-positive': {
|
|
specifier: '^3.1.0',
|
|
version: '3.1.0',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
lockfileVersion: LOCKFILE_VERSION,
|
|
packages: {
|
|
'is-positive@3.1.0': {
|
|
engines: { node: '>=0.10.0' },
|
|
resolution: {
|
|
integrity: 'sha1-hX21hKG6XRyymAUn/DtsQ103sP0=',
|
|
tarball: 'https://registry.npmjs.org/is-positive/-/is-positive-3.1.0.tgz',
|
|
},
|
|
},
|
|
},
|
|
snapshots: {
|
|
'is-positive@3.1.0': {},
|
|
},
|
|
})
|
|
})
|
|
|
|
test('tarball installed through non-standard URL endpoint from the registry domain', async () => {
|
|
nock('https://registry.npmjs.org', { allowUnmocked: true })
|
|
.head('/is-positive/download/is-positive-3.1.0.tgz')
|
|
.reply(200, '')
|
|
nock('https://registry.npmjs.org', { allowUnmocked: true })
|
|
.get('/is-positive/download/is-positive-3.1.0.tgz')
|
|
.replyWithFile(200, tarballPath)
|
|
|
|
const project = prepareEmpty()
|
|
|
|
await addDependenciesToPackage({},
|
|
[
|
|
'https://registry.npmjs.org/is-positive/download/is-positive-3.1.0.tgz',
|
|
], testDefaults({
|
|
fastUnpack: false,
|
|
lockfileOnly: true,
|
|
registries: {
|
|
default: 'https://registry.npmjs.org/',
|
|
},
|
|
save: true,
|
|
})
|
|
)
|
|
|
|
const lockfile = project.readLockfile()
|
|
|
|
expect(lockfile).toStrictEqual({
|
|
settings: {
|
|
autoInstallPeers: true,
|
|
excludeLinksFromLockfile: false,
|
|
},
|
|
importers: {
|
|
'.': {
|
|
dependencies: {
|
|
'is-positive': {
|
|
specifier: 'https://registry.npmjs.org/is-positive/download/is-positive-3.1.0.tgz',
|
|
version: 'https://registry.npmjs.org/is-positive/download/is-positive-3.1.0.tgz',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
lockfileVersion: LOCKFILE_VERSION,
|
|
packages: {
|
|
'is-positive@https://registry.npmjs.org/is-positive/download/is-positive-3.1.0.tgz': {
|
|
engines: { node: '>=0.10.0' },
|
|
resolution: {
|
|
integrity: 'sha512-8ND1j3y9/HP94TOvGzr69/FgbkX2ruOldhLEsTWwcJVfo4oRjwemJmJxt7RJkKYH8tz7vYBP9JcKQY8CLuJ90Q==',
|
|
tarball: 'https://registry.npmjs.org/is-positive/download/is-positive-3.1.0.tgz',
|
|
},
|
|
version: '3.1.0',
|
|
},
|
|
},
|
|
snapshots: {
|
|
'is-positive@https://registry.npmjs.org/is-positive/download/is-positive-3.1.0.tgz': {},
|
|
},
|
|
})
|
|
})
|
|
|
|
// TODO: fix merge conflicts with the new lockfile format (TODOv8)
|
|
test.skip('a lockfile with merge conflicts is autofixed', async () => {
|
|
const project = prepareEmpty()
|
|
|
|
fs.writeFileSync(WANTED_LOCKFILE, `\
|
|
importers:
|
|
.:
|
|
dependencies:
|
|
'@pnpm.e2e/dep-of-pkg-with-1-dep':
|
|
specifier: '>100.0.0'
|
|
<<<<<<< HEAD
|
|
version: 100.0.0
|
|
=======
|
|
version: 100.1.0
|
|
>>>>>>> next
|
|
lockfileVersion: ${LOCKFILE_VERSION}
|
|
packages:
|
|
<<<<<<< HEAD
|
|
'@pnpm.e2e/dep-of-pkg-with-1-dep@100.0.0':
|
|
dev: false
|
|
resolution:
|
|
integrity: ${getIntegrity('@pnpm.e2e/dep-of-pkg-with-1-dep', '100.0.0')}
|
|
=======
|
|
'@pnpm.e2e/dep-of-pkg-with-1-dep@100.1.0':
|
|
dev: false
|
|
resolution:
|
|
integrity: ${getIntegrity('@pnpm.e2e/dep-of-pkg-with-1-dep', '100.1.0')}
|
|
>>>>>>> next`, 'utf8')
|
|
|
|
await install({
|
|
dependencies: {
|
|
'@pnpm.e2e/dep-of-pkg-with-1-dep': '>100.0.0',
|
|
},
|
|
}, testDefaults())
|
|
|
|
const lockfile = project.readLockfile()
|
|
expect(lockfile.importers?.['.'].dependencies?.['@pnpm.e2e/dep-of-pkg-with-1-dep'].version).toBe('100.1.0')
|
|
})
|
|
|
|
test('a lockfile v6 with merge conflicts is autofixed', async () => {
|
|
const project = prepareEmpty()
|
|
|
|
fs.writeFileSync(WANTED_LOCKFILE, `\
|
|
lockfileVersion: '${LOCKFILE_VERSION}'
|
|
importers:
|
|
.:
|
|
dependencies:
|
|
'@pnpm.e2e/dep-of-pkg-with-1-dep':
|
|
specifier: '>100.0.0'
|
|
<<<<<<< HEAD
|
|
version: 100.0.0
|
|
=======
|
|
version: 100.1.0
|
|
>>>>>>> next
|
|
packages:
|
|
<<<<<<< HEAD
|
|
'@pnpm.e2e/dep-of-pkg-with-1-dep@100.0.0':
|
|
dev: false
|
|
resolution:
|
|
integrity: ${getIntegrity('@pnpm.e2e/dep-of-pkg-with-1-dep', '100.0.0')}
|
|
=======
|
|
'@pnpm.e2e/dep-of-pkg-with-1-dep@100.1.0':
|
|
dev: false
|
|
resolution:
|
|
integrity: ${getIntegrity('@pnpm.e2e/dep-of-pkg-with-1-dep', '100.1.0')}
|
|
>>>>>>> next`, 'utf8')
|
|
|
|
await install({
|
|
dependencies: {
|
|
'@pnpm.e2e/dep-of-pkg-with-1-dep': '>100.0.0',
|
|
},
|
|
}, testDefaults())
|
|
|
|
const lockfile = project.readLockfile()
|
|
expect(lockfile.importers?.['.'].dependencies?.['@pnpm.e2e/dep-of-pkg-with-1-dep']).toHaveProperty('version', '100.1.0')
|
|
})
|
|
|
|
test('a lockfile with duplicate keys is fixed', async () => {
|
|
const project = prepareEmpty()
|
|
|
|
fs.writeFileSync(WANTED_LOCKFILE, `\
|
|
importers:
|
|
.:
|
|
dependencies:
|
|
'@pnpm.e2e/dep-of-pkg-with-1-dep':
|
|
specifier: '100.0.0'
|
|
version: 100.0.0
|
|
lockfileVersion: ${LOCKFILE_VERSION}
|
|
packages:
|
|
'@pnpm.e2e/dep-of-pkg-with-1-dep@100.0.0':
|
|
resolution: {integrity: ${getIntegrity('@pnpm.e2e/dep-of-pkg-with-1-dep', '100.0.0')}}
|
|
dev: false
|
|
resolution: {integrity: ${getIntegrity('@pnpm.e2e/dep-of-pkg-with-1-dep', '100.0.0')}}
|
|
`, 'utf8')
|
|
|
|
const reporter = jest.fn()
|
|
await install({
|
|
dependencies: {
|
|
'@pnpm.e2e/dep-of-pkg-with-1-dep': '100.0.0',
|
|
},
|
|
}, testDefaults({ reporter }))
|
|
|
|
const lockfile = project.readLockfile()
|
|
expect(lockfile.importers?.['.'].dependencies?.['@pnpm.e2e/dep-of-pkg-with-1-dep'].version).toBe('100.0.0')
|
|
|
|
expect(reporter).toHaveBeenCalledWith(expect.objectContaining({
|
|
level: 'warn',
|
|
name: 'pnpm',
|
|
prefix: process.cwd(),
|
|
message: expect.stringMatching(/^Ignoring broken lockfile at .* duplicated mapping key/),
|
|
}))
|
|
})
|
|
|
|
test('a lockfile with duplicate keys is causes an exception, when frozenLockfile is true', async () => {
|
|
prepareEmpty()
|
|
|
|
fs.writeFileSync(WANTED_LOCKFILE, `\
|
|
importers:
|
|
.:
|
|
dependencies:
|
|
'@pnpm.e2e/dep-of-pkg-with-1-dep':
|
|
specifier: '100.0.0'
|
|
version: 100.0.0
|
|
lockfileVersion: ${LOCKFILE_VERSION}
|
|
packages:
|
|
'@pnpm.e2e/dep-of-pkg-with-1-dep@100.0.0':
|
|
resolution: {integrity: ${getIntegrity('@pnpm.e2e/dep-of-pkg-with-1-dep', '100.0.0')}}
|
|
dev: false
|
|
resolution: {integrity: ${getIntegrity('@pnpm.e2e/dep-of-pkg-with-1-dep', '100.0.0')}}
|
|
`, 'utf8')
|
|
|
|
await expect(
|
|
install({
|
|
dependencies: {
|
|
'@pnpm.e2e/dep-of-pkg-with-1-dep': '100.0.0',
|
|
},
|
|
}, testDefaults({ frozenLockfile: true }))
|
|
).rejects.toThrow(/^The lockfile at .* is broken: duplicated mapping key/)
|
|
})
|
|
|
|
test('a broken private lockfile is ignored', async () => {
|
|
prepareEmpty()
|
|
|
|
const { updatedManifest: manifest } = await install({
|
|
dependencies: {
|
|
'@pnpm.e2e/dep-of-pkg-with-1-dep': '100.0.0',
|
|
},
|
|
}, testDefaults())
|
|
|
|
fs.writeFileSync('node_modules/.pnpm/lock.yaml', `\
|
|
importers:
|
|
.:
|
|
dependencies:
|
|
'@pnpm.e2e/dep-of-pkg-with-1-dep': 100.0.0
|
|
specifiers:
|
|
'@pnpm.e2e/dep-of-pkg-with-1-dep': '100.0.0'
|
|
lockfileVersion: ${LOCKFILE_VERSION}
|
|
packages:
|
|
'@pnpm.e2e/dep-of-pkg-with-1-dep@100.0.0':
|
|
resolution: {integrity: ${getIntegrity('@pnpm.e2e/dep-of-pkg-with-1-dep', '100.0.0')}}
|
|
dev: false
|
|
resolution: {integrity: ${getIntegrity('@pnpm.e2e/dep-of-pkg-with-1-dep', '100.0.0')}}
|
|
`, 'utf8')
|
|
|
|
const reporter = jest.fn()
|
|
|
|
await mutateModulesInSingleProject({
|
|
mutation: 'install',
|
|
manifest,
|
|
rootDir: process.cwd() as ProjectRootDir,
|
|
}, testDefaults({ reporter }))
|
|
|
|
expect(reporter).toHaveBeenCalledWith(expect.objectContaining({
|
|
level: 'warn',
|
|
name: 'pnpm',
|
|
prefix: process.cwd(),
|
|
message: expect.stringMatching(/^Ignoring broken lockfile at .* duplicated mapping key/),
|
|
}))
|
|
})
|
|
|
|
// Covers https://github.com/pnpm/pnpm/issues/2928
|
|
test('build metadata is always ignored in versions and the lockfile is not flickering because of them', async () => {
|
|
await addDistTag({ package: '@monorepolint/core', version: '0.5.0-alpha.51', distTag: 'latest' })
|
|
const project = prepareEmpty()
|
|
|
|
const { updatedManifest: manifest } = await addDependenciesToPackage({},
|
|
[
|
|
'@monorepolint/cli@0.5.0-alpha.51',
|
|
], testDefaults({ lockfileOnly: true }))
|
|
|
|
const depPath = '@monorepolint/core@0.5.0-alpha.51'
|
|
const initialLockfile = project.readLockfile()
|
|
const initialPkgEntry = initialLockfile.packages[depPath]
|
|
expect(initialPkgEntry?.resolution).toStrictEqual({
|
|
integrity: 'sha512-ihFonHDppOZyG717OW6Bamd37mI2gQHjd09buTjbKhRX8NAHsTbRUKwp39ZYVI5AYgLF1eDlLpgOY4dHy2xGQw==',
|
|
})
|
|
|
|
await addDependenciesToPackage(manifest, ['is-positive'], testDefaults({ lockfileOnly: true }))
|
|
|
|
const updatedLockfile = project.readLockfile()
|
|
expect(initialPkgEntry).toStrictEqual(updatedLockfile.packages[depPath])
|
|
})
|
|
|
|
test('a broken lockfile should not break the store', async () => {
|
|
prepareEmpty()
|
|
const opts = testDefaults()
|
|
|
|
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['is-positive@1.0.0'], { ...opts, lockfileOnly: true })
|
|
|
|
const lockfile: LockfileObject = readYamlFile(WANTED_LOCKFILE)
|
|
lockfile.packages!['is-positive@1.0.0' as DepPath].name = 'bad-name'
|
|
lockfile.packages!['is-positive@1.0.0' as DepPath].version = '1.0.0'
|
|
|
|
writeYamlFile(WANTED_LOCKFILE, lockfile)
|
|
|
|
await mutateModulesInSingleProject({
|
|
manifest,
|
|
mutation: 'install',
|
|
rootDir: process.cwd() as ProjectRootDir,
|
|
}, testDefaults({ lockfileOnly: true, storeDir: path.resolve('store2') }))
|
|
|
|
delete lockfile.packages!['is-positive@1.0.0' as DepPath].name
|
|
delete lockfile.packages!['is-positive@1.0.0' as DepPath].version
|
|
|
|
writeYamlFile(WANTED_LOCKFILE, lockfile)
|
|
rimraf(path.resolve('node_modules'))
|
|
|
|
await mutateModulesInSingleProject({
|
|
manifest,
|
|
mutation: 'install',
|
|
rootDir: process.cwd() as ProjectRootDir,
|
|
}, testDefaults({ lockfileOnly: true, storeDir: path.resolve('store2') }))
|
|
})
|
|
|
|
test('include tarball URL', async () => {
|
|
const project = prepareEmpty()
|
|
|
|
const opts = testDefaults({ fastUnpack: false, lockfileIncludeTarballUrl: true })
|
|
await addDependenciesToPackage({}, ['@pnpm.e2e/pkg-with-1-dep@100.0.0'], opts)
|
|
|
|
const lockfile = project.readLockfile()
|
|
expect((lockfile.packages['@pnpm.e2e/pkg-with-1-dep@100.0.0'].resolution as TarballResolution).tarball)
|
|
.toBe(`http://localhost:${REGISTRY_MOCK_PORT}/@pnpm.e2e/pkg-with-1-dep/-/pkg-with-1-dep-100.0.0.tgz`)
|
|
})
|
|
|
|
test('exclude tarball URL when lockfileIncludeTarballUrl is false', async () => {
|
|
const project = prepareEmpty()
|
|
|
|
const opts = testDefaults({ fastUnpack: false, lockfileIncludeTarballUrl: false })
|
|
await addDependenciesToPackage({}, ['@pnpm.e2e/pkg-with-1-dep@100.0.0'], opts)
|
|
|
|
const lockfile = project.readLockfile()
|
|
expect((lockfile.packages['@pnpm.e2e/pkg-with-1-dep@100.0.0'].resolution as TarballResolution).tarball)
|
|
.toBeUndefined()
|
|
})
|
|
|
|
test('exclude non-standard tarball URL when lockfileIncludeTarballUrl is false', async () => {
|
|
const project = prepareEmpty()
|
|
|
|
await addDependenciesToPackage({}, ['esprima-fb@3001.1.0-dev-harmony-fb'], testDefaults({ fastUnpack: false, lockfileIncludeTarballUrl: false }))
|
|
|
|
const lockfile = project.readLockfile()
|
|
|
|
expect((lockfile.packages['esprima-fb@3001.1.0-dev-harmony-fb'].resolution as TarballResolution).tarball)
|
|
.toBeUndefined()
|
|
})
|
|
|
|
test('lockfile v6', async () => {
|
|
prepareEmpty()
|
|
|
|
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['@pnpm.e2e/pkg-with-1-dep@100.0.0'], testDefaults({ useLockfileV6: true }))
|
|
|
|
{
|
|
const lockfile = readYamlFile<any>(WANTED_LOCKFILE) // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
expect(lockfile.lockfileVersion).toBe(LOCKFILE_VERSION)
|
|
expect(lockfile.packages).toHaveProperty(['@pnpm.e2e/pkg-with-1-dep@100.0.0'])
|
|
}
|
|
|
|
await addDependenciesToPackage(manifest, ['@pnpm.e2e/foo@100.0.0'], testDefaults())
|
|
|
|
{
|
|
const lockfile = readYamlFile<any>(WANTED_LOCKFILE) // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
expect(lockfile.lockfileVersion).toBe(LOCKFILE_VERSION)
|
|
expect(lockfile.packages).toHaveProperty(['@pnpm.e2e/pkg-with-1-dep@100.0.0'])
|
|
expect(lockfile.packages).toHaveProperty(['@pnpm.e2e/foo@100.0.0'])
|
|
}
|
|
})
|
|
|
|
test('lockfile v5 is converted to lockfile v6', async () => {
|
|
const tmp = tempDir()
|
|
f.copy('lockfile-v5', tmp)
|
|
prepareEmpty()
|
|
|
|
await install({ dependencies: { '@pnpm.e2e/pkg-with-1-dep': '100.0.0' } }, testDefaults())
|
|
|
|
const lockfile = readYamlFile<any>(WANTED_LOCKFILE) // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
expect(lockfile.lockfileVersion).toBe(LOCKFILE_VERSION)
|
|
expect(lockfile.packages).toHaveProperty(['@pnpm.e2e/pkg-with-1-dep@100.0.0'])
|
|
})
|
|
|
|
test('update the lockfile when a new project is added to the workspace', async () => {
|
|
preparePackages([
|
|
{
|
|
location: 'project-1',
|
|
package: { name: 'project-1' },
|
|
},
|
|
])
|
|
|
|
const importers: MutatedProject[] = [
|
|
{
|
|
mutation: 'install',
|
|
rootDir: path.resolve('project-1') as ProjectRootDir,
|
|
},
|
|
]
|
|
const allProjects: ProjectOptions[] = [
|
|
{
|
|
buildIndex: 0,
|
|
manifest: {
|
|
name: 'project-1',
|
|
version: '1.0.0',
|
|
|
|
dependencies: {
|
|
'is-positive': '1.0.0',
|
|
},
|
|
},
|
|
rootDir: path.resolve('project-1') as ProjectRootDir,
|
|
},
|
|
]
|
|
await mutateModules(importers, testDefaults({ allProjects }))
|
|
|
|
importers.push({
|
|
mutation: 'install',
|
|
rootDir: path.resolve('project-2') as ProjectRootDir,
|
|
})
|
|
allProjects.push({
|
|
buildIndex: 0,
|
|
manifest: {
|
|
name: 'project-2',
|
|
version: '1.0.0',
|
|
},
|
|
rootDir: path.resolve('project-2') as ProjectRootDir,
|
|
})
|
|
await mutateModules(importers, testDefaults({ allProjects }))
|
|
|
|
const lockfile: LockfileObject = readYamlFile(WANTED_LOCKFILE)
|
|
expect(Object.keys(lockfile.importers)).toStrictEqual(['project-1', 'project-2'])
|
|
})
|
|
|
|
test('update the lockfile when a new project is added to the workspace and lockfile-only installation is used', async () => {
|
|
preparePackages([
|
|
{
|
|
location: 'project-1',
|
|
package: { name: 'project-1' },
|
|
},
|
|
])
|
|
|
|
const importers: MutatedProject[] = [
|
|
{
|
|
mutation: 'install',
|
|
rootDir: path.resolve('project-1') as ProjectRootDir,
|
|
},
|
|
]
|
|
const allProjects: ProjectOptions[] = [
|
|
{
|
|
buildIndex: 0,
|
|
manifest: {
|
|
name: 'project-1',
|
|
version: '1.0.0',
|
|
|
|
dependencies: {
|
|
'is-positive': '1.0.0',
|
|
},
|
|
},
|
|
rootDir: path.resolve('project-1') as ProjectRootDir,
|
|
},
|
|
]
|
|
await mutateModules(importers, testDefaults({ allProjects, lockfileOnly: true }))
|
|
|
|
importers.push({
|
|
mutation: 'install',
|
|
rootDir: path.resolve('project-2') as ProjectRootDir,
|
|
})
|
|
allProjects.push({
|
|
buildIndex: 0,
|
|
manifest: {
|
|
name: 'project-2',
|
|
version: '1.0.0',
|
|
},
|
|
rootDir: path.resolve('project-2') as ProjectRootDir,
|
|
})
|
|
await mutateModules(importers, testDefaults({ allProjects, lockfileOnly: true }))
|
|
|
|
const lockfile: LockfileObject = readYamlFile(WANTED_LOCKFILE)
|
|
expect(Object.keys(lockfile.importers)).toStrictEqual(['project-1', 'project-2'])
|
|
})
|
|
|
|
test('lockfile is not written when it has no changes', async () => {
|
|
prepareEmpty()
|
|
|
|
const { updatedManifest: manifest } = await install({
|
|
dependencies: {
|
|
'@types/semver': '^5.3.31',
|
|
},
|
|
}, testDefaults())
|
|
|
|
const stat = fs.statSync(WANTED_LOCKFILE)
|
|
const initialMtime = stat.mtimeMs
|
|
|
|
await install(manifest, testDefaults())
|
|
expect(fs.statSync(WANTED_LOCKFILE)).toHaveProperty('mtimeMs', initialMtime)
|
|
})
|
|
|
|
test('installation should work with packages that have () in the scope name', async () => {
|
|
prepareEmpty()
|
|
const opts = testDefaults()
|
|
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['@(-.-)/env@0.3.1'], opts)
|
|
await install(manifest, opts)
|
|
})
|
|
|
|
test('setting a custom peersSuffixMaxLength', async () => {
|
|
const project = prepareEmpty()
|
|
|
|
await addDependenciesToPackage({}, ['@pnpm.e2e/abc@1.0.0'], testDefaults({ peersSuffixMaxLength: 10 }))
|
|
|
|
const lockfile = project.readLockfile()
|
|
expect(lockfile.settings.peersSuffixMaxLength).toBe(10)
|
|
expect(lockfile.importers['.']?.dependencies?.['@pnpm.e2e/abc']?.version?.length).toBe(39)
|
|
})
|