diff --git a/packages/nx/src/plugins/js/lock-file/pnpm-parser.ts b/packages/nx/src/plugins/js/lock-file/pnpm-parser.ts index 6eda10f86440c0..428f94062c614c 100644 --- a/packages/nx/src/plugins/js/lock-file/pnpm-parser.ts +++ b/packages/nx/src/plugins/js/lock-file/pnpm-parser.ts @@ -6,6 +6,7 @@ import type { PackageSnapshots, } from '@pnpm/lockfile-types'; import { + isV5Lockfile, loadPnpmHoistedDepsDefinition, parseAndNormalizePnpmLockfile, stringifyToPnpmYaml, @@ -40,7 +41,7 @@ function addNodes( keyMap: Map ) { const nodes: Map> = new Map(); - const hasV5Separator = data.lockfileVersion.toString().startsWith('5'); + const hasV5Separator = isV5Lockfile(data); Object.entries(data.packages).forEach(([key, snapshot]) => { const packageName = findPackageName(key, snapshot, data, hasV5Separator); @@ -127,7 +128,7 @@ function addDependencies( builder: ProjectGraphBuilder, keyMap: Map ) { - const hasV5Separator = data.lockfileVersion.toString().startsWith('5'); + const hasV5Separator = isV5Lockfile(data); Object.entries(data.packages).forEach(([key, snapshot]) => { const node = keyMap.get(key); [snapshot.dependencies, snapshot.optionalDependencies].forEach( @@ -158,10 +159,10 @@ export function stringifyPnpmLockfile( packageJson: NormalizedPackageJson ): string { const data = parseAndNormalizePnpmLockfile(rootLockFileContent); - const hasV5Separator = data.lockfileVersion.toString().startsWith('5'); + const hasV5Separator = isV5Lockfile(data); const output: Lockfile | LockfileV6 = { - lockfileVersion: data.lockfileVersion, + lockfileVersion: normalizeLockfileVersion(data.lockfileVersion), importers: { '.': mapRootSnapshot( packageJson, @@ -178,6 +179,13 @@ export function stringifyPnpmLockfile( return stringifyToPnpmYaml(output); } +function normalizeLockfileVersion(version: string | number) { + if (typeof version === 'string' || version !== Math.floor(version)) { + return version; + } + return version.toFixed(1); +} + function mapSnapshots( packages: PackageSnapshots, hasV5Separator: boolean, @@ -349,9 +357,6 @@ function findPackageName( } // otherwise, it's a standard package if (key.startsWith('/')) { - if (data.lockfileVersion.toString().startsWith('6')) { - return key.slice(1, key.indexOf('@', 2)); - } if (hasV5Separator) { return key.slice(1, key.lastIndexOf('/')); } else { diff --git a/packages/nx/src/plugins/js/lock-file/utils/pnpm-normalizer.ts b/packages/nx/src/plugins/js/lock-file/utils/pnpm-normalizer.ts index 1abb4712e423d7..4d3ff631681eff 100644 --- a/packages/nx/src/plugins/js/lock-file/utils/pnpm-normalizer.ts +++ b/packages/nx/src/plugins/js/lock-file/utils/pnpm-normalizer.ts @@ -39,8 +39,12 @@ const ROOT_KEYS_ORDER = { packages: 16, }; +export function isV5Lockfile(data: InlineSpecifiersLockfile | Lockfile) { + return data.lockfileVersion.toString().startsWith('5.'); +} + export function stringifyToPnpmYaml(lockfile: Lockfile): string { - const isLockfileV6 = lockfile.lockfileVersion.toString().startsWith('6.'); + const isLockfileV6 = !isV5Lockfile(lockfile); const adaptedLockfile = isLockfileV6 ? convertToInlineSpecifiersFormat(lockfile) : lockfile; @@ -152,7 +156,7 @@ function isInlineSpecifierLockfile( ): lockfile is InlineSpecifiersLockfile { const { lockfileVersion } = lockfile; return ( - lockfileVersion.toString().startsWith('6') || + !isV5Lockfile(lockfile) || (typeof lockfileVersion === 'string' && lockfileVersion.endsWith( INLINE_SPECIFIERS_FORMAT_LOCKFILE_VERSION_SUFFIX @@ -161,10 +165,10 @@ function isInlineSpecifierLockfile( } function revertFromInlineSpecifiersFormatIfNecessary( - lockFile: InlineSpecifiersLockfile | Lockfile + lockfile: InlineSpecifiersLockfile | Lockfile ): Lockfile { - if (isInlineSpecifierLockfile(lockFile)) { - const { lockfileVersion, importers, ...rest } = lockFile; + if (isInlineSpecifierLockfile(lockfile)) { + const { lockfileVersion, importers, ...rest } = lockfile; const originalVersionStr = lockfileVersion.replace( INLINE_SPECIFIERS_FORMAT_LOCKFILE_VERSION_SUFFIX, @@ -177,18 +181,95 @@ function revertFromInlineSpecifiersFormatIfNecessary( ); } - const mappedImporters: Record = {}; - Object.entries(importers).forEach(([key, value]) => { - mappedImporters[key] = revertProjectSnapshot(value); - }); - - return { + let revertedImporters = mapValues(importers, revertProjectSnapshot); + let packages = lockfile.packages; + if (originalVersion === 6) { + revertedImporters = Object.fromEntries( + Object.entries(revertedImporters ?? {}).map( + ([importerId, pkgSnapshot]: [string, ProjectSnapshot]) => { + const newSnapshot = { ...pkgSnapshot }; + if (newSnapshot.dependencies != null) { + newSnapshot.dependencies = mapValues( + newSnapshot.dependencies, + convertNewRefToOldRef + ); + } + if (newSnapshot.optionalDependencies != null) { + newSnapshot.optionalDependencies = mapValues( + newSnapshot.optionalDependencies, + convertNewRefToOldRef + ); + } + if (newSnapshot.devDependencies != null) { + newSnapshot.devDependencies = mapValues( + newSnapshot.devDependencies, + convertNewRefToOldRef + ); + } + return [importerId, newSnapshot]; + } + ) + ); + packages = Object.fromEntries( + Object.entries(lockfile.packages ?? {}).map( + ([depPath, pkgSnapshot]) => { + const newSnapshot = { ...pkgSnapshot }; + if (newSnapshot.dependencies != null) { + newSnapshot.dependencies = mapValues( + newSnapshot.dependencies, + convertNewRefToOldRef + ); + } + if (newSnapshot.optionalDependencies != null) { + newSnapshot.optionalDependencies = mapValues( + newSnapshot.optionalDependencies, + convertNewRefToOldRef + ); + } + return [convertNewDepPathToOldDepPath(depPath), newSnapshot]; + } + ) + ); + } + const newLockfile = { ...rest, - lockfileVersion: originalVersion, - importers: mappedImporters, + lockfileVersion: lockfileVersion.endsWith( + INLINE_SPECIFIERS_FORMAT_LOCKFILE_VERSION_SUFFIX + ) + ? originalVersion + : lockfileVersion, + packages, + importers: revertedImporters, }; + if (originalVersion === 6 && newLockfile.time) { + newLockfile.time = Object.fromEntries( + Object.entries(newLockfile.time).map(([depPath, time]) => [ + convertNewDepPathToOldDepPath(depPath), + time, + ]) + ); + } + return newLockfile; + } + return lockfile; +} + +function convertNewDepPathToOldDepPath(oldDepPath: string) { + if (!oldDepPath.includes('@', 2)) return oldDepPath; + const index = oldDepPath.indexOf('@', oldDepPath.indexOf('/@') + 2); + if (oldDepPath.includes('(') && index > oldDepPath.indexOf('(')) + return oldDepPath; + return `${oldDepPath.substring(0, index)}/${oldDepPath.substring(index + 1)}`; +} + +function convertNewRefToOldRef(oldRef: string) { + if (oldRef.startsWith('link:') || oldRef.startsWith('file:')) { + return oldRef; + } + if (oldRef.includes('@')) { + return convertNewDepPathToOldDepPath(oldRef); } - return lockFile; + return oldRef; } function revertProjectSnapshot( @@ -394,7 +475,7 @@ function convertToInlineSpecifiersFormat( ): InlineSpecifiersLockfile { let importers = lockfile.importers; let packages = lockfile.packages; - if (lockfile.lockfileVersion.toString().startsWith('6.')) { + if (!isV5Lockfile(lockfile)) { importers = Object.fromEntries( Object.entries(lockfile.importers ?? {}).map( ([importerId, pkgSnapshot]: [string, ProjectSnapshot]) => { @@ -443,7 +524,7 @@ function convertToInlineSpecifiersFormat( const newLockfile = { ...lockfile, packages, - lockfileVersion: lockfile.lockfileVersion.toString().startsWith('6.') + lockfileVersion: !isV5Lockfile(lockfile) ? lockfile.lockfileVersion.toString() : lockfile.lockfileVersion .toString() @@ -455,10 +536,7 @@ function convertToInlineSpecifiersFormat( convertProjectSnapshotToInlineSpecifiersFormat ), }; - if ( - lockfile.lockfileVersion.toString().startsWith('6.') && - newLockfile.time - ) { + if (!isV5Lockfile(lockfile) && newLockfile.time) { newLockfile.time = Object.fromEntries( Object.entries(newLockfile.time).map(([depPath, time]) => [ convertOldDepPathToNewDepPath(depPath),