Skip to content

Commit

Permalink
fix: link local bins created by postinstall scripts (#6728)
Browse files Browse the repository at this point in the history
close #1801
  • Loading branch information
zkochan committed Jun 30, 2023
1 parent 3a760ba commit dde1410
Show file tree
Hide file tree
Showing 10 changed files with 102 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .changeset/plenty-bags-fix.md
Original file line number Diff line number Diff line change
@@ -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).
1 change: 1 addition & 0 deletions exec/lifecycle/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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:*",
Expand Down
14 changes: 14 additions & 0 deletions exec/lifecycle/src/runLifecycleHooksConcurrently.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -13,6 +15,8 @@ export type RunLifecycleHooksConcurrentlyOptions = Omit<RunLifecycleHookOptions,
> & {
resolveSymlinksInInjectedDirs?: boolean
storeController: StoreController
extraNodePaths?: string[]
preferSymlinkedExecutables?: boolean
}

export interface Importer {
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions exec/lifecycle/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
{
"path": "../../packages/types"
},
{
"path": "../../pkg-manager/link-bins"
},
{
"path": "../../pkg-manifest/read-package-json"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 2 additions & 0 deletions exec/plugin-commands-rebuild/src/implementation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions pkg-manager/core/src/install/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,9 @@ export async function mutateModules (
async function _install (): Promise<UpdatedProject[]> {
const scriptsOpts: RunLifecycleHooksConcurrentlyOptions = {
extraBinPaths: opts.extraBinPaths,
extraNodePaths: ctx.extraNodePaths,
extraEnv: opts.extraEnv,
preferSymlinkedExecutables: opts.preferSymlinkedExecutables,
rawConfig: opts.rawConfig,
resolveSymlinksInInjectedDirs: opts.resolveSymlinksInInjectedDirs,
scriptsPrependNodePath: opts.scriptsPrependNodePath,
Expand Down
65 changes: 65 additions & 0 deletions pkg-manager/core/test/install/multipleImporters.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import fs from 'fs'
import path from 'path'
import { assertProject } from '@pnpm/assert-project'
import { LOCKFILE_VERSION } from '@pnpm/constants'
Expand Down Expand Up @@ -1887,3 +1888,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()
})
2 changes: 2 additions & 0 deletions pkg-manager/headless/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@ export async function headlessInstall (opts: HeadlessOptions) {
const scriptsOpts = {
optional: false,
extraBinPaths: opts.extraBinPaths,
extraNodePaths: opts.extraNodePaths,
preferSymlinkedExecutables: opts.preferSymlinkedExecutables,
extraEnv: opts.extraEnv,
rawConfig: opts.rawConfig,
resolveSymlinksInInjectedDirs: opts.resolveSymlinksInInjectedDirs,
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit dde1410

Please sign in to comment.