feat: allow to set multiple pnpmfiles (#9702)

This commit is contained in:
Zoltan Kochan
2025-07-08 14:54:07 +02:00
committed by GitHub
parent 623da6fd27
commit cf630a8e84
54 changed files with 392 additions and 203 deletions

View File

@@ -0,0 +1,12 @@
---
"@pnpm/workspace.state": major
"@pnpm/pnpmfile": major
"@pnpm/cli-utils": major
"@pnpm/deps.status": major
"@pnpm/plugin-commands-installation": minor
"@pnpm/core": minor
"@pnpm/config": minor
"pnpm": minor
---
Added the possibility to load multiple pnpmfiles. The `pnpmfile` setting can now accept a list of pnpmfile locations [#9702](https://github.com/pnpm/pnpm/pull/9702).

View File

@@ -0,0 +1,5 @@
---
"@pnpm/crypto.hash": minor
---
A new function added: createHashFromMultipleFiles.

View File

@@ -0,0 +1,5 @@
---
"@pnpm/core": major
---
`hooks.preResolution` is now an array of functions.

View File

@@ -35,10 +35,14 @@ export async function getConfig (
})
}
if (!config.ignorePnpmfile) {
config.hooks = requireHooks(config.lockfileDir ?? config.dir, config)
const { hooks, resolvedPnpmfilePaths } = requireHooks(config.lockfileDir ?? config.dir, config)
config.hooks = hooks
config.pnpmfile = resolvedPnpmfilePaths
if (config.hooks?.updateConfig) {
const updateConfigResult = config.hooks.updateConfig(config)
config = updateConfigResult instanceof Promise ? await updateConfigResult : updateConfigResult
for (const updateConfig of config.hooks.updateConfig) {
const updateConfigResult = updateConfig(config)
config = updateConfigResult instanceof Promise ? await updateConfigResult : updateConfigResult // eslint-disable-line no-await-in-loop
}
}
}

View File

