mirror of
https://github.com/pnpm/pnpm.git
synced 2026-07-03 04:15:12 -04:00
* feat: skip lockfile writes for legacy packageManager field When pnpm is pinned via the `packageManager` field in `package.json`, the resolved pnpm integrity info is no longer written to `pnpm-lock.yaml` unless the pinned version is pnpm v12 or newer. `devEngines.packageManager` still populates and reuses `packageManagerDependencies` as before. This keeps the v10 -> v11 transition quiet by avoiding unrelated lockfile churn for projects that pin pnpm the legacy way. * fix: address Copilot review and CI failure - Update `configurationalDependencies.test.ts` to assert the new behavior: the `packageManager` field no longer writes pnpm resolution info to the env lockfile while config dependencies still are. - Fast-path in `switchCliVersion`: when the lockfile is not persisted and the running CLI already matches `pm.version`, skip store access and integrity resolution entirely. - Clarify the `resolvePackageManagerIntegrities` docstring to describe the conditional `save` behavior. * test: add unit tests for shouldPersistLockfile Extract the decision logic for persisting pnpm resolution info to the env lockfile into a dedicated helper so the branches — devEngines source, legacy `packageManager` field with v11 or older, v12+, and invalid/missing version — can all be covered without needing an actual pnpm v12 tarball on the registry.
101 lines
3.6 KiB
TypeScript
101 lines
3.6 KiB
TypeScript
import { convertToLockfileFile, createEnvLockfile, readEnvLockfile, writeEnvLockfile } from '@pnpm/lockfile.fs'
|
|
import { pruneSharedLockfile } from '@pnpm/lockfile.pruner'
|
|
import type { EnvLockfile } from '@pnpm/lockfile.types'
|
|
import type { StoreController } from '@pnpm/store.controller'
|
|
import type { DepPath, ProjectId, Registries } from '@pnpm/types'
|
|
|
|
import { convertToLockfileEnvObject } from './pruneEnvLockfile.js'
|
|
import { resolveManifestDependencies } from './resolveManifestDependencies.js'
|
|
|
|
export interface ResolvePackageManagerIntegritiesOpts {
|
|
envLockfile?: EnvLockfile
|
|
registries: Registries
|
|
rootDir: string
|
|
storeController: StoreController
|
|
storeDir: string
|
|
/**
|
|
* Whether to read from and write to the env lockfile file on disk.
|
|
* When false, resolution happens purely in memory; callers can still use
|
|
* the returned `EnvLockfile` to perform installs without persisting the
|
|
* resolved pnpm integrity info. Defaults to true.
|
|
*/
|
|
save?: boolean
|
|
}
|
|
|
|
/**
|
|
* Checks if the wanted pnpm version integrities are already fully resolved in the env lockfile.
|
|
*/
|
|
export function isPackageManagerResolved (
|
|
envLockfile: EnvLockfile | undefined,
|
|
pnpmVersion: string
|
|
): boolean {
|
|
if (!envLockfile) return false
|
|
|
|
const pmDeps = envLockfile.importers['.'].packageManagerDependencies
|
|
return pmDeps != null &&
|
|
pmDeps['pnpm']?.version === pnpmVersion &&
|
|
pmDeps['@pnpm/exe']?.version === pnpmVersion
|
|
}
|
|
|
|
/**
|
|
* Resolves integrity checksums for `pnpm`, `@pnpm/exe`, and their dependencies
|
|
* by calling resolveManifestDependencies. When `opts.save` is true (the
|
|
* default) the results are written to the `packageManagerDependencies`
|
|
* section of `pnpm-lock.yaml`; when false, resolution happens purely in
|
|
* memory and the returned `EnvLockfile` is never persisted to disk.
|
|
*/
|
|
export async function resolvePackageManagerIntegrities (
|
|
pnpmVersion: string,
|
|
opts: ResolvePackageManagerIntegritiesOpts
|
|
): Promise<EnvLockfile> {
|
|
const save = opts.save ?? true
|
|
const envLockfile = opts.envLockfile ?? (save ? await readEnvLockfile(opts.rootDir) : undefined) ?? createEnvLockfile()
|
|
|
|
if (isPackageManagerResolved(envLockfile, pnpmVersion)) {
|
|
return envLockfile
|
|
}
|
|
|
|
const lockfile = await resolveManifestDependencies(
|
|
{
|
|
dependencies: {
|
|
'pnpm': pnpmVersion,
|
|
'@pnpm/exe': pnpmVersion,
|
|
},
|
|
},
|
|
{
|
|
dir: opts.rootDir,
|
|
registries: opts.registries,
|
|
storeController: opts.storeController,
|
|
storeDir: opts.storeDir,
|
|
}
|
|
)
|
|
|
|
if (lockfile.packages) {
|
|
// Build packageManagerDependencies from the resolved lockfile importers
|
|
const importer = lockfile.importers['.' as ProjectId]
|
|
const packageManagerDependencies: Record<string, { specifier: string, version: string }> = {}
|
|
for (const [name, version] of Object.entries(importer.dependencies ?? {})) {
|
|
packageManagerDependencies[name] = {
|
|
specifier: importer.specifiers[name],
|
|
version,
|
|
}
|
|
}
|
|
envLockfile.importers['.'].packageManagerDependencies = packageManagerDependencies
|
|
|
|
// Merge new packages into the env lockfile object, then prune stale entries
|
|
const merged = convertToLockfileEnvObject(envLockfile)
|
|
for (const [depPath, pkg] of Object.entries(lockfile.packages)) {
|
|
merged.packages![depPath as DepPath] = pkg
|
|
}
|
|
const pruned = pruneSharedLockfile(merged)
|
|
const prunedFile = convertToLockfileFile(pruned)
|
|
envLockfile.packages = prunedFile.packages ?? {}
|
|
envLockfile.snapshots = prunedFile.snapshots ?? {}
|
|
|
|
if (save) {
|
|
await writeEnvLockfile(opts.rootDir, envLockfile)
|
|
}
|
|
}
|
|
return envLockfile
|
|
}
|