import fs from 'node:fs' import path from 'node:path' import { expect, jest, test } from '@jest/globals' import type { VerifyDepsBeforeRun } from '@pnpm/config.reader' import { prepare } from '@pnpm/prepare' import { DEFAULT_OPTS } from './utils/index.js' const originalModule = await import('@pnpm/logger') jest.unstable_mockModule('@pnpm/logger', () => { return { ...originalModule, globalWarn: jest.fn(), } }) jest.unstable_mockModule('@inquirer/prompts', () => { class Separator { separator: string readonly type = 'separator' as const constructor (separator: string) { this.separator = separator } } return { Separator, checkbox: jest.fn(), confirm: jest.fn(), input: jest.fn(), password: jest.fn(), select: jest.fn(), } }) const { run } = await import('@pnpm/exec.commands') const { confirm } = await import('@inquirer/prompts') const { globalWarn } = await import('@pnpm/logger') const mockConfirm = jest.mocked(confirm) const rootProjectManifest = { name: 'root', private: true, dependencies: { 'is-positive': '1.0.0', }, scripts: { test: 'echo hello from script', }, } async function runTest (verifyDepsBeforeRun: VerifyDepsBeforeRun): Promise { await run.handler({ ...DEFAULT_OPTS, bin: 'node_modules/.bin', dir: process.cwd(), extraBinPaths: [], extraEnv: {}, pnpmHomeDir: '', verifyDepsBeforeRun, rootProjectManifest, rootProjectManifestDir: process.cwd(), }, ['test']) } test('throw an error if verifyDepsBeforeRun is set to error', async () => { prepare(rootProjectManifest) let err!: Error try { await runTest('error') } catch (_err) { err = _err as Error } expect(err.message).toContain('Cannot check whether dependencies are outdated') }) test('install the dependencies if verifyDepsBeforeRun is set to install', async () => { prepare(rootProjectManifest) await runTest('install') expect(fs.existsSync(path.resolve('node_modules'))).toBeTruthy() }) test('log a warning if verifyDepsBeforeRun is set to warn', async () => { prepare(rootProjectManifest) await runTest('warn') expect(globalWarn).toHaveBeenCalledWith( expect.stringContaining('Your node_modules are out of sync with your lockfile') ) expect(fs.existsSync(path.resolve('node_modules'))).toBeFalsy() }) test('prompt the user if verifyDepsBeforeRun is set to prompt', async () => { prepare(rootProjectManifest) mockConfirm.mockResolvedValue(true) const originalIsTTY = process.stdin.isTTY Object.defineProperty(process.stdin, 'isTTY', { value: true, configurable: true }) try { await runTest('prompt') } finally { Object.defineProperty(process.stdin, 'isTTY', { value: originalIsTTY, configurable: true }) } expect(mockConfirm).toHaveBeenCalledWith( expect.objectContaining({ message: expect.stringContaining( 'Your "node_modules" directory is out of sync with the "pnpm-lock.yaml" file' ), default: true, }) ) expect(fs.existsSync(path.resolve('node_modules'))).toBeTruthy() }) test('throw an error if verifyDepsBeforeRun is set to prompt in non-TTY environment', async () => { prepare(rootProjectManifest) // Mock non-TTY environment const originalIsTTY = process.stdin.isTTY Object.defineProperty(process.stdin, 'isTTY', { value: false, configurable: true }) let err!: Error try { await runTest('prompt') } catch (_err) { err = _err as Error } finally { // Restore original value Object.defineProperty(process.stdin, 'isTTY', { value: originalIsTTY, configurable: true }) } expect(err.message).toContain('Cannot check whether dependencies are outdated') expect(fs.existsSync(path.resolve('node_modules'))).toBeFalsy() })