Visitar URL original
v11 main: injected freshness checker rejects a fresh aliased file: child (after #17065) · Issue #17130 · pnpm/pnpm · GitHub
Skip to content

v11 main: injected freshness checker rejects a fresh aliased file: child (after #17065) #17130

Closed

Description

@baymer

Verify latest release

  • I verified that the issue exists in the latest pnpm release

pnpm version

v11 main d37c6d9, after #17065; checker-level reproduction, not whole-main CLI.

Which area(s) of pnpm are affected? (leave empty if unsure)

No response

Link to the code that reproduces this issue or a replay of the bug

No response

Reproduction steps

This is a checker-level reproduction on main, not a claim that the published pnpm 12 CLI has this regression.

The workspace has packages: [app, a], sharedWorkspaceLockfile: false, injectWorkspacePackages: true, dedupeInjectedDeps: false:

// app/package.json
{"name":"app","dependencies":{"a":"workspace:*"}}
// a/package.json
{"name":"a","version":"1.0.0","dependencies":{"renamed":"file:../b"}}
// b/package.json
{"name":"b","version":"1.0.0"}

Official pnpm 11.28.6 successfully installs this entirely local/offline layout and writes these relevant entries to app/pnpm-lock.yaml:

packages:
  a@file:../a:
    resolution: {directory: ../a, type: directory}
  b@file:../b:
    resolution: {directory: ../b, type: directory}
snapshots:
  a@file:../a:
    dependencies:
      renamed: b@file:../b
  b@file:../b: {}

With lockfileDir = /app, merging packages and snapshots entries as v11 does, localDirectoryDepIsUpToDate returns false for a@file:../a although no manifests changed. Replacing only the recorded child ref with file:../b makes it return true. This replacement is a diagnostic control, not a proposed workaround.

Equivalent focused regression test for pnpm11/lockfile/verification/test:

import { mkdir, mkdtemp, writeFile } from 'node:fs/promises'
import os from 'node:os'
import path from 'node:path'
import { localDirectoryDepIsUpToDate } from '../src/localDirectoryDepIsUpToDate.js'

test('accepts a fresh aliased file child of a local directory dependency', async () => {
  const root = await mkdtemp(path.join(os.tmpdir(), 'pnpm-aliased-child-'))
  await mkdir(path.join(root, 'a'))
  await mkdir(path.join(root, 'b'))
  await writeFile(path.join(root, 'a/package.json'), JSON.stringify({
    name: 'a', version: '1.0.0', dependencies: { renamed: 'file:../b' },
  }))
  await writeFile(path.join(root, 'b/package.json'), JSON.stringify({ name: 'b', version: '1.0.0' }))
  const actual = await localDirectoryDepIsUpToDate({
    lockfileDir: root,
    workspaceDir: root,
    manifestsByDir: {},
    resolution: { autoInstallPeers: true, frozenLockfile: true },
  }, {
    resolution: { directory: 'a', type: 'directory' },
    dependencies: { renamed: 'b@file:b' },
  })
  expect(actual).toBe(true) // current checker returns false
})

The checker-level calls above were executed. This Jest snippet expresses the same case; I have not run the repository's full Jest/CLI suite.

Describe the Bug

I executed localDirectoryDepIsUpToDate from main commit d37c6d9 (same checker as merged #17065) against a real lockfile produced by official pnpm 11.28.6.

The source harness strips TypeScript types and bridges shared parsing/I/O dependencies to the official 11.28.6 bundle; the checker, dependency-spec helpers, peer-set and newly extracted resolver rules come from the pinned main sources. This is not a full build/run of the main CLI.

localSpecIsUpToDate removes peer suffixes but does not unwrap the name prefix before getLocalPath:

async function localSpecIsUpToDate (
localDep: LocalDirectoryDep,
{ depName, currentSpec, lockfileDep }: DependencyOfLocalDirectoryDep
): Promise<boolean> {
if (currentSpec.startsWith('link:')) {
return (
lockfileDep.startsWith('link:') &&
resolveSpecPath(localDep.localDepDir, currentSpec.slice(5)) === resolveSpecPath(localDep.lockfileDir, lockfileDep.slice(5))
)
}
const cleanLockfileDep = removeSuffix(lockfileDep)
const lockfilePath = getLocalPath(cleanLockfileDep)
if (currentSpec.startsWith('file:')) {
return localPathMatches(localDep, currentSpec.slice(5), lockfilePath)
}
return workspaceDependencyIsUpToDate(localDep, { depName, currentSpec, cleanLockfileDep, lockfilePath })

export function getLocalPath (lockfileDep: string): string | null {
return lockfileDep.startsWith('link:') || lockfileDep.startsWith('file:')
? lockfileDep.slice(5)
: null
}

getLocalPath('b@file:b') returns null because only strings starting with file: or link: are accepted. Path equality therefore fails for a canonical alias ref that pnpm itself writes. The direct importer check already recognizes name@file: refs; this is the child inside an injected/directory package.

Related: #17008, #17065. Main now enables this comparison on frozen installs, so the false verdict can surface as ERR_PNPM_OUTDATED_LOCKFILE. No pnpm 12 regression is claimed.

Environment: macOS 26.7.1 arm64, Node.js 26.2.0. Fixture generated by unmodified official pnpm 11.28.6. Analysis and source-level reproduction prepared and executed with Codex.

Expected Behavior

A freshly generated lockfile should pass this check. Parse/normalize an aliased local ref with package identity retained before comparing its path; peer/patch suffixes should continue to be handled correctly.

Which Node.js version are you using?

26.2.0

Which operating systems have you used?

  • macOS
  • Windows
  • Linux

If your OS is a Linux based, which one it is? (Include the version if relevant)

No response

Contributing a fix

  • I am working on a fix

Activity

  1. added theissue type on Oct 19, 2026
  2. added a commit that references this issue on Oct 20, 2026
    d5a07e0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions