mirror of
https://github.com/pnpm/pnpm.git
synced 2026-01-08 15:08:27 -05:00
fix: verify the name and version of the package in the store (#3224)
* fix: verify the name and version of the package in the store ref #3202 * fix: autofix broken lockfile * docs: add changesets
This commit is contained in:
committed by
Zoltan Kochan
parent
96e10ff26f
commit
8d1dfa89c6
14
.changeset/large-poets-smell.md
Normal file
14
.changeset/large-poets-smell.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
"@pnpm/headless": major
|
||||
"@pnpm/package-requester": major
|
||||
"@pnpm/package-store": major
|
||||
"@pnpm/store-connection-manager": major
|
||||
"@pnpm/server": major
|
||||
"@pnpm/store-controller-types": major
|
||||
"supi": minor
|
||||
"@pnpm/resolve-dependencies": major
|
||||
---
|
||||
|
||||
Breaking changes to the store controller API.
|
||||
|
||||
The options to `requestPackage()` and `fetchPackage()` changed.
|
||||
5
.changeset/old-birds-rhyme.md
Normal file
5
.changeset/old-birds-rhyme.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/cafs": minor
|
||||
---
|
||||
|
||||
New fields added to `PackageFilesIndex`: `name` and `version`.
|
||||
@@ -11,6 +11,13 @@ const limit = pLimit(20)
|
||||
const MAX_BULK_SIZE = 1 * 1024 * 1024 // 1MB
|
||||
|
||||
export interface PackageFilesIndex {
|
||||
// name and version are nullable for backward compatibility
|
||||
// the intitial specs of pnpm store v3 did not require these fields.
|
||||
// However, it might be possible that some types of dependencies don't
|
||||
// have the name/version fields, like the local tarball dependencies.
|
||||
name?: string
|
||||
version?: string
|
||||
|
||||
files: Record<string, PackageFileInfo>
|
||||
sideEffects?: Record<string, Record<string, PackageFileInfo>>
|
||||
}
|
||||
|
||||
@@ -598,8 +598,12 @@ async function lockfileToDepGraph (
|
||||
fetchResponse = opts.storeController.fetchPackage({
|
||||
force: false,
|
||||
lockfileDir: opts.lockfileDir,
|
||||
pkgId: packageId,
|
||||
resolution,
|
||||
pkg: {
|
||||
name: pkgName,
|
||||
version: pkgVersion,
|
||||
id: packageId,
|
||||
resolution,
|
||||
},
|
||||
})
|
||||
if (fetchResponse instanceof Promise) fetchResponse = await fetchResponse
|
||||
} catch (err) {
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"dependencies": {
|
||||
"@pnpm/cafs": "workspace:2.0.5",
|
||||
"@pnpm/core-loggers": "workspace:5.0.3",
|
||||
"@pnpm/error": "workspace:1.4.0",
|
||||
"@pnpm/fetcher-base": "workspace:9.0.4",
|
||||
"@pnpm/read-package-json": "workspace:3.1.9",
|
||||
"@pnpm/resolver-base": "workspace:7.1.1",
|
||||
|
||||
@@ -7,6 +7,7 @@ import createCafs, {
|
||||
PackageFilesIndex,
|
||||
} from '@pnpm/cafs'
|
||||
import { fetchingProgressLogger } from '@pnpm/core-loggers'
|
||||
import PnpmError from '@pnpm/error'
|
||||
import {
|
||||
Cafs,
|
||||
DeferredManifestPromise,
|
||||
@@ -126,8 +127,8 @@ async function resolveAndFetch (
|
||||
let latest: string | undefined
|
||||
let manifest: DependencyManifest | undefined
|
||||
let normalizedPref: string | undefined
|
||||
let resolution = options.currentResolution as Resolution
|
||||
let pkgId = options.currentPackageId
|
||||
let resolution = options.currentPkg?.resolution as Resolution
|
||||
let pkgId = options.currentPkg?.id
|
||||
const skipResolution = resolution && !options.update
|
||||
let forceFetch = false
|
||||
let updated = false
|
||||
@@ -138,8 +139,8 @@ async function resolveAndFetch (
|
||||
// When we don't fetch, the only way to get the package's manifest is via resolving it.
|
||||
//
|
||||
// The resolution step is never skipped for local dependencies.
|
||||
if (!skipResolution || options.skipFetch === true || pkgId?.startsWith('file:')) {
|
||||
const resolveResult = await ctx.requestsQueue.add<ResolveResult>(() => ctx.resolve(wantedDependency, {
|
||||
if (!skipResolution || options.skipFetch === true || Boolean(pkgId?.startsWith('file:'))) {
|
||||
const resolveResult = await ctx.requestsQueue.add<ResolveResult>(async () => ctx.resolve(wantedDependency, {
|
||||
alwaysTryWorkspacePackages: options.alwaysTryWorkspacePackages,
|
||||
defaultTag: options.defaultTag,
|
||||
lockfileDir: options.lockfileDir,
|
||||
@@ -157,17 +158,13 @@ async function resolveAndFetch (
|
||||
// If the integrity of a local tarball dependency has changed,
|
||||
// the local tarball should be unpacked, so a fetch to the store should be forced
|
||||
forceFetch = Boolean(
|
||||
options.currentResolution &&
|
||||
options.currentPkg?.resolution &&
|
||||
pkgId?.startsWith('file:') &&
|
||||
options.currentResolution['integrity'] !== resolveResult.resolution['integrity'] // eslint-disable-line @typescript-eslint/dot-notation
|
||||
options.currentPkg?.resolution['integrity'] !== resolveResult.resolution['integrity'] // eslint-disable-line @typescript-eslint/dot-notation
|
||||
)
|
||||
|
||||
updated = pkgId !== resolveResult.id || !resolution || forceFetch
|
||||
// Keep the lockfile resolution when possible
|
||||
// to keep the original shasum.
|
||||
if (updated) {
|
||||
resolution = resolveResult.resolution
|
||||
}
|
||||
resolution = resolveResult.resolution
|
||||
pkgId = resolveResult.id
|
||||
normalizedPref = resolveResult.normalizedPref
|
||||
}
|
||||
@@ -212,8 +209,11 @@ async function resolveAndFetch (
|
||||
fetchRawManifest: updated || !manifest,
|
||||
force: forceFetch,
|
||||
lockfileDir: options.lockfileDir,
|
||||
pkgId: id,
|
||||
resolution: resolution,
|
||||
pkg: {
|
||||
...R.pick(['name', 'version'], manifest ?? options.currentPkg ?? {}),
|
||||
id,
|
||||
resolution,
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
@@ -260,11 +260,15 @@ function fetchToStore (
|
||||
verifyStoreIntegrity: boolean
|
||||
},
|
||||
opts: {
|
||||
pkg: {
|
||||
name?: string
|
||||
version?: string
|
||||
id: string
|
||||
resolution: Resolution
|
||||
}
|
||||
fetchRawManifest?: boolean
|
||||
force: boolean
|
||||
pkgId: string
|
||||
lockfileDir: string
|
||||
resolution: Resolution
|
||||
}
|
||||
): {
|
||||
bundledManifest?: () => Promise<BundledManifest>
|
||||
@@ -272,28 +276,28 @@ function fetchToStore (
|
||||
files: () => Promise<PackageFilesResponse>
|
||||
finishing: () => Promise<void>
|
||||
} {
|
||||
const targetRelative = depPathToFilename(opts.pkgId, opts.lockfileDir)
|
||||
const targetRelative = depPathToFilename(opts.pkg.id, opts.lockfileDir)
|
||||
const target = path.join(ctx.storeDir, targetRelative)
|
||||
|
||||
if (!ctx.fetchingLocker.has(opts.pkgId)) {
|
||||
if (!ctx.fetchingLocker.has(opts.pkg.id)) {
|
||||
const bundledManifest = pDefer<BundledManifest>()
|
||||
const files = pDefer<PackageFilesResponse>()
|
||||
const finishing = pDefer<undefined>()
|
||||
const filesIndexFile = opts.resolution['integrity']
|
||||
? ctx.getFilePathInCafs(opts.resolution['integrity'], 'index')
|
||||
const filesIndexFile = opts.pkg.resolution['integrity']
|
||||
? ctx.getFilePathInCafs(opts.pkg.resolution['integrity'], 'index')
|
||||
: path.join(target, 'integrity.json')
|
||||
|
||||
doFetchToStore(filesIndexFile, bundledManifest, files, finishing) // eslint-disable-line
|
||||
|
||||
if (opts.fetchRawManifest) {
|
||||
ctx.fetchingLocker.set(opts.pkgId, {
|
||||
ctx.fetchingLocker.set(opts.pkg.id, {
|
||||
bundledManifest: removeKeyOnFail(bundledManifest.promise),
|
||||
files: removeKeyOnFail(files.promise),
|
||||
filesIndexFile,
|
||||
finishing: removeKeyOnFail(finishing.promise),
|
||||
})
|
||||
} else {
|
||||
ctx.fetchingLocker.set(opts.pkgId, {
|
||||
ctx.fetchingLocker.set(opts.pkg.id, {
|
||||
files: removeKeyOnFail(files.promise),
|
||||
filesIndexFile,
|
||||
finishing: removeKeyOnFail(finishing.promise),
|
||||
@@ -312,13 +316,13 @@ function fetchToStore (
|
||||
return
|
||||
}
|
||||
|
||||
const tmp = ctx.fetchingLocker.get(opts.pkgId)
|
||||
const tmp = ctx.fetchingLocker.get(opts.pkg.id)
|
||||
|
||||
// If fetching failed then it was removed from the cache.
|
||||
// It is OK. In that case there is no need to update it.
|
||||
if (!tmp) return
|
||||
|
||||
ctx.fetchingLocker.set(opts.pkgId, {
|
||||
ctx.fetchingLocker.set(opts.pkg.id, {
|
||||
...tmp,
|
||||
files: Promise.resolve({
|
||||
...cache,
|
||||
@@ -327,11 +331,11 @@ function fetchToStore (
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
ctx.fetchingLocker.delete(opts.pkgId)
|
||||
ctx.fetchingLocker.delete(opts.pkg.id)
|
||||
})
|
||||
}
|
||||
|
||||
const result = ctx.fetchingLocker.get(opts.pkgId)!
|
||||
const result = ctx.fetchingLocker.get(opts.pkg.id)!
|
||||
|
||||
if (opts.fetchRawManifest && !result.bundledManifest) {
|
||||
result.bundledManifest = removeKeyOnFail(
|
||||
@@ -354,7 +358,7 @@ function fetchToStore (
|
||||
try {
|
||||
return await p
|
||||
} catch (err) {
|
||||
ctx.fetchingLocker.delete(opts.pkgId)
|
||||
ctx.fetchingLocker.delete(opts.pkg.id)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -366,13 +370,13 @@ function fetchToStore (
|
||||
finishing: pDefer.DeferredPromise<void>
|
||||
) {
|
||||
try {
|
||||
const isLocalTarballDep = opts.pkgId.startsWith('file:')
|
||||
const isLocalTarballDep = opts.pkg.id.startsWith('file:')
|
||||
|
||||
if (
|
||||
!opts.force &&
|
||||
(
|
||||
!isLocalTarballDep ||
|
||||
await tarballIsUpToDate(opts.resolution as any, target, opts.lockfileDir) // eslint-disable-line
|
||||
await tarballIsUpToDate(opts.pkg.resolution as any, target, opts.lockfileDir) // eslint-disable-line
|
||||
)
|
||||
) {
|
||||
let pkgFilesIndex
|
||||
@@ -387,6 +391,17 @@ function fetchToStore (
|
||||
const manifest = opts.fetchRawManifest
|
||||
? safeDeferredPromise<DependencyManifest>()
|
||||
: undefined
|
||||
if (
|
||||
pkgFilesIndex.name != null && opts.pkg.name != null && pkgFilesIndex.name !== opts.pkg.name ||
|
||||
pkgFilesIndex.version != null && opts.pkg.version != null && pkgFilesIndex.version !== opts.pkg.version
|
||||
) {
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
throw new PnpmError('UNEXPECTED_PKG_CONTENT_IN_STORE', `\
|
||||
Package name mismatch found while reading ${JSON.stringify(opts.pkg.resolution)} from the store. \
|
||||
This means that the lockfile is broken. Expected package: ${opts.pkg.name}@${opts.pkg.version}. \
|
||||
Actual package in the store by the given integrity: ${pkgFilesIndex.name}@${pkgFilesIndex.version}.`)
|
||||
/* eslint-enable @typescript-eslint/restrict-template-expressions */
|
||||
}
|
||||
const verified = await ctx.checkFilesIntegrity(pkgFilesIndex.files, manifest)
|
||||
if (verified) {
|
||||
files.resolve({
|
||||
@@ -426,23 +441,23 @@ function fetchToStore (
|
||||
.then((manifest) => bundledManifest.resolve(pickBundledManifest(manifest)))
|
||||
.catch(bundledManifest.reject)
|
||||
}
|
||||
const fetchedPackage = await ctx.requestsQueue.add(() => ctx.fetch(
|
||||
opts.pkgId,
|
||||
opts.resolution,
|
||||
const fetchedPackage = await ctx.requestsQueue.add(async () => ctx.fetch(
|
||||
opts.pkg.id,
|
||||
opts.pkg.resolution,
|
||||
{
|
||||
lockfileDir: opts.lockfileDir,
|
||||
manifest: fetchManifest,
|
||||
onProgress: (downloaded) => {
|
||||
fetchingProgressLogger.debug({
|
||||
downloaded,
|
||||
packageId: opts.pkgId,
|
||||
packageId: opts.pkg.id,
|
||||
status: 'in_progress',
|
||||
})
|
||||
},
|
||||
onStart: (size, attempt) => {
|
||||
fetchingProgressLogger.debug({
|
||||
attempt,
|
||||
packageId: opts.pkgId,
|
||||
packageId: opts.pkg.id,
|
||||
size,
|
||||
status: 'started',
|
||||
})
|
||||
@@ -471,11 +486,15 @@ function fetchToStore (
|
||||
}
|
||||
})
|
||||
)
|
||||
await writeJsonFile(filesIndexFile, { files: integrity })
|
||||
await writeJsonFile(filesIndexFile, {
|
||||
name: opts.pkg.name,
|
||||
version: opts.pkg.version,
|
||||
files: integrity,
|
||||
})
|
||||
|
||||
if (isLocalTarballDep && opts.resolution['integrity']) { // eslint-disable-line @typescript-eslint/dot-notation
|
||||
if (isLocalTarballDep && opts.pkg.resolution['integrity']) { // eslint-disable-line @typescript-eslint/dot-notation
|
||||
await fs.mkdir(target, { recursive: true })
|
||||
await fs.writeFile(path.join(target, TARBALL_INTEGRITY_FILENAME), opts.resolution['integrity'], 'utf8') // eslint-disable-line @typescript-eslint/dot-notation
|
||||
await fs.writeFile(path.join(target, TARBALL_INTEGRITY_FILENAME), opts.pkg.resolution['integrity'], 'utf8') // eslint-disable-line @typescript-eslint/dot-notation
|
||||
}
|
||||
|
||||
files.resolve({
|
||||
|
||||
@@ -112,11 +112,15 @@ test('request package but skip fetching, when resolution is already available',
|
||||
|
||||
const projectDir = tempy.directory()
|
||||
const pkgResponse = await requestPackage({ alias: 'is-positive', pref: '1.0.0' }, {
|
||||
currentPackageId: 'registry.npmjs.org/is-positive/1.0.0',
|
||||
currentResolution: {
|
||||
integrity: 'sha1-iACYVrZKLx632LsBeUGEJK4EUss=',
|
||||
registry: 'https://registry.npmjs.org/',
|
||||
tarball: 'https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz',
|
||||
currentPkg: {
|
||||
name: 'is-positive',
|
||||
version: '1.0.0',
|
||||
id: 'registry.npmjs.org/is-positive/1.0.0',
|
||||
resolution: {
|
||||
integrity: 'sha1-iACYVrZKLx632LsBeUGEJK4EUss=',
|
||||
registry: 'https://registry.npmjs.org/',
|
||||
tarball: 'https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz',
|
||||
},
|
||||
},
|
||||
downloadPriority: 0,
|
||||
lockfileDir: projectDir,
|
||||
@@ -162,7 +166,6 @@ test('refetch local tarball if its integrity has changed', async () => {
|
||||
const storeDir = tempy.directory()
|
||||
const pkgId = `file:${normalize(tarballRelativePath)}`
|
||||
const requestPackageOpts = {
|
||||
currentPackageId: pkgId,
|
||||
downloadPriority: 0,
|
||||
lockfileDir: projectDir,
|
||||
preferredVersions: {},
|
||||
@@ -180,9 +183,14 @@ test('refetch local tarball if its integrity has changed', async () => {
|
||||
|
||||
const response = await requestPackage(wantedPackage, {
|
||||
...requestPackageOpts,
|
||||
currentResolution: {
|
||||
integrity: 'sha512-lqODmYcc/FKOGROEUByd5Sbugqhzgkv+Hij9PXH0sZVQsU2npTQ0x3L81GCtHilFKme8lhBtD31Vxg/AKYrAvg==',
|
||||
tarball,
|
||||
currentPkg: {
|
||||
name: '@pnpm/package-requester',
|
||||
version: '0.8.1',
|
||||
id: pkgId,
|
||||
resolution: {
|
||||
integrity: 'sha512-lqODmYcc/FKOGROEUByd5Sbugqhzgkv+Hij9PXH0sZVQsU2npTQ0x3L81GCtHilFKme8lhBtD31Vxg/AKYrAvg==',
|
||||
tarball,
|
||||
},
|
||||
},
|
||||
}) as PackageResponse & {
|
||||
files: () => Promise<PackageFilesResponse>
|
||||
@@ -207,9 +215,14 @@ test('refetch local tarball if its integrity has changed', async () => {
|
||||
|
||||
const response = await requestPackage(wantedPackage, {
|
||||
...requestPackageOpts,
|
||||
currentResolution: {
|
||||
integrity: 'sha512-lqODmYcc/FKOGROEUByd5Sbugqhzgkv+Hij9PXH0sZVQsU2npTQ0x3L81GCtHilFKme8lhBtD31Vxg/AKYrAvg==',
|
||||
tarball,
|
||||
currentPkg: {
|
||||
name: '@pnpm/package-requester',
|
||||
version: '0.8.1',
|
||||
id: pkgId,
|
||||
resolution: {
|
||||
integrity: 'sha512-lqODmYcc/FKOGROEUByd5Sbugqhzgkv+Hij9PXH0sZVQsU2npTQ0x3L81GCtHilFKme8lhBtD31Vxg/AKYrAvg==',
|
||||
tarball,
|
||||
},
|
||||
},
|
||||
})
|
||||
await response.files!()
|
||||
@@ -228,9 +241,14 @@ test('refetch local tarball if its integrity has changed', async () => {
|
||||
|
||||
const response = await requestPackage(wantedPackage, {
|
||||
...requestPackageOpts,
|
||||
currentResolution: {
|
||||
integrity: 'sha512-v3uhYkN+Eh3Nus4EZmegjQhrfpdPIH+2FjrkeBc6ueqZJWWRaLnSYIkD0An6m16D3v+6HCE18ox6t95eGxj5Pw==',
|
||||
tarball,
|
||||
currentPkg: {
|
||||
name: '@pnpm/package-requester',
|
||||
version: '0.8.1',
|
||||
id: pkgId,
|
||||
resolution: {
|
||||
integrity: 'sha512-v3uhYkN+Eh3Nus4EZmegjQhrfpdPIH+2FjrkeBc6ueqZJWWRaLnSYIkD0An6m16D3v+6HCE18ox6t95eGxj5Pw==',
|
||||
tarball,
|
||||
},
|
||||
},
|
||||
}) as PackageResponse & {
|
||||
files: () => Promise<PackageFilesResponse>
|
||||
@@ -329,11 +347,15 @@ test('fetchPackageToStore()', async () => {
|
||||
const fetchResult = packageRequester.fetchPackageToStore({
|
||||
force: false,
|
||||
lockfileDir: tempy.directory(),
|
||||
pkgId,
|
||||
resolution: {
|
||||
integrity: 'sha1-iACYVrZKLx632LsBeUGEJK4EUss=',
|
||||
registry: 'https://registry.npmjs.org/',
|
||||
tarball: 'https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz',
|
||||
pkg: {
|
||||
name: 'is-positive',
|
||||
version: '1.0.0',
|
||||
id: pkgId,
|
||||
resolution: {
|
||||
integrity: 'sha1-iACYVrZKLx632LsBeUGEJK4EUss=',
|
||||
registry: 'https://registry.npmjs.org/',
|
||||
tarball: 'https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -353,11 +375,15 @@ test('fetchPackageToStore()', async () => {
|
||||
fetchRawManifest: true,
|
||||
force: false,
|
||||
lockfileDir: tempy.directory(),
|
||||
pkgId,
|
||||
resolution: {
|
||||
integrity: 'sha1-iACYVrZKLx632LsBeUGEJK4EUss=',
|
||||
registry: 'https://registry.npmjs.org/',
|
||||
tarball: 'https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz',
|
||||
pkg: {
|
||||
name: 'is-positive',
|
||||
version: '1.0.0',
|
||||
id: pkgId,
|
||||
resolution: {
|
||||
integrity: 'sha1-iACYVrZKLx632LsBeUGEJK4EUss=',
|
||||
registry: 'https://registry.npmjs.org/',
|
||||
tarball: 'https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -391,21 +417,29 @@ test('fetchPackageToStore() concurrency check', async () => {
|
||||
packageRequester.fetchPackageToStore({
|
||||
force: false,
|
||||
lockfileDir: projectDir1,
|
||||
pkgId,
|
||||
resolution: {
|
||||
integrity: 'sha1-iACYVrZKLx632LsBeUGEJK4EUss=',
|
||||
registry: 'https://registry.npmjs.org/',
|
||||
tarball: 'https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz',
|
||||
pkg: {
|
||||
name: 'is-positive',
|
||||
version: '1.0.0',
|
||||
id: pkgId,
|
||||
resolution: {
|
||||
integrity: 'sha1-iACYVrZKLx632LsBeUGEJK4EUss=',
|
||||
registry: 'https://registry.npmjs.org/',
|
||||
tarball: 'https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz',
|
||||
},
|
||||
},
|
||||
}),
|
||||
packageRequester.fetchPackageToStore({
|
||||
force: false,
|
||||
lockfileDir: projectDir2,
|
||||
pkgId,
|
||||
resolution: {
|
||||
integrity: 'sha1-iACYVrZKLx632LsBeUGEJK4EUss=',
|
||||
registry: 'https://registry.npmjs.org/',
|
||||
tarball: 'https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz',
|
||||
pkg: {
|
||||
name: 'is-positive',
|
||||
version: '1.0.0',
|
||||
id: pkgId,
|
||||
resolution: {
|
||||
integrity: 'sha1-iACYVrZKLx632LsBeUGEJK4EUss=',
|
||||
registry: 'https://registry.npmjs.org/',
|
||||
tarball: 'https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz',
|
||||
},
|
||||
},
|
||||
}),
|
||||
])
|
||||
@@ -466,11 +500,15 @@ test('fetchPackageToStore() does not cache errors', async () => {
|
||||
const badRequest = packageRequester.fetchPackageToStore({
|
||||
force: false,
|
||||
lockfileDir: tempy.directory(),
|
||||
pkgId,
|
||||
resolution: {
|
||||
integrity: 'sha1-iACYVrZKLx632LsBeUGEJK4EUss=',
|
||||
registry: 'https://registry.npmjs.org/',
|
||||
tarball: 'https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz',
|
||||
pkg: {
|
||||
name: 'is-positive',
|
||||
version: '1.0.0',
|
||||
id: pkgId,
|
||||
resolution: {
|
||||
integrity: 'sha1-iACYVrZKLx632LsBeUGEJK4EUss=',
|
||||
registry: 'https://registry.npmjs.org/',
|
||||
tarball: 'https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz',
|
||||
},
|
||||
},
|
||||
})
|
||||
await expect(badRequest.files()).rejects.toThrow()
|
||||
@@ -478,11 +516,15 @@ test('fetchPackageToStore() does not cache errors', async () => {
|
||||
const fetchResult = packageRequester.fetchPackageToStore({
|
||||
force: false,
|
||||
lockfileDir: tempy.directory(),
|
||||
pkgId,
|
||||
resolution: {
|
||||
integrity: 'sha1-iACYVrZKLx632LsBeUGEJK4EUss=',
|
||||
registry: 'https://registry.npmjs.org/',
|
||||
tarball: 'https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz',
|
||||
pkg: {
|
||||
name: 'is-positive',
|
||||
version: '1.0.0',
|
||||
id: pkgId,
|
||||
resolution: {
|
||||
integrity: 'sha1-iACYVrZKLx632LsBeUGEJK4EUss=',
|
||||
registry: 'https://registry.npmjs.org/',
|
||||
tarball: 'https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz',
|
||||
},
|
||||
},
|
||||
})
|
||||
const files = await fetchResult.files()
|
||||
@@ -519,11 +561,15 @@ test('always return a package manifest in the response', async () => {
|
||||
|
||||
{
|
||||
const pkgResponse = await requestPackage({ alias: 'is-positive', pref: '1.0.0' }, {
|
||||
currentPackageId: 'registry.npmjs.org/is-positive/1.0.0',
|
||||
currentResolution: {
|
||||
integrity: 'sha1-iACYVrZKLx632LsBeUGEJK4EUss=',
|
||||
registry: 'https://registry.npmjs.org/',
|
||||
tarball: 'https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz',
|
||||
currentPkg: {
|
||||
name: 'is-positive',
|
||||
version: '1.0.0',
|
||||
id: 'registry.npmjs.org/is-positive/1.0.0',
|
||||
resolution: {
|
||||
integrity: 'sha1-iACYVrZKLx632LsBeUGEJK4EUss=',
|
||||
registry: 'https://registry.npmjs.org/',
|
||||
tarball: 'https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz',
|
||||
},
|
||||
},
|
||||
downloadPriority: 0,
|
||||
lockfileDir: projectDir,
|
||||
@@ -568,15 +614,23 @@ test('fetchPackageToStore() fetch raw manifest of cached package', async () => {
|
||||
fetchRawManifest: false,
|
||||
force: false,
|
||||
lockfileDir: tempy.directory(),
|
||||
pkgId,
|
||||
resolution,
|
||||
pkg: {
|
||||
name: 'is-positive',
|
||||
version: '1.0.0',
|
||||
id: pkgId,
|
||||
resolution,
|
||||
},
|
||||
}),
|
||||
packageRequester.fetchPackageToStore({
|
||||
fetchRawManifest: true,
|
||||
force: false,
|
||||
lockfileDir: tempy.directory(),
|
||||
pkgId,
|
||||
resolution,
|
||||
pkg: {
|
||||
name: 'is-positive',
|
||||
version: '1.0.0',
|
||||
id: pkgId,
|
||||
resolution,
|
||||
},
|
||||
}),
|
||||
])
|
||||
|
||||
@@ -607,8 +661,12 @@ test('refetch package to store if it has been modified', async () => {
|
||||
fetchRawManifest: false,
|
||||
force: false,
|
||||
lockfileDir,
|
||||
pkgId,
|
||||
resolution,
|
||||
pkg: {
|
||||
name: 'magic-hook',
|
||||
version: '2.0.0',
|
||||
id: pkgId,
|
||||
resolution,
|
||||
},
|
||||
})
|
||||
|
||||
const { filesIndex } = await fetchResult.files()
|
||||
@@ -633,8 +691,12 @@ test('refetch package to store if it has been modified', async () => {
|
||||
fetchRawManifest: false,
|
||||
force: false,
|
||||
lockfileDir,
|
||||
pkgId,
|
||||
resolution,
|
||||
pkg: {
|
||||
name: 'magic-hook',
|
||||
version: '2.0.0',
|
||||
id: pkgId,
|
||||
resolution,
|
||||
},
|
||||
})
|
||||
|
||||
await fetchResult.files()
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
{
|
||||
"path": "../dependency-path"
|
||||
},
|
||||
{
|
||||
"path": "../error"
|
||||
},
|
||||
{
|
||||
"path": "../fetcher-base"
|
||||
},
|
||||
|
||||
@@ -20,11 +20,13 @@ describe('store.importPackage()', () => {
|
||||
const fetchResponse = storeController.fetchPackage({
|
||||
force: false,
|
||||
lockfileDir: tempy.directory(),
|
||||
pkgId,
|
||||
resolution: {
|
||||
integrity: 'sha1-iACYVrZKLx632LsBeUGEJK4EUss=',
|
||||
registry: 'https://registry.npmjs.org/',
|
||||
tarball: 'https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz',
|
||||
pkg: {
|
||||
id: pkgId,
|
||||
resolution: {
|
||||
integrity: 'sha1-iACYVrZKLx632LsBeUGEJK4EUss=',
|
||||
registry: 'https://registry.npmjs.org/',
|
||||
tarball: 'https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz',
|
||||
},
|
||||
},
|
||||
})
|
||||
const importTo = tempy.directory()
|
||||
@@ -53,11 +55,13 @@ describe('store.importPackage()', () => {
|
||||
const fetchResponse = storeController.fetchPackage({
|
||||
force: false,
|
||||
lockfileDir: tempy.directory(),
|
||||
pkgId,
|
||||
resolution: {
|
||||
integrity: 'sha1-iACYVrZKLx632LsBeUGEJK4EUss=',
|
||||
registry: 'https://registry.npmjs.org/',
|
||||
tarball: 'https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz',
|
||||
pkg: {
|
||||
id: pkgId,
|
||||
resolution: {
|
||||
integrity: 'sha1-iACYVrZKLx632LsBeUGEJK4EUss=',
|
||||
registry: 'https://registry.npmjs.org/',
|
||||
tarball: 'https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz',
|
||||
},
|
||||
},
|
||||
})
|
||||
const importTo = tempy.directory()
|
||||
|
||||
@@ -486,6 +486,8 @@ function referenceSatisfiesWantedSpec (
|
||||
interface InfoFromLockfile {
|
||||
dependencyLockfile?: PackageSnapshot
|
||||
depPath: string
|
||||
name?: string
|
||||
version?: string
|
||||
pkgId: string
|
||||
resolution?: Resolution
|
||||
}
|
||||
@@ -525,6 +527,7 @@ function getInfoFromLockfile (
|
||||
}
|
||||
|
||||
return {
|
||||
...nameVerFromPkgSnapshot(depPath, dependencyLockfile),
|
||||
dependencyLockfile,
|
||||
depPath,
|
||||
pkgId: packageIdFromSnapshot(depPath, dependencyLockfile, registries),
|
||||
@@ -542,6 +545,8 @@ interface ResolveDependencyOptions {
|
||||
currentDepth: number
|
||||
currentPkg?: {
|
||||
depPath?: string
|
||||
name?: string
|
||||
version?: string
|
||||
pkgId?: string
|
||||
resolution?: Resolution
|
||||
dependencyLockfile?: PackageSnapshot
|
||||
@@ -574,7 +579,7 @@ async function resolveDependency (
|
||||
ctx.virtualStoreDir,
|
||||
dp.depPathToFilename(currentPkg.depPath, ctx.prefix),
|
||||
'node_modules',
|
||||
nameVerFromPkgSnapshot(currentPkg.depPath, currentPkg.dependencyLockfile).name,
|
||||
currentPkg.name!,
|
||||
'package.json'
|
||||
)
|
||||
)
|
||||
@@ -588,8 +593,12 @@ async function resolveDependency (
|
||||
try {
|
||||
pkgResponse = await ctx.storeController.requestPackage(wantedDependency, {
|
||||
alwaysTryWorkspacePackages: ctx.linkWorkspacePackagesDepth >= options.currentDepth,
|
||||
currentPackageId: currentPkg.pkgId,
|
||||
currentResolution: currentPkg.resolution,
|
||||
currentPkg: currentPkg ? {
|
||||
name: currentPkg.name,
|
||||
version: currentPkg.version,
|
||||
id: currentPkg.pkgId,
|
||||
resolution: currentPkg.resolution,
|
||||
} : undefined,
|
||||
defaultTag: ctx.defaultTag,
|
||||
downloadPriority: -options.currentDepth,
|
||||
lockfileDir: ctx.lockfileDir,
|
||||
|
||||
@@ -2,7 +2,6 @@ import { IncomingMessage, Server, ServerResponse } from 'http'
|
||||
import { globalInfo } from '@pnpm/logger'
|
||||
import {
|
||||
RequestPackageOptions,
|
||||
Resolution,
|
||||
StoreController,
|
||||
WantedDependency,
|
||||
} from '@pnpm/store-controller-types'
|
||||
@@ -96,7 +95,7 @@ export default function (
|
||||
case '/fetchPackage': {
|
||||
try {
|
||||
body = await bodyPromise
|
||||
const pkgResponse = store.fetchPackage(body.options as RequestPackageOptions & {force: boolean, pkgId: string, resolution: Resolution})
|
||||
const pkgResponse = store.fetchPackage(body.options as any) // eslint-disable-line
|
||||
if (pkgResponse['bundledManifest']) { // eslint-disable-line
|
||||
rawManifestPromises[body.msgId] = pkgResponse['bundledManifest'] // eslint-disable-line
|
||||
}
|
||||
|
||||
@@ -84,11 +84,13 @@ test('fetchPackage', async () => {
|
||||
fetchRawManifest: true,
|
||||
force: false,
|
||||
lockfileDir: process.cwd(),
|
||||
pkgId,
|
||||
resolution: {
|
||||
integrity: 'sha1-iACYVrZKLx632LsBeUGEJK4EUss=',
|
||||
registry: 'https://registry.npmjs.org/',
|
||||
tarball: 'https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz',
|
||||
pkg: {
|
||||
id: pkgId,
|
||||
resolution: {
|
||||
integrity: 'sha1-iACYVrZKLx632LsBeUGEJK4EUss=',
|
||||
registry: 'https://registry.npmjs.org/',
|
||||
tarball: 'https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -50,8 +50,12 @@ export interface FetchPackageToStoreOptions {
|
||||
fetchRawManifest?: boolean
|
||||
force: boolean
|
||||
lockfileDir: string
|
||||
pkgId: string
|
||||
resolution: Resolution
|
||||
pkg: {
|
||||
id: string
|
||||
name?: string
|
||||
version?: string
|
||||
resolution: Resolution
|
||||
}
|
||||
}
|
||||
|
||||
export type ImportPackageFunction = (
|
||||
@@ -83,8 +87,12 @@ export type RequestPackageFunction = (
|
||||
|
||||
export interface RequestPackageOptions {
|
||||
alwaysTryWorkspacePackages?: boolean
|
||||
currentPackageId?: string
|
||||
currentResolution?: Resolution
|
||||
currentPkg?: {
|
||||
id?: string
|
||||
name?: string
|
||||
version?: string
|
||||
resolution?: Resolution
|
||||
}
|
||||
defaultTag?: string
|
||||
downloadPriority: number
|
||||
projectDir: string
|
||||
|
||||
@@ -175,7 +175,7 @@ export async function mutateModules (
|
||||
return result
|
||||
|
||||
async function _install (): Promise<Array<{ rootDir: string, manifest: ProjectManifest }>> {
|
||||
const needsFullResolution = !R.equals(ctx.wantedLockfile.overrides ?? {}, overrides ?? {}) ||
|
||||
let needsFullResolution = !R.equals(ctx.wantedLockfile.overrides ?? {}, overrides ?? {}) ||
|
||||
!R.equals((ctx.wantedLockfile.neverBuiltDependencies ?? []).sort(), (neverBuiltDependencies ?? []).sort())
|
||||
ctx.wantedLockfile.overrides = overrides
|
||||
ctx.wantedLockfile.neverBuiltDependencies = neverBuiltDependencies
|
||||
@@ -255,7 +255,15 @@ export async function mutateModules (
|
||||
})
|
||||
return projects
|
||||
} catch (error) {
|
||||
if (frozenLockfile || error.code !== 'ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY') throw error
|
||||
if (
|
||||
frozenLockfile ||
|
||||
error.code !== 'ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY' && error.code !== 'ERR_PNPM_UNEXPECTED_PKG_CONTENT_IN_STORE'
|
||||
) throw error
|
||||
if (error.code === 'ERR_PNPM_UNEXPECTED_PKG_CONTENT_IN_STORE') {
|
||||
needsFullResolution = true
|
||||
// Ideally, we would not update but currently there is no other way to redownload the integrity of the package
|
||||
opts.update = true
|
||||
}
|
||||
// A broken lockfile may be caused by a badly resolved Git conflict
|
||||
logger.warn({
|
||||
error,
|
||||
@@ -577,7 +585,7 @@ export type ImporterToUpdate = {
|
||||
wantedDependencies: Array<WantedDependency & { isNew?: Boolean, updateSpec?: Boolean }>
|
||||
} & DependenciesMutation
|
||||
|
||||
async function installInContext (
|
||||
type InstallFunction = (
|
||||
projects: ImporterToUpdate[],
|
||||
ctx: PnpmContext<DependenciesMutation>,
|
||||
opts: StrictInstallOptions & {
|
||||
@@ -589,7 +597,9 @@ async function installInContext (
|
||||
preferredVersions?: PreferredVersions
|
||||
currentLockfileIsUpToDate: boolean
|
||||
}
|
||||
) {
|
||||
) => Promise<Array<{ rootDir: string, manifest: ProjectManifest }>>
|
||||
|
||||
const _installInContext: InstallFunction = async (projects, ctx, opts) => {
|
||||
if (opts.lockfileOnly && ctx.existsCurrentLockfile) {
|
||||
logger.warn({
|
||||
message: '`node_modules` is present. Lockfile only installation will make it out-of-date',
|
||||
@@ -883,6 +893,25 @@ async function installInContext (
|
||||
return projectsToResolve.map(({ manifest, rootDir }) => ({ rootDir, manifest }))
|
||||
}
|
||||
|
||||
const installInContext: InstallFunction = async (projects, ctx, opts) => {
|
||||
try {
|
||||
return await _installInContext(projects, ctx, opts)
|
||||
} catch (error) {
|
||||
if (
|
||||
error.code !== 'ERR_PNPM_UNEXPECTED_PKG_CONTENT_IN_STORE'
|
||||
) throw error
|
||||
opts.needsFullResolution = true
|
||||
// Ideally, we would not update but currently there is no other way to redownload the integrity of the package
|
||||
opts.update = true
|
||||
logger.warn({
|
||||
error,
|
||||
message: 'The lockfile is broken! pnpm will attempt to fix it.',
|
||||
prefix: ctx.lockfileDir,
|
||||
})
|
||||
return _installInContext(projects, ctx, opts)
|
||||
}
|
||||
}
|
||||
|
||||
async function toResolveImporter (
|
||||
opts: {
|
||||
defaultUpdateDepth: number
|
||||
|
||||
61
packages/supi/test/brokenLockfileIntegrity.ts
Normal file
61
packages/supi/test/brokenLockfileIntegrity.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { WANTED_LOCKFILE } from '@pnpm/constants'
|
||||
import { prepareEmpty } from '@pnpm/prepare'
|
||||
import rimraf from '@zkochan/rimraf'
|
||||
import {
|
||||
addDependenciesToPackage,
|
||||
mutateModules,
|
||||
} from 'supi'
|
||||
import writeYamlFile from 'write-yaml-file'
|
||||
import {
|
||||
addDistTag,
|
||||
testDefaults,
|
||||
} from './utils'
|
||||
|
||||
test('installation breaks if the lockfile contains the wrong checksum', async () => {
|
||||
await addDistTag('dep-of-pkg-with-1-dep', '100.0.0', 'latest')
|
||||
const project = prepareEmpty()
|
||||
|
||||
const manifest = await addDependenciesToPackage({},
|
||||
[
|
||||
'pkg-with-1-dep@100.0.0',
|
||||
],
|
||||
await testDefaults({ lockfileOnly: true })
|
||||
)
|
||||
|
||||
const lockfile = await project.readLockfile()
|
||||
// breaking the lockfile
|
||||
lockfile.packages['/pkg-with-1-dep/100.0.0'].resolution['integrity'] = lockfile.packages['/dep-of-pkg-with-1-dep/100.0.0'].resolution['integrity']
|
||||
await writeYamlFile(WANTED_LOCKFILE, lockfile, { lineWidth: 1000 })
|
||||
|
||||
await expect(mutateModules([
|
||||
{
|
||||
buildIndex: 0,
|
||||
manifest,
|
||||
mutation: 'install',
|
||||
rootDir: process.cwd(),
|
||||
},
|
||||
], await testDefaults({ frozenLockfile: true }))).rejects.toThrowError(/Package name mismatch found while reading/)
|
||||
|
||||
await mutateModules([
|
||||
{
|
||||
buildIndex: 0,
|
||||
manifest,
|
||||
mutation: 'install',
|
||||
rootDir: process.cwd(),
|
||||
},
|
||||
], await testDefaults())
|
||||
|
||||
// Breaking the lockfile again
|
||||
await writeYamlFile(WANTED_LOCKFILE, lockfile, { lineWidth: 1000 })
|
||||
|
||||
await rimraf('node_modules')
|
||||
|
||||
await mutateModules([
|
||||
{
|
||||
buildIndex: 0,
|
||||
manifest,
|
||||
mutation: 'install',
|
||||
rootDir: process.cwd(),
|
||||
},
|
||||
], await testDefaults({ preferFrozenLockfile: false }))
|
||||
})
|
||||
2
pnpm-lock.yaml
generated
2
pnpm-lock.yaml
generated
@@ -1489,6 +1489,7 @@ importers:
|
||||
dependencies:
|
||||
'@pnpm/cafs': link:../cafs
|
||||
'@pnpm/core-loggers': link:../core-loggers
|
||||
'@pnpm/error': link:../error
|
||||
'@pnpm/fetcher-base': link:../fetcher-base
|
||||
'@pnpm/read-package-json': link:../read-package-json
|
||||
'@pnpm/resolver-base': link:../resolver-base
|
||||
@@ -1523,6 +1524,7 @@ importers:
|
||||
'@pnpm/cafs': workspace:2.0.5
|
||||
'@pnpm/client': workspace:2.0.22
|
||||
'@pnpm/core-loggers': workspace:5.0.3
|
||||
'@pnpm/error': workspace:1.4.0
|
||||
'@pnpm/fetcher-base': workspace:9.0.4
|
||||
'@pnpm/logger': ^3.2.3
|
||||
'@pnpm/package-requester': 'link:'
|
||||
|
||||
Reference in New Issue
Block a user