mirror of
https://github.com/pnpm/pnpm.git
synced 2026-05-12 01:54:53 -04:00
fix(publish): strip semver build metadata before packing (#11525)
When a published version contained a `+<build>` segment (e.g. `1.0.0-canary.0+abc1234`), `pnpm publish --provenance` was rejected by the registry with a 422 verifying the sigstore provenance bundle. `libnpmpublish.publish()` runs `semver.clean()` on `manifest.version`, which strips build metadata, before computing the provenance subject. pnpm was packing the tarball with the original version, so the version embedded in the packed `package.json` no longer matched the version in the metadata payload and the bundle's subject — causing the registry to reject the publish. Strip build metadata from the published version after creating the publish manifest, then derive both the tarball filename and the manifest packed inside the tarball from that cleaned version. Closes #11518.
This commit is contained in:
6
.changeset/publish-strip-build-metadata.md
Normal file
6
.changeset/publish-strip-build-metadata.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@pnpm/releasing.commands": patch
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
Fixed `pnpm publish --provenance` failing with a 422 from the registry when the package version contained semver build metadata (e.g. `1.0.0-canary.0+abc1234`). The `+<build>` segment is now stripped before packing so that the version embedded in the tarball, the metadata sent to the registry, and the sigstore provenance subject all agree [#11518](https://github.com/pnpm/pnpm/issues/11518).
|
||||
@@ -223,21 +223,6 @@ export async function api (opts: PackOptions): Promise<PackResult> {
|
||||
if (!manifest.version) {
|
||||
throw new PnpmError('PACKAGE_VERSION_NOT_FOUND', `Package version is not defined in the ${manifestFileName}.`)
|
||||
}
|
||||
let tarballName: string
|
||||
let packDestination: string | undefined
|
||||
const normalizedName = manifest.name.replace('@', '').replace('/', '-')
|
||||
if (opts.out) {
|
||||
if (opts.packDestination) {
|
||||
throw new PnpmError('INVALID_OPTION', 'Cannot use --pack-destination and --out together')
|
||||
}
|
||||
const preparedOut = opts.out.replaceAll('%s', normalizedName).replaceAll('%v', manifest.version)
|
||||
const parsedOut = path.parse(preparedOut)
|
||||
packDestination = parsedOut.dir ? parsedOut.dir : opts.packDestination
|
||||
tarballName = parsedOut.base
|
||||
} else {
|
||||
tarballName = `${normalizedName}-${manifest.version}.tgz`
|
||||
packDestination = opts.packDestination
|
||||
}
|
||||
const publishManifest = await createPublishManifest({
|
||||
projectDir: dir,
|
||||
modulesDir: path.join(opts.dir, 'node_modules'),
|
||||
@@ -246,6 +231,28 @@ export async function api (opts: PackOptions): Promise<PackResult> {
|
||||
catalogs: opts.catalogs ?? {},
|
||||
hooks: opts.hooks,
|
||||
})
|
||||
// Strip semver build metadata (the `+<build>` segment) from the published version so that
|
||||
// the tarball, the manifest packed inside it, and the metadata sent to the registry all agree.
|
||||
// libnpmpublish runs `semver.clean()` on `manifest.version` before computing the provenance
|
||||
// subject, which removes build metadata. Leaving it in here would mismatch the version embedded
|
||||
// in the tarball's package.json and cause the registry to reject the publish with a 422 when
|
||||
// verifying the sigstore provenance bundle. See https://github.com/pnpm/pnpm/issues/11518.
|
||||
publishManifest.version = stripBuildMetadata(publishManifest.version!)
|
||||
let tarballName: string
|
||||
let packDestination: string | undefined
|
||||
const normalizedName = manifest.name.replace('@', '').replace('/', '-')
|
||||
if (opts.out) {
|
||||
if (opts.packDestination) {
|
||||
throw new PnpmError('INVALID_OPTION', 'Cannot use --pack-destination and --out together')
|
||||
}
|
||||
const preparedOut = opts.out.replaceAll('%s', normalizedName).replaceAll('%v', publishManifest.version)
|
||||
const parsedOut = path.parse(preparedOut)
|
||||
packDestination = parsedOut.dir ? parsedOut.dir : opts.packDestination
|
||||
tarballName = parsedOut.base
|
||||
} else {
|
||||
tarballName = `${normalizedName}-${publishManifest.version}.tgz`
|
||||
packDestination = opts.packDestination
|
||||
}
|
||||
const files = await packlist(dir, {
|
||||
manifest: publishManifest as Record<string, unknown>,
|
||||
})
|
||||
@@ -320,6 +327,11 @@ export interface PackResult {
|
||||
unpackedSize: number
|
||||
}
|
||||
|
||||
function stripBuildMetadata (version: string): string {
|
||||
const plusIndex = version.indexOf('+')
|
||||
return plusIndex === -1 ? version : version.slice(0, plusIndex)
|
||||
}
|
||||
|
||||
function preventBundledDependenciesWithoutHoistedNodeLinker (nodeLinker: Config['nodeLinker'], manifest: ProjectManifest): void {
|
||||
if (nodeLinker === 'hoisted') return
|
||||
for (const key of ['bundledDependencies', 'bundleDependencies'] as const) {
|
||||
|
||||
@@ -227,6 +227,31 @@ test('pack a package without package version', async () => {
|
||||
})).rejects.toThrow('Package version is not defined in the package.json.')
|
||||
})
|
||||
|
||||
test('pack: strips semver build metadata from the version', async () => {
|
||||
prepare({
|
||||
name: 'test-strip-build-metadata',
|
||||
version: '1.0.0-canary.0+abc1234',
|
||||
})
|
||||
|
||||
await pack.handler({
|
||||
...DEFAULT_OPTS,
|
||||
argv: { original: [] },
|
||||
dir: process.cwd(),
|
||||
extraBinPaths: [],
|
||||
packDestination: process.cwd(),
|
||||
})
|
||||
|
||||
expect(fs.existsSync('test-strip-build-metadata-1.0.0-canary.0.tgz')).toBeTruthy()
|
||||
expect(fs.existsSync('test-strip-build-metadata-1.0.0-canary.0+abc1234.tgz')).toBeFalsy()
|
||||
|
||||
await tar.x({ file: 'test-strip-build-metadata-1.0.0-canary.0.tgz' })
|
||||
|
||||
expect((await import(path.resolve('package/package.json'))).default).toEqual({
|
||||
name: 'test-strip-build-metadata',
|
||||
version: '1.0.0-canary.0',
|
||||
})
|
||||
})
|
||||
|
||||
test('pack: runs prepack, prepare, and postpack', async () => {
|
||||
prepare({
|
||||
name: 'test-publish-package.json',
|
||||
|
||||
Reference in New Issue
Block a user