Files
pnpm/lockfile/fs/test/gitBranchLockfile.test.ts
Abdullah Alaqeel 61969fbddf fix(deps-status): detect lockfile-only changes (#12106)
## Summary

Fixes `pnpm install` with `optimisticRepeatInstall` incorrectly returning `Already up to date` when `pnpm-lock.yaml` changed but project manifests did not.

Fixes #12100.

## Root Cause

`checkDepsStatus` used modified manifest mtimes as the only signal for whether it needed to validate dependency status. If no manifest was newer than `workspaceState.lastValidatedTimestamp`, it returned `upToDate: true` before checking whether the wanted lockfile had changed.

That skipped lockfile validation for workflows like:

- `git checkout HEAD~1 -- pnpm-lock.yaml`
- restoring only `pnpm-lock.yaml` from a stash
- external tools rewriting the lockfile without touching manifests

## Changes

- Check wanted lockfile mtimes before taking the optimistic fast path.
- If any wanted lockfile is missing or newer than the workspace state timestamp, validate all projects instead of only modified manifests.
- Add a regression test proving a lockfile-only change does not skip wanted-lockfile validation.
- Add a patch changeset for `@pnpm/deps.status` and `pnpm`.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-06-16 22:04:07 +00:00

43 lines
1.5 KiB
TypeScript

import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { expect, test } from '@jest/globals'
import { getGitBranchLockfileNames, getGitBranchLockfileNamesSync } from '../lib/gitBranchLockfile.js'
process.chdir(import.meta.dirname)
test('getGitBranchLockfileNames()', async () => {
const lockfileDir: string = path.join('fixtures', '6')
const gitBranchLockfileNames = await getGitBranchLockfileNames(lockfileDir)
expect(gitBranchLockfileNames).toEqual(['pnpm-lock.branch.yaml'])
})
test('getGitBranchLockfileNamesSync()', () => {
const lockfileDir: string = path.join('fixtures', '6')
expect(getGitBranchLockfileNamesSync(lockfileDir)).toEqual(['pnpm-lock.branch.yaml'])
})
test('git-branch lockfile matcher requires literal dots and a branch segment', () => {
const lockfileDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pnpm-git-branch-lockfile-'))
try {
for (const name of [
'pnpm-lock.main.yaml', // branch lockfile
'pnpm-lock.feature.x.yaml', // branch name containing a dot
'pnpm-lock.yaml', // base lockfile, not a branch lockfile
'pnpm-lock-main-yaml', // no literal dots
'my-pnpm-lock.main.yaml', // does not start at the beginning
'README.md',
]) {
fs.writeFileSync(path.join(lockfileDir, name), '')
}
expect(getGitBranchLockfileNamesSync(lockfileDir).sort()).toEqual([
'pnpm-lock.feature.x.yaml',
'pnpm-lock.main.yaml',
])
} finally {
fs.rmSync(lockfileDir, { force: true, recursive: true })
}
})