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 { sync as rimraf } from '@zkochan/rimraf' import isWindows from 'is-windows' import loadJsonFile from 'load-json-file' import PATH from 'path-name' import sinon from 'sinon' import { testDefaults } from '../utils' const testOnNonWindows = isWindows() ? test.skip : test test('run pre/postinstall scripts', async () => { const project = prepareEmpty() const manifest = await addDependenciesToPackage({}, ['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'], testDefaults({ fastUnpack: false, targetDependenciesField: 'devDependencies' }) ) { 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') } rimraf('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 })) { 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', }) ) 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 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' })) 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 })) 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 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()}`, }, }, [], testDefaults({ fastUnpack: false })) await install(manifest, testDefaults({ fastUnpack: false })) expect(server.getLines()).toStrictEqual([`preinstall-${process.cwd()}`, `install-${process.cwd()}`, `postinstall-${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 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, })) const childEnv = loadJsonFile.sync<{ INIT_CWD: string }>(path.join(rootDir, 'node_modules/@pnpm.e2e/write-lifecycle-env/env.json')) expect(childEnv.INIT_CWD).toBe(rootDir) const output = loadJsonFile.sync(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 = sinon.spy() await addDependenciesToPackage({}, ['@pnpm.e2e/count-to-10'], testDefaults({ fastUnpack: false, reporter })) expect(reporter.calledWithMatch({ depPath: '@pnpm.e2e/count-to-10@1.0.0', level: 'debug', name: 'pnpm:lifecycle', script: 'node postinstall', stage: 'postinstall', } as LifecycleLog)).toBeTruthy() expect(reporter.calledWithMatch({ depPath: '@pnpm.e2e/count-to-10@1.0.0', level: 'debug', line: '1', name: 'pnpm:lifecycle', stage: 'postinstall', stdio: 'stdout', } as LifecycleLog)).toBeTruthy() expect(reporter.calledWithMatch({ depPath: '@pnpm.e2e/count-to-10@1.0.0', level: 'debug', line: '2', name: 'pnpm:lifecycle', stage: 'postinstall', stdio: 'stdout', } as LifecycleLog)).toBeTruthy() expect(reporter.calledWithMatch({ depPath: '@pnpm.e2e/count-to-10@1.0.0', level: 'debug', line: '6', name: 'pnpm:lifecycle', stage: 'postinstall', stdio: 'stderr', } as LifecycleLog)).toBeTruthy() expect(reporter.calledWithMatch({ depPath: '@pnpm.e2e/count-to-10@1.0.0', exitCode: 0, level: 'debug', name: 'pnpm:lifecycle', stage: 'postinstall', } as LifecycleLog)).toBeTruthy() }) test("reports child's close event", async () => { prepareEmpty() const reporter = sinon.spy() await expect( addDependenciesToPackage({}, ['@pnpm.e2e/failing-postinstall'], testDefaults({ reporter })) ).rejects.toThrow() expect(reporter.calledWithMatch({ depPath: '@pnpm.e2e/failing-postinstall@1.0.0', exitCode: 1, level: 'debug', name: 'pnpm:lifecycle', stage: 'postinstall', } as LifecycleLog)).toBeTruthy() }) 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 })) 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 })) 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 })) 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 manifest = await addDependenciesToPackage({}, ['@pnpm.e2e/generated-bins'], testDefaults({ fastUnpack: false })) project.isExecutable('.bin/cmd1') project.isExecutable('.bin/cmd2') rimraf('node_modules') await mutateModulesInSingleProject({ manifest, mutation: 'install', rootDir: process.cwd() as ProjectRootDir, }, testDefaults({ frozenLockfile: 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 manifest = await addDependenciesToPackage({}, ['@pnpm.e2e/has-generated-bins-as-dep'], testDefaults({ fastUnpack: false, hoistPattern: '*' })) // project.isExecutable('.pnpm/node_modules/.bin/cmd1') // project.isExecutable('.pnpm/node_modules/.bin/cmd2') // Testing the same with headless installation rimraf('node_modules') await mutateModulesInSingleProject({ manifest, mutation: 'install', rootDir: process.cwd() as ProjectRootDir, }, testDefaults({ frozenLockfile: true, hoistPattern: '*' })) // 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 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() rimraf('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 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 })) ).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: '*' }) ) 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 neverBuiltDependencies', async () => { prepareEmpty() const neverBuiltDependencies = ['@pnpm.e2e/pre-and-postinstall-scripts-example'] const manifest = await addDependenciesToPackage({}, ['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'], testDefaults({ fastUnpack: false, neverBuiltDependencies }) ) 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() rimraf('node_modules') await install(manifest, testDefaults({ fastUnpack: false, frozenLockfile: true, neverBuiltDependencies })) 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('throw an exception when both neverBuiltDependencies and onlyBuiltDependencies are used', async () => { prepareEmpty() await expect( addDependenciesToPackage( {}, ['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'], testDefaults({ onlyBuiltDependencies: ['@pnpm.e2e/foo'], neverBuiltDependencies: ['@pnpm.e2e/bar'] }) ) ).rejects.toThrow(/Cannot have both/) }) test('selectively allow scripts in some dependencies by onlyBuiltDependencies', async () => { prepareEmpty() const onlyBuiltDependencies = ['@pnpm.e2e/install-script-example'] const manifest = await addDependenciesToPackage({}, ['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'], testDefaults({ fastUnpack: false, onlyBuiltDependencies }) ) 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() rimraf('node_modules') await install(manifest, testDefaults({ fastUnpack: false, frozenLockfile: true, onlyBuiltDependencies })) 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 onlyBuiltDependenciesFile', async () => { prepareEmpty() const onlyBuiltDependenciesFile = path.resolve('node_modules/@pnpm.e2e/build-allow-list/list.json') const manifest = await addDependenciesToPackage({}, ['@pnpm.e2e/build-allow-list', '@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'], testDefaults({ fastUnpack: false, onlyBuiltDependenciesFile }) ) 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() rimraf('node_modules') await install(manifest, testDefaults({ fastUnpack: false, frozenLockfile: true, onlyBuiltDependenciesFile })) 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 onlyBuiltDependenciesFile and onlyBuiltDependencies', async () => { prepareEmpty() const onlyBuiltDependenciesFile = path.resolve('node_modules/@pnpm.e2e/build-allow-list/list.json') const onlyBuiltDependencies = ['@pnpm.e2e/pre-and-postinstall-scripts-example'] const manifest = await addDependenciesToPackage({}, ['@pnpm.e2e/build-allow-list', '@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'], testDefaults({ fastUnpack: false, onlyBuiltDependenciesFile, onlyBuiltDependencies }) ) 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() rimraf('node_modules') await install(manifest, testDefaults({ fastUnpack: false, frozenLockfile: true, onlyBuiltDependenciesFile, onlyBuiltDependencies })) 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() }) 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 }) ) 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 })) rimraf('node_modules') await mutateModulesInSingleProject({ manifest, mutation: 'install', rootDir: process.cwd() as ProjectRootDir, }, testDefaults({ fastUnpack: false, frozenLockfile: 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() rimraf('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', })) 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 manifest = await addDependenciesToPackage({}, ['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'], testDefaults({ fastUnpack: false, targetDependenciesField: 'devDependencies', nodeLinker: 'hoisted', sideEffectsCacheRead: true, sideEffectsCacheWrite: 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, }) ) 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')}`, })) })