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
54 lines
1.8 KiB
TypeScript
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')
|
|
})
|