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:
Brandon Cheng
2024-06-30 03:53:39 -04:00
committed by GitHub
parent b339e10855
commit 9bf9f71ad3
4 changed files with 82 additions and 5 deletions

View 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.

View File

@@ -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,
}
}

View File

@@ -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

View File

@@ -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