refactor: lockfile-file (#7655)

This commit is contained in:
Zoltan Kochan
2024-02-16 11:14:47 +01:00
committed by GitHub
parent 9f8948c24a
commit c7d0564225
25 changed files with 467 additions and 524 deletions

View File

@@ -1,21 +1,28 @@
lockfileVersion: '6.1'
dependencies:
is-negative: 1.0.0
is-negative:
version: 1.0.0
specifier: 1.0.0
devDependencies:
is-positive: 2.0.0
lockfileVersion: 5.1
is-positive:
version: 2.0.0
specifier: 2.0.0
packages:
/is-negative/1.0.0:
/is-negative@1.0.0:
dev: false
engines:
node: '>=0.10.0'
resolution:
integrity: sha1-clmHeoPIAKwxkd17nZ+80PdS1P4=
/is-positive/2.0.0:
/is-positive@2.0.0:
dev: true
engines:
node: '>=0.10.0'
resolution:
integrity: sha1-sU8GvS24EK5sixJ0HRNr+u8Nh70=
specifiers:
is-negative: 1.0.0
is-positive: 2.0.0

View File

@@ -1,19 +1,24 @@
lockfileVersion: '6.0'
dependencies:
is-negative:
version: 1.0.0
specifier: 1.0.0
devDependencies:
is-positive:
version: 2.0.0
specifier: 2.0.0
packages:
/is-negative@1.0.0:
dev: false
engines:
node: '>=0.10.0'
resolution:
integrity: sha512-1aKMsFUc7vYQGzt//8zhkjRWPoYkajY/I5MJEvrc0pDoHXrW7n5ri8DYxhy3rR+Dk0QFl7GjHHsZU1sppQrWtw==
/is-positive@2.0.0:
dev: true
engines:

View File

@@ -1,7 +1,7 @@
import path from 'path'
import { assertStore } from '@pnpm/assert-store'
import { WANTED_LOCKFILE } from '@pnpm/constants'
import { type LockfileV6 as Lockfile, type ProjectSnapshotV6 as ProjectSnapshot } from '@pnpm/lockfile-types'
import { type LockfileFile } from '@pnpm/lockfile-types'
import { type Modules, readModulesManifest } from '@pnpm/modules-yaml'
import { REGISTRY_MOCK_PORT } from '@pnpm/registry-mock'
import readYamlFile from 'read-yaml-file'
@@ -11,8 +11,6 @@ import isExecutable from './isExecutable'
export { isExecutable, type Modules }
export type RawLockfile = Lockfile & Partial<ProjectSnapshot>
export interface Project {
// eslint-disable-next-line
requireModule: (moduleName: string) => any
@@ -32,14 +30,14 @@ export interface Project {
*
* https://github.com/microsoft/TypeScript/pull/32695 might help with this.
*/
readCurrentLockfile: () => Promise<Required<RawLockfile>>
readCurrentLockfile: () => Promise<Required<LockfileFile>>
readModulesManifest: () => Promise<Modules | null>
/**
* TODO: Remove the `Required<T>` cast.
*
* https://github.com/microsoft/TypeScript/pull/32695 might help with this.
*/
readLockfile: (lockfileName?: string) => Promise<Required<RawLockfile>>
readLockfile: (lockfileName?: string) => Promise<Required<LockfileFile>>
writePackageJson: (pkgJson: object) => Promise<void>
}

View File

@@ -1,122 +0,0 @@
import type { Lockfile, ProjectSnapshot, ResolvedDependencies } from '@pnpm/lockfile-types'
import {
INLINE_SPECIFIERS_FORMAT_LOCKFILE_VERSION_SUFFIX,
type InlineSpecifiersLockfile,
type InlineSpecifiersProjectSnapshot,
type InlineSpecifiersResolvedDependencies,
} from './InlineSpecifiersLockfile'
export function isExperimentalInlineSpecifiersFormat (
lockfile: InlineSpecifiersLockfile | Lockfile
): lockfile is InlineSpecifiersLockfile {
const { lockfileVersion } = lockfile
return lockfileVersion.toString().startsWith('6.') || typeof lockfileVersion === 'string' && lockfileVersion.endsWith(INLINE_SPECIFIERS_FORMAT_LOCKFILE_VERSION_SUFFIX)
}
export function convertToInlineSpecifiersFormat (lockfile: Lockfile): InlineSpecifiersLockfile {
const newLockfile = {
...lockfile,
lockfileVersion: lockfile.lockfileVersion.toString().startsWith('6.')
? lockfile.lockfileVersion.toString()
: (
lockfile.lockfileVersion.toString().endsWith(INLINE_SPECIFIERS_FORMAT_LOCKFILE_VERSION_SUFFIX)
? lockfile.lockfileVersion.toString()
: `${lockfile.lockfileVersion}${INLINE_SPECIFIERS_FORMAT_LOCKFILE_VERSION_SUFFIX}`
),
importers: mapValues(lockfile.importers, convertProjectSnapshotToInlineSpecifiersFormat),
}
return newLockfile
}
export function revertFromInlineSpecifiersFormatIfNecessary (lockfile: Lockfile | InlineSpecifiersLockfile): Lockfile {
return isExperimentalInlineSpecifiersFormat(lockfile)
? revertFromInlineSpecifiersFormat(lockfile)
: lockfile
}
export function revertFromInlineSpecifiersFormat (lockfile: InlineSpecifiersLockfile): Lockfile {
const { lockfileVersion, importers, ...rest } = lockfile
const originalVersionStr = lockfileVersion.replace(INLINE_SPECIFIERS_FORMAT_LOCKFILE_VERSION_SUFFIX, '')
const originalVersion = Number(originalVersionStr)
if (isNaN(originalVersion)) {
throw new Error(`Unable to revert lockfile from inline specifiers format. Invalid version parsed: ${originalVersionStr}`)
}
const newLockfile = {
...rest,
lockfileVersion: lockfileVersion.endsWith(INLINE_SPECIFIERS_FORMAT_LOCKFILE_VERSION_SUFFIX) ? originalVersion : lockfileVersion,
importers: mapValues(importers, revertProjectSnapshot),
}
return newLockfile
}
function convertProjectSnapshotToInlineSpecifiersFormat (
projectSnapshot: ProjectSnapshot
): InlineSpecifiersProjectSnapshot {
const { specifiers, ...rest } = projectSnapshot
const convertBlock = (block?: ResolvedDependencies) =>
block != null
? convertResolvedDependenciesToInlineSpecifiersFormat(block, { specifiers })
: block
return {
...rest,
dependencies: convertBlock(projectSnapshot.dependencies),
optionalDependencies: convertBlock(projectSnapshot.optionalDependencies),
devDependencies: convertBlock(projectSnapshot.devDependencies),
}
}
function convertResolvedDependenciesToInlineSpecifiersFormat (
resolvedDependencies: ResolvedDependencies,
{ specifiers }: { specifiers: ResolvedDependencies }
): InlineSpecifiersResolvedDependencies {
return mapValues(resolvedDependencies, (version, depName) => ({
specifier: specifiers[depName],
version,
}))
}
function revertProjectSnapshot (from: InlineSpecifiersProjectSnapshot): ProjectSnapshot {
const specifiers: ResolvedDependencies = {}
function moveSpecifiers (from: InlineSpecifiersResolvedDependencies): ResolvedDependencies {
const resolvedDependencies: ResolvedDependencies = {}
for (const [depName, { specifier, version }] of Object.entries(from)) {
const existingValue = specifiers[depName]
if (existingValue != null && existingValue !== specifier) {
throw new Error(`Project snapshot lists the same dependency more than once with conflicting versions: ${depName}`)
}
specifiers[depName] = specifier
resolvedDependencies[depName] = version
}
return resolvedDependencies
}
const dependencies = from.dependencies == null
? from.dependencies
: moveSpecifiers(from.dependencies)
const devDependencies = from.devDependencies == null
? from.devDependencies
: moveSpecifiers(from.devDependencies)
const optionalDependencies = from.optionalDependencies == null
? from.optionalDependencies
: moveSpecifiers(from.optionalDependencies)
return {
...from,
specifiers,
dependencies,
devDependencies,
optionalDependencies,
}
}
function mapValues<T, U> (obj: Record<string, T>, mapper: (val: T, key: string) => U): Record<string, U> {
const result: Record<string, U> = {}
for (const [key, value] of Object.entries(obj)) {
result[key] = mapper(value, key)
}
return result
}

View File

@@ -1,18 +1,18 @@
import { type Lockfile } from '@pnpm/lockfile-types'
import { type Lockfile, type LockfileFile } from '@pnpm/lockfile-types'
import { mergeLockfileChanges } from '@pnpm/merge-lockfile-changes'
import yaml from 'js-yaml'
import { revertFromInlineSpecifiersFormatIfNecessary } from './experiments/inlineSpecifiersLockfileConverters'
import { convertToLockfileObject } from './lockfileFormatConverters'
const MERGE_CONFLICT_PARENT = '|||||||'
const MERGE_CONFLICT_END = '>>>>>>>'
const MERGE_CONFLICT_THEIRS = '======='
const MERGE_CONFLICT_OURS = '<<<<<<<'
export function autofixMergeConflicts (fileContent: string) {
export function autofixMergeConflicts (fileContent: string): Lockfile {
const { ours, theirs } = parseMergeFile(fileContent)
return mergeLockfileChanges(
revertFromInlineSpecifiersFormatIfNecessary(yaml.load(ours) as Lockfile),
revertFromInlineSpecifiersFormatIfNecessary(yaml.load(theirs) as Lockfile)
convertToLockfileObject(yaml.load(ours) as LockfileFile),
convertToLockfileObject(yaml.load(theirs) as LockfileFile)
)
}

View File

@@ -0,0 +1,224 @@
import {
type Lockfile,
type ProjectSnapshot,
type ResolvedDependencies,
type LockfileFile,
type InlineSpecifiersLockfile,
type InlineSpecifiersProjectSnapshot,
type InlineSpecifiersResolvedDependencies,
} from '@pnpm/lockfile-types'
import { DEPENDENCIES_FIELDS } from '@pnpm/types'
import equals from 'ramda/src/equals'
import isEmpty from 'ramda/src/isEmpty'
import _mapValues from 'ramda/src/map'
import pickBy from 'ramda/src/pickBy'
export interface NormalizeLockfileOpts {
forceSharedFormat: boolean
}
export function convertToLockfileFile (lockfile: Lockfile, opts: NormalizeLockfileOpts): LockfileFile {
const newLockfile = {
...lockfile,
lockfileVersion: lockfile.lockfileVersion.toString(),
importers: mapValues(lockfile.importers, convertProjectSnapshotToInlineSpecifiersFormat),
}
return normalizeLockfile(newLockfile, opts)
}
function normalizeLockfile (lockfile: InlineSpecifiersLockfile, opts: NormalizeLockfileOpts): LockfileFile {
let lockfileToSave!: LockfileFile
if (!opts.forceSharedFormat && equals(Object.keys(lockfile.importers ?? {}), ['.'])) {
lockfileToSave = {
...lockfile,
...lockfile.importers?.['.'],
}
delete lockfileToSave.importers
for (const depType of DEPENDENCIES_FIELDS) {
if (isEmpty(lockfileToSave[depType])) {
delete lockfileToSave[depType]
}
}
if (isEmpty(lockfileToSave.packages) || (lockfileToSave.packages == null)) {
delete lockfileToSave.packages
}
} else {
lockfileToSave = {
...lockfile,
importers: _mapValues((importer) => {
const normalizedImporter: Partial<InlineSpecifiersProjectSnapshot> = {}
if (importer.dependenciesMeta != null && !isEmpty(importer.dependenciesMeta)) {
normalizedImporter.dependenciesMeta = importer.dependenciesMeta
}
for (const depType of DEPENDENCIES_FIELDS) {
if (!isEmpty(importer[depType] ?? {})) {
normalizedImporter[depType] = importer[depType]
}
}
if (importer.publishDirectory) {
normalizedImporter.publishDirectory = importer.publishDirectory
}
return normalizedImporter as InlineSpecifiersProjectSnapshot
}, lockfile.importers ?? {}),
}
if (isEmpty(lockfileToSave.packages) || (lockfileToSave.packages == null)) {
delete lockfileToSave.packages
}
}
if (lockfileToSave.time) {
lockfileToSave.time = pruneTimeInLockfileV6(lockfileToSave.time, lockfile.importers ?? {})
}
if ((lockfileToSave.overrides != null) && isEmpty(lockfileToSave.overrides)) {
delete lockfileToSave.overrides
}
if ((lockfileToSave.patchedDependencies != null) && isEmpty(lockfileToSave.patchedDependencies)) {
delete lockfileToSave.patchedDependencies
}
if (lockfileToSave.neverBuiltDependencies != null) {
if (isEmpty(lockfileToSave.neverBuiltDependencies)) {
delete lockfileToSave.neverBuiltDependencies
} else {
lockfileToSave.neverBuiltDependencies = lockfileToSave.neverBuiltDependencies.sort()
}
}
if (lockfileToSave.onlyBuiltDependencies != null) {
lockfileToSave.onlyBuiltDependencies = lockfileToSave.onlyBuiltDependencies.sort()
}
if (!lockfileToSave.packageExtensionsChecksum) {
delete lockfileToSave.packageExtensionsChecksum
}
return lockfileToSave
}
function pruneTimeInLockfileV6 (time: Record<string, string>, importers: Record<string, InlineSpecifiersProjectSnapshot>): Record<string, string> {
const rootDepPaths = new Set<string>()
for (const importer of Object.values(importers)) {
for (const depType of DEPENDENCIES_FIELDS) {
for (const [depName, ref] of Object.entries(importer[depType] ?? {})) {
const suffixStart = ref.version.indexOf('(')
const refWithoutPeerSuffix = suffixStart === -1 ? ref.version : ref.version.slice(0, suffixStart)
const depPath = refToRelative(refWithoutPeerSuffix, depName)
if (!depPath) continue
rootDepPaths.add(depPath)
}
}
}
return pickBy((_, depPath) => rootDepPaths.has(depPath), time)
}
function refToRelative (
reference: string,
pkgName: string
): string | null {
if (reference.startsWith('link:')) {
return null
}
if (reference.startsWith('file:')) {
return reference
}
if (!reference.includes('/') || !reference.replace(/(\([^)]+\))+$/, '').includes('/')) {
return `/${pkgName}@${reference}`
}
return reference
}
/**
* Reverts changes from the "forceSharedFormat" write option if necessary.
*/
function convertFromLockfileFileMutable (lockfileFile: LockfileFile): InlineSpecifiersLockfile {
if (typeof lockfileFile?.['importers'] === 'undefined') {
lockfileFile.importers = {
'.': {
dependenciesMeta: lockfileFile['dependenciesMeta'],
publishDirectory: lockfileFile['publishDirectory'],
},
}
for (const depType of DEPENDENCIES_FIELDS) {
if (lockfileFile[depType] != null) {
lockfileFile.importers['.'][depType] = lockfileFile[depType]
delete lockfileFile[depType]
}
}
}
return lockfileFile as InlineSpecifiersLockfile
}
export function convertToLockfileObject (lockfile: LockfileFile): Lockfile {
const { importers, ...rest } = convertFromLockfileFileMutable(lockfile)
const newLockfile = {
...rest,
importers: mapValues(importers ?? {}, revertProjectSnapshot),
}
return newLockfile
}
function convertProjectSnapshotToInlineSpecifiersFormat (
projectSnapshot: ProjectSnapshot
): InlineSpecifiersProjectSnapshot {
const { specifiers, ...rest } = projectSnapshot
const convertBlock = (block?: ResolvedDependencies) =>
block != null
? convertResolvedDependenciesToInlineSpecifiersFormat(block, { specifiers })
: block
return {
...rest,
dependencies: convertBlock(projectSnapshot.dependencies ?? {}),
optionalDependencies: convertBlock(projectSnapshot.optionalDependencies ?? {}),
devDependencies: convertBlock(projectSnapshot.devDependencies ?? {}),
}
}
function convertResolvedDependenciesToInlineSpecifiersFormat (
resolvedDependencies: ResolvedDependencies,
{ specifiers }: { specifiers: ResolvedDependencies }
): InlineSpecifiersResolvedDependencies {
return mapValues(resolvedDependencies, (version, depName) => ({
specifier: specifiers[depName],
version,
}))
}
function revertProjectSnapshot (from: InlineSpecifiersProjectSnapshot): ProjectSnapshot {
const specifiers: ResolvedDependencies = {}
function moveSpecifiers (from: InlineSpecifiersResolvedDependencies): ResolvedDependencies {
const resolvedDependencies: ResolvedDependencies = {}
for (const [depName, { specifier, version }] of Object.entries(from)) {
const existingValue = specifiers[depName]
if (existingValue != null && existingValue !== specifier) {
throw new Error(`Project snapshot lists the same dependency more than once with conflicting versions: ${depName}`)
}
specifiers[depName] = specifier
resolvedDependencies[depName] = version
}
return resolvedDependencies
}
const dependencies = from.dependencies == null
? from.dependencies
: moveSpecifiers(from.dependencies)
const devDependencies = from.devDependencies == null
? from.devDependencies
: moveSpecifiers(from.devDependencies)
const optionalDependencies = from.optionalDependencies == null
? from.optionalDependencies
: moveSpecifiers(from.optionalDependencies)
return {
...from,
specifiers,
dependencies,
devDependencies,
optionalDependencies,
}
}
function mapValues<T, U> (obj: Record<string, T>, mapper: (val: T, key: string) => U): Record<string, U> {
const result: Record<string, U> = {}
for (const [key, value] of Object.entries(obj)) {
result[key] = mapper(value, key)
}
return result
}

View File

@@ -7,7 +7,6 @@ import {
import { PnpmError } from '@pnpm/error'
import { mergeLockfileChanges } from '@pnpm/merge-lockfile-changes'
import { type Lockfile } from '@pnpm/lockfile-types'
import { DEPENDENCIES_FIELDS } from '@pnpm/types'
import comverToSemver from 'comver-to-semver'
import yaml from 'js-yaml'
import semver from 'semver'
@@ -15,10 +14,9 @@ import stripBom from 'strip-bom'
import { LockfileBreakingChangeError } from './errors'
import { autofixMergeConflicts, isDiff } from './gitMergeFile'
import { lockfileLogger as logger } from './logger'
import { type LockfileFile } from './write'
import { getWantedLockfileName } from './lockfileName'
import { getGitBranchLockfileNames } from './gitBranchLockfile'
import { revertFromInlineSpecifiersFormatIfNecessary } from './experiments/inlineSpecifiersLockfileConverters'
import { convertToLockfileObject } from './lockfileFormatConverters'
export async function readCurrentLockfile (
virtualStoreDir: string,
@@ -88,14 +86,14 @@ async function _read (
let lockfile: Lockfile
let hadConflicts!: boolean
try {
lockfile = revertFromInlineSpecifiersFormatIfNecessary(convertFromLockfileFileMutable(yaml.load(lockfileRawContent) as Lockfile))
lockfile = convertToLockfileObject(yaml.load(lockfileRawContent) as any) // eslint-disable-line
hadConflicts = false
} catch (err: any) { // eslint-disable-line
if (!opts.autofixMergeConflicts || !isDiff(lockfileRawContent)) {
throw new PnpmError('BROKEN_LOCKFILE', `The lockfile at "${lockfilePath}" is broken: ${err.message as string}`)
}
hadConflicts = true
lockfile = convertFromLockfileFileMutable(autofixMergeConflicts(lockfileRawContent))
lockfile = autofixMergeConflicts(lockfileRawContent)
logger.info({
message: `Merge conflict detected in ${WANTED_LOCKFILE} and successfully merged`,
prefix,
@@ -235,26 +233,3 @@ async function _readGitBranchLockfiles (
return Promise.all(files.map((file) => _read(path.join(lockfileDir, file), prefix, opts)))
}
/**
* Reverts changes from the "forceSharedFormat" write option if necessary.
*/
function convertFromLockfileFileMutable (lockfileFile: LockfileFile): Lockfile {
if (typeof lockfileFile?.['importers'] === 'undefined') {
lockfileFile.importers = {
'.': {
specifiers: lockfileFile['specifiers'] ?? {},
dependenciesMeta: lockfileFile['dependenciesMeta'],
publishDirectory: lockfileFile['publishDirectory'],
},
}
delete lockfileFile.specifiers
for (const depType of DEPENDENCIES_FIELDS) {
if (lockfileFile[depType] != null) {
lockfileFile.importers['.'][depType] = lockfileFile[depType]
delete lockfileFile[depType]
}
}
}
return lockfileFile as Lockfile
}

View File

@@ -1,6 +1,6 @@
import { lexCompare } from '@pnpm/util.lex-comparator'
import sortKeys from 'sort-keys'
import { type LockfileFile } from './write'
import { type LockfileFile } from '@pnpm/lockfile-types'
const ORDERED_KEYS = {
resolution: 1,
@@ -78,7 +78,7 @@ export function sortLockfileKeys (lockfile: LockfileFile) {
})
}
}
for (const key of ['specifiers', 'dependencies', 'devDependencies', 'optionalDependencies', 'time', 'patchedDependencies'] as const) {
for (const key of ['dependencies', 'devDependencies', 'optionalDependencies', 'time', 'patchedDependencies'] as const) {
if (!lockfile[key]) continue
lockfile[key] = sortKeys<any>(lockfile[key]) // eslint-disable-line @typescript-eslint/no-explicit-any
}

View File

@@ -1,19 +1,15 @@
import { promises as fs } from 'fs'
import path from 'path'
import { DEPENDENCIES_FIELDS } from '@pnpm/types'
import { type Lockfile, type ProjectSnapshot } from '@pnpm/lockfile-types'
import { type Lockfile, type LockfileFile } from '@pnpm/lockfile-types'
import { WANTED_LOCKFILE } from '@pnpm/constants'
import rimraf from '@zkochan/rimraf'
import yaml from 'js-yaml'
import equals from 'ramda/src/equals'
import pickBy from 'ramda/src/pickBy'
import isEmpty from 'ramda/src/isEmpty'
import mapValues from 'ramda/src/map'
import writeFileAtomicCB from 'write-file-atomic'
import { lockfileLogger as logger } from './logger'
import { sortLockfileKeys } from './sortLockfileKeys'
import { getWantedLockfileName } from './lockfileName'
import { convertToInlineSpecifiersFormat } from './experiments/inlineSpecifiersLockfileConverters'
import { convertToLockfileFile } from './lockfileFormatConverters'
async function writeFileAtomic (filename: string, data: string) {
return new Promise<void>((resolve, reject) => {
@@ -72,137 +68,24 @@ async function writeLockfile (
) {
const lockfilePath = path.join(pkgPath, lockfileFilename)
const isLockfileV6 = wantedLockfile['lockfileVersion'].toString().startsWith('6.')
const lockfileToStringify = isLockfileV6
? convertToInlineSpecifiersFormat(wantedLockfile) as unknown as Lockfile
: wantedLockfile
const yamlDoc = yamlStringify(lockfileToStringify, {
const lockfileToStringify = convertToLockfileFile(wantedLockfile, {
forceSharedFormat: opts?.forceSharedFormat === true,
includeEmptySpecifiersField: !isLockfileV6,
})
const yamlDoc = yamlStringify(lockfileToStringify)
return writeFileAtomic(lockfilePath, yamlDoc)
}
function yamlStringify (lockfile: Lockfile, opts: NormalizeLockfileOpts) {
let normalizedLockfile = normalizeLockfile(lockfile, opts)
normalizedLockfile = sortLockfileKeys(normalizedLockfile)
return yaml.dump(normalizedLockfile, LOCKFILE_YAML_FORMAT)
function yamlStringify (lockfile: LockfileFile) {
const sortedLockfile = sortLockfileKeys(lockfile)
return yaml.dump(sortedLockfile, LOCKFILE_YAML_FORMAT)
}
export function isEmptyLockfile (lockfile: Lockfile) {
return Object.values(lockfile.importers).every((importer) => isEmpty(importer.specifiers ?? {}) && isEmpty(importer.dependencies ?? {}))
}
export type LockfileFile = Omit<Lockfile, 'importers'> & Partial<ProjectSnapshot> & Partial<Pick<Lockfile, 'importers'>>
export interface NormalizeLockfileOpts {
forceSharedFormat: boolean
includeEmptySpecifiersField: boolean
}
export function normalizeLockfile (lockfile: Lockfile, opts: NormalizeLockfileOpts) {
let lockfileToSave!: LockfileFile
if (!opts.forceSharedFormat && equals(Object.keys(lockfile.importers), ['.'])) {
lockfileToSave = {
...lockfile,
...lockfile.importers['.'],
}
delete lockfileToSave.importers
for (const depType of DEPENDENCIES_FIELDS) {
if (isEmpty(lockfileToSave[depType])) {
delete lockfileToSave[depType]
}
}
if (isEmpty(lockfileToSave.packages) || (lockfileToSave.packages == null)) {
delete lockfileToSave.packages
}
} else {
lockfileToSave = {
...lockfile,
importers: mapValues((importer) => {
const normalizedImporter: Partial<ProjectSnapshot> = {}
if (!isEmpty(importer.specifiers ?? {}) || opts.includeEmptySpecifiersField) {
normalizedImporter['specifiers'] = importer.specifiers ?? {}
}
if (importer.dependenciesMeta != null && !isEmpty(importer.dependenciesMeta)) {
normalizedImporter['dependenciesMeta'] = importer.dependenciesMeta
}
for (const depType of DEPENDENCIES_FIELDS) {
if (!isEmpty(importer[depType] ?? {})) {
normalizedImporter[depType] = importer[depType]
}
}
if (importer.publishDirectory) {
normalizedImporter.publishDirectory = importer.publishDirectory
}
return normalizedImporter as ProjectSnapshot
}, lockfile.importers),
}
if (isEmpty(lockfileToSave.packages) || (lockfileToSave.packages == null)) {
delete lockfileToSave.packages
}
}
if (lockfileToSave.time) {
lockfileToSave.time = pruneTimeInLockfileV6(lockfileToSave.time, lockfile.importers)
}
if ((lockfileToSave.overrides != null) && isEmpty(lockfileToSave.overrides)) {
delete lockfileToSave.overrides
}
if ((lockfileToSave.patchedDependencies != null) && isEmpty(lockfileToSave.patchedDependencies)) {
delete lockfileToSave.patchedDependencies
}
if (lockfileToSave.neverBuiltDependencies != null) {
if (isEmpty(lockfileToSave.neverBuiltDependencies)) {
delete lockfileToSave.neverBuiltDependencies
} else {
lockfileToSave.neverBuiltDependencies = lockfileToSave.neverBuiltDependencies.sort()
}
}
if (lockfileToSave.onlyBuiltDependencies != null) {
lockfileToSave.onlyBuiltDependencies = lockfileToSave.onlyBuiltDependencies.sort()
}
if (!lockfileToSave.packageExtensionsChecksum) {
delete lockfileToSave.packageExtensionsChecksum
}
return lockfileToSave
}
function pruneTimeInLockfileV6 (time: Record<string, string>, importers: Record<string, ProjectSnapshot>): Record<string, string> {
const rootDepPaths = new Set<string>()
for (const importer of Object.values(importers)) {
for (const depType of DEPENDENCIES_FIELDS) {
for (let [depName, ref] of Object.entries(importer[depType] ?? {})) {
// @ts-expect-error
if (ref['version']) ref = ref['version']
const suffixStart = ref.indexOf('(')
const refWithoutPeerSuffix = suffixStart === -1 ? ref : ref.slice(0, suffixStart)
const depPath = refToRelative(refWithoutPeerSuffix, depName)
if (!depPath) continue
rootDepPaths.add(depPath)
}
}
}
return pickBy((_, depPath) => rootDepPaths.has(depPath), time)
}
function refToRelative (
reference: string,
pkgName: string
): string | null {
if (reference.startsWith('link:')) {
return null
}
if (reference.startsWith('file:')) {
return reference
}
if (!reference.includes('/') || !reference.replace(/(\([^)]+\))+$/, '').includes('/')) {
return `/${pkgName}@${reference}`
}
return reference
}
export async function writeLockfiles (
opts: {
forceSharedFormat?: boolean
@@ -219,15 +102,11 @@ export async function writeLockfiles (
const currentLockfilePath = path.join(opts.currentLockfileDir, 'lock.yaml')
const forceSharedFormat = opts?.forceSharedFormat === true
const isLockfileV6 = opts.wantedLockfile.lockfileVersion.toString().startsWith('6.')
const wantedLockfileToStringify = isLockfileV6
? convertToInlineSpecifiersFormat(opts.wantedLockfile) as unknown as Lockfile
: opts.wantedLockfile
const normalizeOpts = {
forceSharedFormat,
includeEmptySpecifiersField: !isLockfileV6,
}
const yamlDoc = yamlStringify(wantedLockfileToStringify, normalizeOpts)
const wantedLockfileToStringify = convertToLockfileFile(opts.wantedLockfile, normalizeOpts)
const yamlDoc = yamlStringify(wantedLockfileToStringify)
// in most cases the `pnpm-lock.yaml` and `node_modules/.pnpm-lock.yaml` are equal
// in those cases the YAML document can be stringified only once for both files
@@ -252,10 +131,8 @@ export async function writeLockfiles (
prefix: opts.wantedLockfileDir,
})
const currentLockfileToStringify = opts.wantedLockfile.lockfileVersion.toString().startsWith('6.')
? convertToInlineSpecifiersFormat(opts.currentLockfile) as unknown as Lockfile
: opts.currentLockfile
const currentYamlDoc = yamlStringify(currentLockfileToStringify, normalizeOpts)
const currentLockfileToStringify = convertToLockfileFile(opts.currentLockfile, normalizeOpts)
const currentYamlDoc = yamlStringify(currentLockfileToStringify)
await Promise.all([
writeFileAtomic(wantedLockfilePath, yamlDoc),

View File

@@ -1,6 +1,8 @@
lockfileVersion: 3
specifiers:
foo: '1'
lockfileVersion: '6.0'
dependencies:
foo:
version: '1.0.0'
specifier: '1'
dependenciesMeta:
foo:
injected: true

View File

@@ -1,6 +1,8 @@
lockfileVersion: 3
specifiers:
foo: '1'
lockfileVersion: '6.0'
dependencies:
foo:
version: '1.0.0'
specifier: '1'
dependenciesMeta:
foo:
injected: true

View File

@@ -1,9 +1,11 @@
lockfileVersion: 5.3
lockfileVersion: '6.0'
specifiers:
is-positive: '2.0.0'
dependencies:
is-positive:
version: '2.0.0'
specifier: '2.0.0'
packages:
/is-positive/2.0.0:
/is-positive@2.0.0:
resolution: {integrity: 'sha1-ChbBDewTLAqLCzb793Fo5VDvg/g='}

View File

@@ -1,8 +1,10 @@
lockfileVersion: 5.3
lockfileVersion: '6.0'
specifiers:
is-positive: '1.0.0'
dependencies:
is-positive:
version: '1.0.0'
specifier: '1.0.0'
packages:
/is-positive/1.0.0:
resolution: {integrity: 'sha1-ChbBDewTLAqLCzb793Fo5VDvg/g='}
/is-positive@1.0.0:
resolution: {integrity: 'sha1-ChbBDewTLAqLCzb793Fo5VDvg/g='}

View File

@@ -1,6 +1,6 @@
import { convertToInlineSpecifiersFormat, revertFromInlineSpecifiersFormat } from '../lib/experiments/inlineSpecifiersLockfileConverters'
import { convertToLockfileFile, convertToLockfileObject } from '../lib/lockfileFormatConverters'
test('convertToInlineSpecifiersFormat()', () => {
test('convertToLockfileFile()', () => {
const lockfileV5 = {
lockfileVersion: '6.0',
importers: {
@@ -81,11 +81,11 @@ test('convertToInlineSpecifiersFormat()', () => {
},
},
}
expect(convertToInlineSpecifiersFormat(lockfileV5)).toEqual(lockfileV6)
expect(revertFromInlineSpecifiersFormat(lockfileV6)).toEqual(lockfileV5)
expect(convertToLockfileFile(lockfileV5, { forceSharedFormat: false })).toEqual(lockfileV6)
expect(convertToLockfileObject(lockfileV6)).toEqual(lockfileV5)
})
test('convertToInlineSpecifiersFormat() with lockfile v6', () => {
test('convertToLockfileFile() with lockfile v6', () => {
const lockfileV5 = {
lockfileVersion: '6.0',
importers: {
@@ -166,6 +166,6 @@ test('convertToInlineSpecifiersFormat() with lockfile v6', () => {
},
},
}
expect(convertToInlineSpecifiersFormat(lockfileV5)).toEqual(lockfileV6)
expect(revertFromInlineSpecifiersFormat(lockfileV6)).toEqual(lockfileV5)
expect(convertToLockfileFile(lockfileV5, { forceSharedFormat: false })).toEqual(lockfileV6)
expect(convertToLockfileObject(lockfileV6)).toEqual(lockfileV5)
})

View File

@@ -1,8 +1,8 @@
import { LOCKFILE_VERSION } from '@pnpm/constants'
import { normalizeLockfile } from '../lib/write'
import { convertToLockfileFile } from '../lib/lockfileFormatConverters'
test('empty overrides and neverBuiltDependencies are removed during lockfile normalization', () => {
expect(normalizeLockfile({
expect(convertToLockfileFile({
lockfileVersion: LOCKFILE_VERSION,
// but this should be preserved.
onlyBuiltDependencies: [],
@@ -22,67 +22,24 @@ test('empty overrides and neverBuiltDependencies are removed during lockfile nor
},
}, {
forceSharedFormat: false,
includeEmptySpecifiersField: false,
})).toStrictEqual({
lockfileVersion: LOCKFILE_VERSION,
onlyBuiltDependencies: [],
importers: {
foo: {
dependencies: {
bar: 'link:../bar',
},
specifiers: {
bar: 'link:../bar',
bar: {
version: 'link:../bar',
specifier: 'link:../bar',
},
},
},
},
})
})
test('empty specifiers field is preserved', () => {
expect(normalizeLockfile({
lockfileVersion: LOCKFILE_VERSION,
packages: {},
importers: {
foo: {
specifiers: {},
},
},
}, {
forceSharedFormat: false,
includeEmptySpecifiersField: true,
})).toStrictEqual({
lockfileVersion: LOCKFILE_VERSION,
importers: {
foo: {
specifiers: {},
},
},
})
})
test('empty specifiers field is removed', () => {
expect(normalizeLockfile({
lockfileVersion: LOCKFILE_VERSION,
packages: {},
importers: {
foo: {
specifiers: {},
},
},
}, {
forceSharedFormat: false,
includeEmptySpecifiersField: false,
})).toStrictEqual({
lockfileVersion: LOCKFILE_VERSION,
importers: {
foo: {},
},
})
})
test('redundant fields are removed from "time"', () => {
expect(normalizeLockfile({
expect(convertToLockfileFile({
lockfileVersion: LOCKFILE_VERSION,
packages: {},
importers: {
@@ -111,24 +68,27 @@ test('redundant fields are removed from "time"', () => {
},
}, {
forceSharedFormat: false,
includeEmptySpecifiersField: false,
})).toStrictEqual({
lockfileVersion: LOCKFILE_VERSION,
importers: {
foo: {
dependencies: {
bar: '1.0.0',
bar: {
version: '1.0.0',
specifier: '1.0.0',
},
},
devDependencies: {
foo: '1.0.0(react@18.0.0)',
foo: {
version: '1.0.0(react@18.0.0)',
specifier: '1.0.0',
},
},
optionalDependencies: {
qar: '1.0.0',
},
specifiers: {
bar: '1.0.0',
foo: '1.0.0',
qar: '1.0.0',
qar: {
version: '1.0.0',
specifier: '1.0.0',
},
},
},
},

View File

@@ -18,9 +18,14 @@ test('readWantedLockfile()', async () => {
const lockfile = await readWantedLockfile(path.join('fixtures', '2'), {
ignoreIncompatible: false,
})
expect(lockfile?.lockfileVersion).toEqual(3)
expect(lockfile?.lockfileVersion).toEqual('6.0')
expect(lockfile?.importers).toStrictEqual({
'.': {
dependencies: {
foo: '1.0.0',
},
devDependencies: undefined,
optionalDependencies: undefined,
specifiers: {
foo: '1',
},
@@ -65,7 +70,7 @@ test('readCurrentLockfile()', async () => {
const lockfile = await readCurrentLockfile('fixtures/2/node_modules/.pnpm', {
ignoreIncompatible: false,
})
expect(lockfile!.lockfileVersion).toEqual(3)
expect(lockfile!.lockfileVersion).toEqual('6.0')
})
test('writeWantedLockfile()', async () => {
@@ -83,9 +88,9 @@ test('writeWantedLockfile()', async () => {
},
},
},
lockfileVersion: 3,
lockfileVersion: '6.0',
packages: {
'/is-negative/1.0.0': {
'/is-negative@1.0.0': {
dependencies: {
'is-positive': '2.0.0',
},
@@ -93,12 +98,12 @@ test('writeWantedLockfile()', async () => {
integrity: 'sha1-ChbBDewTLAqLCzb793Fo5VDvg/g=',
},
},
'/is-positive/1.0.0': {
'/is-positive@1.0.0': {
resolution: {
integrity: 'sha1-ChbBDewTLAqLCzb793Fo5VDvg/g=',
},
},
'/is-positive/2.0.0': {
'/is-positive@2.0.0': {
resolution: {
integrity: 'sha1-ChbBDewTLAqLCzb793Fo5VDvg/g=',
},
@@ -126,9 +131,9 @@ test('writeCurrentLockfile()', async () => {
},
},
},
lockfileVersion: 3,
lockfileVersion: '6.0',
packages: {
'/is-negative/1.0.0': {
'/is-negative@1.0.0': {
dependencies: {
'is-positive': '2.0.0',
},
@@ -136,12 +141,12 @@ test('writeCurrentLockfile()', async () => {
integrity: 'sha1-ChbBDewTLAqLCzb793Fo5VDvg/g=',
},
},
'/is-positive/1.0.0': {
'/is-positive@1.0.0': {
resolution: {
integrity: 'sha1-ChbBDewTLAqLCzb793Fo5VDvg/g=',
},
},
'/is-positive/2.0.0': {
'/is-positive@2.0.0': {
resolution: {
integrity: 'sha1-ChbBDewTLAqLCzb793Fo5VDvg/g=',
},
@@ -202,13 +207,16 @@ test('readWantedLockfile() when useGitBranchLockfile', async () => {
})
expect(lockfile?.importers).toEqual({
'.': {
dependencies: {
'is-positive': '1.0.0',
},
specifiers: {
'is-positive': '1.0.0',
},
},
})
expect(lockfile?.packages).toStrictEqual({
'/is-positive/1.0.0': {
'/is-positive@1.0.0': {
resolution: {
integrity: 'sha1-ChbBDewTLAqLCzb793Fo5VDvg/g=',
},
@@ -221,13 +229,16 @@ test('readWantedLockfile() when useGitBranchLockfile', async () => {
})
expect(gitBranchLockfile?.importers).toEqual({
'.': {
dependencies: {
'is-positive': '2.0.0',
},
specifiers: {
'is-positive': '2.0.0',
},
},
})
expect(gitBranchLockfile?.packages).toStrictEqual({
'/is-positive/2.0.0': {
'/is-positive@2.0.0': {
resolution: {
integrity: 'sha1-ChbBDewTLAqLCzb793Fo5VDvg/g=',
},
@@ -244,18 +255,21 @@ test('readWantedLockfile() when useGitBranchLockfile and mergeGitBranchLockfiles
})
expect(lockfile?.importers).toEqual({
'.': {
dependencies: {
'is-positive': '2.0.0',
},
specifiers: {
'is-positive': '2.0.0',
},
},
})
expect(lockfile?.packages).toStrictEqual({
'/is-positive/1.0.0': {
'/is-positive@1.0.0': {
resolution: {
integrity: 'sha1-ChbBDewTLAqLCzb793Fo5VDvg/g=',
},
},
'/is-positive/2.0.0': {
'/is-positive@2.0.0': {
resolution: {
integrity: 'sha1-ChbBDewTLAqLCzb793Fo5VDvg/g=',
},

View File

@@ -7,22 +7,26 @@ test('sorts keys alphabetically', () => {
importers: {
foo: {
dependencies: {
zzz: 'link:../zzz',
bar: 'link:../bar',
aaa: 'link:../aaa',
},
specifiers: {
zzz: 'link:../zzz',
bar: 'link:../bar',
aaa: 'link:../aaa',
zzz: {
version: 'link:../zzz',
specifier: 'link:../zzz',
},
bar: {
version: 'link:../bar',
specifier: 'link:../bar',
},
aaa: {
version: 'link:../aaa',
specifier: 'link:../aaa',
},
},
},
bar: {
specifiers: {
baz: 'link:../baz',
},
dependencies: {
baz: 'link:../baz',
baz: {
version: 'link:../baz',
specifier: 'link:../baz',
},
},
},
},
@@ -38,22 +42,26 @@ test('sorts keys alphabetically', () => {
importers: {
bar: {
dependencies: {
baz: 'link:../baz',
},
specifiers: {
baz: 'link:../baz',
baz: {
version: 'link:../baz',
specifier: 'link:../baz',
},
},
},
foo: {
dependencies: {
aaa: 'link:../aaa',
bar: 'link:../bar',
zzz: 'link:../zzz',
},
specifiers: {
aaa: 'link:../aaa',
bar: 'link:../bar',
zzz: 'link:../zzz',
aaa: {
version: 'link:../aaa',
specifier: 'link:../aaa',
},
bar: {
version: 'link:../bar',
specifier: 'link:../bar',
},
zzz: {
version: 'link:../zzz',
specifier: 'link:../zzz',
},
},
},
},
@@ -64,7 +72,6 @@ test('sorts keys alphabetically', () => {
},
})
expect(Object.keys(normalizedLockfile.importers?.foo.dependencies ?? {})).toStrictEqual(['aaa', 'bar', 'zzz'])
expect(Object.keys(normalizedLockfile.importers?.foo.specifiers ?? {})).toStrictEqual(['aaa', 'bar', 'zzz'])
expect(Object.keys(normalizedLockfile.patchedDependencies ?? {})).toStrictEqual(['aaa', 'bar', 'zzz'])
})
@@ -75,14 +82,18 @@ test('sorting does not care about locale (e.g. Czech has "ch" as a single charac
importers: {
foo: {
dependencies: {
bar: 'link:../bar',
href: 'link:../href',
chmod: 'link:../chmod',
},
specifiers: {
bar: 'link:../bar',
href: 'link:../href',
chmod: 'link:../chmod',
bar: {
version: 'link:../bar',
specifier: 'link:../bar',
},
href: {
version: 'link:../href',
specifier: 'link:../href',
},
chmod: {
version: 'link:../chmod',
specifier: 'link:../chmod',
},
},
},
},
@@ -94,18 +105,21 @@ test('sorting does not care about locale (e.g. Czech has "ch" as a single charac
importers: {
foo: {
dependencies: {
bar: 'link:../bar',
chmod: 'link:../chmod',
href: 'link:../href',
},
specifiers: {
bar: 'link:../bar',
chmod: 'link:../chmod',
href: 'link:../href',
bar: {
version: 'link:../bar',
specifier: 'link:../bar',
},
chmod: {
version: 'link:../chmod',
specifier: 'link:../chmod',
},
href: {
version: 'link:../href',
specifier: 'link:../href',
},
},
},
},
})
expect(Object.keys(normalizedLockfile.importers?.foo.dependencies ?? {})).toStrictEqual(['bar', 'chmod', 'href'])
expect(Object.keys(normalizedLockfile.importers?.foo.specifiers ?? {})).toStrictEqual(['bar', 'chmod', 'href'])
})

View File

@@ -2,6 +2,8 @@ import { type DependenciesMeta, type PatchFile } from '@pnpm/types'
export type { PatchFile }
export * from './lockfileFileTypes'
export interface LockfileSettings {
autoInstallPeers?: boolean
excludeLinksFromLockfile?: boolean
@@ -29,27 +31,6 @@ export interface ProjectSnapshot {
publishDirectory?: string
}
export interface LockfileV6 {
importers: Record<string, ProjectSnapshotV6>
lockfileVersion: number | string
time?: Record<string, string>
packages?: PackageSnapshots
neverBuiltDependencies?: string[]
onlyBuiltDependencies?: string[]
overrides?: Record<string, string>
packageExtensionsChecksum?: string
patchedDependencies?: Record<string, PatchFile>
settings?: LockfileSettings
}
export interface ProjectSnapshotV6 {
dependencies?: ResolvedDependenciesOfImporters
optionalDependencies?: ResolvedDependenciesOfImporters
devDependencies?: ResolvedDependenciesOfImporters
dependenciesMeta?: DependenciesMeta
publishDirectory?: string
}
export type ResolvedDependenciesOfImporters = Record<string, { version: string, specifier: string }>
export interface PackageSnapshots {

View File

@@ -1,7 +1,9 @@
import type { Lockfile } from '@pnpm/lockfile-types'
import type { Lockfile, ProjectSnapshot } from '.'
import type { DependenciesMeta } from '@pnpm/types'
export const INLINE_SPECIFIERS_FORMAT_LOCKFILE_VERSION_SUFFIX = '-inlineSpecifiers'
export type LockfileFile = Omit<InlineSpecifiersLockfile, 'importers'> &
Partial<InlineSpecifiersProjectSnapshot> &
Partial<Pick<InlineSpecifiersLockfile, 'importers'>>
/**
* Similar to the current Lockfile importers format (lockfile version 5.4 at
@@ -13,7 +15,7 @@ export const INLINE_SPECIFIERS_FORMAT_LOCKFILE_VERSION_SUFFIX = '-inlineSpecifie
*/
export interface InlineSpecifiersLockfile extends Omit<Lockfile, 'lockfileVersion' | 'importers'> {
lockfileVersion: string
importers: Record<string, InlineSpecifiersProjectSnapshot>
importers?: Record<string, InlineSpecifiersProjectSnapshot>
}
/**
@@ -21,7 +23,7 @@ export interface InlineSpecifiersLockfile extends Omit<Lockfile, 'lockfileVersio
* field in favor of inlining each specifier next to its version resolution in
* dependency blocks.
*/
export interface InlineSpecifiersProjectSnapshot {
export type InlineSpecifiersProjectSnapshot = Omit<ProjectSnapshot, 'dependencies' | 'devDependencies' | 'optionalDependencies' | 'dependenciesMeta' | 'specifiers'> & {
dependencies?: InlineSpecifiersResolvedDependencies
devDependencies?: InlineSpecifiersResolvedDependencies
optionalDependencies?: InlineSpecifiersResolvedDependencies

View File

@@ -2,7 +2,7 @@ import { type Lockfile, type PackageSnapshot, type PackageSnapshots } from '@pnp
import comverToSemver from 'comver-to-semver'
import semver from 'semver'
export function mergeLockfileChanges (ours: Lockfile, theirs: Lockfile) {
export function mergeLockfileChanges (ours: Lockfile, theirs: Lockfile): Lockfile {
const newLockfile: Lockfile = {
importers: {},
lockfileVersion: semver.gt(comverToSemver(theirs.lockfileVersion.toString()), comverToSemver(ours.lockfileVersion.toString()))

View File

@@ -8,7 +8,7 @@ import {
type MutatedProject,
type ProjectOptions,
} from '@pnpm/core'
import { type Lockfile, type LockfileV6 } from '@pnpm/lockfile-types'
import { type Lockfile, type LockfileFile } from '@pnpm/lockfile-types'
import { prepareEmpty, preparePackages, tempDir } from '@pnpm/prepare'
import { addDistTag } from '@pnpm/registry-mock'
import { fixtures } from '@pnpm/test-fixtures'
@@ -78,9 +78,9 @@ test('links are not added to the lockfile when excludeLinksFromLockfile is true'
},
]
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()
const lockfile: LockfileFile = 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/index.js'))).toBeTruthy()
expect(fs.existsSync(path.resolve('project-2/node_modules/external-2/index.js'))).toBeTruthy()
@@ -90,8 +90,8 @@ test('links are not added to the lockfile when excludeLinksFromLockfile is true'
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(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/index.js'))).toBeTruthy()
expect(fs.existsSync(path.resolve('project-2/node_modules/external-2/index.js'))).toBeTruthy()
@@ -101,8 +101,8 @@ test('links are not added to the lockfile when excludeLinksFromLockfile is true'
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(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/index.js'))).toBeTruthy()
expect(fs.existsSync(path.resolve('project-2/node_modules/external-2/index.js'))).toBeTruthy()
@@ -110,9 +110,9 @@ test('links are not added to the lockfile when excludeLinksFromLockfile is true'
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(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/index.js'))).toBeTruthy()
// expect(fs.existsSync(path.resolve('project-2/node_modules/external-2'))).toBeFalsy() // Should we remove external links that are not in deps anymore?
@@ -301,8 +301,8 @@ test('links resolved from workspace protocol dependencies are not removed', asyn
workspacePackages,
}))
const lockfile: LockfileV6 = await readYamlFile(WANTED_LOCKFILE)
expect(lockfile.importers['project-1'].dependencies?.['project-2']).toStrictEqual({
const lockfile: LockfileFile = await readYamlFile(WANTED_LOCKFILE)
expect(lockfile.importers?.['project-1'].dependencies?.['project-2']).toStrictEqual({
specifier: 'workspace:*',
version: 'link:../project-2',
})

View File

@@ -5,7 +5,7 @@ import { type RootLog } from '@pnpm/core-loggers'
import { type PnpmError } from '@pnpm/error'
import { fixtures } from '@pnpm/test-fixtures'
import { type Lockfile, type TarballResolution } from '@pnpm/lockfile-file'
import { type LockfileV6 } from '@pnpm/lockfile-types'
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'
@@ -855,18 +855,18 @@ test('lockfile file has correct format when lockfile directory does not equal th
expect(modules.pendingBuilds.length).toBe(0)
{
const lockfile: LockfileV6 = await readYamlFile(WANTED_LOCKFILE)
const lockfile: LockfileFile = await 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.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.packages![id].dependencies).toHaveProperty(['@pnpm.e2e/dep-of-pkg-with-1-dep'])
expect(lockfile.packages![id].resolution).toHaveProperty(['integrity'])
@@ -889,16 +889,16 @@ test('lockfile file has correct format when lockfile directory does not equal th
}))
{
const lockfile = await readYamlFile<LockfileV6>(path.join('..', WANTED_LOCKFILE))
const lockfile = await 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.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.packages).toHaveProperty([id])
expect(lockfile.packages![id].dependencies).toBeTruthy()
@@ -977,9 +977,9 @@ test(`doing named installation when shared ${WANTED_LOCKFILE} exists already`, a
})
)
const currentLockfile = await readYamlFile<LockfileV6>(path.resolve('node_modules/.pnpm/lock.yaml'))
const currentLockfile = await readYamlFile<LockfileFile>(path.resolve('node_modules/.pnpm/lock.yaml'))
expect(Object.keys(currentLockfile['importers'])).toStrictEqual(['pkg2'])
expect(Object.keys(currentLockfile.importers ?? {})).toStrictEqual(['pkg2'])
await mutateModules(
[

View File

@@ -2,7 +2,7 @@ import { promises as fs } from 'fs'
import path from 'path'
import { type PnpmError } from '@pnpm/error'
import { readProjects } from '@pnpm/filter-workspace-packages'
import { type LockfileV6 as Lockfile } from '@pnpm/lockfile-types'
import { type LockfileFile } from '@pnpm/lockfile-types'
import { add, install, remove, update } from '@pnpm/plugin-commands-installation'
import { preparePackages } from '@pnpm/prepare'
import { addDistTag } from '@pnpm/registry-mock'
@@ -617,8 +617,8 @@ test('recursive install on workspace with custom lockfile-dir', async () => {
workspaceDir: process.cwd(),
})
const lockfile = await readYamlFile<Lockfile>(path.join(lockfileDir, 'pnpm-lock.yaml'))
expect(Object.keys(lockfile.importers)).toStrictEqual(['../project-1', '../project-2'])
const lockfile = await readYamlFile<LockfileFile>(path.join(lockfileDir, 'pnpm-lock.yaml'))
expect(Object.keys(lockfile.importers!)).toStrictEqual(['../project-1', '../project-2'])
})
test('recursive install in a monorepo with different modules directories', async () => {
@@ -725,8 +725,8 @@ test('prefer-workspace-package', async () => {
workspaceDir: process.cwd(),
})
const lockfile = await readYamlFile<Lockfile>(path.resolve('pnpm-lock.yaml'))
expect(lockfile.importers['project-1'].dependencies?.['@pnpm.e2e/foo'].version).toBe('link:../foo')
const lockfile = await readYamlFile<LockfileFile>(path.resolve('pnpm-lock.yaml'))
expect(lockfile.importers?.['project-1'].dependencies?.['@pnpm.e2e/foo'].version).toBe('link:../foo')
})
test('installing in monorepo with shared lockfile should work on virtual drives', async () => {

View File

@@ -3,7 +3,7 @@ import { promises as fs } from 'fs'
import path from 'path'
import { LOCKFILE_VERSION, WANTED_LOCKFILE } from '@pnpm/constants'
import { findWorkspacePackages } from '@pnpm/workspace.find-packages'
import { type LockfileV6 as Lockfile } from '@pnpm/lockfile-types'
import { type LockfileFile } from '@pnpm/lockfile-types'
import { readModulesManifest } from '@pnpm/modules-yaml'
import {
prepare,
@@ -635,9 +635,9 @@ test('shared-workspace-lockfile: installation with --link-workspace-packages lin
await execPnpm(['recursive', 'install'])
{
const lockfile = await readYamlFile<Lockfile>(WANTED_LOCKFILE)
expect(lockfile.importers.project!.dependencies!['is-positive'].version).toBe('2.0.0')
expect(lockfile.importers.project!.dependencies!.negative.version).toBe('/is-negative@1.0.0')
const lockfile = await readYamlFile<LockfileFile>(WANTED_LOCKFILE)
expect(lockfile.importers!.project!.dependencies!['is-positive'].version).toBe('2.0.0')
expect(lockfile.importers!.project!.dependencies!.negative.version).toBe('/is-negative@1.0.0')
}
await projects['is-positive'].writePackageJson({
@@ -653,9 +653,9 @@ test('shared-workspace-lockfile: installation with --link-workspace-packages lin
await execPnpm(['recursive', 'install'])
{
const lockfile = await readYamlFile<Lockfile>(WANTED_LOCKFILE)
expect(lockfile.importers.project!.dependencies!['is-positive'].version).toBe('link:../is-positive')
expect(lockfile.importers.project!.dependencies!.negative.version).toBe('link:../is-negative')
const lockfile = await readYamlFile<LockfileFile>(WANTED_LOCKFILE)
expect(lockfile.importers!.project!.dependencies!['is-positive'].version).toBe('link:../is-positive')
expect(lockfile.importers!.project!.dependencies!.negative.version).toBe('link:../is-negative')
}
})
@@ -710,8 +710,8 @@ test('recursive install with link-workspace-packages and shared-workspace-lockfi
expect(projects['is-positive'].requireModule('is-negative')).toBeTruthy()
expect(projects['project-1'].requireModule('is-positive/package.json').author).toBeFalsy()
const sharedLockfile = await readYamlFile<Lockfile>(WANTED_LOCKFILE)
expect(sharedLockfile.importers['project-1']!.devDependencies!['is-positive'].version).toBe('link:../is-positive')
const sharedLockfile = await readYamlFile<LockfileFile>(WANTED_LOCKFILE)
expect(sharedLockfile.importers!['project-1']!.devDependencies!['is-positive'].version).toBe('link:../is-positive')
expect(server.getLines()).toStrictEqual(['is-positive', 'project-1'])
@@ -860,7 +860,7 @@ test('recursive installation with shared-workspace-lockfile and a readPackage ho
await execPnpm(['recursive', 'install', '--shared-workspace-lockfile', '--store-dir', 'store'])
const lockfile = await readYamlFile<Lockfile>(`./${WANTED_LOCKFILE}`)
const lockfile = await readYamlFile<LockfileFile>(`./${WANTED_LOCKFILE}`)
expect(lockfile.packages).toHaveProperty(['/@pnpm.e2e/dep-of-pkg-with-1-dep@100.1.0'])
await execPnpm(['recursive', 'install', '--shared-workspace-lockfile', '--store-dir', 'store', '--filter', 'project-1'])
@@ -909,7 +909,7 @@ test('shared-workspace-lockfile: create shared lockfile format when installation
await execPnpm(['install', '--store-dir', 'store'])
const lockfile = await readYamlFile<Lockfile>(WANTED_LOCKFILE)
const lockfile = await readYamlFile<LockfileFile>(WANTED_LOCKFILE)
expect(lockfile.importers).toHaveProperty(['.'])
expect(lockfile.lockfileVersion).toBe(LOCKFILE_VERSION)
@@ -952,7 +952,7 @@ test("shared-workspace-lockfile: don't install dependencies in projects that are
await execPnpm(['recursive', 'install', '--store-dir', 'store', '--shared-workspace-lockfile', '--link-workspace-packages'])
const lockfile = await readYamlFile<Lockfile>(WANTED_LOCKFILE)
const lockfile = await readYamlFile<LockfileFile>(WANTED_LOCKFILE)
expect(lockfile).toStrictEqual({
settings: {
@@ -1033,7 +1033,7 @@ test('shared-workspace-lockfile: install dependencies in projects that are relat
await execPnpm(['-r', 'install', '--store-dir', 'store', '--shared-workspace-lockfile', '--link-workspace-packages', '--no-save-workspace-protocol'])
const lockfile = await readYamlFile<Lockfile>(WANTED_LOCKFILE)
const lockfile = await readYamlFile<LockfileFile>(WANTED_LOCKFILE)
expect(lockfile).toStrictEqual({
settings: {
@@ -1123,8 +1123,8 @@ test('shared-workspace-lockfile: entries of removed projects should be removed f
await execPnpm(['recursive', 'install', '--store-dir', 'store', '--shared-workspace-lockfile', '--link-workspace-packages'])
{
const lockfile = await readYamlFile<Lockfile>(WANTED_LOCKFILE)
expect(Object.keys(lockfile.importers)).toStrictEqual(['package-1', 'package-2'])
const lockfile = await readYamlFile<LockfileFile>(WANTED_LOCKFILE)
expect(Object.keys(lockfile.importers!)).toStrictEqual(['package-1', 'package-2'])
}
await rimraf('package-2')
@@ -1132,8 +1132,8 @@ test('shared-workspace-lockfile: entries of removed projects should be removed f
await execPnpm(['recursive', 'install', '--store-dir', 'store', '--shared-workspace-lockfile', '--link-workspace-packages'])
{
const lockfile = await readYamlFile<Lockfile>(WANTED_LOCKFILE)
expect(Object.keys(lockfile.importers)).toStrictEqual(['package-1'])
const lockfile = await readYamlFile<LockfileFile>(WANTED_LOCKFILE)
expect(Object.keys(lockfile.importers!)).toStrictEqual(['package-1'])
}
})
@@ -1196,7 +1196,7 @@ test('shared-workspace-lockfile: removing a package recursively', async () => {
expect(pkg.dependencies).toStrictEqual({ 'is-negative': '1.0.0' }) // is-positive removed from project2')
}
const lockfile = await readYamlFile<Lockfile>(WANTED_LOCKFILE)
const lockfile = await readYamlFile<LockfileFile>(WANTED_LOCKFILE)
expect(Object.keys(lockfile.packages ?? {})).toStrictEqual(['/is-negative@1.0.0']) // is-positive removed from ${WANTED_LOCKFILE}
})
@@ -1228,8 +1228,8 @@ auto-install-peers=false`, 'utf8')
await execPnpm(['install', 'ajv@4.10.4', 'ajv-keywords@1.5.0'])
{
const lockfile = await readYamlFile<Lockfile>(path.resolve('..', WANTED_LOCKFILE))
expect(lockfile.importers.foo).toStrictEqual({
const lockfile = await readYamlFile<LockfileFile>(path.resolve('..', WANTED_LOCKFILE))
expect(lockfile.importers!.foo).toStrictEqual({
dependencies: {
ajv: {
specifier: '4.10.4',
@@ -1250,8 +1250,8 @@ auto-install-peers=false`, 'utf8')
await execPnpm(['uninstall', 'ajv', '--no-strict-peer-dependencies'])
{
const lockfile = await readYamlFile<Lockfile>(path.resolve('..', WANTED_LOCKFILE))
expect(lockfile.importers.foo).toStrictEqual({
const lockfile = await readYamlFile<LockfileFile>(path.resolve('..', WANTED_LOCKFILE))
expect(lockfile.importers!.foo).toStrictEqual({
dependencies: {
'ajv-keywords': {
specifier: '1.5.0',

View File

@@ -1,7 +1,7 @@
import fs from 'fs'
import path from 'path'
import { assertStore } from '@pnpm/assert-store'
import { type LockfileV6 as Lockfile } from '@pnpm/lockfile-file'
import { type LockfileFile } from '@pnpm/lockfile-file'
import { store } from '@pnpm/plugin-commands-store'
import { prepare } from '@pnpm/prepare'
import { REGISTRY_MOCK_PORT } from '@pnpm/registry-mock'
@@ -136,7 +136,7 @@ test('keep dependencies used by others', async () => {
await project.hasNot('map-obj')
// all dependencies are marked as dev
const lockfile = await project.readLockfile() as Lockfile
const lockfile = await project.readLockfile() as LockfileFile
expect(isEmpty(lockfile.packages)).toBeFalsy()
Object.entries(lockfile.packages ?? {}).forEach(([_, dep]) => {