fix(publish): respect publishConfig.registry in non-recursive publish (#11449)

The native publish flow introduced in v11 (replacing the `npm publish`
shell-out with `libnpmpublish`) only read the registry from `registries`
config, ignoring `publishConfig.registry` from the package's
`package.json`. Restore the prior behavior by giving
`publishConfig.registry` precedence over the configured registries.

Closes #11419
This commit is contained in:
Zoltan Kochan
2026-05-04 17:36:31 +02:00
committed by GitHub
parent 4852e6f85d
commit bc9c3af36f
3 changed files with 37 additions and 3 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/releasing.commands": patch
"pnpm": patch
---
Fixed `pnpm publish` to honor `publishConfig.registry` from `package.json` when publishing a single package. The native publish flow introduced in v11 was reading the registry from `.npmrc` only, ignoring the per-package override [#11419](https://github.com/pnpm/pnpm/issues/11419).

View File

@@ -59,7 +59,10 @@ export async function publishPackedPkg (
}
async function createPublishOptions (manifest: ExportedManifest, options: PublishPackedPkgOptions): Promise<PublishOptions> {
const { registry, config } = findRegistryInfo(manifest, options)
const publishConfigRegistry = typeof manifest.publishConfig?.registry === 'string'
? manifest.publishConfig.registry
: undefined
const { registry, config } = findRegistryInfo(manifest, options, publishConfigRegistry)
const { creds, tls } = config ?? {}
const {
@@ -130,16 +133,19 @@ interface RegistryInfo {
/**
* Find credentials and SSL info for a package's registry.
* Follows {@link https://docs.npmjs.com/cli/v10/configuring-npm/npmrc#auth-related-configuration}.
*
* The manifest's `publishConfig.registry`, when set, takes precedence over `registries`.
*/
function findRegistryInfo (
{ name }: ExportedManifest,
{ configByUri, registries }: Pick<Config, 'configByUri' | 'registries'>
{ configByUri, registries }: Pick<Config, 'configByUri' | 'registries'>,
publishConfigRegistry?: string
): Partial<RegistryInfo> {
// eslint-disable-next-line regexp/no-unused-capturing-group
const scopedMatches = /@(?<scope>[^/]+)\/(?<slug>[^/]+)/.exec(name)
const registryName = scopedMatches?.groups ? `@${scopedMatches.groups.scope}` : 'default'
const nonNormalizedRegistry = registries[registryName] ?? registries.default
const nonNormalizedRegistry = publishConfigRegistry ?? registries[registryName] ?? registries.default
const supportedRegistryInfo = parseSupportedRegistryUrl(nonNormalizedRegistry)
if (!supportedRegistryInfo) {

View File

@@ -304,6 +304,28 @@ test('publish: package with all possible fields in publishConfig', async () => {
})
})
test('publish: package with publishConfig.registry overrides the default registry', async () => {
const pkgName = `test-publish-config-registry-${Date.now()}`
prepare({
name: pkgName,
version: '1.0.0',
publishConfig: {
registry: `http://localhost:${REGISTRY_MOCK_PORT}`,
},
})
await publish.handler({
...DEFAULT_OPTS,
argv: { original: ['publish'] },
configByUri: CONFIG_BY_URI,
dir: process.cwd(),
registries: { default: 'https://__fake_npm_registry__.com' },
}, [])
await checkPkgExists(pkgName, '1.0.0')
})
test('publish: package with publishConfig.directory', async () => {
const packages = preparePackages([
{