fix: link local bins created by postinstall scripts (#6728)

close #1801
This commit is contained in:
Zoltan Kochan
2023-06-29 11:59:56 +03:00
committed by GitHub
parent e0325bda9b
commit dddb8ad713
10 changed files with 102 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
---
"@pnpm/plugin-commands-rebuild": patch
"@pnpm/headless": patch
"@pnpm/core": patch
"@pnpm/lifecycle": patch
"pnpm": patch
---
Local workspace bin files that should be compiled first are linked to dependent projects after compilation [#1801](https://github.com/pnpm/pnpm/issues/1801).

View File

@@ -38,6 +38,7 @@
"@pnpm/core-loggers": "workspace:*",
"@pnpm/directory-fetcher": "workspace:*",
"@pnpm/error": "workspace:*",
"@pnpm/link-bins": "workspace:*",
"@pnpm/npm-lifecycle": "^2.0.1",
"@pnpm/read-package-json": "workspace:*",
"@pnpm/store-controller-types": "workspace:*",

View File

@@ -1,4 +1,6 @@
import fs from 'fs'
import { linkBins } from '@pnpm/link-bins'
import { logger } from '@pnpm/logger'
import path from 'path'
import { fetchFromDir } from '@pnpm/directory-fetcher'
import { type StoreController } from '@pnpm/store-controller-types'
@@ -13,6 +15,8 @@ export type RunLifecycleHooksConcurrentlyOptions = Omit<RunLifecycleHookOptions,
> & {
resolveSymlinksInInjectedDirs?: boolean
storeController: StoreController
extraNodePaths?: string[]
preferSymlinkedExecutables?: boolean
}
export interface Importer {
@@ -43,6 +47,16 @@ export async function runLifecycleHooksConcurrently (
const importers = importersByBuildIndex.get(buildIndex)!
return importers.map(({ manifest, modulesDir, rootDir, stages: importerStages, targetDirs }) =>
async () => {
// We are linking the bin files, in case they were created by lifecycle scripts of other workspace packages.
await linkBins(modulesDir, path.join(modulesDir, '.bin'), {
extraNodePaths: opts.extraNodePaths,
allowExoticManifests: true,
preferSymlinkedExecutables: opts.preferSymlinkedExecutables,
projectManifest: manifest,
warn: (message: string) => {
logger.warn({ message, prefix: rootDir })
},
})
const runLifecycleHookOpts = {
...opts,
depPath: rootDir,

View File

@@ -24,6 +24,9 @@
{
"path": "../../packages/types"
},
{
"path": "../../pkg-manager/link-bins"
},
{
"path": "../../pkg-manifest/read-package-json"
},

View File

@@ -14,6 +14,7 @@ export interface StrictRebuildOptions {
extraEnv: Record<string, string>
lockfileDir: string
nodeLinker: 'isolated' | 'hoisted' | 'pnp'
preferSymlinkedExecutables?: boolean
scriptShell?: string
sideEffectsCacheRead: boolean
scriptsPrependNodePath: boolean | 'warn-only'

View File

@@ -157,7 +157,9 @@ export async function rebuildProjects (
const store = await createOrConnectStoreController(opts)
const scriptsOpts = {
extraBinPaths: ctx.extraBinPaths,
extraNodePaths: ctx.extraNodePaths,
extraEnv: opts.extraEnv,
preferSymlinkedExecutables: opts.preferSymlinkedExecutables,
rawConfig: opts.rawConfig,
scriptsPrependNodePath: opts.scriptsPrependNodePath,
scriptShell: opts.scriptShell,

View File

@@ -293,7 +293,9 @@ export async function mutateModules (
async function _install (): Promise<{ updatedProjects: UpdatedProject[], stats?: InstallationResultStats }> {
const scriptsOpts: RunLifecycleHooksConcurrentlyOptions = {
extraBinPaths: opts.extraBinPaths,
extraNodePaths: ctx.extraNodePaths,
extraEnv: opts.extraEnv,
preferSymlinkedExecutables: opts.preferSymlinkedExecutables,
rawConfig: opts.rawConfig,
resolveSymlinksInInjectedDirs: opts.resolveSymlinksInInjectedDirs,
scriptsPrependNodePath: opts.scriptsPrependNodePath,

View File

@@ -1,3 +1,4 @@
import fs from 'fs'
import path from 'path'
import { assertProject } from '@pnpm/assert-project'
import { LOCKFILE_VERSION_V6 as LOCKFILE_VERSION } from '@pnpm/constants'
@@ -1894,3 +1895,67 @@ test('do not symlink local package from the location described in its publishCon
const linkedManifest = await loadJsonFile<{ name: string }>('project-2/node_modules/project-1/package.json')
expect(linkedManifest.name).toBe('project-1')
})
test('link the bin file of a workspace project that is created by a lifecycle script', async () => {
const pkg1 = {
name: 'project-1',
version: '1.0.0',
scripts: {
prepare: 'bin',
},
dependencies: {
'project-2': 'link:../project-2',
},
}
const pkg2 = {
name: 'project-2',
version: '1.0.0',
bin: {
bin: 'bin.js',
},
scripts: {
prepare: 'node -e "require(\'fs\').renameSync(\'__bin.js\', \'bin.js\')"',
},
dependencies: {},
}
preparePackages([pkg1, pkg2])
fs.writeFileSync('project-2/__bin.js', `#!/usr/bin/env node
require("fs").writeFileSync("created-by-prepare", "", "utf8")`)
const importers: MutatedProject[] = [
{
mutation: 'install',
rootDir: path.resolve('project-1'),
},
{
mutation: 'install',
rootDir: path.resolve('project-2'),
},
]
const allProjects = [
{
buildIndex: 1,
manifest: pkg1,
rootDir: path.resolve('project-1'),
},
{
buildIndex: 0,
manifest: pkg2,
rootDir: path.resolve('project-2'),
},
]
await mutateModules(importers, await testDefaults({ allProjects }))
expect(fs.existsSync('project-1/created-by-prepare')).toBeTruthy()
await rimraf('node_modules')
await rimraf('project-1/node_modules')
await rimraf('project-2/node_modules')
fs.renameSync('project-2/bin.js', 'project-2/__bin.js')
await mutateModules(importers, await testDefaults({ allProjects, frozenLockfile: true }))
expect(fs.existsSync('project-1/created-by-prepare')).toBeTruthy()
})

View File

@@ -195,6 +195,8 @@ export async function headlessInstall (opts: HeadlessOptions): Promise<Installat
const scriptsOpts = {
optional: false,
extraBinPaths: opts.extraBinPaths,
extraNodePaths: opts.extraNodePaths,
preferSymlinkedExecutables: opts.preferSymlinkedExecutables,
extraEnv: opts.extraEnv,
rawConfig: opts.rawConfig,
resolveSymlinksInInjectedDirs: opts.resolveSymlinksInInjectedDirs,

3
pnpm-lock.yaml generated
View File

@@ -1020,6 +1020,9 @@ importers:
'@pnpm/error':
specifier: workspace:*
version: link:../../packages/error
'@pnpm/link-bins':
specifier: workspace:*
version: link:../../pkg-manager/link-bins
'@pnpm/logger':
specifier: ^5.0.0
version: 5.0.0