mirror of
https://github.com/pnpm/pnpm.git
synced 2026-07-21 05:02:30 -04:00
When `runLifecycleHooksConcurrently` re-imports a workspace package into its injected targets after running prepare/postinstall, it has to preserve the existing `node_modules/` inside each target — set up during the initial install (bin symlinks, transitive deps) — while overlaying the script's output (typically a freshly-built `dist/`). The previous approach was a manual `scanDir` over `<targetDir>/node_modules` that fed absolute paths into the re-import's `filesMap`. That worked in 2022 (#4299) when `importIndexedDir` always staged-and-renamed, but #11088 made the fast path (write directly into the final dir) the default. The fast path's `makeEmptyDirSync(targetDir)` wipes the very files the scanned entries point at, so `tryImportIndexedDir`'s first copy throws: ERR_PNPM_ENOENT on <targetDir>/node_modules/.bin/<tool> Reproduces on any workspace package that has a `prepare`/`postinstall` script + a dep with a bin entry, when injected via `injectWorkspacePackages: true` or `dependenciesMeta.<dep>.injected`. Fix: skip the `importPackage` round-trip entirely. After lifecycle scripts complete, iterate the source's `filesMap` (from `fetchFromDir`, which already excludes `node_modules/`) and copy each entry directly into each `targetDir`. The target's existing `node_modules/` is never touched — its bin links + transitive deps stay intact — and there's no tmp-dir staging swap to trip over `.bin/<tool>` symlinks. Regression test in `injectLocalPackages.ts`: an injected workspace package with a `prepublishOnly` script + `@pnpm.e2e/hello-world-js-bin` dep. Before the fix this throws `ERR_PNPM_ENOENT` on `.bin/hello-world-js-bin`; after the fix both the script's output and the bin symlink survive into the injected copy.
@pnpm/exec.lifecycle
Package lifecycle hook runner
Installation
pnpm add @pnpm/logger @pnpm/exec.lifecycle
Usage
import runLifecycleHook, {runPostinstallHooks} from '@pnpm/exec.lifecycle'
const targetPkgRoot = path.resolve('node_modules/target-pkg')
const pkg = require(path.join(targetPkgRoot, 'package.json'))
// Run a specific hook
await runLifecycleHook('preinstall', pkg, {
pkgId: 'target-pkg/1.0.0',
pkgRoot: targetPkgRoot,
rawConfig: {},
rootModulesDir: path.resolve('node_modules'),
unsafePerm: true,
})
// Run all install hooks
await runPostinstallHooks({
pkgId: 'target-pkg/1.0.0',
pkgRoot: targetPkgRoot,
rawConfig: {},
rootModulesDir: path.resolve('node_modules'),
unsafePerm: true,
})
API
runLifecycleHook(stage, packageManifest, opts): Promise<void>
runPostinstallHooks(opts): Promise<void>
License
MIT