Files
pnpm/pnpm11/workspace/state/test/updateWorkspaceState.test.ts
758b2cc3e7 fix(workspace-state): retry the v11 state-file rename on Windows (#14670)
pnpm/pnpm#15550 made the workspace-state write non-fatal and gave
pacquet's rename the Windows lock retry. pnpm v11 still wrote the file
through write-file-atomic, whose rename does not retry, so a concurrent
holder of .pnpm-workspace-state-v1.json on Windows still lost the write.

v11 now writes a path-temp file beside the state file and renames it
with renameFileWithRetryAsync, as the lockfile writer does. The temp file
is removed when the write fails. write-file-atomic's fsync and same-path
serialization go with it, matching pacquet's writer.

pacquet gains a Windows test that holds the state file open and asserts
the write waits the lock out, and checks that no temp file is left
beside the state file.

Refs pnpm/pnpm#14550

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 01:05:38 +02:00

52 lines
1.7 KiB
TypeScript

import fs from 'node:fs'
import path from 'node:path'
import { expect, test } from '@jest/globals'
import { prepareEmpty } from '@pnpm/prepare'
import { getFilePath } from '../src/filePath.js'
import { updateWorkspaceState, type UpdateWorkspaceStateOptions } from '../src/index.js'
test('updateWorkspaceState() leaves no temp file beside the state file', async () => {
prepareEmpty()
const workspaceDir = process.cwd()
await updateWorkspaceState(options(workspaceDir))
await updateWorkspaceState(options(workspaceDir))
expect(fs.existsSync(getFilePath(workspaceDir))).toBe(true)
expect(cacheDirEntries(workspaceDir)).toStrictEqual([path.basename(getFilePath(workspaceDir))])
})
// A rename onto a directory fails outright on Unix. On Windows the same setup
// reports EPERM, which is retried, so the test would spend the retry budget.
const testOnUnix = process.platform === 'win32' ? test.skip : test
testOnUnix('updateWorkspaceState() leaves no temp file when the write cannot land', async () => {
prepareEmpty()
const workspaceDir = process.cwd()
const cacheFile = getFilePath(workspaceDir)
fs.mkdirSync(cacheFile, { recursive: true })
await expect(updateWorkspaceState(options(workspaceDir))).resolves.toBeUndefined()
expect(cacheDirEntries(workspaceDir)).toStrictEqual([path.basename(cacheFile)])
})
function options (workspaceDir: string): UpdateWorkspaceStateOptions {
return {
allProjects: [],
settings: {
excludeLinksFromLockfile: false,
linkWorkspacePackages: false,
preferWorkspacePackages: false,
},
workspaceDir,
pnpmfiles: [],
filteredInstall: false,
}
}
function cacheDirEntries (workspaceDir: string): string[] {
return fs.readdirSync(path.dirname(getFilePath(workspaceDir))).sort()
}