@@ -138,7 +138,7 @@ export interface Config extends OptionsFromRootManifest {
lockfileOnly?: boolean // like npm's --package-lock-only
childConcurrency?: number
ignorePnpmfile?: boolean
pnpmfile: string
pnpmfile: string[] | string
hooks?: Hooks
packageImportMethod?: 'auto' | 'hardlink' | 'copy' | 'clone' | 'clone-or-copy'
hoistPattern?: string[]

View File

@@ -15,6 +15,14 @@ export function createHash (input: string): string {
return `sha256-${crypto.hash('sha256', input, 'base64')}`
}
export async function createHashFromMultipleFiles (files: string[]): Promise<string> {
if (files.length === 1) {
return createHashFromFile(files[0])
}
const hashes = await Promise.all(files.map(createHashFromFile))
return createHash(hashes.join(','))
}
export async function createHashFromFile (file: string): Promise<string> {
return createHash(await readNormalizedFile(file))
}

View File

@@ -41,7 +41,6 @@
"@pnpm/lockfile.settings-checker": "workspace:*",
"@pnpm/lockfile.verification": "workspace:*",
"@pnpm/parse-overrides": "workspace:*",
"@pnpm/pnpmfile": "workspace:*",
"@pnpm/resolver-base": "workspace:*",
"@pnpm/types": "workspace:*",
"@pnpm/workspace.find-packages": "workspace:*",

View File

@@ -28,7 +28,6 @@ import {
} from '@pnpm/lockfile.verification'
import { globalWarn, logger } from '@pnpm/logger'
import { parseOverrides } from '@pnpm/parse-overrides'
import { getPnpmfilePath } from '@pnpm/pnpmfile'
import { type WorkspacePackages } from '@pnpm/resolver-base'
import {
type DependencyManifest,
@@ -58,11 +57,11 @@ export type CheckDepsStatusOptions = Pick<Config,
| 'sharedWorkspaceLockfile'
| 'workspaceDir'
| 'patchesDir'
| 'pnpmfile'
| 'configDependencies'
> & {
ignoreFilteredInstallCache?: boolean
ignoredWorkspaceStateSettings?: Array<keyof WorkspaceStateSettings>
pnpmfile: string[]
} & WorkspaceStateSettings
export interface CheckDepsStatusResult {
@@ -227,12 +226,12 @@ async function _checkDepsStatus (opts: CheckDepsStatusOptions, workspaceState: W
return { upToDate: true, workspaceState }
}
const issue = await patchesAreModified({
const issue = await patchesOrHooksAreModified({
rootManifestOptions,
rootDir: rootProjectManifestDir,
lastValidatedTimestamp: workspaceState.lastValidatedTimestamp,
pnpmfile: opts.pnpmfile,
hadPnpmfile: workspaceState.pnpmfileExists,
currentPnpmfiles: opts.pnpmfile,
previousPnpmfiles: workspaceState.pnpmfiles,
})
if (issue) {
return { upToDate: false, issue, workspaceState }
@@ -328,7 +327,7 @@ async function _checkDepsStatus (opts: CheckDepsStatusOptions, workspaceState: W
await updateWorkspaceState({
allProjects,
workspaceDir,
pnpmfileExists: workspaceState.pnpmfileExists,
pnpmfiles: workspaceState.pnpmfiles,
settings: opts,
filteredInstall: workspaceState.filteredInstall,
})
@@ -370,12 +369,12 @@ async function _checkDepsStatus (opts: CheckDepsStatusOptions, workspaceState: W
if (!wantedLockfileStats) return throwLockfileNotFound(rootProjectManifestDir)
const issue = await patchesAreModified({
const issue = await patchesOrHooksAreModified({
rootManifestOptions,
rootDir: rootProjectManifestDir,
lastValidatedTimestamp: wantedLockfileStats.mtime.valueOf(),
pnpmfile: opts.pnpmfile,
hadPnpmfile: workspaceState.pnpmfileExists,
currentPnpmfiles: opts.pnpmfile,
previousPnpmfiles: workspaceState.pnpmfiles,
})
if (issue) {
return { upToDate: false, issue, workspaceState }
@@ -545,12 +544,12 @@ function throwLockfileNotFound (wantedLockfileDir: string): never {
})
}
async function patchesAreModified (opts: {
async function patchesOrHooksAreModified (opts: {
rootManifestOptions: OptionsFromRootManifest | undefined
rootDir: string
lastValidatedTimestamp: number
pnpmfile: string
hadPnpmfile: boolean
currentPnpmfiles: string[]
previousPnpmfiles: string[]
}): Promise<string | undefined> {
if (opts.rootManifestOptions?.patchedDependencies) {
const allPatchStats = await Promise.all(Object.values(opts.rootManifestOptions.patchedDependencies).map((patchFile) => {
@@ -563,16 +562,17 @@ async function patchesAreModified (opts: {
return 'Patches were modified'
}
}
const pnpmfilePath = getPnpmfilePath(opts.rootDir, opts.pnpmfile)
const pnpmfileStats = safeStatSync(pnpmfilePath)
if (pnpmfileStats != null && pnpmfileStats.mtime.valueOf() > opts.lastValidatedTimestamp) {
return `pnpmfile at "${pnpmfilePath}" was modified`
if (!equals(opts.currentPnpmfiles, opts.previousPnpmfiles)) {
return 'The list of pnpmfiles changed.'
}
if (opts.hadPnpmfile && pnpmfileStats == null) {
return `pnpmfile at "${pnpmfilePath}" was removed`
}
if (!opts.hadPnpmfile && pnpmfileStats != null) {
return `pnpmfile at "${pnpmfilePath}" was added`
for (const pnpmfilePath of opts.currentPnpmfiles) {
const pnpmfileStats = safeStatSync(pnpmfilePath)
if (pnpmfileStats == null) {
return `pnpmfile at "${pnpmfilePath}" was removed`
}
if (pnpmfileStats.mtime.valueOf() > opts.lastValidatedTimestamp) {
return `pnpmfile at "${pnpmfilePath}" was modified`
}
}
return undefined
}

View File

@@ -0,0 +1,87 @@
import { checkDepsStatus, type CheckDepsStatusOptions } from '@pnpm/deps.status'
import * as workspaceStateModule from '@pnpm/workspace.state'
import * as lockfileFs from '@pnpm/lockfile.fs'
import * as fsUtils from '../lib/safeStat'
import * as statManifestFileUtils from '../lib/statManifestFile'
jest.mock('../lib/safeStat', () => ({
...jest.requireActual('../lib/safeStat'),
safeStatSync: jest.fn(),
safeStat: jest.fn(),
}))
jest.mock('../lib/statManifestFile', () => ({
...jest.requireActual('../lib/statManifestFile'),
statManifestFile: jest.fn(),
}))
jest.mock('@pnpm/lockfile.fs', () => ({
...jest.requireActual('@pnpm/lockfile.fs'),
readCurrentLockfile: jest.fn(),
readWantedLockfile: jest.fn(),
}))
describe('checkDepsStatus - pnpmfile modification', () => {
beforeEach(() => {
jest.resetModules()
jest.clearAllMocks()
})
it('returns upToDate: false when a pnpmfile was modified', async () => {
const lastValidatedTimestamp = Date.now() - 10_000
const beforeLastValidation = lastValidatedTimestamp - 10_000
const afterLastValidation = lastValidatedTimestamp + 1_000
const mockWorkspaceState: workspaceStateModule.WorkspaceState = {
lastValidatedTimestamp,
pnpmfiles: ['pnpmfile.js', 'modifiedPnpmfile.js'],
settings: {
excludeLinksFromLockfile: false,
linkWorkspacePackages: true,
preferWorkspacePackages: true,
},
projects: {},
filteredInstall: false,
}
jest.spyOn(workspaceStateModule, 'loadWorkspaceState').mockReturnValue(mockWorkspaceState)
;(fsUtils.safeStatSync as jest.Mock).mockImplementation((filePath: string) => {
if (filePath === 'pnpmfile.js') {
return {
mtime: new Date(beforeLastValidation),
mtimeMs: beforeLastValidation,
}
}
if (filePath === 'modifiedPnpmfile.js') {
return {
mtime: new Date(afterLastValidation),
mtimeMs: afterLastValidation,
}
}
return undefined
})
;(fsUtils.safeStat as jest.Mock).mockImplementation(async () => {
return {
mtime: new Date(beforeLastValidation),
mtimeMs: beforeLastValidation,
}
})
;(statManifestFileUtils.statManifestFile as jest.Mock).mockImplementation(async () => {
return undefined
})
const returnEmptyLockfile = async () => ({})
;(lockfileFs.readCurrentLockfile as jest.Mock).mockImplementation(returnEmptyLockfile)
;(lockfileFs.readWantedLockfile as jest.Mock).mockImplementation(returnEmptyLockfile)
const opts: CheckDepsStatusOptions = {
rootProjectManifest: {},
rootProjectManifestDir: '/project',
pnpmfile: mockWorkspaceState.pnpmfiles,
...mockWorkspaceState.settings,
}
const result = await checkDepsStatus(opts)
expect(result.upToDate).toBe(false)
expect(result.issue).toBe('pnpmfile at "modifiedPnpmfile.js" was modified')
})
})

View File

@@ -21,9 +21,6 @@
{
"path": "../../crypto/object-hasher"
},
{
"path": "../../hooks/pnpmfile"
},
{
"path": "../../lockfile/fs"
},

View File

@@ -36,6 +36,7 @@ async function approveSomeBuilds (opts?: ApproveBuildsOptions) {
})).config),
storeDir: path.resolve('store'),
cacheDir: path.resolve('cache'),
pnpmfile: [], // this is only needed because the pnpmfile returned by getConfig is string | string[]
}
await install.handler({ ...config, argv: { original: [] } })
@@ -66,6 +67,7 @@ async function approveNoBuilds (opts?: ApproveBuildsOptions) {
})).config),
storeDir: path.resolve('store'),
cacheDir: path.resolve('cache'),
pnpmfile: [], // this is only needed because the pnpmfile returned by getConfig is string | string[]
}
await install.handler({ ...config, argv: { original: [] } })

View File

@@ -32,7 +32,7 @@ export const DEFAULT_OPTS = {
networkConcurrency: 16,
offline: false,
pending: false,
pnpmfile: './.pnpmfile.cjs',
pnpmfile: ['./.pnpmfile.cjs'],
pnpmHomeDir: '',
proxy: undefined,
rawConfig: { registry: REGISTRY },

View File

@@ -37,7 +37,7 @@ export const DEFAULT_OPTS = {
networkConcurrency: 16,
offline: false,
pending: false,
pnpmfile: './.pnpmfile.cjs',
pnpmfile: ['./.pnpmfile.cjs'],
pnpmHomeDir: '',
preferWorkspacePackages: true,
proxy: undefined,
@@ -80,7 +80,7 @@ export const DLX_DEFAULT_OPTS = {
},
linkWorkspacePackages: true,
lock: true,
pnpmfile: '.pnpmfile.cjs',
pnpmfile: ['.pnpmfile.cjs'],
pnpmHomeDir: '',
preferWorkspacePackages: true,
rawConfig: { registry: REGISTRY_URL },

View File

@@ -1,14 +0,0 @@
import path from 'path'
import { getPnpmfilePath } from './getPnpmfilePath'
test('getPnpmfilePath() when pnpmfile is undefined', () => {
expect(getPnpmfilePath('PREFIX', undefined)).toBe(path.join('PREFIX', '.pnpmfile.cjs'))
})
test('getPnpmfilePath() when pnpmfile is a relative path', () => {
expect(getPnpmfilePath('PREFIX', 'hooks/pnpm.js')).toBe(path.join('PREFIX', 'hooks/pnpm.js'))
})
test('getPnpmfilePath() when pnpmfile is an absolute path', () => {
expect(getPnpmfilePath('PREFIX', '/global/pnpmfile.cjs')).toBe('/global/pnpmfile.cjs')
})

View File

@@ -1,10 +0,0 @@
import path from 'path'
export function getPnpmfilePath (prefix: string, pnpmfile?: string): string {
if (!pnpmfile) {
pnpmfile = '.pnpmfile.cjs'
} else if (path.isAbsolute(pnpmfile)) {
return pnpmfile
}
return path.join(prefix, pnpmfile)
}

View File

@@ -1,7 +1,6 @@
import type { CookedHooks } from './requireHooks'
export { getPnpmfilePath } from './getPnpmfilePath'
export { requireHooks } from './requireHooks'
export { requirePnpmfile, BadReadPackageHookError } from './requirePnpmfile'
export { BadReadPackageHookError } from './requirePnpmfile'
export type { HookContext } from './Hooks'
export type Hooks = CookedHooks

View File

@@ -1,12 +1,11 @@
import type { PreResolutionHookContext, PreResolutionHookLogger } from '@pnpm/hooks.types'
import { PnpmError } from '@pnpm/error'
import { hookLogger } from '@pnpm/core-loggers'
import { createHashFromFile } from '@pnpm/crypto.hash'
import { createHashFromMultipleFiles } from '@pnpm/crypto.hash'
import pathAbsolute from 'path-absolute'
import type { CustomFetchers } from '@pnpm/fetcher-base'
import { type ImportIndexedPackageAsync } from '@pnpm/store-controller-types'
import { getPnpmfilePath } from './getPnpmfilePath'
import { requirePnpmfile } from './requirePnpmfile'
import { requirePnpmfile, type Pnpmfile } from './requirePnpmfile'
import { type HookContext, type Hooks } from './Hooks'
// eslint-disable-next-line
@@ -16,103 +15,195 @@ type Cook<T extends (...args: any[]) => any> = (
...otherArgs: any[]
) => ReturnType<T>
interface PnpmfileEntry {
path: string
includeInChecksum: boolean
optional?: boolean
}
interface PnpmfileEntryLoaded {
file: string
hooks: Pnpmfile['hooks'] | undefined
includeInChecksum: boolean
}
export interface CookedHooks {
readPackage?: Array<Cook<Required<Hooks>['readPackage']>>
preResolution?: Cook<Required<Hooks>['preResolution']>
preResolution?: Array<Cook<Required<Hooks>['preResolution']>>
afterAllResolved?: Array<Cook<Required<Hooks>['afterAllResolved']>>
filterLog?: Array<Cook<Required<Hooks>['filterLog']>>
updateConfig?: Hooks['updateConfig']
updateConfig?: Array<Cook<Required<Hooks>['updateConfig']>>
importPackage?: ImportIndexedPackageAsync
fetchers?: CustomFetchers
calculatePnpmfileChecksum?: () => Promise<string | undefined>
calculatePnpmfileChecksum?: () => Promise<string>
}
export interface RequireHooksResult {
hooks: CookedHooks
resolvedPnpmfilePaths: string[]
}
export function requireHooks (
prefix: string,
opts: {
globalPnpmfile?: string
pnpmfile?: string
pnpmfile?: string[] | string
}
): RequireHooksResult {
const pnpmfiles: PnpmfileEntry[] = []
if (opts.globalPnpmfile) {
pnpmfiles.push({
path: opts.globalPnpmfile,
includeInChecksum: false,
})
}
pnpmfiles.push({
path: '.pnpmfile.cjs',
includeInChecksum: true,
optional: true,
})
if (opts.pnpmfile) {
if (Array.isArray(opts.pnpmfile)) {
for (const pnpmfile of opts.pnpmfile) {
pnpmfiles.push({
path: pnpmfile,
includeInChecksum: true,
})
}
} else {
pnpmfiles.push({
path: opts.pnpmfile,
includeInChecksum: true,
})
}
}
const entries: PnpmfileEntryLoaded[] = []
const loadedFiles: string[] = []
for (const { path, includeInChecksum, optional } of pnpmfiles) {
const file = pathAbsolute(path, prefix)
if (!loadedFiles.includes(file)) {
loadedFiles.push(file)
const requirePnpmfileResult = requirePnpmfile(file, prefix)
if (requirePnpmfileResult != null) {
entries.push({
file,
includeInChecksum,
hooks: requirePnpmfileResult.pnpmfileModule?.hooks,
})
} else if (!optional) {
throw new PnpmError('PNPMFILE_NOT_FOUND', `pnpmfile at "${file}" is not found`)
}
}
}
): CookedHooks {
const globalPnpmfile = opts.globalPnpmfile ? requirePnpmfile(pathAbsolute(opts.globalPnpmfile, prefix), prefix) : undefined
let globalHooks: Hooks | undefined = globalPnpmfile?.hooks
const pnpmfilePath = getPnpmfilePath(prefix, opts.pnpmfile)
const pnpmFile = requirePnpmfile(pnpmfilePath, prefix)
let hooks: Hooks | undefined = pnpmFile?.hooks
if (!globalHooks && !hooks) return { afterAllResolved: [], filterLog: [], readPackage: [] }
const calculatePnpmfileChecksum = hooks ? () => createHashFromFile(pnpmfilePath) : undefined
globalHooks = globalHooks ?? {}
hooks = hooks ?? {}
const cookedHooks: CookedHooks & Required<Pick<CookedHooks, 'filterLog'>> = {
const cookedHooks: CookedHooks & Required<Pick<CookedHooks, 'readPackage' | 'preResolution' | 'afterAllResolved' | 'filterLog' | 'updateConfig'>> = {
readPackage: [],
preResolution: [],
afterAllResolved: [],
filterLog: [],
readPackage: [],
calculatePnpmfileChecksum,
}
for (const hookName of ['readPackage', 'afterAllResolved'] as const) {
if (globalHooks[hookName]) {
const globalHook = globalHooks[hookName]
const context = createReadPackageHookContext(globalPnpmfile!.filename, prefix, hookName)
cookedHooks[hookName]!.push((pkg: object) => globalHook!(pkg as any, context)) // eslint-disable-line @typescript-eslint/no-explicit-any
}
if (hooks[hookName]) {
const hook = hooks[hookName]
const context = createReadPackageHookContext(pnpmFile!.filename, prefix, hookName)
cookedHooks[hookName]!.push((pkg: object) => hook!(pkg as any, context)) // eslint-disable-line @typescript-eslint/no-explicit-any
}
}
if (globalHooks.filterLog != null) {
cookedHooks.filterLog.push(globalHooks.filterLog)
}
if (hooks.filterLog != null) {
cookedHooks.filterLog.push(hooks.filterLog)
updateConfig: [],
}
// `importPackage`, `preResolution` and `fetchers` can only be defined via a global pnpmfile
cookedHooks.importPackage = hooks.importPackage ?? globalHooks.importPackage
const preResolutionHook = hooks.preResolution ?? globalHooks.preResolution
cookedHooks.preResolution = preResolutionHook
? (ctx: PreResolutionHookContext) => preResolutionHook(ctx, createPreResolutionHookLogger(prefix))
: undefined
cookedHooks.fetchers = hooks.fetchers ?? globalHooks.fetchers
if (hooks.updateConfig != null) {
const updateConfig = hooks.updateConfig
cookedHooks.updateConfig = (config) => {
const updatedConfig = updateConfig(config)
if (updatedConfig == null) {
throw new PnpmError('CONFIG_IS_UNDEFINED', 'The updateConfig hook returned undefined')
// calculate combined checksum for all included files
if (entries.some((entry) => entry.hooks != null)) {
cookedHooks.calculatePnpmfileChecksum = async () => {
const filesToIncludeInHash: string[] = []
for (const { includeInChecksum, file } of entries) {
if (includeInChecksum) {
filesToIncludeInHash.push(file)
}
}
return updatedConfig
filesToIncludeInHash.sort()
return createHashFromMultipleFiles(filesToIncludeInHash)
}
}
return cookedHooks
let importProvider: string | undefined
let fetchersProvider: string | undefined
// process hooks in order
for (const { hooks, file } of entries) {
const fileHooks: Hooks = hooks ?? {}
// readPackage & afterAllResolved
for (const hookName of ['readPackage', 'afterAllResolved'] as const) {
const fn = fileHooks[hookName]
if (fn) {
const context = createReadPackageHookContext(file, prefix, hookName)
cookedHooks[hookName].push((pkg: object) => fn(pkg as any, context)) // eslint-disable-line @typescript-eslint/no-explicit-any
}
}
// filterLog
if (fileHooks.filterLog) {
cookedHooks.filterLog.push(fileHooks.filterLog)
}
// updateConfig
if (fileHooks.updateConfig) {
const updateConfig = fileHooks.updateConfig
cookedHooks.updateConfig.push((config: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any
const updated = updateConfig(config)
if (updated == null) {
throw new PnpmError('CONFIG_IS_UNDEFINED', 'The updateConfig hook returned undefined')
}
return updated
})
}
// preResolution
if (fileHooks.preResolution) {
const preRes = fileHooks.preResolution
cookedHooks.preResolution.push((ctx: PreResolutionHookContext) => preRes(ctx, createPreResolutionHookLogger(prefix)))
}
// importPackage: only one allowed
if (fileHooks.importPackage) {
if (importProvider) {
throw new PnpmError(
'MULTIPLE_IMPORT_PACKAGE',
`importPackage hook defined in both ${importProvider} and ${file}`
)
}
importProvider = file
cookedHooks.importPackage = fileHooks.importPackage
}
// fetchers: only one allowed
if (fileHooks.fetchers) {
if (fetchersProvider) {
throw new PnpmError(
'MULTIPLE_FETCHERS',
`fetchers hook defined in both ${fetchersProvider} and ${file}`
)
}
fetchersProvider = file
cookedHooks.fetchers = fileHooks.fetchers
}
}
return {
hooks: cookedHooks,
resolvedPnpmfilePaths: entries.map(({ file }) => file),
}
}
function createReadPackageHookContext (calledFrom: string, prefix: string, hook: string): HookContext {
return {
log: (message: string) => {
hookLogger.debug({
from: calledFrom,
hook,
message,
prefix,
})
hookLogger.debug({ from: calledFrom, hook, message, prefix })
},
}
}
function createPreResolutionHookLogger (prefix: string): PreResolutionHookLogger {
const hook = 'preResolution'
return {
info: (message: string) => hookLogger.info({ message, prefix, hook } as any), // eslint-disable-line
warn: (message: string) => hookLogger.warn({ message, prefix, hook } as any), // eslint-disable-line
info: (message: string) => {
hookLogger.info({ message, prefix, hook } as any) // eslint-disable-line @typescript-eslint/no-explicit-any
},
warn: (message: string) => {
hookLogger.warn({ message, prefix, hook } as any) // eslint-disable-line @typescript-eslint/no-explicit-any
},
}
}

View File

@@ -29,18 +29,17 @@ class PnpmFileFailError extends PnpmError {
export interface Pnpmfile {
hooks?: Hooks
filename: string
}
export function requirePnpmfile (pnpmFilePath: string, prefix: string): Pnpmfile | undefined {
export function requirePnpmfile (pnpmFilePath: string, prefix: string): { pnpmfileModule: Pnpmfile | undefined } | undefined {
try {
const pnpmfile: { hooks?: { readPackage?: unknown }, filename?: unknown } = require(pnpmFilePath) // eslint-disable-line
const pnpmfile: Pnpmfile = require(pnpmFilePath) // eslint-disable-line
if (typeof pnpmfile === 'undefined') {
logger.warn({
message: `Ignoring the pnpmfile at "${pnpmFilePath}". It exports "undefined".`,
prefix,
})
return undefined
return { pnpmfileModule: undefined }
}
if (pnpmfile?.hooks?.readPackage && typeof pnpmfile.hooks.readPackage !== 'function') {
throw new TypeError('hooks.readPackage should be a function')
@@ -65,11 +64,10 @@ export function requirePnpmfile (pnpmFilePath: string, prefix: string): Pnpmfile
return newPkg
}
}
pnpmfile.filename = pnpmFilePath
return pnpmfile as Pnpmfile
return { pnpmfileModule: pnpmfile }
} catch (err: unknown) {
if (err instanceof SyntaxError) {
console.error(chalk.red('A syntax error in the .pnpmfile.cjs\n'))
console.error(chalk.red(`A syntax error in the "${pnpmFilePath}"\n`))
console.error(err)
process.exit(1)
}

View File

@@ -1,17 +1,18 @@
import path from 'path'
import { type Log } from '@pnpm/core-loggers'
import { requireHooks, requirePnpmfile, BadReadPackageHookError, type HookContext } from '@pnpm/pnpmfile'
import { requireHooks, BadReadPackageHookError, type HookContext } from '@pnpm/pnpmfile'
import { requirePnpmfile } from '../src/requirePnpmfile'
const defaultHookContext: HookContext = { log () {} }
test('ignoring a pnpmfile that exports undefined', () => {
const pnpmfile = requirePnpmfile(path.join(__dirname, '__fixtures__/undefined.js'), __dirname)
const { pnpmfileModule: pnpmfile } = requirePnpmfile(path.join(__dirname, '__fixtures__/undefined.js'), __dirname)!
expect(pnpmfile).toBeUndefined()
})
test('readPackage hook run fails when returns undefined ', () => {
const pnpmfilePath = path.join(__dirname, '__fixtures__/readPackageNoReturn.js')
const pnpmfile = requirePnpmfile(pnpmfilePath, __dirname)
const { pnpmfileModule: pnpmfile } = requirePnpmfile(pnpmfilePath, __dirname)!
return expect(
pnpmfile!.hooks!.readPackage!({}, defaultHookContext)
@@ -20,7 +21,7 @@ test('readPackage hook run fails when returns undefined ', () => {
test('readPackage hook run fails when returned dependencies is not an object ', () => {
const pnpmfilePath = path.join(__dirname, '__fixtures__/readPackageNoObject.js')
const pnpmfile = requirePnpmfile(pnpmfilePath, __dirname)
const { pnpmfileModule: pnpmfile } = requirePnpmfile(pnpmfilePath, __dirname)!
return expect(
pnpmfile!.hooks!.readPackage!({}, defaultHookContext)
).rejects.toEqual(new BadReadPackageHookError(pnpmfilePath, 'readPackage hook returned package manifest object\'s property \'dependencies\' must be an object.'))
@@ -29,7 +30,7 @@ test('readPackage hook run fails when returned dependencies is not an object ',
test('filterLog hook combines with the global hook', () => {
const globalPnpmfile = path.join(__dirname, '__fixtures__/globalFilterLog.js')
const pnpmfile = path.join(__dirname, '__fixtures__/filterLog.js')
const hooks = requireHooks(__dirname, { globalPnpmfile, pnpmfile })
const { hooks } = requireHooks(__dirname, { globalPnpmfile, pnpmfile: [pnpmfile] })
expect(hooks.filterLog).toBeDefined()
expect(hooks.filterLog!.length).toBe(2)
@@ -47,24 +48,28 @@ test('filterLog hook combines with the global hook', () => {
})
test('calculatePnpmfileChecksum is undefined when pnpmfile does not exist', async () => {
const hooks = requireHooks(__dirname, { pnpmfile: 'file-that-does-not-exist.js' })
const { hooks } = requireHooks(__dirname, {})
expect(hooks.calculatePnpmfileChecksum).toBeUndefined()
})
test('calculatePnpmfileChecksum resolves to hash string for existing pnpmfile', async () => {
const pnpmfile = path.join(__dirname, '__fixtures__/readPackageNoObject.js')
const hooks = requireHooks(__dirname, { pnpmfile })
const { hooks } = requireHooks(__dirname, { pnpmfile: [pnpmfile] })
expect(typeof await hooks.calculatePnpmfileChecksum?.()).toBe('string')
})
test('calculatePnpmfileChecksum is undefined if pnpmfile even when it exports undefined', async () => {
const pnpmfile = path.join(__dirname, '__fixtures__/undefined.js')
const hooks = requireHooks(__dirname, { pnpmfile })
const { hooks } = requireHooks(__dirname, { pnpmfile: [pnpmfile] })
expect(hooks.calculatePnpmfileChecksum).toBeUndefined()
})
test('updateConfig throws an error if it returns undefined', async () => {
const pnpmfile = path.join(__dirname, '__fixtures__/updateConfigReturnsUndefined.js')
const hooks = requireHooks(__dirname, { pnpmfile })
expect(() => hooks.updateConfig!({})).toThrow('The updateConfig hook returned undefined')
const { hooks } = requireHooks(__dirname, { pnpmfile: [pnpmfile] })
expect(() => hooks.updateConfig![0]!({})).toThrow('The updateConfig hook returned undefined')
})
test('requireHooks throw an error if one of the specified pnpmfiles does not exist', async () => {
expect(() => requireHooks(__dirname, { pnpmfile: ['does-not-exist.cjs'] })).toThrow('is not found')
})

View File

@@ -45,7 +45,7 @@ export const DEFAULT_OPTS = {
networkConcurrency: 16,
offline: false,
pending: false,
pnpmfile: './.pnpmfile.cjs',
pnpmfile: ['./.pnpmfile.cjs'],
pnpmHomeDir: '',
preferWorkspacePackages: true,
proxy: undefined,

View File

@@ -33,7 +33,7 @@ export const DEFAULT_OPTS = {
networkConcurrency: 16,
offline: false,
pending: false,
pnpmfile: './.pnpmfile.cjs',
pnpmfile: ['./.pnpmfile.cjs'],
pnpmHomeDir: '',
preferWorkspacePackages: true,
proxy: undefined,

View File

@@ -79,7 +79,7 @@ export interface StrictInstallOptions {
nodeVersion?: string
packageExtensions: Record<string, PackageExtension>
ignoredOptionalDependencies: string[]
pnpmfile: string
pnpmfile: string[] | string
ignorePnpmfile: boolean
packageManager: {
name: string
@@ -88,7 +88,7 @@ export interface StrictInstallOptions {
pruneLockfileImporters: boolean
hooks: {
readPackage?: ReadPackageHook[]
preResolution?: (ctx: PreResolutionHookContext) => Promise<void>
preResolution?: Array<(ctx: PreResolutionHookContext) => Promise<void>>
afterAllResolved?: Array<(lockfile: LockfileObject) => LockfileObject | Promise<LockfileObject>>
calculatePnpmfileChecksum?: () => Promise<string | undefined>
}

View File

@@ -308,15 +308,18 @@ export async function mutateModules (
}
if (opts.hooks.preResolution) {
await opts.hooks.preResolution({
currentLockfile: ctx.currentLockfile,
wantedLockfile: ctx.wantedLockfile,
existsCurrentLockfile: ctx.existsCurrentLockfile,
existsNonEmptyWantedLockfile: ctx.existsNonEmptyWantedLockfile,
lockfileDir: ctx.lockfileDir,
storeDir: ctx.storeDir,
registries: ctx.registries,
})
for (const preResolution of opts.hooks.preResolution) {
// eslint-disable-next-line no-await-in-loop
await preResolution({
currentLockfile: ctx.currentLockfile,
wantedLockfile: ctx.wantedLockfile,
existsCurrentLockfile: ctx.existsCurrentLockfile,
existsNonEmptyWantedLockfile: ctx.existsNonEmptyWantedLockfile,
lockfileDir: ctx.lockfileDir,
storeDir: ctx.storeDir,
registries: ctx.registries,
})
}
}
const pruneVirtualStore = !opts.enableGlobalVirtualStore && (ctx.modulesFile?.prunedAt && opts.modulesCacheMaxAge > 0

View File

@@ -282,7 +282,6 @@ export type InstallCommandOptions = Pick<Config,
| 'modulesDir'
| 'nodeLinker'
| 'patchedDependencies'
| 'pnpmfile'
| 'preferFrozenLockfile'
| 'preferWorkspacePackages'
| 'production'
@@ -334,6 +333,7 @@ export type InstallCommandOptions = Pick<Config,
workspace?: boolean
includeOnlyPackageFiles?: boolean
confirmModulesPurge?: boolean
pnpmfile: string[]
} & Partial<Pick<Config, 'ci' | 'modulesCacheMaxAge' | 'pnpmHomeDir' | 'preferWorkspacePackages' | 'useLockfile' | 'symlink'>>
export async function handler (opts: InstallCommandOptions): Promise<void> {

View File

@@ -70,7 +70,6 @@ export type InstallDepsOptions = Pick<Config,
| 'linkWorkspacePackages'
| 'lockfileDir'
| 'lockfileOnly'
| 'pnpmfile'
| 'production'
| 'preferWorkspacePackages'
| 'rawLocalConfig'
@@ -137,6 +136,7 @@ export type InstallDepsOptions = Pick<Config,
prepareExecutionEnv: PrepareExecutionEnv
fetchFullMetadata?: boolean
pruneLockfileImporters?: boolean
pnpmfile: string[]
} & Partial<Pick<Config, 'pnpmHomeDir' | 'strictDepBuilds'>>
export async function installDeps (
@@ -327,7 +327,7 @@ when running add/update with the --workspace option')
allProjects,
settings: opts,
workspaceDir: opts.workspaceDir ?? opts.lockfileDir ?? opts.dir,
pnpmfileExists: opts.hooks?.calculatePnpmfileChecksum != null,
pnpmfiles: opts.pnpmfile,
filteredInstall: allProjects.length !== Object.keys(opts.selectedProjectsGraph ?? {}).length,
configDependencies: opts.configDependencies,
})
@@ -390,7 +390,7 @@ when running add/update with the --workspace option')
allProjects,
settings: opts,
workspaceDir: opts.workspaceDir ?? opts.lockfileDir ?? opts.dir,
pnpmfileExists: opts.hooks?.calculatePnpmfileChecksum != null,
pnpmfiles: opts.pnpmfile,
filteredInstall: allProjects.length !== Object.keys(opts.selectedProjectsGraph ?? {}).length,
configDependencies: opts.configDependencies,
})
@@ -416,7 +416,7 @@ async function recursiveInstallThenUpdateWorkspaceState (
allProjects,
settings: opts,
workspaceDir: opts.workspaceDir,
pnpmfileExists: opts.hooks?.calculatePnpmfileChecksum != null,
pnpmfiles: opts.pnpmfile,
filteredInstall: allProjects.length !== Object.keys(opts.selectedProjectsGraph ?? {}).length,
configDependencies: opts.configDependencies,
})

View File

@@ -66,7 +66,6 @@ export type RecursiveOptions = CreateStoreControllerOptions & Pick<Config,
| 'lockfileDir'
| 'lockfileOnly'
| 'modulesDir'
| 'pnpmfile'
| 'rawLocalConfig'
| 'registries'
| 'rootProjectManifest'
@@ -106,6 +105,7 @@ export type RecursiveOptions = CreateStoreControllerOptions & Pick<Config,
ctrl: StoreController
dir: string
}
pnpmfile: string[]
} & Partial<
Pick<Config,
| 'sort'
@@ -312,7 +312,7 @@ export async function recursive (
const hooks = opts.ignorePnpmfile
? {}
: (() => {
const pnpmfileHooks = requireHooks(rootDir, opts)
const { hooks: pnpmfileHooks } = requireHooks(rootDir, opts)
return {
...opts.hooks,
...pnpmfileHooks,

View File

@@ -138,7 +138,6 @@ export async function handler (
| 'linkWorkspacePackages'
| 'lockfileDir'
| 'optional'
| 'pnpmfile'
| 'production'
| 'rawLocalConfig'
| 'registries'
@@ -153,6 +152,7 @@ export async function handler (
| 'sharedWorkspaceLockfile'
> & {
recursive?: boolean
pnpmfile: string[]
},
params: string[]
): Promise<void> {

View File

@@ -27,7 +27,7 @@ const DEFAULT_OPTIONS = {
},
lock: true,
preferWorkspacePackages: true,
pnpmfile: '.pnpmfile.cjs',
pnpmfile: ['.pnpmfile.cjs'],
pnpmHomeDir: '',
rawConfig: { registry: REGISTRY_URL },
rawLocalConfig: { registry: REGISTRY_URL },

View File

@@ -23,7 +23,7 @@ const DEFAULT_OPTIONS = {
},
lock: true,
preferWorkspacePackages: true,
pnpmfile: '.pnpmfile.cjs',
pnpmfile: ['.pnpmfile.cjs'],
pnpmHomeDir: '',
rawConfig: { registry: REGISTRY_URL },
rawLocalConfig: { registry: REGISTRY_URL },

View File

@@ -26,7 +26,7 @@ const DEFAULT_OPTIONS = {
optionalDependencies: true,
},
lock: true,
pnpmfile: '.pnpmfile.cjs',
pnpmfile: ['.pnpmfile.cjs'],
preferWorkspacePackages: true,
rawConfig: { registry: REGISTRY_URL },
rawLocalConfig: { registry: REGISTRY_URL },

View File

@@ -24,7 +24,7 @@ const DEFAULT_OPTIONS = {
optionalDependencies: true,
},
lock: true,
pnpmfile: '.pnpmfile.cjs',
pnpmfile: ['.pnpmfile.cjs'],
pnpmHomeDir: '',
preferWorkspacePackages: true,
rawConfig: { registry: REGISTRY_URL },

View File

@@ -27,7 +27,7 @@ const DEFAULT_OPTIONS = {
},
lock: true,
linkWorkspacePackages: true,
pnpmfile: '.pnpmfile.cjs',
pnpmfile: ['.pnpmfile.cjs'],
pnpmHomeDir: '',
preferWorkspacePackages: true,
rawConfig: { registry: REGISTRY_URL },

View File

@@ -31,7 +31,7 @@ const DEFAULT_OPTIONS = {
optionalDependencies: true,
},
lock: true,
pnpmfile: '.pnpmfile.cjs',
pnpmfile: ['.pnpmfile.cjs'],
pnpmHomeDir: '',
preferWorkspacePackages: true,
rawConfig: { registry: REGISTRY_URL },

View File

@@ -28,7 +28,7 @@ const DEFAULT_OPTIONS = {
optionalDependencies: true,
},
lock: true,
pnpmfile: '.pnpmfile.cjs',
pnpmfile: ['.pnpmfile.cjs'],
pnpmHomeDir: '',
preferWorkspacePackages: true,
rawConfig: { registry: REGISTRY_URL },

View File

@@ -35,7 +35,7 @@ export const DEFAULT_OPTS = {
networkConcurrency: 16,
offline: false,
pending: false,
pnpmfile: './.pnpmfile.cjs',
pnpmfile: ['./.pnpmfile.cjs'],
pnpmHomeDir: '',
preferWorkspacePackages: true,
proxy: undefined,

3
pnpm-lock.yaml generated
View File

@@ -2005,9 +2005,6 @@ importers:
'@pnpm/parse-overrides':
specifier: workspace:*
version: link:../../config/parse-overrides
'@pnpm/pnpmfile':
specifier: workspace:*
version: link:../../hooks/pnpmfile
'@pnpm/resolver-base':
specifier: workspace:*
version: link:../../resolving/resolver-base

View File

@@ -4,7 +4,7 @@ import {
type ReadPackageHook,
} from '@pnpm/types'
export type PnpmOptions = Omit<Config, 'reporter'> & {
export type PnpmOptions = Omit<Config, 'reporter' | 'pnpmfile'> & {
argv: {
cooked: string[]
original: string[]
@@ -12,6 +12,7 @@ export type PnpmOptions = Omit<Config, 'reporter'> & {
}
cliOptions: object
reporter?: (logObj: LogBase) => void
pnpmfile: string[]
packageManager?: {
name: string
version: string

View File

@@ -30,7 +30,7 @@ test('restores deleted modules dir of a workspace package', async () => {
writeYamlFile('pnpm-workspace.yaml', { packages: ['packages/*'] })
await execPnpm(['install'])
expect(fs.readdirSync('node_modules')).toContain('.pnpm-workspace-state.json')
expect(fs.readdirSync('node_modules')).toContain('.pnpm-workspace-state-v1.json')
expect(fs.readdirSync('packages/foo/node_modules')).toContain('is-positive')
fs.rmSync('packages/foo/node_modules', { recursive: true })

View File

@@ -167,7 +167,7 @@ test('multi-project workspace', async () => {
const workspaceState = loadWorkspaceState(process.cwd())
expect(workspaceState).toMatchObject({
lastValidatedTimestamp: expect.any(Number),
pnpmfileExists: false,
pnpmfiles: [],
filteredInstall: false,
projects: {
[path.resolve('.')]: { name: 'root', version: '0.0.0' },
@@ -322,7 +322,7 @@ test('multi-project workspace', async () => {
const workspaceState = loadWorkspaceState(process.cwd())
expect(workspaceState).toMatchObject({
lastValidatedTimestamp: expect.any(Number),
pnpmfileExists: false,
pnpmfiles: [],
filteredInstall: false,
projects: {
[path.resolve('.')]: { name: 'root', version: '0.0.0' },

View File

@@ -49,7 +49,7 @@ test('hoisted node linker and node_modules not exist (#9424)', async () => {
// pnpm install should create a packages list cache
expect(loadWorkspaceState(process.cwd())).toMatchObject({
lastValidatedTimestamp: expect.any(Number),
pnpmfileExists: false,
pnpmfiles: [] as string[],
filteredInstall: false,
projects: {
[path.resolve('has-deps')]: { name: 'has-deps', version: '0.0.0' },

View File

@@ -92,7 +92,7 @@ test('single dependency', async () => {
const workspaceState = loadWorkspaceState(process.cwd())
expect(workspaceState).toMatchObject({
lastValidatedTimestamp: expect.any(Number),
pnpmfileExists: false,
pnpmfiles: [],
filteredInstall: false,
projects: {
[path.resolve('.')]: { name: 'root', version: '0.0.0' },
@@ -261,7 +261,7 @@ test('single dependency', async () => {
const workspaceState = loadWorkspaceState(process.cwd())
expect(workspaceState).toMatchObject({
lastValidatedTimestamp: expect.any(Number),
pnpmfileExists: false,
pnpmfiles: [],
filteredInstall: false,
projects: {
[path.resolve('.')]: { name: 'root', version: '0.0.0' },
@@ -383,7 +383,7 @@ test('multiple lockfiles', async () => {
const workspaceState = loadWorkspaceState(process.cwd())
expect(workspaceState).toMatchObject({
lastValidatedTimestamp: expect.any(Number),
pnpmfileExists: false,
pnpmfiles: [],
filteredInstall: false,
projects: {
[path.resolve('.')]: { name: 'root', version: '0.0.0' },
@@ -547,7 +547,7 @@ test('multiple lockfiles', async () => {
const workspaceState = loadWorkspaceState(process.cwd())
expect(workspaceState).toMatchObject({
lastValidatedTimestamp: expect.any(Number),
pnpmfileExists: false,
pnpmfiles: [],
filteredInstall: false,
projects: {
[path.resolve('.')]: { name: 'root', version: '0.0.0' },
@@ -694,7 +694,7 @@ test('no dependencies', async () => {
const workspaceState = loadWorkspaceState(process.cwd())
expect(workspaceState).toMatchObject({
lastValidatedTimestamp: expect.any(Number),
pnpmfileExists: false,
pnpmfiles: [],
filteredInstall: false,
projects: {
[path.resolve('.')]: { name: 'root', version: '0.0.0' },
@@ -879,7 +879,7 @@ test('should check for outdated catalogs', async () => {
default: workspaceManifest.catalog,
},
}),
pnpmfileExists: false,
pnpmfiles: [],
filteredInstall: false,
lastValidatedTimestamp: expect.any(Number),
projects: {

View File

@@ -35,7 +35,7 @@ export const DEFAULT_OPTS = {
networkConcurrency: 16,
offline: false,
pending: false,
pnpmfile: './.pnpmfile.cjs',
pnpmfile: ['./.pnpmfile.cjs'],
pnpmHomeDir: '',
preferWorkspacePackages: true,
proxy: undefined,

View File

@@ -31,7 +31,7 @@ export const DEFAULT_OPTS = {
networkConcurrency: 16,
offline: false,
pending: false,
pnpmfile: './.pnpmfile.cjs',
pnpmfile: ['./.pnpmfile.cjs'],
pnpmHomeDir: '',
proxy: undefined,
rawConfig: { registry: REGISTRY },

View File

@@ -31,7 +31,7 @@ export const DEFAULT_OPTS = {
networkConcurrency: 16,
offline: false,
pending: false,
pnpmfile: './.pnpmfile.cjs',
pnpmfile: ['./.pnpmfile.cjs'],
pnpmHomeDir: '',
preferWorkspacePackages: true,
proxy: undefined,

View File

@@ -33,7 +33,7 @@ export const DEFAULT_OPTS = {
networkConcurrency: 16,
offline: false,
pending: false,
pnpmfile: './.pnpmfile.cjs',
pnpmfile: ['./.pnpmfile.cjs'],
pnpmHomeDir: '',
proxy: undefined,
preferWorkspacePackages: true,

View File

@@ -36,7 +36,7 @@ export const DEFAULT_OPTS = {
networkConcurrency: 16,
offline: false,
pending: false,
pnpmfile: './.pnpmfile.cjs',
pnpmfile: ['./.pnpmfile.cjs'],
pnpmHomeDir: '',
preferWorkspacePackages: true,
proxy: undefined,

View File

@@ -3,7 +3,7 @@ import { type WorkspaceState, type WorkspaceStateSettings, type ProjectsList } f
export interface CreateWorkspaceStateOptions {
allProjects: ProjectsList
pnpmfileExists: boolean
pnpmfiles: string[]
filteredInstall: boolean
settings: WorkspaceStateSettings
configDependencies?: Record<string, string>
@@ -18,7 +18,7 @@ export const createWorkspaceState = (opts: CreateWorkspaceStateOptions): Workspa
version: project.manifest.version,
},
])),
pnpmfileExists: opts.pnpmfileExists,
pnpmfiles: opts.pnpmfiles,
settings: pick([
'autoInstallPeers',
'catalogs',

View File

@@ -1,4 +1,4 @@
import path from 'path'
export const getFilePath = (workspaceDir: string): string =>
path.join(workspaceDir, 'node_modules', '.pnpm-workspace-state.json')
path.join(workspaceDir, 'node_modules', '.pnpm-workspace-state-v1.json')

View File

@@ -9,7 +9,7 @@ export interface WorkspaceState {
name?: string
version?: string
}>
pnpmfileExists: boolean
pnpmfiles: string[]
filteredInstall: boolean
configDependencies?: Record<string, string>
settings: WorkspaceStateSettings

View File

@@ -9,7 +9,7 @@ export interface UpdateWorkspaceStateOptions {
allProjects: ProjectsList
settings: WorkspaceStateSettings
workspaceDir: string
pnpmfileExists: boolean
pnpmfiles: string[]
filteredInstall: boolean
configDependencies?: Record<string, string>
}

View File

@@ -9,7 +9,7 @@ test('createWorkspaceState() on empty list', () => {
expect(
createWorkspaceState({
allProjects: [],
pnpmfileExists: true,
pnpmfiles: [],
filteredInstall: false,
settings: {
autoInstallPeers: true,
@@ -22,7 +22,7 @@ test('createWorkspaceState() on empty list', () => {
})
).toStrictEqual(expect.objectContaining({
projects: {},
pnpmfileExists: true,
pnpmfiles: [],
lastValidatedTimestamp: expect.any(Number),
}))
})
@@ -54,7 +54,7 @@ test('createWorkspaceState() on non-empty list', () => {
},
},
},
pnpmfileExists: false,
pnpmfiles: [],
filteredInstall: false,
})
).toStrictEqual(expect.objectContaining({
@@ -72,6 +72,6 @@ test('createWorkspaceState() on non-empty list', () => {
[path.resolve('packages/c')]: {},
[path.resolve('packages/d')]: {},
},
pnpmfileExists: false,
pnpmfiles: [],
}))
})

View File

@@ -7,6 +7,6 @@ test('getFilePath()', () => {
expect(
getFilePath(process.cwd())
).toStrictEqual(
path.resolve(path.resolve('node_modules/.pnpm-workspace-state.json'))
path.resolve(path.resolve('node_modules/.pnpm-workspace-state-v1.json'))
)
})

View File

@@ -61,7 +61,7 @@ test('loadWorkspaceState() when cache file exists and is correct', async () => {
[path.resolve('packages/c') as ProjectRootDir]: {},
[path.resolve('packages/d') as ProjectRootDir]: {},
},
pnpmfileExists: false,
pnpmfiles: [],
filteredInstall: false,
}
fs.writeFileSync(cacheFile, JSON.stringify(workspaceState))

View File

@@ -21,7 +21,7 @@ test('updateWorkspaceState()', async () => {
logger.debug = jest.fn(originalLoggerDebug)
await updateWorkspaceState({
pnpmfileExists: true,
pnpmfiles: [],
workspaceDir,
allProjects: [],
filteredInstall: false,
@@ -42,7 +42,7 @@ test('updateWorkspaceState()', async () => {
logger.debug = jest.fn(originalLoggerDebug)
await updateWorkspaceState({
pnpmfileExists: false,
pnpmfiles: [],
workspaceDir,
settings: {
autoInstallPeers: true,