Files
pnpm/pkg-manager/core/test/install/lifecycleScripts.ts
Zoltan Kochan 3a5bfaa94f chore: update zkochan packages to latest versions (#10930)
Update all packages from zkochan/packages to their latest major versions
and exclude them from minimumReleaseAge requirement. This includes
updating catalog entries, adapting to breaking API changes (default
exports replaced with named exports, sync functions renamed with Sync
suffix), and updating type declarations.
2026-03-11 13:47:46 +01:00

781 lines
31 KiB
TypeScript

import * as path from 'path'
import fs from 'fs'
import { assertProject } from '@pnpm/assert-project'
import type { LifecycleLog } from '@pnpm/core-loggers'
import { prepareEmpty, preparePackages } from '@pnpm/prepare'
import {
addDependenciesToPackage,
install,
mutateModulesInSingleProject,
type MutatedProject,
mutateModules,
} from '@pnpm/core'
import { createTestIpcServer } from '@pnpm/test-ipc-server'
import type { ProjectRootDir } from '@pnpm/types'
import { restartWorkerPool } from '@pnpm/worker'
import { jest } from '@jest/globals'
import { rimrafSync } from '@zkochan/rimraf'
import isWindows from 'is-windows'
import { loadJsonFileSync } from 'load-json-file'
import PATH from 'path-name'
import { testDefaults } from '../utils/index.js'
const testOnNonWindows = isWindows() ? test.skip : test
test('run pre/postinstall scripts', async () => {
const project = prepareEmpty()
const { updatedManifest: manifest } = await addDependenciesToPackage({},
['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'],
testDefaults({ fastUnpack: false, targetDependenciesField: 'devDependencies', allowBuilds: { '@pnpm.e2e/pre-and-postinstall-scripts-example': true } })
)
{
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-prepare.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeTruthy()
const generatedByPreinstall = project.requireModule('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall')
expect(typeof generatedByPreinstall).toBe('function')
const generatedByPostinstall = project.requireModule('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall')
expect(typeof generatedByPostinstall).toBe('function')
}
rimrafSync('node_modules')
// testing that the packages are not installed even though they are in lockfile
// and that their scripts are not tried to be executed
await install(manifest, testDefaults({ fastUnpack: false, production: true, allowBuilds: { '@pnpm.e2e/pre-and-postinstall-scripts-example': true } }))
{
const generatedByPreinstall = project.requireModule('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall')
expect(typeof generatedByPreinstall).toBe('function')
const generatedByPostinstall = project.requireModule('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall')
expect(typeof generatedByPostinstall).toBe('function')
}
})
test('return the list of packages that should be build', async () => {
prepareEmpty()
const allProjects = [
{
buildIndex: 0,
manifest: {
name: 'project',
version: '1.0.0',
dependencies: {
'@pnpm.e2e/pre-and-postinstall-scripts-example': '1.0.0',
},
},
rootDir: path.resolve('project') as ProjectRootDir,
},
]
const importers: MutatedProject[] = [
{
mutation: 'install',
rootDir: path.resolve('project') as ProjectRootDir,
},
]
const { depsRequiringBuild } = await mutateModules(importers,
testDefaults({ allProjects, enableModulesDir: false, returnListOfDepsRequiringBuild: true })
)
expect(depsRequiringBuild).toStrictEqual(['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'])
})
test('run pre/postinstall scripts, when PnP is used and no symlinks', async () => {
prepareEmpty()
await addDependenciesToPackage({},
['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'],
testDefaults({
fastUnpack: false,
enablePnp: true,
symlink: false,
targetDependenciesField: 'devDependencies',
allowBuilds: { '@pnpm.e2e/pre-and-postinstall-scripts-example': true },
})
)
const pkgDir = 'node_modules/.pnpm/@pnpm.e2e+pre-and-postinstall-scripts-example@1.0.0/node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example'
expect(fs.existsSync(path.resolve(pkgDir, 'generated-by-prepare.js'))).toBeFalsy()
expect(fs.existsSync(path.resolve(pkgDir, 'generated-by-preinstall.js'))).toBeTruthy()
expect(fs.existsSync(path.resolve(pkgDir, 'generated-by-postinstall.js'))).toBeTruthy()
})
test('testing that the bins are linked when the package with the bins was already in node_modules', async () => {
const project = prepareEmpty()
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['@pnpm.e2e/hello-world-js-bin'], testDefaults({ fastUnpack: false }))
await addDependenciesToPackage(manifest, ['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'], testDefaults({ fastUnpack: false, targetDependenciesField: 'devDependencies', allowBuilds: { '@pnpm.e2e/pre-and-postinstall-scripts-example': true } }))
const generatedByPreinstall = project.requireModule('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall')
expect(typeof generatedByPreinstall).toBe('function')
const generatedByPostinstall = project.requireModule('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall')
expect(typeof generatedByPostinstall).toBe('function')
})
test('run install scripts', async () => {
const project = prepareEmpty()
await addDependenciesToPackage({}, ['@pnpm.e2e/install-script-example'], testDefaults({ fastUnpack: false, allowBuilds: { '@pnpm.e2e/install-script-example': true } }))
const generatedByInstall = project.requireModule('@pnpm.e2e/install-script-example/generated-by-install')
expect(typeof generatedByInstall).toBe('function')
})
test('run install scripts in the current project', async () => {
await using server = await createTestIpcServer()
await using serverForDevPreinstall = await createTestIpcServer()
prepareEmpty()
const { updatedManifest: manifest } = await addDependenciesToPackage({
scripts: {
'pnpm:devPreinstall': `node -e "console.log('pnpm:devPreinstall-' + process.cwd())" | ${serverForDevPreinstall.generateSendStdinScript()}`,
install: `node -e "console.log('install-' + process.cwd())" | ${server.generateSendStdinScript()}`,
postinstall: `node -e "console.log('postinstall-' + process.cwd())" | ${server.generateSendStdinScript()}`,
preinstall: `node -e "console.log('preinstall-' + process.cwd())" | ${server.generateSendStdinScript()}`,
preprepare: `node -e "console.log('preprepare-' + process.cwd())" | ${server.generateSendStdinScript()}`,
postprepare: `node -e "console.log('postprepare-' + process.cwd())" | ${server.generateSendStdinScript()}`,
},
}, [], testDefaults({ fastUnpack: false }))
await install(manifest, testDefaults({ fastUnpack: false }))
expect(server.getLines()).toStrictEqual([`preinstall-${process.cwd()}`, `install-${process.cwd()}`, `postinstall-${process.cwd()}`, `preprepare-${process.cwd()}`, `postprepare-${process.cwd()}`])
expect(serverForDevPreinstall.getLines()).toStrictEqual([
// The pnpm:devPreinstall script runs twice in this test. Once for the
// initial "addDependenciesToPackage" test setup stage and again for the
// dedicated install afterwards.
`pnpm:devPreinstall-${process.cwd()}`,
`pnpm:devPreinstall-${process.cwd()}`,
])
})
test('run install scripts in the current project when its name is different than its directory', async () => {
await using server = await createTestIpcServer()
prepareEmpty()
const { updatedManifest: manifest } = await addDependenciesToPackage({
name: 'different-name',
scripts: {
install: `node -e "console.log('install-' + process.cwd())" | ${server.generateSendStdinScript()}`,
postinstall: `node -e "console.log('postinstall-' + process.cwd())" | ${server.generateSendStdinScript()}`,
preinstall: `node -e "console.log('preinstall-' + process.cwd())" | ${server.generateSendStdinScript()}`,
},
}, [], testDefaults({ fastUnpack: false }))
await install(manifest, testDefaults({ fastUnpack: false }))
expect(server.getLines()).toStrictEqual([
`preinstall-${process.cwd()}`,
`install-${process.cwd()}`,
`postinstall-${process.cwd()}`,
])
})
test('installation fails if lifecycle script fails', async () => {
prepareEmpty()
await expect(
install({
scripts: {
preinstall: 'exit 1',
},
}, testDefaults({ fastUnpack: false }))
).rejects.toThrow(/@ preinstall: `exit 1`/)
})
test('INIT_CWD is always set to lockfile directory', async () => {
prepareEmpty()
const rootDir = process.cwd() as ProjectRootDir
fs.mkdirSync('sub_dir')
process.chdir('sub_dir')
await mutateModulesInSingleProject({
mutation: 'install',
manifest: {
dependencies: {
'@pnpm.e2e/write-lifecycle-env': '1.0.0',
},
scripts: {
install: 'node -e "fs.writeFileSync(\'output.json\', JSON.stringify(process.env.INIT_CWD))"',
},
},
rootDir,
}, testDefaults({
fastUnpack: false,
lockfileDir: rootDir,
allowBuilds: { '@pnpm.e2e/write-lifecycle-env': true },
}))
const childEnv = loadJsonFileSync<{ INIT_CWD: string }>(path.join(rootDir, 'node_modules/@pnpm.e2e/write-lifecycle-env/env.json'))
expect(childEnv.INIT_CWD).toBe(rootDir)
const output = loadJsonFileSync(path.join(rootDir, 'output.json'))
expect(output).toStrictEqual(process.cwd())
})
// TODO: duplicate this test to @pnpm/lifecycle
test("reports child's output", async () => {
prepareEmpty()
const reporter = jest.fn()
await addDependenciesToPackage({}, ['@pnpm.e2e/count-to-10'], testDefaults({ fastUnpack: false, reporter, allowBuilds: { '@pnpm.e2e/count-to-10': true } }))
expect(reporter).toHaveBeenCalledWith(expect.objectContaining({
depPath: '@pnpm.e2e/count-to-10@1.0.0',
level: 'debug',
name: 'pnpm:lifecycle',
script: 'node postinstall',
stage: 'postinstall',
} as LifecycleLog))
expect(reporter).toHaveBeenCalledWith(expect.objectContaining({
depPath: '@pnpm.e2e/count-to-10@1.0.0',
level: 'debug',
line: '1',
name: 'pnpm:lifecycle',
stage: 'postinstall',
stdio: 'stdout',
} as LifecycleLog))
expect(reporter).toHaveBeenCalledWith(expect.objectContaining({
depPath: '@pnpm.e2e/count-to-10@1.0.0',
level: 'debug',
line: '2',
name: 'pnpm:lifecycle',
stage: 'postinstall',
stdio: 'stdout',
} as LifecycleLog))
expect(reporter).toHaveBeenCalledWith(expect.objectContaining({
depPath: '@pnpm.e2e/count-to-10@1.0.0',
level: 'debug',
line: '6',
name: 'pnpm:lifecycle',
stage: 'postinstall',
stdio: 'stderr',
} as LifecycleLog))
expect(reporter).toHaveBeenCalledWith(expect.objectContaining({
depPath: '@pnpm.e2e/count-to-10@1.0.0',
exitCode: 0,
level: 'debug',
name: 'pnpm:lifecycle',
stage: 'postinstall',
} as LifecycleLog))
})
test("reports child's close event", async () => {
prepareEmpty()
const reporter = jest.fn()
await expect(
addDependenciesToPackage({}, ['@pnpm.e2e/failing-postinstall'], testDefaults({ reporter, allowBuilds: { '@pnpm.e2e/failing-postinstall': true } }))
).rejects.toThrow()
expect(reporter).toHaveBeenCalledWith(expect.objectContaining({
depPath: '@pnpm.e2e/failing-postinstall@1.0.0',
exitCode: 1,
level: 'debug',
name: 'pnpm:lifecycle',
stage: 'postinstall',
} as LifecycleLog))
})
testOnNonWindows('lifecycle scripts have access to node-gyp', async () => {
prepareEmpty()
// `npm test` adds node-gyp to the PATH
// it is removed here to test that pnpm adds it
const initialPath = process.env[PATH]
if (typeof initialPath !== 'string') throw new Error('PATH is not defined')
process.env[PATH] = initialPath
.split(path.delimiter)
.filter((p: string) => !p.includes('node-gyp-bin') &&
!p.includes(`${path.sep}npm${path.sep}`) &&
!p.includes(`${path.sep}.npm${path.sep}`))
.join(path.delimiter)
await addDependenciesToPackage({}, ['drivelist@5.1.8'], testDefaults({ fastUnpack: false, allowBuilds: { drivelist: true } }))
process.env[PATH] = initialPath
})
test('run lifecycle scripts of dependent packages after running scripts of their deps', async () => {
const project = prepareEmpty()
await addDependenciesToPackage({}, ['@pnpm.e2e/with-postinstall-a'], testDefaults({ fastUnpack: false, allowBuilds: { '@pnpm.e2e/with-postinstall-a': true, '@pnpm.e2e/with-postinstall-b': true } }))
expect(+project.requireModule('.pnpm/@pnpm.e2e+with-postinstall-b@1.0.0/node_modules/@pnpm.e2e/with-postinstall-b/output.json')[0] < +project.requireModule('@pnpm.e2e/with-postinstall-a/output.json')[0]).toBeTruthy()
})
test('run prepare script for git-hosted dependencies', async () => {
const project = prepareEmpty()
await addDependenciesToPackage({}, ['pnpm/test-git-fetch#8b333f12d5357f4f25a654c305c826294cb073bf'], testDefaults({
fastUnpack: false,
allowBuilds: { 'test-git-fetch': true },
neverBuiltDependencies: undefined,
}))
const scripts = project.requireModule('test-git-fetch/output.json')
expect(scripts).toStrictEqual([
'preinstall',
'install',
'postinstall',
'prepare',
'preinstall',
'install',
'postinstall',
])
})
test('lifecycle scripts run before linking bins', async () => {
const project = prepareEmpty()
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['@pnpm.e2e/generated-bins'], testDefaults({ fastUnpack: false, allowBuilds: { '@pnpm.e2e/generated-bins': true } }))
project.isExecutable('.bin/cmd1')
project.isExecutable('.bin/cmd2')
rimrafSync('node_modules')
await mutateModulesInSingleProject({
manifest,
mutation: 'install',
rootDir: process.cwd() as ProjectRootDir,
}, testDefaults({ frozenLockfile: true, allowBuilds: { '@pnpm.e2e/generated-bins': true } }))
project.isExecutable('.bin/cmd1')
project.isExecutable('.bin/cmd2')
})
test('hoisting does not fail on commands that will be created by lifecycle scripts on a later stage', async () => {
prepareEmpty()
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['@pnpm.e2e/has-generated-bins-as-dep'], testDefaults({ fastUnpack: false, hoistPattern: '*', allowBuilds: { '@pnpm.e2e/has-generated-bins-as-dep': true, '@pnpm.e2e/generated-bins': true } }))
// project.isExecutable('.pnpm/node_modules/.bin/cmd1')
// project.isExecutable('.pnpm/node_modules/.bin/cmd2')
// Testing the same with headless installation
rimrafSync('node_modules')
await mutateModulesInSingleProject({
manifest,
mutation: 'install',
rootDir: process.cwd() as ProjectRootDir,
}, testDefaults({ frozenLockfile: true, hoistPattern: '*', allowBuilds: { '@pnpm.e2e/has-generated-bins-as-dep': true, '@pnpm.e2e/generated-bins': true } }))
// project.isExecutable('.pnpm/node_modules/.bin/cmd1')
// project.isExecutable('.pnpm/node_modules/.bin/cmd2')
})
test('bins are linked even if lifecycle scripts are ignored', async () => {
const project = prepareEmpty()
const { updatedManifest: manifest } = await addDependenciesToPackage(
{},
[
'@pnpm.e2e/pkg-with-peer-having-bin',
'@pnpm.e2e/peer-with-bin',
'@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0',
],
testDefaults({ fastUnpack: false, ignoreScripts: true })
)
project.isExecutable('.bin/peer-with-bin')
project.isExecutable('@pnpm.e2e/pkg-with-peer-having-bin/node_modules/.bin/hello-world-js-bin')
// Verifying that the scripts were ignored
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/package.json')).toBeTruthy()
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeFalsy()
rimrafSync('node_modules')
await mutateModulesInSingleProject({
manifest,
mutation: 'install',
rootDir: process.cwd() as ProjectRootDir,
}, testDefaults({ frozenLockfile: true, ignoreScripts: true }))
project.isExecutable('.bin/peer-with-bin')
project.isExecutable('@pnpm.e2e/pkg-with-peer-having-bin/node_modules/.bin/hello-world-js-bin')
// Verifying that the scripts were ignored
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/package.json')).toBeTruthy()
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeFalsy()
})
test('dependency should not be added to current lockfile if it was not built successfully during headless install', async () => {
const project = prepareEmpty()
const { updatedManifest: manifest } = await addDependenciesToPackage(
{},
[
'package-that-cannot-be-installed@0.0.0', // TODO: this package should be replaced
],
testDefaults({
ignoreScripts: true,
lockfileOnly: true,
})
)
await expect(
mutateModulesInSingleProject({
manifest,
mutation: 'install',
rootDir: process.cwd() as ProjectRootDir,
}, testDefaults({ frozenLockfile: true, allowBuilds: { 'package-that-cannot-be-installed': true } }))
).rejects.toThrow()
expect(project.readCurrentLockfile()).toBeFalsy()
})
test('scripts have access to unlisted bins when hoisting is used', async () => {
const project = prepareEmpty()
await addDependenciesToPackage(
{},
['@pnpm.e2e/pkg-that-calls-unlisted-dep-in-hooks'],
testDefaults({ fastUnpack: false, hoistPattern: '*', allowBuilds: { '@pnpm.e2e/pkg-that-calls-unlisted-dep-in-hooks': true } })
)
expect(project.requireModule('@pnpm.e2e/pkg-that-calls-unlisted-dep-in-hooks/output.json')).toStrictEqual(['Hello world!'])
})
test('selectively ignore scripts in some dependencies by allowBuilds (not others)', async () => {
prepareEmpty()
const allowBuilds = { '@pnpm.e2e/install-script-example': true }
const { updatedManifest: manifest } = await addDependenciesToPackage({},
['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'],
testDefaults({ fastUnpack: false, allowBuilds })
)
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
rimrafSync('node_modules')
await install(manifest, testDefaults({ fastUnpack: false, frozenLockfile: true, allowBuilds }))
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
})
test('selectively allow scripts in some dependencies by allowBuilds', async () => {
prepareEmpty()
const reporter = jest.fn()
const allowBuilds = { '@pnpm.e2e/install-script-example': true }
const { updatedManifest: manifest } = await addDependenciesToPackage({},
['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'],
testDefaults({ fastUnpack: false, allowBuilds, reporter })
)
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
{
const ignoredPkgsLog = reporter.mock.calls.find((call) => (call[0] as Record<string, unknown>).name === 'pnpm:ignored-scripts')![0] as Record<string, unknown>
expect(ignoredPkgsLog.packageNames).toStrictEqual(['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'])
}
reporter.mockClear()
rimrafSync('node_modules')
await install(manifest, testDefaults({
fastUnpack: false,
frozenLockfile: true,
allowBuilds: { ...allowBuilds, '@pnpm.e2e/pre-and-postinstall-scripts-example': false },
reporter,
}))
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
{
const ignoredPkgsLog = reporter.mock.calls.find((call) => (call[0] as Record<string, unknown>).name === 'pnpm:ignored-scripts')![0] as Record<string, unknown>
expect(ignoredPkgsLog.packageNames).toStrictEqual([])
}
})
test('selectively allow scripts in some dependencies by allowBuilds using exact versions', async () => {
prepareEmpty()
const reporter = jest.fn()
const allowBuilds = { '@pnpm.e2e/install-script-example@1.0.0': true }
const { updatedManifest: manifest } = await addDependenciesToPackage({},
['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'],
testDefaults({ fastUnpack: false, allowBuilds, reporter })
)
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
{
const ignoredPkgsLog = reporter.mock.calls.find((call) => (call[0] as Record<string, unknown>).name === 'pnpm:ignored-scripts')![0] as Record<string, unknown>
expect(ignoredPkgsLog.packageNames).toStrictEqual(['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'])
}
reporter.mockClear()
rimrafSync('node_modules')
await install(manifest, testDefaults({
fastUnpack: false,
frozenLockfile: true,
allowBuilds: { ...allowBuilds, '@pnpm.e2e/pre-and-postinstall-scripts-example': false },
reporter,
}))
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
{
const ignoredPkgsLog = reporter.mock.calls.find((call) => (call[0] as Record<string, unknown>).name === 'pnpm:ignored-scripts')![0] as Record<string, unknown>
expect(ignoredPkgsLog.packageNames).toStrictEqual([])
}
})
test('lifecycle scripts have access to package\'s own binary by binary name', async () => {
const project = prepareEmpty()
await addDependenciesToPackage({},
['@pnpm.e2e/runs-own-bin'],
testDefaults({ fastUnpack: false, allowBuilds: { '@pnpm.e2e/runs-own-bin': true } })
)
project.isExecutable('.pnpm/@pnpm.e2e+runs-own-bin@1.0.0/node_modules/@pnpm.e2e/runs-own-bin/node_modules/.bin/runs-own-bin')
})
test('lifecycle scripts run after linking root dependencies', async () => {
prepareEmpty()
const manifest = {
dependencies: {
'is-positive': '1.0.0',
'@pnpm.e2e/postinstall-requires-is-positive': '1.0.0',
},
}
await mutateModulesInSingleProject({
manifest,
mutation: 'install',
rootDir: process.cwd() as ProjectRootDir,
}, testDefaults({ fastUnpack: false, allowBuilds: { '@pnpm.e2e/postinstall-requires-is-positive': true } }))
rimrafSync('node_modules')
await mutateModulesInSingleProject({
manifest,
mutation: 'install',
rootDir: process.cwd() as ProjectRootDir,
}, testDefaults({ fastUnpack: false, frozenLockfile: true, allowBuilds: { '@pnpm.e2e/postinstall-requires-is-positive': true } }))
// if there was no exception, the test passed
})
test('ignore-dep-scripts', async () => {
await using server1 = await createTestIpcServer()
await using server2 = await createTestIpcServer()
prepareEmpty()
const manifest = {
scripts: {
'pnpm:devPreinstall': server2.sendLineScript('pnpm:devPreinstall'),
install: server1.sendLineScript('install'),
postinstall: server1.sendLineScript('postinstall'),
preinstall: server1.sendLineScript('preinstall'),
},
dependencies: {
'@pnpm.e2e/pre-and-postinstall-scripts-example': '1.0.0',
},
}
await install(manifest, testDefaults({ fastUnpack: false, ignoreDepScripts: true }))
expect(server1.getLines()).toStrictEqual(['preinstall', 'install', 'postinstall'])
expect(server2.getLines()).toStrictEqual(['pnpm:devPreinstall'])
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeFalsy()
rimrafSync('node_modules')
server1.clear()
server2.clear()
await install(manifest, testDefaults({ fastUnpack: false, ignoreDepScripts: true }))
expect(server1.getLines()).toStrictEqual(['preinstall', 'install', 'postinstall'])
expect(server2.getLines()).toStrictEqual(['pnpm:devPreinstall'])
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeFalsy()
})
test('run pre/postinstall scripts in a workspace that uses node-linker=hoisted', async () => {
await restartWorkerPool()
const projects = preparePackages([
{
location: 'project-1',
package: { name: 'project-1' },
},
{
location: 'project-2',
package: { name: 'project-2' },
},
{
location: 'project-3',
package: { name: 'project-3' },
},
{
location: 'project-4',
package: { name: 'project-4' },
},
])
const importers: MutatedProject[] = [
{
mutation: 'install',
rootDir: path.resolve('project-1') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('project-2') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('project-3') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('project-4') as ProjectRootDir,
},
]
const allProjects = [
{
buildIndex: 0,
manifest: {
name: 'project-1',
version: '1.0.0',
dependencies: {
'@pnpm.e2e/pre-and-postinstall-scripts-example': '1',
},
},
rootDir: path.resolve('project-1') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: {
name: 'project-2',
version: '1.0.0',
dependencies: {
'@pnpm.e2e/pre-and-postinstall-scripts-example': '1',
},
},
rootDir: path.resolve('project-2') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: {
name: 'project-3',
version: '1.0.0',
dependencies: {
'@pnpm.e2e/pre-and-postinstall-scripts-example': '2',
},
},
rootDir: path.resolve('project-3') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: {
name: 'project-4',
version: '1.0.0',
dependencies: {
'@pnpm.e2e/pre-and-postinstall-scripts-example': '2',
},
},
rootDir: path.resolve('project-4') as ProjectRootDir,
},
]
await mutateModules(importers, testDefaults({
allProjects,
fastUnpack: false,
nodeLinker: 'hoisted',
allowBuilds: { '@pnpm.e2e/pre-and-postinstall-scripts-example': true },
}))
const rootProject = assertProject(process.cwd())
rootProject.has('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')
rootProject.has('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')
projects['project-1'].hasNot('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')
projects['project-1'].hasNot('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')
projects['project-2'].hasNot('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')
projects['project-2'].hasNot('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')
projects['project-3'].has('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')
projects['project-3'].has('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')
projects['project-4'].has('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')
projects['project-4'].has('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')
})
test('run pre/postinstall scripts in a project that uses node-linker=hoisted. Should not fail on repeat install', async () => {
const project = prepareEmpty()
const { updatedManifest: manifest } = await addDependenciesToPackage({},
['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'],
testDefaults({ fastUnpack: false, targetDependenciesField: 'devDependencies', nodeLinker: 'hoisted', sideEffectsCacheRead: true, sideEffectsCacheWrite: true, allowBuilds: { '@pnpm.e2e/pre-and-postinstall-scripts-example': true } })
)
{
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-prepare.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeTruthy()
const generatedByPreinstall = project.requireModule('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall')
expect(typeof generatedByPreinstall).toBe('function')
const generatedByPostinstall = project.requireModule('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall')
expect(typeof generatedByPostinstall).toBe('function')
}
const reporter = jest.fn()
await addDependenciesToPackage(manifest,
['example@npm:@pnpm.e2e/pre-and-postinstall-scripts-example@2.0.0'],
testDefaults({
fastUnpack: false,
targetDependenciesField: 'devDependencies',
nodeLinker: 'hoisted',
reporter,
sideEffectsCacheRead: true,
sideEffectsCacheWrite: true,
allowBuilds: { '@pnpm.e2e/pre-and-postinstall-scripts-example': true },
})
)
expect(reporter).not.toHaveBeenCalledWith(expect.objectContaining({
level: 'warn',
message: `An error occurred while uploading ${path.resolve('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example')}`,
}))
})
test('build dependencies that were not previously built after allowBuilds changes', async () => {
prepareEmpty()
const { updatedManifest: manifest } = await addDependenciesToPackage({},
['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'],
testDefaults({
fastUnpack: false,
allowBuilds: { '@pnpm.e2e/install-script-example': true },
})
)
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
await install(manifest, testDefaults({
fastUnpack: false,
frozenLockfile: true,
allowBuilds: { '@pnpm.e2e/install-script-example': true, '@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0': true },
}))
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeTruthy()
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeTruthy()
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
})