mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-23 23:29:17 -05:00
* fix(git-fetcher): block git dependencies from running prepare scripts unless allowed * Update exec/prepare-package/src/index.ts Co-authored-by: Zoltan Kochan <z@kochan.io> * Also implement in gitHostedTarballFetcher * refactor: move allowBuild function creation to the store manager * refactor: pass allowBuild function to fetch function directly * refactor: revert not needed changes and update changesets * test: fix * fix: implemented CR suggestions * test: fix * test: fix --------- Co-authored-by: Zoltan Kochan <z@kochan.io>
800 lines
30 KiB
TypeScript
800 lines
30 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 { sync as rimraf } from '@zkochan/rimraf'
|
|
import isWindows from 'is-windows'
|
|
import { loadJsonFileSync } from 'load-json-file'
|
|
import PATH from 'path-name'
|
|
import sinon from 'sinon'
|
|
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' })
|
|
)
|
|
|
|
{
|
|
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 { 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' }))
|
|
|
|
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 { 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,
|
|
}))
|
|
|
|
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 = 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,
|
|
onlyBuiltDependencies: ['test-git-fetch'],
|
|
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 }))
|
|
|
|
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 { updatedManifest: 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 { 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()
|
|
|
|
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 { 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 }))
|
|
).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 { updatedManifest: 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 reporter = sinon.spy()
|
|
const onlyBuiltDependencies = ['@pnpm.e2e/install-script-example']
|
|
const neverBuiltDependencies: string[] | undefined = undefined
|
|
const { updatedManifest: manifest } = await addDependenciesToPackage({},
|
|
['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'],
|
|
testDefaults({ fastUnpack: false, onlyBuiltDependencies, neverBuiltDependencies, 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.getCalls().find((call) => call.firstArg.name === 'pnpm:ignored-scripts')?.firstArg
|
|
expect(ignoredPkgsLog.packageNames).toStrictEqual(['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'])
|
|
}
|
|
reporter.resetHistory()
|
|
|
|
rimraf('node_modules')
|
|
|
|
await install(manifest, testDefaults({
|
|
fastUnpack: false,
|
|
frozenLockfile: true,
|
|
ignoredBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example'],
|
|
neverBuiltDependencies,
|
|
onlyBuiltDependencies,
|
|
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.getCalls().find((call) => call.firstArg.name === 'pnpm:ignored-scripts')?.firstArg
|
|
expect(ignoredPkgsLog.packageNames).toStrictEqual([])
|
|
}
|
|
})
|
|
|
|
test('selectively allow scripts in some dependencies by onlyBuiltDependencies using exact versions', async () => {
|
|
prepareEmpty()
|
|
const reporter = sinon.spy()
|
|
const onlyBuiltDependencies = ['@pnpm.e2e/install-script-example@1.0.0']
|
|
const neverBuiltDependencies: string[] | undefined = undefined
|
|
const { updatedManifest: manifest } = await addDependenciesToPackage({},
|
|
['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'],
|
|
testDefaults({ fastUnpack: false, onlyBuiltDependencies, neverBuiltDependencies, 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.getCalls().find((call) => call.firstArg.name === 'pnpm:ignored-scripts')?.firstArg
|
|
expect(ignoredPkgsLog.packageNames).toStrictEqual(['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'])
|
|
}
|
|
reporter.resetHistory()
|
|
|
|
rimraf('node_modules')
|
|
|
|
await install(manifest, testDefaults({
|
|
fastUnpack: false,
|
|
frozenLockfile: true,
|
|
ignoredBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example'],
|
|
neverBuiltDependencies,
|
|
onlyBuiltDependencies,
|
|
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.getCalls().find((call) => call.firstArg.name === 'pnpm:ignored-scripts')?.firstArg
|
|
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 })
|
|
)
|
|
|
|
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 { 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 })
|
|
)
|
|
|
|
{
|
|
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')}`,
|
|
}))
|
|
})
|
|
|
|
test('build dependencies that were not previously built after onlyBuiltDependencies changes', async () => {
|
|
prepareEmpty()
|
|
const neverBuiltDependencies: string[] | undefined = undefined
|
|
const { updatedManifest: manifest } = await addDependenciesToPackage({},
|
|
['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'],
|
|
testDefaults({
|
|
fastUnpack: false,
|
|
onlyBuiltDependencies: ['@pnpm.e2e/install-script-example'],
|
|
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()
|
|
|
|
await install(manifest, testDefaults({
|
|
fastUnpack: false,
|
|
frozenLockfile: true,
|
|
ignoredBuiltDependencies: [],
|
|
neverBuiltDependencies,
|
|
onlyBuiltDependencies: ['@pnpm.e2e/install-script-example', '@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'],
|
|
}))
|
|
|
|
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()
|
|
})
|