fix: run lifecycle scripts for version command (#11511)

Co-authored-by: cyphercodes <cyphercodes@users.noreply.github.com>
This commit is contained in:
Rayan Salhab
2026-05-07 09:18:59 +03:00
committed by Zoltan Kochan
parent cfa271b7ee
commit ce474ccfc7
3 changed files with 75 additions and 1 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/releasing.commands": patch
"pnpm": patch
---
Run `preversion`, `version`, and `postversion` lifecycle scripts for `pnpm version`.

View File

@@ -3,6 +3,7 @@ import path from 'node:path'
import { readProjectManifest } from '@pnpm/cli.utils'
import { type Config, types as allTypes } from '@pnpm/config.reader'
import { PnpmError } from '@pnpm/error'
import { runLifecycleHook, type RunLifecycleHookOptions } from '@pnpm/exec.lifecycle'
import { isGitRepo, isWorkingTreeClean } from '@pnpm/network.git-utils'
import { filterProjectsFromDir, type WorkspaceFilter } from '@pnpm/workspace.projects-filter'
import { safeExeca as execa } from 'execa'
@@ -196,6 +197,8 @@ export async function handler (
await commitAndTag(changes, { ...opts, cwd: gitCwd })
}
await Promise.all(changes.map(change => runVersionLifecycleHook('postversion', change, opts)))
if (opts.json) {
return JSON.stringify(changes.map(({ manifestPath: _manifestPath, ...change }) => change), null, 2)
}
@@ -226,6 +229,15 @@ async function bumpPackageVersion (
throw new PnpmError('INVALID_VERSION', `Invalid version in ${pkgDir}: ${currentVersion}`)
}
const preVersionChange: VersionChange = {
name: manifest.name,
currentVersion,
newVersion: currentVersion,
path: pkgDir,
manifestPath: path.join(pkgDir, fileName),
}
await runVersionLifecycleHook('preversion', preVersionChange, opts)
const newVersion = explicitVersion ?? inc(currentVersion, rawBump as BumpType, false, opts.preid)
if (!newVersion) {
@@ -239,13 +251,37 @@ async function bumpPackageVersion (
manifest.version = newVersion
await writeProjectManifest(manifest)
return {
const change = {
name: manifest.name,
currentVersion,
newVersion,
path: pkgDir,
manifestPath: path.join(pkgDir, fileName),
}
await runVersionLifecycleHook('version', change, opts)
return change
}
async function runVersionLifecycleHook (stage: 'preversion' | 'version' | 'postversion', change: VersionChange, opts: VersionHandlerOptions): Promise<void> {
if (opts.ignoreScripts === true) return
const { manifest } = await readProjectManifest(change.path)
const lifecycleOpts: RunLifecycleHookOptions = {
depPath: change.name,
extraBinPaths: opts.extraBinPaths,
extraEnv: opts.extraEnv,
initCwd: opts.dir,
pkgRoot: change.path,
rootModulesDir: path.join(change.path, opts.modulesDir ?? 'node_modules'),
scriptShell: opts.scriptShell,
scriptsPrependNodePath: opts.scriptsPrependNodePath,
shellEmulator: opts.shellEmulator,
stdio: 'inherit',
unsafePerm: opts.unsafePerm ?? false,
userAgent: opts.userAgent,
}
await runLifecycleHook(stage, manifest, lifecycleOpts)
}
async function commitAndTag (changes: VersionChange[], opts: VersionHandlerOptions & { cwd: string }): Promise<void> {

View File

@@ -229,6 +229,38 @@ describe('version command', () => {
).rejects.toMatchObject({ code: 'ERR_PNPM_INVALID_VERSION' })
})
it('should run version lifecycle scripts in order', async () => {
const lifecycleLog = path.join(tempDir, 'lifecycle.log')
const logLifecycleScript = path.join(tempDir, 'log-lifecycle.cjs')
fs.writeFileSync(logLifecycleScript, `const fs = require('fs')
const path = require('path')
const manifest = require(path.join(process.cwd(), 'package.json'))
fs.appendFileSync(process.argv[2], process.argv[3] + ':' + manifest.version + '\\n')
`)
fs.writeFileSync(path.join(tempDir, 'package.json'), JSON.stringify({
name: 'test-pkg',
version: '1.0.0',
scripts: {
preversion: `node ${JSON.stringify(logLifecycleScript)} ${JSON.stringify(lifecycleLog)} preversion`,
version: `node ${JSON.stringify(logLifecycleScript)} ${JSON.stringify(lifecycleLog)} version`,
postversion: `node ${JSON.stringify(logLifecycleScript)} ${JSON.stringify(lifecycleLog)} postversion`,
},
}))
await handler({
dir: tempDir,
workspaceDir: tempDir,
gitChecks: false,
gitTagVersion: false,
} as any, ['patch']) // eslint-disable-line @typescript-eslint/no-explicit-any
expect(fs.readFileSync(lifecycleLog, 'utf-8')).toBe(
'preversion:1.0.0\n' +
'version:1.0.1\n' +
'postversion:1.0.1\n'
)
})
describe('git integration', () => {
let origCwd: string