mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-28 01:28:13 -05:00
feat: don't add linked in deps to the lockfile when excludeLinksFromLockfile is set to true (#6369)
This commit is contained in:
5
.changeset/brave-radios-cough.md
Normal file
5
.changeset/brave-radios-cough.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/lockfile-utils": major
|
||||
---
|
||||
|
||||
Breaking changes to the API of `satisfiesPackageManifest`.
|
||||
7
.changeset/tiny-camels-divide.md
Normal file
7
.changeset/tiny-camels-divide.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@pnpm/resolve-dependencies": minor
|
||||
"@pnpm/headless": minor
|
||||
"@pnpm/core": minor
|
||||
---
|
||||
|
||||
When `excludeLinksFromLockfile` is set to `true`, linked dependencies are not added to the lockfile.
|
||||
@@ -1,20 +1,22 @@
|
||||
import { type Lockfile } from '@pnpm/lockfile-types'
|
||||
import { type ProjectSnapshot } from '@pnpm/lockfile-types'
|
||||
import {
|
||||
DEPENDENCIES_FIELDS,
|
||||
type ProjectManifest,
|
||||
} from '@pnpm/types'
|
||||
import equals from 'ramda/src/equals'
|
||||
import pickBy from 'ramda/src/pickBy'
|
||||
import omit from 'ramda/src/omit'
|
||||
|
||||
export function satisfiesPackageManifest (
|
||||
lockfile: Lockfile,
|
||||
pkg: ProjectManifest,
|
||||
importerId: string,
|
||||
opts?: { autoInstallPeers?: boolean }
|
||||
opts: {
|
||||
autoInstallPeers?: boolean
|
||||
excludeLinksFromLockfile?: boolean
|
||||
},
|
||||
importer: ProjectSnapshot | undefined,
|
||||
pkg: ProjectManifest
|
||||
) {
|
||||
const importer = lockfile.importers[importerId]
|
||||
if (!importer) return false
|
||||
let existingDeps = { ...pkg.devDependencies, ...pkg.dependencies, ...pkg.optionalDependencies }
|
||||
let existingDeps: Record<string, string> = { ...pkg.devDependencies, ...pkg.dependencies, ...pkg.optionalDependencies }
|
||||
if (opts?.autoInstallPeers) {
|
||||
pkg = {
|
||||
...pkg,
|
||||
@@ -28,6 +30,10 @@ export function satisfiesPackageManifest (
|
||||
...existingDeps,
|
||||
}
|
||||
}
|
||||
const pickNonLinkedDeps = pickBy((spec) => !spec.startsWith('link:'))
|
||||
if (opts?.excludeLinksFromLockfile) {
|
||||
existingDeps = pickNonLinkedDeps(existingDeps)
|
||||
}
|
||||
if (
|
||||
!equals(existingDeps, importer.specifiers) ||
|
||||
importer.publishDirectory !== pkg.publishConfig?.directory
|
||||
@@ -37,7 +43,10 @@ export function satisfiesPackageManifest (
|
||||
if (!equals(pkg.dependenciesMeta ?? {}, importer.dependenciesMeta ?? {})) return false
|
||||
for (const depField of DEPENDENCIES_FIELDS) {
|
||||
const importerDeps = importer[depField] ?? {}
|
||||
const pkgDeps = pkg[depField] ?? {}
|
||||
let pkgDeps: Record<string, string> = pkg[depField] ?? {}
|
||||
if (opts?.excludeLinksFromLockfile) {
|
||||
pkgDeps = pickNonLinkedDeps(pkgDeps)
|
||||
}
|
||||
|
||||
let pkgDepNames!: string[]
|
||||
switch (depField) {
|
||||
|
||||
@@ -1,130 +1,113 @@
|
||||
import { satisfiesPackageManifest } from '@pnpm/lockfile-utils'
|
||||
|
||||
const DEFAULT_LOCKFILE_FIELDS = {
|
||||
lockfileVersion: 3,
|
||||
}
|
||||
|
||||
const DEFAULT_PKG_FIELDS = {
|
||||
name: 'project',
|
||||
version: '1.0.0',
|
||||
}
|
||||
|
||||
test('satisfiesPackageManifest()', () => {
|
||||
expect(satisfiesPackageManifest({
|
||||
...DEFAULT_LOCKFILE_FIELDS,
|
||||
importers: {
|
||||
'.': {
|
||||
dependencies: { foo: '1.0.0' },
|
||||
specifiers: { foo: '^1.0.0' },
|
||||
},
|
||||
expect(satisfiesPackageManifest(
|
||||
{},
|
||||
{
|
||||
dependencies: { foo: '1.0.0' },
|
||||
specifiers: { foo: '^1.0.0' },
|
||||
},
|
||||
}, {
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
dependencies: { foo: '^1.0.0' },
|
||||
}, '.')).toBe(true)
|
||||
expect(satisfiesPackageManifest({
|
||||
...DEFAULT_LOCKFILE_FIELDS,
|
||||
importers: {
|
||||
'.': {
|
||||
dependencies: { foo: '1.0.0' },
|
||||
devDependencies: {},
|
||||
specifiers: { foo: '^1.0.0' },
|
||||
},
|
||||
{
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
dependencies: { foo: '^1.0.0' },
|
||||
}
|
||||
)).toBe(true)
|
||||
expect(satisfiesPackageManifest(
|
||||
{},
|
||||
{
|
||||
dependencies: { foo: '1.0.0' },
|
||||
devDependencies: {},
|
||||
specifiers: { foo: '^1.0.0' },
|
||||
},
|
||||
}, {
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
dependencies: { foo: '^1.0.0' },
|
||||
}, '.')).toBe(true)
|
||||
expect(satisfiesPackageManifest({
|
||||
...DEFAULT_LOCKFILE_FIELDS,
|
||||
importers: {
|
||||
'.': {
|
||||
devDependencies: { foo: '1.0.0' },
|
||||
specifiers: { foo: '^1.0.0' },
|
||||
},
|
||||
{
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
dependencies: { foo: '^1.0.0' },
|
||||
}
|
||||
)).toBe(true)
|
||||
expect(satisfiesPackageManifest(
|
||||
{},
|
||||
{
|
||||
devDependencies: { foo: '1.0.0' },
|
||||
specifiers: { foo: '^1.0.0' },
|
||||
},
|
||||
}, {
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
devDependencies: { foo: '^1.0.0' },
|
||||
}, '.')).toBe(true)
|
||||
expect(satisfiesPackageManifest({
|
||||
...DEFAULT_LOCKFILE_FIELDS,
|
||||
importers: {
|
||||
'.': {
|
||||
optionalDependencies: { foo: '1.0.0' },
|
||||
specifiers: { foo: '^1.0.0' },
|
||||
},
|
||||
{
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
devDependencies: { foo: '^1.0.0' },
|
||||
}
|
||||
)).toBe(true)
|
||||
expect(satisfiesPackageManifest(
|
||||
{},
|
||||
{
|
||||
optionalDependencies: { foo: '1.0.0' },
|
||||
specifiers: { foo: '^1.0.0' },
|
||||
},
|
||||
}, {
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
optionalDependencies: { foo: '^1.0.0' },
|
||||
}, '.')).toBe(true)
|
||||
expect(satisfiesPackageManifest({
|
||||
...DEFAULT_LOCKFILE_FIELDS,
|
||||
importers: {
|
||||
'.': {
|
||||
dependencies: { foo: '1.0.0' },
|
||||
specifiers: { foo: '^1.0.0' },
|
||||
},
|
||||
{
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
optionalDependencies: { foo: '^1.0.0' },
|
||||
}
|
||||
)).toBe(true)
|
||||
expect(satisfiesPackageManifest(
|
||||
{},
|
||||
{
|
||||
dependencies: { foo: '1.0.0' },
|
||||
specifiers: { foo: '^1.0.0' },
|
||||
},
|
||||
}, {
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
optionalDependencies: { foo: '^1.0.0' },
|
||||
}, '.')).toBe(false)
|
||||
expect(satisfiesPackageManifest({
|
||||
...DEFAULT_LOCKFILE_FIELDS,
|
||||
importers: {
|
||||
'.': {
|
||||
dependencies: { foo: '1.0.0' },
|
||||
specifiers: { foo: '^1.0.0' },
|
||||
},
|
||||
{
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
optionalDependencies: { foo: '^1.0.0' },
|
||||
}
|
||||
)).toBe(false)
|
||||
expect(satisfiesPackageManifest(
|
||||
{},
|
||||
{
|
||||
dependencies: { foo: '1.0.0' },
|
||||
specifiers: { foo: '^1.0.0' },
|
||||
},
|
||||
}, {
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
dependencies: { foo: '^1.1.0' },
|
||||
}, '.')).toBe(false)
|
||||
expect(satisfiesPackageManifest({
|
||||
...DEFAULT_LOCKFILE_FIELDS,
|
||||
importers: {
|
||||
'.': {
|
||||
dependencies: { foo: '1.0.0' },
|
||||
specifiers: { foo: '^1.0.0' },
|
||||
},
|
||||
{
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
dependencies: { foo: '^1.1.0' },
|
||||
}
|
||||
)).toBe(false)
|
||||
expect(satisfiesPackageManifest(
|
||||
{},
|
||||
{
|
||||
dependencies: { foo: '1.0.0' },
|
||||
specifiers: { foo: '^1.0.0' },
|
||||
},
|
||||
}, {
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
dependencies: { foo: '^1.0.0', bar: '2.0.0' },
|
||||
}, '.')).toBe(false)
|
||||
{
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
dependencies: { foo: '^1.0.0', bar: '2.0.0' },
|
||||
}
|
||||
)).toBe(false)
|
||||
|
||||
expect(satisfiesPackageManifest({
|
||||
...DEFAULT_LOCKFILE_FIELDS,
|
||||
importers: {
|
||||
'.': {
|
||||
dependencies: { foo: '1.0.0' },
|
||||
specifiers: { foo: '^1.0.0', bar: '2.0.0' },
|
||||
},
|
||||
expect(satisfiesPackageManifest(
|
||||
{},
|
||||
{
|
||||
dependencies: { foo: '1.0.0' },
|
||||
specifiers: { foo: '^1.0.0', bar: '2.0.0' },
|
||||
},
|
||||
}, {
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
dependencies: { foo: '^1.0.0', bar: '2.0.0' },
|
||||
}, '.')).toBe(false)
|
||||
{
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
dependencies: { foo: '^1.0.0', bar: '2.0.0' },
|
||||
}
|
||||
)).toBe(false)
|
||||
|
||||
{
|
||||
const lockfile = {
|
||||
...DEFAULT_LOCKFILE_FIELDS,
|
||||
importers: {
|
||||
'.': {
|
||||
dependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
optionalDependencies: {
|
||||
bar: '2.0.0',
|
||||
},
|
||||
specifiers: {
|
||||
bar: '2.0.0',
|
||||
foo: '^1.0.0',
|
||||
},
|
||||
},
|
||||
const importer = {
|
||||
dependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
optionalDependencies: {
|
||||
bar: '2.0.0',
|
||||
},
|
||||
specifiers: {
|
||||
bar: '2.0.0',
|
||||
foo: '^1.0.0',
|
||||
},
|
||||
}
|
||||
const pkg = {
|
||||
@@ -137,23 +120,18 @@ test('satisfiesPackageManifest()', () => {
|
||||
bar: '2.0.0',
|
||||
},
|
||||
}
|
||||
expect(satisfiesPackageManifest(lockfile, pkg, '.')).toBe(true)
|
||||
expect(satisfiesPackageManifest({}, importer, pkg)).toBe(true)
|
||||
}
|
||||
|
||||
{
|
||||
const lockfile = {
|
||||
...DEFAULT_LOCKFILE_FIELDS,
|
||||
importers: {
|
||||
'.': {
|
||||
dependencies: {
|
||||
bar: '2.0.0',
|
||||
qar: '1.0.0',
|
||||
},
|
||||
specifiers: {
|
||||
bar: '2.0.0',
|
||||
qar: '^1.0.0',
|
||||
},
|
||||
},
|
||||
const importer = {
|
||||
dependencies: {
|
||||
bar: '2.0.0',
|
||||
qar: '1.0.0',
|
||||
},
|
||||
specifiers: {
|
||||
bar: '2.0.0',
|
||||
qar: '^1.0.0',
|
||||
},
|
||||
}
|
||||
const pkg = {
|
||||
@@ -162,22 +140,17 @@ test('satisfiesPackageManifest()', () => {
|
||||
bar: '2.0.0',
|
||||
},
|
||||
}
|
||||
expect(satisfiesPackageManifest(lockfile, pkg, '.')).toBe(false)
|
||||
expect(satisfiesPackageManifest({}, importer, pkg)).toBe(false)
|
||||
}
|
||||
|
||||
{
|
||||
const lockfile = {
|
||||
...DEFAULT_LOCKFILE_FIELDS,
|
||||
importers: {
|
||||
'.': {
|
||||
dependencies: {
|
||||
bar: '2.0.0',
|
||||
qar: '1.0.0',
|
||||
},
|
||||
specifiers: {
|
||||
bar: '2.0.0',
|
||||
},
|
||||
},
|
||||
const importer = {
|
||||
dependencies: {
|
||||
bar: '2.0.0',
|
||||
qar: '1.0.0',
|
||||
},
|
||||
specifiers: {
|
||||
bar: '2.0.0',
|
||||
},
|
||||
}
|
||||
const pkg = {
|
||||
@@ -186,185 +159,195 @@ test('satisfiesPackageManifest()', () => {
|
||||
bar: '2.0.0',
|
||||
},
|
||||
}
|
||||
expect(satisfiesPackageManifest(lockfile, pkg, '.')).toBe(false)
|
||||
expect(satisfiesPackageManifest({}, importer, pkg)).toBe(false)
|
||||
}
|
||||
|
||||
expect(satisfiesPackageManifest({
|
||||
...DEFAULT_LOCKFILE_FIELDS,
|
||||
importers: {
|
||||
'.': {
|
||||
dependencies: { foo: '1.0.0', linked: 'link:../linked' },
|
||||
specifiers: { foo: '^1.0.0' },
|
||||
},
|
||||
expect(satisfiesPackageManifest(
|
||||
{},
|
||||
{
|
||||
dependencies: { foo: '1.0.0', linked: 'link:../linked' },
|
||||
specifiers: { foo: '^1.0.0' },
|
||||
},
|
||||
}, {
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
dependencies: { foo: '^1.0.0' },
|
||||
}, '.')).toBe(true)
|
||||
{
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
dependencies: { foo: '^1.0.0' },
|
||||
}
|
||||
)).toBe(true)
|
||||
|
||||
expect(satisfiesPackageManifest({
|
||||
...DEFAULT_LOCKFILE_FIELDS,
|
||||
importers: {
|
||||
'packages/foo': {
|
||||
dependencies: { foo: '1.0.0' },
|
||||
specifiers: { foo: '^1.0.0' },
|
||||
},
|
||||
},
|
||||
}, {
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
dependencies: { foo: '^1.0.0' },
|
||||
}, '.')).toBe(false)
|
||||
expect(satisfiesPackageManifest(
|
||||
{},
|
||||
undefined,
|
||||
{
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
dependencies: { foo: '^1.0.0' },
|
||||
}
|
||||
)).toBe(false)
|
||||
|
||||
expect(satisfiesPackageManifest({
|
||||
...DEFAULT_LOCKFILE_FIELDS,
|
||||
importers: {
|
||||
'.': {
|
||||
dependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
specifiers: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
expect(satisfiesPackageManifest(
|
||||
{},
|
||||
{
|
||||
dependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
specifiers: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
},
|
||||
}, {
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
dependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
devDependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
}, '.')).toBe(true)
|
||||
{
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
dependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
devDependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
}
|
||||
)).toBe(true)
|
||||
|
||||
expect(satisfiesPackageManifest({
|
||||
...DEFAULT_LOCKFILE_FIELDS,
|
||||
importers: {
|
||||
'.': {
|
||||
dependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
specifiers: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
expect(satisfiesPackageManifest(
|
||||
{},
|
||||
{
|
||||
dependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
specifiers: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
},
|
||||
}, {
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
dependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
devDependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
dependenciesMeta: {},
|
||||
}, '.')).toBe(true)
|
||||
{
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
dependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
devDependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
dependenciesMeta: {},
|
||||
}
|
||||
)).toBe(true)
|
||||
|
||||
expect(satisfiesPackageManifest({
|
||||
...DEFAULT_LOCKFILE_FIELDS,
|
||||
importers: {
|
||||
'.': {
|
||||
dependencies: {
|
||||
foo: '1.0.0',
|
||||
bar: '1.0.0',
|
||||
},
|
||||
specifiers: {
|
||||
foo: '1.0.0',
|
||||
bar: '^1.0.0',
|
||||
},
|
||||
expect(satisfiesPackageManifest(
|
||||
{ autoInstallPeers: true },
|
||||
{
|
||||
dependencies: {
|
||||
foo: '1.0.0',
|
||||
bar: '1.0.0',
|
||||
},
|
||||
specifiers: {
|
||||
foo: '1.0.0',
|
||||
bar: '^1.0.0',
|
||||
},
|
||||
},
|
||||
}, {
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
dependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
peerDependencies: {
|
||||
bar: '^1.0.0',
|
||||
},
|
||||
}, '.', { autoInstallPeers: true })).toBe(true)
|
||||
{
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
dependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
peerDependencies: {
|
||||
bar: '^1.0.0',
|
||||
},
|
||||
}
|
||||
)).toBe(true)
|
||||
|
||||
expect(satisfiesPackageManifest({
|
||||
...DEFAULT_LOCKFILE_FIELDS,
|
||||
importers: {
|
||||
'.': {
|
||||
dependencies: {
|
||||
qar: '1.0.0',
|
||||
},
|
||||
optionalDependencies: {
|
||||
bar: '1.0.0',
|
||||
},
|
||||
devDependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
specifiers: {
|
||||
foo: '1.0.0',
|
||||
bar: '1.0.0',
|
||||
qar: '1.0.0',
|
||||
},
|
||||
expect(satisfiesPackageManifest(
|
||||
{ autoInstallPeers: true },
|
||||
{
|
||||
dependencies: {
|
||||
qar: '1.0.0',
|
||||
},
|
||||
optionalDependencies: {
|
||||
bar: '1.0.0',
|
||||
},
|
||||
devDependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
specifiers: {
|
||||
foo: '1.0.0',
|
||||
bar: '1.0.0',
|
||||
qar: '1.0.0',
|
||||
},
|
||||
},
|
||||
}, {
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
dependencies: {
|
||||
qar: '1.0.0',
|
||||
},
|
||||
optionalDependencies: {
|
||||
bar: '1.0.0',
|
||||
},
|
||||
devDependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
peerDependencies: {
|
||||
foo: '^1.0.0',
|
||||
bar: '^1.0.0',
|
||||
qar: '^1.0.0',
|
||||
},
|
||||
}, '.', { autoInstallPeers: true })).toBe(true)
|
||||
{
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
dependencies: {
|
||||
qar: '1.0.0',
|
||||
},
|
||||
optionalDependencies: {
|
||||
bar: '1.0.0',
|
||||
},
|
||||
devDependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
peerDependencies: {
|
||||
foo: '^1.0.0',
|
||||
bar: '^1.0.0',
|
||||
qar: '^1.0.0',
|
||||
},
|
||||
}
|
||||
)).toBe(true)
|
||||
|
||||
expect(satisfiesPackageManifest({
|
||||
...DEFAULT_LOCKFILE_FIELDS,
|
||||
importers: {
|
||||
'.': {
|
||||
dependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
specifiers: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
publishDirectory: 'dist',
|
||||
expect(satisfiesPackageManifest(
|
||||
{},
|
||||
{
|
||||
dependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
specifiers: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
publishDirectory: 'dist',
|
||||
},
|
||||
}, {
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
dependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
publishConfig: {
|
||||
directory: 'dist',
|
||||
},
|
||||
}, '.')).toBe(true)
|
||||
{
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
dependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
publishConfig: {
|
||||
directory: 'dist',
|
||||
},
|
||||
}
|
||||
)).toBe(true)
|
||||
|
||||
expect(satisfiesPackageManifest({
|
||||
...DEFAULT_LOCKFILE_FIELDS,
|
||||
importers: {
|
||||
'.': {
|
||||
dependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
specifiers: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
publishDirectory: 'dist',
|
||||
expect(satisfiesPackageManifest(
|
||||
{},
|
||||
{
|
||||
dependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
specifiers: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
publishDirectory: 'dist',
|
||||
},
|
||||
{
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
dependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
publishConfig: {
|
||||
directory: 'lib',
|
||||
},
|
||||
}
|
||||
)).toBe(false)
|
||||
|
||||
expect(satisfiesPackageManifest(
|
||||
{
|
||||
excludeLinksFromLockfile: true,
|
||||
},
|
||||
{
|
||||
dependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
specifiers: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
},
|
||||
}, {
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
dependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
publishConfig: {
|
||||
directory: 'lib',
|
||||
},
|
||||
}, '.')).toBe(false)
|
||||
{
|
||||
...DEFAULT_PKG_FIELDS,
|
||||
dependencies: {
|
||||
foo: '1.0.0',
|
||||
bar: 'link:../bar',
|
||||
},
|
||||
}
|
||||
)).toBe(true)
|
||||
})
|
||||
|
||||
@@ -20,13 +20,17 @@ export async function allProjectsAreUpToDate (
|
||||
projects: Array<ProjectOptions & { id: string }>,
|
||||
opts: {
|
||||
autoInstallPeers: boolean
|
||||
excludeLinksFromLockfile: boolean
|
||||
linkWorkspacePackages: boolean
|
||||
wantedLockfile: Lockfile
|
||||
workspacePackages: WorkspacePackages
|
||||
}
|
||||
) {
|
||||
const manifestsByDir = opts.workspacePackages ? getWorkspacePackagesByDirectory(opts.workspacePackages) : {}
|
||||
const _satisfiesPackageManifest = satisfiesPackageManifest.bind(null, opts.wantedLockfile)
|
||||
const _satisfiesPackageManifest = satisfiesPackageManifest.bind(null, {
|
||||
autoInstallPeers: opts.autoInstallPeers,
|
||||
excludeLinksFromLockfile: opts.excludeLinksFromLockfile,
|
||||
})
|
||||
const _linkedPackagesAreUpToDate = linkedPackagesAreUpToDate.bind(null, {
|
||||
linkWorkspacePackages: opts.linkWorkspacePackages,
|
||||
manifestsByDir,
|
||||
@@ -35,7 +39,7 @@ export async function allProjectsAreUpToDate (
|
||||
return pEvery(projects, (project) => {
|
||||
const importer = opts.wantedLockfile.importers[project.id]
|
||||
return !hasLocalTarballDepsInRoot(importer) &&
|
||||
_satisfiesPackageManifest(project.manifest, project.id, { autoInstallPeers: opts.autoInstallPeers }) &&
|
||||
_satisfiesPackageManifest(importer, project.manifest) &&
|
||||
_linkedPackagesAreUpToDate({
|
||||
dir: project.rootDir,
|
||||
manifest: project.manifest,
|
||||
|
||||
@@ -120,6 +120,7 @@ export interface StrictInstallOptions {
|
||||
dedupeDirectDeps: boolean
|
||||
dedupePeerDependents: boolean
|
||||
extendNodePath: boolean
|
||||
excludeLinksFromLockfile: boolean
|
||||
}
|
||||
|
||||
export type InstallOptions =
|
||||
@@ -207,6 +208,7 @@ const defaults = async (opts: InstallOptions) => {
|
||||
resolvePeersFromWorkspaceRoot: true,
|
||||
extendNodePath: true,
|
||||
ignoreWorkspaceCycles: false,
|
||||
excludeLinksFromLockfile: false,
|
||||
} as StrictInstallOptions
|
||||
}
|
||||
|
||||
|
||||
@@ -345,6 +345,7 @@ export async function mutateModules (
|
||||
) &&
|
||||
await allProjectsAreUpToDate(Object.values(ctx.projects), {
|
||||
autoInstallPeers: opts.autoInstallPeers,
|
||||
excludeLinksFromLockfile: opts.excludeLinksFromLockfile,
|
||||
linkWorkspacePackages: opts.linkWorkspacePackagesDepth >= 0,
|
||||
wantedLockfile: ctx.wantedLockfile,
|
||||
workspacePackages: opts.workspacePackages,
|
||||
@@ -892,6 +893,7 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
|
||||
dedupePeerDependents: opts.dedupePeerDependents,
|
||||
dryRun: opts.lockfileOnly,
|
||||
engineStrict: opts.engineStrict,
|
||||
excludeLinksFromLockfile: opts.excludeLinksFromLockfile,
|
||||
force: opts.force,
|
||||
forceFullResolution,
|
||||
ignoreScripts: opts.ignoreScripts,
|
||||
|
||||
@@ -33,6 +33,7 @@ test('allProjectsAreUpToDate(): works with packages linked through the workspace
|
||||
},
|
||||
], {
|
||||
autoInstallPeers: false,
|
||||
excludeLinksFromLockfile: false,
|
||||
linkWorkspacePackages: true,
|
||||
wantedLockfile: {
|
||||
importers: {
|
||||
@@ -74,6 +75,7 @@ test('allProjectsAreUpToDate(): works with aliased local dependencies', async ()
|
||||
},
|
||||
], {
|
||||
autoInstallPeers: false,
|
||||
excludeLinksFromLockfile: false,
|
||||
linkWorkspacePackages: true,
|
||||
wantedLockfile: {
|
||||
importers: {
|
||||
@@ -115,6 +117,7 @@ test('allProjectsAreUpToDate(): works with aliased local dependencies that speci
|
||||
},
|
||||
], {
|
||||
autoInstallPeers: false,
|
||||
excludeLinksFromLockfile: false,
|
||||
linkWorkspacePackages: true,
|
||||
wantedLockfile: {
|
||||
importers: {
|
||||
@@ -156,6 +159,7 @@ test('allProjectsAreUpToDate(): returns false if the aliased dependency version
|
||||
},
|
||||
], {
|
||||
autoInstallPeers: false,
|
||||
excludeLinksFromLockfile: false,
|
||||
linkWorkspacePackages: true,
|
||||
wantedLockfile: {
|
||||
importers: {
|
||||
@@ -230,6 +234,7 @@ test('allProjectsAreUpToDate(): use link and registry version if linkWorkspacePa
|
||||
],
|
||||
{
|
||||
autoInstallPeers: false,
|
||||
excludeLinksFromLockfile: false,
|
||||
linkWorkspacePackages: false,
|
||||
wantedLockfile: {
|
||||
importers: {
|
||||
@@ -296,6 +301,7 @@ test('allProjectsAreUpToDate(): returns false if dependenciesMeta differs', asyn
|
||||
},
|
||||
], {
|
||||
autoInstallPeers: false,
|
||||
excludeLinksFromLockfile: false,
|
||||
linkWorkspacePackages: true,
|
||||
wantedLockfile: {
|
||||
importers: {
|
||||
@@ -342,6 +348,7 @@ test('allProjectsAreUpToDate(): returns true if dependenciesMeta matches', async
|
||||
},
|
||||
], {
|
||||
autoInstallPeers: false,
|
||||
excludeLinksFromLockfile: false,
|
||||
linkWorkspacePackages: true,
|
||||
wantedLockfile: {
|
||||
importers: {
|
||||
|
||||
109
pkg-manager/core/test/install/excludeLinksFromLockfile.ts
Normal file
109
pkg-manager/core/test/install/excludeLinksFromLockfile.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { WANTED_LOCKFILE } from '@pnpm/constants'
|
||||
import {
|
||||
mutateModules,
|
||||
type MutatedProject,
|
||||
type ProjectOptions,
|
||||
} from '@pnpm/core'
|
||||
import { type LockfileV6 } from '@pnpm/lockfile-types'
|
||||
import { preparePackages, tempDir } from '@pnpm/prepare'
|
||||
import rimraf from '@zkochan/rimraf'
|
||||
import readYamlFile from 'read-yaml-file'
|
||||
import { testDefaults } from '../utils'
|
||||
|
||||
test('links are not added to the lockfile when excludeLinksFromLockfile is true', async () => {
|
||||
const externalPkg1 = tempDir(false)
|
||||
const externalPkg2 = tempDir(false)
|
||||
const externalPkg3 = tempDir(false)
|
||||
preparePackages([
|
||||
{
|
||||
location: 'project-1',
|
||||
package: { name: 'project-1' },
|
||||
},
|
||||
{
|
||||
location: 'project-2',
|
||||
package: { name: 'project-2' },
|
||||
},
|
||||
])
|
||||
const importers: MutatedProject[] = [
|
||||
{
|
||||
mutation: 'install',
|
||||
rootDir: path.resolve('project-1'),
|
||||
},
|
||||
{
|
||||
mutation: 'install',
|
||||
rootDir: path.resolve('project-2'),
|
||||
},
|
||||
]
|
||||
const project1Dir = path.resolve('project-1')
|
||||
const project2Dir = path.resolve('project-2')
|
||||
const allProjects: ProjectOptions[] = [
|
||||
{
|
||||
buildIndex: 0,
|
||||
manifest: {
|
||||
name: 'project-1',
|
||||
version: '1.0.0',
|
||||
|
||||
dependencies: {
|
||||
'is-positive': '1.0.0',
|
||||
'external-1': `link:${path.relative(project1Dir, externalPkg1)}`,
|
||||
},
|
||||
},
|
||||
rootDir: project1Dir,
|
||||
},
|
||||
{
|
||||
buildIndex: 0,
|
||||
manifest: {
|
||||
name: 'project-2',
|
||||
version: '1.0.0',
|
||||
|
||||
dependencies: {
|
||||
'is-negative': '1.0.0',
|
||||
'external-2': `link:${path.relative(project2Dir, externalPkg2)}`,
|
||||
},
|
||||
},
|
||||
rootDir: project2Dir,
|
||||
},
|
||||
]
|
||||
await mutateModules(importers, await testDefaults({ allProjects, excludeLinksFromLockfile: true }))
|
||||
const lockfile: LockfileV6 = await readYamlFile(WANTED_LOCKFILE)
|
||||
expect(lockfile.importers['project-1'].dependencies?.['external-1']).toBeUndefined()
|
||||
expect(lockfile.importers['project-2'].dependencies?.['external-2']).toBeUndefined()
|
||||
|
||||
expect(fs.existsSync(path.resolve('project-1/node_modules/external-1'))).toBeTruthy()
|
||||
expect(fs.existsSync(path.resolve('project-2/node_modules/external-2'))).toBeTruthy()
|
||||
|
||||
await rimraf('node_modules')
|
||||
await rimraf('project-1/node_modules')
|
||||
await rimraf('project-2/node_modules')
|
||||
|
||||
await mutateModules(importers, await testDefaults({ allProjects, excludeLinksFromLockfile: true, frozenLockfile: true }))
|
||||
expect(lockfile.importers['project-1'].dependencies?.['external-1']).toBeUndefined()
|
||||
expect(lockfile.importers['project-2'].dependencies?.['external-2']).toBeUndefined()
|
||||
|
||||
expect(fs.existsSync(path.resolve('project-1/node_modules/external-1'))).toBeTruthy()
|
||||
expect(fs.existsSync(path.resolve('project-2/node_modules/external-2'))).toBeTruthy()
|
||||
|
||||
await rimraf('node_modules')
|
||||
await rimraf('project-1/node_modules')
|
||||
await rimraf('project-2/node_modules')
|
||||
|
||||
await mutateModules(importers, await testDefaults({ allProjects, excludeLinksFromLockfile: true, frozenLockfile: false, preferFrozenLockfile: false }))
|
||||
expect(lockfile.importers['project-1'].dependencies?.['external-1']).toBeUndefined()
|
||||
expect(lockfile.importers['project-2'].dependencies?.['external-2']).toBeUndefined()
|
||||
|
||||
expect(fs.existsSync(path.resolve('project-1/node_modules/external-1'))).toBeTruthy()
|
||||
expect(fs.existsSync(path.resolve('project-2/node_modules/external-2'))).toBeTruthy()
|
||||
|
||||
delete allProjects[1].manifest.dependencies!['external-2']
|
||||
allProjects[1].manifest.dependencies!['external-3'] = `link:${path.relative(project2Dir, externalPkg3)}`
|
||||
await mutateModules(importers, await testDefaults({ allProjects, excludeLinksFromLockfile: true }))
|
||||
expect(lockfile.importers['project-1'].dependencies?.['external-1']).toBeUndefined()
|
||||
expect(lockfile.importers['project-2'].dependencies?.['external-2']).toBeUndefined()
|
||||
expect(lockfile.importers['project-2'].dependencies?.['external-3']).toBeUndefined()
|
||||
|
||||
expect(fs.existsSync(path.resolve('project-1/node_modules/external-1'))).toBeTruthy()
|
||||
// expect(fs.existsSync(path.resolve('project-2/node_modules/external-2'))).toBeFalsy() // Should we remove external links that are not in deps anymore?
|
||||
expect(fs.existsSync(path.resolve('project-2/node_modules/external-3'))).toBeTruthy()
|
||||
})
|
||||
@@ -56,7 +56,7 @@ import {
|
||||
type StoreController,
|
||||
} from '@pnpm/store-controller-types'
|
||||
import { symlinkDependency } from '@pnpm/symlink-dependency'
|
||||
import { type DependencyManifest, type HoistedDependencies, type ProjectManifest, type Registries } from '@pnpm/types'
|
||||
import { type DependencyManifest, type HoistedDependencies, type ProjectManifest, type Registries, DEPENDENCIES_FIELDS } from '@pnpm/types'
|
||||
import * as dp from '@pnpm/dependency-path'
|
||||
import pLimit from 'p-limit'
|
||||
import pathAbsolute from 'path-absolute'
|
||||
@@ -104,6 +104,7 @@ export interface HeadlessOptions {
|
||||
dedupeDirectDeps?: boolean
|
||||
enablePnp?: boolean
|
||||
engineStrict: boolean
|
||||
excludeLinksFromLockfile?: boolean
|
||||
extraBinPaths?: string[]
|
||||
extraEnv?: Record<string, string>
|
||||
extraNodePaths?: string[]
|
||||
@@ -184,8 +185,12 @@ export async function headlessInstall (opts: HeadlessOptions) {
|
||||
const selectedProjects = Object.values(pick(opts.selectedProjectDirs, opts.allProjects))
|
||||
|
||||
if (!opts.ignorePackageManifest) {
|
||||
const _satisfiesPackageManifest = satisfiesPackageManifest.bind(null, {
|
||||
autoInstallPeers: opts.autoInstallPeers,
|
||||
excludeLinksFromLockfile: opts.excludeLinksFromLockfile,
|
||||
})
|
||||
for (const { id, manifest, rootDir } of selectedProjects) {
|
||||
if (!satisfiesPackageManifest(wantedLockfile, manifest, id, { autoInstallPeers: opts.autoInstallPeers })) {
|
||||
if (!_satisfiesPackageManifest(wantedLockfile.importers[id], manifest)) {
|
||||
throw new PnpmError('OUTDATED_LOCKFILE',
|
||||
`Cannot install with "frozen-lockfile" because ${WANTED_LOCKFILE} is not up to date with ` +
|
||||
path.relative(lockfileDir, path.join(rootDir, 'package.json')), {
|
||||
@@ -260,6 +265,22 @@ export async function headlessInstall (opts: HeadlessOptions) {
|
||||
includeIncompatiblePackages: opts.force,
|
||||
lockfileDir,
|
||||
})
|
||||
if (opts.excludeLinksFromLockfile) {
|
||||
for (const { id, manifest } of selectedProjects) {
|
||||
if (filteredLockfile.importers[id]) {
|
||||
for (const depType of DEPENDENCIES_FIELDS) {
|
||||
for (const [depName, spec] of Object.entries(manifest[depType] ?? {})) {
|
||||
if (spec.startsWith('link:')) {
|
||||
if (!filteredLockfile.importers[id][depType]) {
|
||||
filteredLockfile.importers[id][depType] = {}
|
||||
}
|
||||
filteredLockfile.importers[id][depType]![depName] = spec
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update selectedProjects to add missing projects. importerIds will have the updated ids, found from deeply linked workspace projects
|
||||
const initialImporterIdSet = new Set(initialImporterIds)
|
||||
|
||||
@@ -94,6 +94,7 @@ export async function resolveDependencies (
|
||||
opts: ResolveDependenciesOptions & {
|
||||
defaultUpdateDepth: number
|
||||
dedupePeerDependents?: boolean
|
||||
excludeLinksFromLockfile?: boolean
|
||||
preserveWorkspaceProtocol: boolean
|
||||
saveWorkspaceProtocol: 'rolling' | boolean
|
||||
lockfileIncludeTarballUrl?: boolean
|
||||
@@ -170,7 +171,7 @@ export async function resolveDependencies (
|
||||
resolvedImporter.linkedDependencies,
|
||||
resolvedImporter.directDependencies,
|
||||
opts.registries,
|
||||
opts.autoInstallPeers
|
||||
opts.excludeLinksFromLockfile
|
||||
)
|
||||
}
|
||||
|
||||
@@ -354,7 +355,7 @@ function addDirectDependenciesToLockfile (
|
||||
linkedPackages: Array<{ alias: string }>,
|
||||
directDependencies: ResolvedDirectDependency[],
|
||||
registries: Registries,
|
||||
autoInstallPeers?: boolean
|
||||
excludeLinksFromLockfile?: boolean
|
||||
): ProjectSnapshot {
|
||||
const newProjectSnapshot: ProjectSnapshot & Required<Pick<ProjectSnapshot, 'dependencies' | 'devDependencies' | 'optionalDependencies'>> = {
|
||||
dependencies: {},
|
||||
@@ -379,7 +380,7 @@ function addDirectDependenciesToLockfile (
|
||||
const allDeps = Array.from(new Set(Object.keys(getAllDependenciesFromManifest(newManifest))))
|
||||
|
||||
for (const alias of allDeps) {
|
||||
if (directDependenciesByAlias[alias]) {
|
||||
if (directDependenciesByAlias[alias] && (!excludeLinksFromLockfile || !(directDependenciesByAlias[alias] as LinkedDependency).isLinkedDependency)) {
|
||||
const dep = directDependenciesByAlias[alias]
|
||||
const ref = depPathToRef(dep.pkgId, {
|
||||
alias: dep.alias,
|
||||
|
||||
Reference in New Issue
Block a user