Files
pnpm/pnpm11/worker/test/tarballIntegrityError.test.ts
Zoltan Kochan 6d72710fe1 fix: warn when an optional dependency cannot be fetched (#16522)
An optional package whose tarball fetch failed (a 404, an integrity
mismatch) was dropped without a word. With fetch retries off nothing on
stdout or stderr said the package was missing, and no ndjson record
carried the skip. pnpm v12 also counted the package in `Packages: +N`,
and pnpm v11 left a dangling `node_modules/<name>` symlink and listed
the package among the added dependencies.

Both CLIs now emit `pnpm:skipped-optional-dependency` with a new
`fetch_failure` reason, the error code and message in `details`, and
the default reporter renders it as a warning wherever the package sits
in the graph.

In v12 the `pnpm:stats added` event moves from planning to after the
cold downloads and leaves out the failed snapshots. The paired
`removed` event comes from the link phase, so the summary line renders
at the same point as before.

In v11 the isolated layout drops failed packages from the graph and
from the direct dependencies, so they are neither hoisted, built, nor
linked into the project. The current lockfile still lists them, and
their missing directory makes the next install fetch them again. The
v11 `Packages: +N` count is unchanged: that line renders before the
fetches start there.

A parent package's slot links its children while the downloads are
still running, so the link to an optional child whose fetch failed is
removed afterwards: v12 after the cold download batch (except under
the hoisted linker and `frozenStore`), v11 when it drops the failed
packages from the graph.

Both default reporters render a `fetch_failure` skip whatever its
prefix. The skip carries the lockfile directory, so the usual
current-project filter hid it when `pnpm install` ran from a workspace
project.

The v11 integrity error message now redacts the tarball URL with
`redactUrlForDisplay`, matching the v12 tarball errors, so the warning
cannot print credentials or a signed query token. An absolute local
tarball path is kept as is.

Closes pnpm/pnpm#16514
2026-10-02 19:52:01 +02:00

54 lines
1.8 KiB
TypeScript

import { expect, test } from '@jest/globals'
import { TarballIntegrityError } from '../src/index.js'
test('the integrity error message hides the credentials and query of the tarball URL', () => {
const error = new TarballIntegrityError({
algorithm: 'sha512',
expected: 'sha512-expected',
found: 'sha512-found',
sri: 'sha512-expected',
url: 'https://user:secret@registry.example/foo/-/foo-1.0.0.tgz?token=signed#frag',
})
expect(error.message).toBe('Got unexpected checksum for "https://registry.example/foo/-/foo-1.0.0.tgz". Wanted "sha512-expected". Got "sha512-found".')
})
test('the integrity error message shows a local tarball path as is', () => {
const tarball = process.platform === 'win32' ? 'C:\\project\\local-tarball.tgz' : '/project/local-tarball.tgz'
const error = new TarballIntegrityError({
algorithm: 'sha512',
expected: 'sha512-expected',
found: 'sha512-found',
sri: 'sha512-expected',
url: tarball,
})
expect(error.message).toContain(`"${tarball}"`)
})
test('the integrity error message hides the query of a tarball URL with one slash after the scheme', () => {
const error = new TarballIntegrityError({
algorithm: 'sha512',
expected: 'sha512-expected',
found: 'sha512-found',
sri: 'sha512-expected',
url: 'https:/registry.example/foo/-/foo-1.0.0.tgz?token=signed',
})
expect(error.message).not.toContain('signed')
})
test('the integrity error message strips control characters from a local tarball path', () => {
const error = new TarballIntegrityError({
algorithm: 'sha512',
expected: 'sha512-expected',
found: 'sha512-found',
sri: 'sha512-expected',
url: '/project/evil\n\u001b[2J/tarball.tgz',
})
expect(error.message).not.toContain('\n')
expect(error.message).not.toContain('\u001b')
})