mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-24 07:38:12 -05:00
fix: show better error when encountering external catalog protocol usage (#8254)
* fix: show better error when encountering external catalog protocol usage * refactor: reuse SPEC_NOT_SUPPORTED error * Apply suggestions from code review --------- Co-authored-by: Zoltan Kochan <z@kochan.io>
This commit is contained in:
6
.changeset/chatty-buses-cheer.md
Normal file
6
.changeset/chatty-buses-cheer.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@pnpm/resolve-dependencies": patch
|
||||
"@pnpm/default-reporter": patch
|
||||
---
|
||||
|
||||
When encountering an external dependency using the `catalog:` protocol, a clearer error will be shown. Previously a confusing `ERR_PNPM_SPEC_NOT_SUPPORTED_BY_ANY_RESOLVER` error was thrown. The new error message will explain that the author of the dependency needs to run `pnpm publish` to replace the catalog protocol.
|
||||
@@ -83,6 +83,8 @@ function getErrorInfo (logObj: Log, config?: Config, peerDependencyRules?: PeerD
|
||||
return reportPeerDependencyIssuesError(err, logObj as any, peerDependencyRules) // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
case 'ERR_PNPM_DEDUPE_CHECK_ISSUES':
|
||||
return reportDedupeCheckIssuesError(err, logObj as any) // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
case 'ERR_PNPM_SPEC_NOT_SUPPORTED_BY_ANY_RESOLVER':
|
||||
return reportSpecNotSupportedByAnyResolverError(err, logObj as any) // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
case 'ERR_PNPM_FETCH_401':
|
||||
case 'ERR_PNPM_FETCH_403':
|
||||
return reportAuthError(err, logObj as any, config) // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
@@ -443,3 +445,52 @@ Run ${chalk.yellow('pnpm dedupe')} to apply the changes above.
|
||||
`,
|
||||
}
|
||||
}
|
||||
|
||||
function reportSpecNotSupportedByAnyResolverError (err: Error, logObj: Log): ErrorInfo {
|
||||
// If the catalog protocol specifier was sent to a "real resolver", it'll
|
||||
// eventually throw a "specifier not supported" error since the catalog
|
||||
// protocol is meant to be replaced before it's passed to any of the real
|
||||
// resolvers.
|
||||
//
|
||||
// If this kind of error is thrown, and the dependency pref is using the
|
||||
// catalog protocol it's most likely because we're trying to install an out of
|
||||
// repo dependency that was published incorrectly. For example, it may be been
|
||||
// mistakenly published with 'npm publish' instead of 'pnpm publish'. Report a
|
||||
// more clear error in this case.
|
||||
if (logObj['package']?.['pref']?.startsWith('catalog:')) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return reportExternalCatalogProtocolError(err, logObj as any)
|
||||
}
|
||||
|
||||
return {
|
||||
title: err.message ?? '',
|
||||
body: logObj['hint'],
|
||||
}
|
||||
}
|
||||
|
||||
function reportExternalCatalogProtocolError (err: Error, logObj: Log): ErrorInfo {
|
||||
const pkgsStack: Array<{ id: string, name: string, version: string }> | undefined = logObj['pkgsStack']
|
||||
const problemDep = pkgsStack?.[0]
|
||||
|
||||
let body = `\
|
||||
An external package outside of the pnpm workspace declared a dependency using
|
||||
the catalog protocol. This is likely a bug in that external package. Only
|
||||
packages within the pnpm workspace may use catalogs. Usages of the catalog
|
||||
protocol are replaced with real specifiers on 'pnpm publish'.
|
||||
`
|
||||
|
||||
if (problemDep != null) {
|
||||
body += `\
|
||||
|
||||
This is likely a bug in the publishing automation of this package. Consider filing
|
||||
a bug with the authors of:
|
||||
|
||||
${highlight(`${problemDep.name}@${problemDep.version}`)}
|
||||
`
|
||||
}
|
||||
|
||||
return {
|
||||
title: err.message,
|
||||
body,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,6 +299,24 @@ test('lockfile catalog snapshots retain existing entries on --filter', async ()
|
||||
})
|
||||
})
|
||||
|
||||
test('external dependency using catalog protocol errors', async () => {
|
||||
const { options, projects } = preparePackagesAndReturnObjects([
|
||||
{
|
||||
name: 'project1',
|
||||
dependencies: {
|
||||
'@pnpm.e2e/pkg-with-accidentally-published-catalog-protocol': '1.0.0',
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
await expect(() =>
|
||||
mutateModules(installProjects(projects), {
|
||||
...options,
|
||||
lockfileOnly: true,
|
||||
})
|
||||
).rejects.toThrow("@pnpm.e2e/hello-world-js-bin@catalog:foo isn't supported by any available resolver.")
|
||||
})
|
||||
|
||||
// If a catalog specifier was used in one or more package.json files and all
|
||||
// usages were removed later, we should remove the catalog snapshot from
|
||||
// pnpm-lock.yaml. This should happen even if the dependency is still defined in
|
||||
|
||||
@@ -1224,20 +1224,22 @@ async function resolveDependency (
|
||||
updateToLatest: options.updateToLatest,
|
||||
})
|
||||
} catch (err: any) { // eslint-disable-line
|
||||
const wantedDependencyDetails = {
|
||||
name: wantedDependency.alias,
|
||||
pref: wantedDependency.pref,
|
||||
version: wantedDependency.alias ? wantedDependency.pref : undefined,
|
||||
}
|
||||
if (wantedDependency.optional) {
|
||||
skippedOptionalDependencyLogger.debug({
|
||||
details: err.toString(),
|
||||
package: {
|
||||
name: wantedDependency.alias,
|
||||
pref: wantedDependency.pref,
|
||||
version: wantedDependency.alias ? wantedDependency.pref : undefined,
|
||||
},
|
||||
package: wantedDependencyDetails,
|
||||
parents: getPkgsInfoFromIds(options.parentIds, ctx.resolvedPkgsById),
|
||||
prefix: options.prefix,
|
||||
reason: 'resolution_failure',
|
||||
})
|
||||
return null
|
||||
}
|
||||
err.package = wantedDependencyDetails
|
||||
err.prefix = options.prefix
|
||||
err.pkgsStack = getPkgsInfoFromIds(options.parentIds, ctx.resolvedPkgsById)
|
||||
throw err
|
||||
|
||||
Reference in New Issue
Block a user