From dd34bde61bd46ecc994a027953600532b5c868fd Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Mon, 30 Jun 2025 02:33:09 +0200 Subject: [PATCH 01/18] feat: allow to set multiple pnpmfiles --- cli/cli-utils/src/getConfig.ts | 6 +- hooks/pnpmfile/src/requireHooks.ts | 170 ++++++++++++------ hooks/pnpmfile/test/index.ts | 12 +- .../core/src/install/extendInstallOptions.ts | 2 +- pkg-manager/core/src/install/index.ts | 21 ++- 5 files changed, 135 insertions(+), 76 deletions(-) diff --git a/cli/cli-utils/src/getConfig.ts b/cli/cli-utils/src/getConfig.ts index ace42c55e91..52f1b5f8936 100644 --- a/cli/cli-utils/src/getConfig.ts +++ b/cli/cli-utils/src/getConfig.ts @@ -37,8 +37,10 @@ export async function getConfig ( if (!config.ignorePnpmfile) { config.hooks = requireHooks(config.lockfileDir ?? config.dir, config) 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 + } } } diff --git a/hooks/pnpmfile/src/requireHooks.ts b/hooks/pnpmfile/src/requireHooks.ts index 86738d99b60..7af2df5daeb 100644 --- a/hooks/pnpmfile/src/requireHooks.ts +++ b/hooks/pnpmfile/src/requireHooks.ts @@ -2,10 +2,10 @@ import type { PreResolutionHookContext, PreResolutionHookLogger } from '@pnpm/ho import { PnpmError } from '@pnpm/error' import { hookLogger } from '@pnpm/core-loggers' import { createHashFromFile } from '@pnpm/crypto.hash' +import { createHash } from 'crypto' 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 { type HookContext, type Hooks } from './Hooks' @@ -16,79 +16,135 @@ type Cook any> = ( ...otherArgs: any[] ) => ReturnType +export interface PnpmfileEntry { + path: string + includeInChecksum: boolean +} + export interface CookedHooks { readPackage?: Array['readPackage']>> - preResolution?: Cook['preResolution']> + preResolution?: Array['preResolution']>> afterAllResolved?: Array['afterAllResolved']>> filterLog?: Array['filterLog']>> - updateConfig?: Hooks['updateConfig'] + updateConfig?: Array['updateConfig']>> importPackage?: ImportIndexedPackageAsync fetchers?: CustomFetchers - calculatePnpmfileChecksum?: () => Promise + calculatePnpmfileChecksum?: () => Promise } export function requireHooks ( prefix: string, opts: { globalPnpmfile?: string - pnpmfile?: string + pnpmfiles?: string[] } ): 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> = { - afterAllResolved: [], - filterLog: [], - readPackage: [], - calculatePnpmfileChecksum, + const pnpmfiles: PnpmfileEntry[] = [] + if (opts.globalPnpmfile) { + pnpmfiles.push({ + path: opts.globalPnpmfile, + includeInChecksum: false, + }) } - 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 (opts.pnpmfiles) { + for (const pnpmfile of opts.pnpmfiles) { + pnpmfiles.push({ + path: pnpmfile, + includeInChecksum: true, + }) } } - if (globalHooks.filterLog != null) { - cookedHooks.filterLog.push(globalHooks.filterLog) + const entries = pnpmfiles.map(({ path, includeInChecksum }) => ({ + file: pathAbsolute(path, prefix), + includeInChecksum, + module: requirePnpmfile(pathAbsolute(path, prefix), prefix), + })) ?? [] + + const cookedHooks: CookedHooks & Required> = { + readPackage: [], + preResolution: [], + afterAllResolved: [], + filterLog: [], + updateConfig: [], } - if (hooks.filterLog != null) { - cookedHooks.filterLog.push(hooks.filterLog) + + // calculate combined checksum for all included files + if (entries.length > 0) { + cookedHooks.calculatePnpmfileChecksum = async () => { + const checksums = await Promise.all( + entries + .filter((e) => e.includeInChecksum) + .map((e) => createHashFromFile(e.file)) + ) + const hasher = createHash('sha256') + for (const sum of checksums) { + hasher.update(sum) + } + return hasher.digest('hex') + } } - // `importPackage`, `preResolution` and `fetchers` can only be defined via a global pnpmfile + let importProvider: string | undefined + let fetchersProvider: string | undefined - cookedHooks.importPackage = hooks.importPackage ?? globalHooks.importPackage + // process hooks in order + for (const { module, file } of entries) { + const fileHooks: Hooks = module?.hooks ?? {} - const preResolutionHook = hooks.preResolution ?? globalHooks.preResolution + // 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))) + } - cookedHooks.preResolution = preResolutionHook - ? (ctx: PreResolutionHookContext) => preResolutionHook(ctx, createPreResolutionHookLogger(prefix)) - : undefined + // 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 + } - 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') + // fetchers: only one allowed + if (fileHooks.fetchers) { + if (fetchersProvider) { + throw new PnpmError( + 'MULTIPLE_FETCHERS', + `fetchers hook defined in both ${fetchersProvider} and ${file}` + ) } - return updatedConfig + fetchersProvider = file + cookedHooks.fetchers = fileHooks.fetchers } } @@ -98,21 +154,19 @@ export function requireHooks ( 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 + }, } } diff --git a/hooks/pnpmfile/test/index.ts b/hooks/pnpmfile/test/index.ts index 6cd099c452e..b3496d8f494 100644 --- a/hooks/pnpmfile/test/index.ts +++ b/hooks/pnpmfile/test/index.ts @@ -29,7 +29,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, pnpmfiles: [pnpmfile] }) expect(hooks.filterLog).toBeDefined() expect(hooks.filterLog!.length).toBe(2) @@ -47,24 +47,24 @@ 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, { pnpmfiles: ['file-that-does-not-exist.js'] }) 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, { pnpmfiles: [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, { pnpmfiles: [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, { pnpmfiles: [pnpmfile] }) + expect(() => hooks.updateConfig![0]!({})).toThrow('The updateConfig hook returned undefined') }) diff --git a/pkg-manager/core/src/install/extendInstallOptions.ts b/pkg-manager/core/src/install/extendInstallOptions.ts index 7ca5b1a53c8..354b9f4bfe2 100644 --- a/pkg-manager/core/src/install/extendInstallOptions.ts +++ b/pkg-manager/core/src/install/extendInstallOptions.ts @@ -88,7 +88,7 @@ export interface StrictInstallOptions { pruneLockfileImporters: boolean hooks: { readPackage?: ReadPackageHook[] - preResolution?: (ctx: PreResolutionHookContext) => Promise + preResolution?: Array<(ctx: PreResolutionHookContext) => Promise> afterAllResolved?: Array<(lockfile: LockfileObject) => LockfileObject | Promise> calculatePnpmfileChecksum?: () => Promise } diff --git a/pkg-manager/core/src/install/index.ts b/pkg-manager/core/src/install/index.ts index 233d7019a64..07040273447 100644 --- a/pkg-manager/core/src/install/index.ts +++ b/pkg-manager/core/src/install/index.ts @@ -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 From acce41a3f3487b552218c465b4697be7968f562a Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Mon, 30 Jun 2025 03:06:05 +0200 Subject: [PATCH 02/18] test: fix --- hooks/pnpmfile/src/requireHooks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/pnpmfile/src/requireHooks.ts b/hooks/pnpmfile/src/requireHooks.ts index 7af2df5daeb..c0b3f938288 100644 --- a/hooks/pnpmfile/src/requireHooks.ts +++ b/hooks/pnpmfile/src/requireHooks.ts @@ -69,7 +69,7 @@ export function requireHooks ( } // calculate combined checksum for all included files - if (entries.length > 0) { + if (entries.some((entry) => entry.module != null)) { cookedHooks.calculatePnpmfileChecksum = async () => { const checksums = await Promise.all( entries From 2ce65b1264dfa02f7ccbe9b347b0e667dcf76bd9 Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Tue, 1 Jul 2025 13:28:59 +0200 Subject: [PATCH 03/18] test: fix --- config/config/src/Config.ts | 2 +- deps/status/src/checkDepsStatus.ts | 24 ++++++++++--------- hooks/pnpmfile/src/requireHooks.ts | 15 ++++++++---- hooks/pnpmfile/test/index.ts | 10 ++++---- .../core/src/install/extendInstallOptions.ts | 2 +- 5 files changed, 31 insertions(+), 22 deletions(-) diff --git a/config/config/src/Config.ts b/config/config/src/Config.ts index 1eb15dd95f2..094dc0b018e 100644 --- a/config/config/src/Config.ts +++ b/config/config/src/Config.ts @@ -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[] diff --git a/deps/status/src/checkDepsStatus.ts b/deps/status/src/checkDepsStatus.ts index a6e995edd4f..c121e15ad2e 100644 --- a/deps/status/src/checkDepsStatus.ts +++ b/deps/status/src/checkDepsStatus.ts @@ -549,7 +549,7 @@ async function patchesAreModified (opts: { rootManifestOptions: OptionsFromRootManifest | undefined rootDir: string lastValidatedTimestamp: number - pnpmfile: string + pnpmfile: string[] | string hadPnpmfile: boolean }): Promise { if (opts.rootManifestOptions?.patchedDependencies) { @@ -563,16 +563,18 @@ 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 (opts.hadPnpmfile && pnpmfileStats == null) { - return `pnpmfile at "${pnpmfilePath}" was removed` - } - if (!opts.hadPnpmfile && pnpmfileStats != null) { - return `pnpmfile at "${pnpmfilePath}" was added` + for (const pnpmfile of opts.pnpmfile) { + const pnpmfilePath = getPnpmfilePath(opts.rootDir, pnpmfile) + const pnpmfileStats = safeStatSync(pnpmfilePath) + if (pnpmfileStats != null && pnpmfileStats.mtime.valueOf() > opts.lastValidatedTimestamp) { + return `pnpmfile at "${pnpmfilePath}" was modified` + } + if (opts.hadPnpmfile && pnpmfileStats == null) { + return `pnpmfile at "${pnpmfilePath}" was removed` + } + if (!opts.hadPnpmfile && pnpmfileStats != null) { + return `pnpmfile at "${pnpmfilePath}" was added` + } } return undefined } diff --git a/hooks/pnpmfile/src/requireHooks.ts b/hooks/pnpmfile/src/requireHooks.ts index c0b3f938288..ff87144053c 100644 --- a/hooks/pnpmfile/src/requireHooks.ts +++ b/hooks/pnpmfile/src/requireHooks.ts @@ -36,7 +36,7 @@ export function requireHooks ( prefix: string, opts: { globalPnpmfile?: string - pnpmfiles?: string[] + pnpmfile?: string[] | string } ): CookedHooks { const pnpmfiles: PnpmfileEntry[] = [] @@ -46,10 +46,17 @@ export function requireHooks ( includeInChecksum: false, }) } - if (opts.pnpmfiles) { - for (const pnpmfile of opts.pnpmfiles) { + if (opts.pnpmfile) { + if (Array.isArray(opts.pnpmfile)) { + for (const pnpmfile of opts.pnpmfile) { + pnpmfiles.push({ + path: pnpmfile, + includeInChecksum: true, + }) + } + } else { pnpmfiles.push({ - path: pnpmfile, + path: opts.pnpmfile, includeInChecksum: true, }) } diff --git a/hooks/pnpmfile/test/index.ts b/hooks/pnpmfile/test/index.ts index b3496d8f494..aba9f2b74b7 100644 --- a/hooks/pnpmfile/test/index.ts +++ b/hooks/pnpmfile/test/index.ts @@ -29,7 +29,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, pnpmfiles: [pnpmfile] }) + const hooks = requireHooks(__dirname, { globalPnpmfile, pnpmfile: [pnpmfile] }) expect(hooks.filterLog).toBeDefined() expect(hooks.filterLog!.length).toBe(2) @@ -47,24 +47,24 @@ test('filterLog hook combines with the global hook', () => { }) test('calculatePnpmfileChecksum is undefined when pnpmfile does not exist', async () => { - const hooks = requireHooks(__dirname, { pnpmfiles: ['file-that-does-not-exist.js'] }) + const hooks = requireHooks(__dirname, { pnpmfile: ['file-that-does-not-exist.js'] }) 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, { pnpmfiles: [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, { pnpmfiles: [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, { pnpmfiles: [pnpmfile] }) + const hooks = requireHooks(__dirname, { pnpmfile: [pnpmfile] }) expect(() => hooks.updateConfig![0]!({})).toThrow('The updateConfig hook returned undefined') }) diff --git a/pkg-manager/core/src/install/extendInstallOptions.ts b/pkg-manager/core/src/install/extendInstallOptions.ts index 354b9f4bfe2..c19573ad3ec 100644 --- a/pkg-manager/core/src/install/extendInstallOptions.ts +++ b/pkg-manager/core/src/install/extendInstallOptions.ts @@ -79,7 +79,7 @@ export interface StrictInstallOptions { nodeVersion?: string packageExtensions: Record ignoredOptionalDependencies: string[] - pnpmfile: string + pnpmfile: string[] | string ignorePnpmfile: boolean packageManager: { name: string From ad8d29ef2eb3bc8e9a68c40fc80f567140bb3a0a Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Wed, 2 Jul 2025 00:38:50 +0200 Subject: [PATCH 04/18] test: fix --- deps/status/src/checkDepsStatus.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/status/src/checkDepsStatus.ts b/deps/status/src/checkDepsStatus.ts index c121e15ad2e..08f4bb7f491 100644 --- a/deps/status/src/checkDepsStatus.ts +++ b/deps/status/src/checkDepsStatus.ts @@ -563,7 +563,7 @@ async function patchesAreModified (opts: { return 'Patches were modified' } } - for (const pnpmfile of opts.pnpmfile) { + for (const pnpmfile of Array.isArray(opts.pnpmfile) ? opts.pnpmfile : [opts.pnpmfile]) { const pnpmfilePath = getPnpmfilePath(opts.rootDir, pnpmfile) const pnpmfileStats = safeStatSync(pnpmfilePath) if (pnpmfileStats != null && pnpmfileStats.mtime.valueOf() > opts.lastValidatedTimestamp) { From 0460daec49a48c51b301cb38b0699409b0a71f10 Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Sat, 5 Jul 2025 17:23:48 +0200 Subject: [PATCH 05/18] test: fix --- crypto/hash/src/index.ts | 8 +++++ hooks/pnpmfile/src/requireHooks.ts | 55 +++++++++++++++++++----------- 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/crypto/hash/src/index.ts b/crypto/hash/src/index.ts index 3f0d327859a..6374ee13427 100644 --- a/crypto/hash/src/index.ts +++ b/crypto/hash/src/index.ts @@ -19,6 +19,14 @@ export async function createHashFromFile (file: string): Promise { return createHash(await readNormalizedFile(file)) } +export async function createHashFromFiles (files: string[]): Promise { + if (files.length === 1) { + return createHashFromFile(files[0]) + } + const hashes = await Promise.all(files.map(createHashFromFile)) + return createHash(hashes.join(',')) +} + export async function createHexHashFromFile (file: string): Promise { return createHexHash(await readNormalizedFile(file)) } diff --git a/hooks/pnpmfile/src/requireHooks.ts b/hooks/pnpmfile/src/requireHooks.ts index ff87144053c..e3d82fb7ecc 100644 --- a/hooks/pnpmfile/src/requireHooks.ts +++ b/hooks/pnpmfile/src/requireHooks.ts @@ -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 { createHash } from 'crypto' +import { createHashFromFiles } 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 { requirePnpmfile } from './requirePnpmfile' +import { requirePnpmfile, type Pnpmfile } from './requirePnpmfile' import { type HookContext, type Hooks } from './Hooks' // eslint-disable-next-line @@ -16,11 +15,17 @@ type Cook any> = ( ...otherArgs: any[] ) => ReturnType -export interface PnpmfileEntry { +interface PnpmfileEntry { path: string includeInChecksum: boolean } +interface PnpmfileEntryLoaded { + file: string + module: Pnpmfile | undefined + includeInChecksum: boolean +} + export interface CookedHooks { readPackage?: Array['readPackage']>> preResolution?: Array['preResolution']>> @@ -39,7 +44,10 @@ export function requireHooks ( pnpmfile?: string[] | string } ): CookedHooks { - const pnpmfiles: PnpmfileEntry[] = [] + const pnpmfiles: PnpmfileEntry[] = [{ + path: '.pnpmfile.cjs', + includeInChecksum: true, + }] if (opts.globalPnpmfile) { pnpmfiles.push({ path: opts.globalPnpmfile, @@ -61,11 +69,22 @@ export function requireHooks ( }) } } - const entries = pnpmfiles.map(({ path, includeInChecksum }) => ({ - file: pathAbsolute(path, prefix), - includeInChecksum, - module: requirePnpmfile(pathAbsolute(path, prefix), prefix), - })) ?? [] + const entries: PnpmfileEntryLoaded[] = [] + const loadedFiles: string[] = [] + for (const { path, includeInChecksum } of pnpmfiles) { + const file = pathAbsolute(path, prefix) + if (!loadedFiles.includes(file)) { + loadedFiles.push(file) + const module = requirePnpmfile(pathAbsolute(path, prefix), prefix) + if (module != null) { + entries.push({ + file, + includeInChecksum, + module, + }) + } + } + } const cookedHooks: CookedHooks & Required> = { readPackage: [], @@ -78,16 +97,14 @@ export function requireHooks ( // calculate combined checksum for all included files if (entries.some((entry) => entry.module != null)) { cookedHooks.calculatePnpmfileChecksum = async () => { - const checksums = await Promise.all( - entries - .filter((e) => e.includeInChecksum) - .map((e) => createHashFromFile(e.file)) - ) - const hasher = createHash('sha256') - for (const sum of checksums) { - hasher.update(sum) + const filesToIncludeInHash: string[] = [] + for (const { includeInChecksum, file } of entries) { + if (includeInChecksum) { + filesToIncludeInHash.push(file) + } } - return hasher.digest('hex') + filesToIncludeInHash.sort() + return createHashFromFiles(filesToIncludeInHash) } } From ef000620c8155b1d3d76824a2af3906a2dc58bfe Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Sat, 5 Jul 2025 19:34:03 +0200 Subject: [PATCH 06/18] test: fix --- hooks/pnpmfile/src/requireHooks.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/hooks/pnpmfile/src/requireHooks.ts b/hooks/pnpmfile/src/requireHooks.ts index e3d82fb7ecc..017d05848fe 100644 --- a/hooks/pnpmfile/src/requireHooks.ts +++ b/hooks/pnpmfile/src/requireHooks.ts @@ -44,16 +44,17 @@ export function requireHooks ( pnpmfile?: string[] | string } ): CookedHooks { - const pnpmfiles: PnpmfileEntry[] = [{ - path: '.pnpmfile.cjs', - includeInChecksum: true, - }] + const pnpmfiles: PnpmfileEntry[] = [] if (opts.globalPnpmfile) { pnpmfiles.push({ path: opts.globalPnpmfile, includeInChecksum: false, }) } + pnpmfiles.push({ + path: '.pnpmfile.cjs', + includeInChecksum: true, + }) if (opts.pnpmfile) { if (Array.isArray(opts.pnpmfile)) { for (const pnpmfile of opts.pnpmfile) { From 0e8579da607c5b0ce3693fd532dc7e5df489edab Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Sun, 6 Jul 2025 01:05:34 +0200 Subject: [PATCH 07/18] fix: verifyDepsBeforeRun with multiple pnpmfiles --- cli/cli-utils/src/getConfig.ts | 4 +- crypto/hash/src/index.ts | 8 ++-- deps/status/package.json | 1 - deps/status/src/checkDepsStatus.ts | 37 +++++++++---------- deps/status/tsconfig.json | 3 -- hooks/pnpmfile/src/getPnpmfilePath.test.ts | 14 ------- hooks/pnpmfile/src/getPnpmfilePath.ts | 10 ----- hooks/pnpmfile/src/index.ts | 1 - hooks/pnpmfile/src/requireHooks.ts | 12 +++++- hooks/pnpmfile/test/index.ts | 10 ++--- .../src/installDeps.ts | 6 +-- .../src/recursive.ts | 2 +- pnpm-lock.yaml | 3 -- pnpm/test/verifyDepsBeforeRun/exec.ts | 4 +- pnpm/test/verifyDepsBeforeRun/issue-9424.ts | 2 +- .../multiProjectWorkspace.ts | 12 +++--- workspace/state/src/createWorkspaceState.ts | 4 +- workspace/state/src/types.ts | 2 +- workspace/state/src/updateWorkspaceState.ts | 2 +- .../state/test/createWorkspaceState.test.ts | 4 +- .../state/test/loadWorkspaceState.test.ts | 2 +- .../state/test/updatePackagesList.test.ts | 4 +- 22 files changed, 62 insertions(+), 85 deletions(-) delete mode 100644 hooks/pnpmfile/src/getPnpmfilePath.test.ts delete mode 100644 hooks/pnpmfile/src/getPnpmfilePath.ts diff --git a/cli/cli-utils/src/getConfig.ts b/cli/cli-utils/src/getConfig.ts index 52f1b5f8936..80f06268309 100644 --- a/cli/cli-utils/src/getConfig.ts +++ b/cli/cli-utils/src/getConfig.ts @@ -35,7 +35,9 @@ 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) { for (const updateConfig of config.hooks.updateConfig) { const updateConfigResult = updateConfig(config) diff --git a/crypto/hash/src/index.ts b/crypto/hash/src/index.ts index 6374ee13427..c6f8ad610d9 100644 --- a/crypto/hash/src/index.ts +++ b/crypto/hash/src/index.ts @@ -15,10 +15,6 @@ export function createHash (input: string): string { return `sha256-${crypto.hash('sha256', input, 'base64')}` } -export async function createHashFromFile (file: string): Promise { - return createHash(await readNormalizedFile(file)) -} - export async function createHashFromFiles (files: string[]): Promise { if (files.length === 1) { return createHashFromFile(files[0]) @@ -27,6 +23,10 @@ export async function createHashFromFiles (files: string[]): Promise { return createHash(hashes.join(',')) } +export async function createHashFromFile (file: string): Promise { + return createHash(await readNormalizedFile(file)) +} + export async function createHexHashFromFile (file: string): Promise { return createHexHash(await readNormalizedFile(file)) } diff --git a/deps/status/package.json b/deps/status/package.json index 092ae9bea3d..9fba50b2c56 100644 --- a/deps/status/package.json +++ b/deps/status/package.json @@ -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:*", diff --git a/deps/status/src/checkDepsStatus.ts b/deps/status/src/checkDepsStatus.ts index 08f4bb7f491..ba9ba699719 100644 --- a/deps/status/src/checkDepsStatus.ts +++ b/deps/status/src/checkDepsStatus.ts @@ -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, @@ -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 as string[], + 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 as string[], + 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[] | string - hadPnpmfile: boolean + currentPnpmfiles: string[] + previousPnpmfiles: string[] }): Promise { if (opts.rootManifestOptions?.patchedDependencies) { const allPatchStats = await Promise.all(Object.values(opts.rootManifestOptions.patchedDependencies).map((patchFile) => { @@ -563,17 +562,17 @@ async function patchesAreModified (opts: { return 'Patches were modified' } } - for (const pnpmfile of Array.isArray(opts.pnpmfile) ? opts.pnpmfile : [opts.pnpmfile]) { - const pnpmfilePath = getPnpmfilePath(opts.rootDir, pnpmfile) + if (!equals(opts.currentPnpmfiles, opts.previousPnpmfiles)) { + return 'The list of pnpmfiles changed.' + } + for (let i = 0; i < opts.currentPnpmfiles.length; i++) { + const pnpmfilePath = opts.currentPnpmfiles[0] const pnpmfileStats = safeStatSync(pnpmfilePath) - if (pnpmfileStats != null && pnpmfileStats.mtime.valueOf() > opts.lastValidatedTimestamp) { - return `pnpmfile at "${pnpmfilePath}" was modified` - } - if (opts.hadPnpmfile && pnpmfileStats == null) { + if (pnpmfileStats == null) { return `pnpmfile at "${pnpmfilePath}" was removed` } - if (!opts.hadPnpmfile && pnpmfileStats != null) { - return `pnpmfile at "${pnpmfilePath}" was added` + if (pnpmfileStats.mtime.valueOf() > opts.lastValidatedTimestamp) { + return `pnpmfile at "${pnpmfilePath}" was modified` } } return undefined diff --git a/deps/status/tsconfig.json b/deps/status/tsconfig.json index b710f789738..b330bcada06 100644 --- a/deps/status/tsconfig.json +++ b/deps/status/tsconfig.json @@ -21,9 +21,6 @@ { "path": "../../crypto/object-hasher" }, - { - "path": "../../hooks/pnpmfile" - }, { "path": "../../lockfile/fs" }, diff --git a/hooks/pnpmfile/src/getPnpmfilePath.test.ts b/hooks/pnpmfile/src/getPnpmfilePath.test.ts deleted file mode 100644 index 736079b893c..00000000000 --- a/hooks/pnpmfile/src/getPnpmfilePath.test.ts +++ /dev/null @@ -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') -}) diff --git a/hooks/pnpmfile/src/getPnpmfilePath.ts b/hooks/pnpmfile/src/getPnpmfilePath.ts deleted file mode 100644 index 7d73a466357..00000000000 --- a/hooks/pnpmfile/src/getPnpmfilePath.ts +++ /dev/null @@ -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) -} diff --git a/hooks/pnpmfile/src/index.ts b/hooks/pnpmfile/src/index.ts index c0dbf4612f6..af208c8f992 100644 --- a/hooks/pnpmfile/src/index.ts +++ b/hooks/pnpmfile/src/index.ts @@ -1,6 +1,5 @@ import type { CookedHooks } from './requireHooks' -export { getPnpmfilePath } from './getPnpmfilePath' export { requireHooks } from './requireHooks' export { requirePnpmfile, BadReadPackageHookError } from './requirePnpmfile' export type { HookContext } from './Hooks' diff --git a/hooks/pnpmfile/src/requireHooks.ts b/hooks/pnpmfile/src/requireHooks.ts index 017d05848fe..31219b9a2e0 100644 --- a/hooks/pnpmfile/src/requireHooks.ts +++ b/hooks/pnpmfile/src/requireHooks.ts @@ -37,13 +37,18 @@ export interface CookedHooks { calculatePnpmfileChecksum?: () => Promise } +export interface RequireHooksResult { + hooks: CookedHooks + resolvedPnpmfilePaths: string[] +} + export function requireHooks ( prefix: string, opts: { globalPnpmfile?: string pnpmfile?: string[] | string } -): CookedHooks { +): RequireHooksResult { const pnpmfiles: PnpmfileEntry[] = [] if (opts.globalPnpmfile) { pnpmfiles.push({ @@ -173,7 +178,10 @@ export function requireHooks ( } } - return cookedHooks + return { + hooks: cookedHooks, + resolvedPnpmfilePaths: entries.map(({ file }) => file), + } } function createReadPackageHookContext (calledFrom: string, prefix: string, hook: string): HookContext { diff --git a/hooks/pnpmfile/test/index.ts b/hooks/pnpmfile/test/index.ts index aba9f2b74b7..994504c9fad 100644 --- a/hooks/pnpmfile/test/index.ts +++ b/hooks/pnpmfile/test/index.ts @@ -29,7 +29,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: [pnpmfile] }) + const { hooks } = requireHooks(__dirname, { globalPnpmfile, pnpmfile: [pnpmfile] }) expect(hooks.filterLog).toBeDefined() expect(hooks.filterLog!.length).toBe(2) @@ -47,24 +47,24 @@ 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, { pnpmfile: ['file-that-does-not-exist.js'] }) 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: [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: [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: [pnpmfile] }) + const { hooks } = requireHooks(__dirname, { pnpmfile: [pnpmfile] }) expect(() => hooks.updateConfig![0]!({})).toThrow('The updateConfig hook returned undefined') }) diff --git a/pkg-manager/plugin-commands-installation/src/installDeps.ts b/pkg-manager/plugin-commands-installation/src/installDeps.ts index eb53e1695de..0ab9b393a9a 100644 --- a/pkg-manager/plugin-commands-installation/src/installDeps.ts +++ b/pkg-manager/plugin-commands-installation/src/installDeps.ts @@ -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 as string[], 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 as string[], 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 as string[], filteredInstall: allProjects.length !== Object.keys(opts.selectedProjectsGraph ?? {}).length, configDependencies: opts.configDependencies, }) diff --git a/pkg-manager/plugin-commands-installation/src/recursive.ts b/pkg-manager/plugin-commands-installation/src/recursive.ts index cc791b11cc3..dafc1753d84 100755 --- a/pkg-manager/plugin-commands-installation/src/recursive.ts +++ b/pkg-manager/plugin-commands-installation/src/recursive.ts @@ -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, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dd4430cfd6f..f5419f4e875 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2008,9 +2008,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 diff --git a/pnpm/test/verifyDepsBeforeRun/exec.ts b/pnpm/test/verifyDepsBeforeRun/exec.ts index 9ae115cde8a..77aa696bc2a 100644 --- a/pnpm/test/verifyDepsBeforeRun/exec.ts +++ b/pnpm/test/verifyDepsBeforeRun/exec.ts @@ -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' }, diff --git a/pnpm/test/verifyDepsBeforeRun/issue-9424.ts b/pnpm/test/verifyDepsBeforeRun/issue-9424.ts index d530841c016..a3acfb9f845 100644 --- a/pnpm/test/verifyDepsBeforeRun/issue-9424.ts +++ b/pnpm/test/verifyDepsBeforeRun/issue-9424.ts @@ -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' }, diff --git a/pnpm/test/verifyDepsBeforeRun/multiProjectWorkspace.ts b/pnpm/test/verifyDepsBeforeRun/multiProjectWorkspace.ts index 5969ac0e833..a371a148302 100644 --- a/pnpm/test/verifyDepsBeforeRun/multiProjectWorkspace.ts +++ b/pnpm/test/verifyDepsBeforeRun/multiProjectWorkspace.ts @@ -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: { diff --git a/workspace/state/src/createWorkspaceState.ts b/workspace/state/src/createWorkspaceState.ts index 8f3e31cae07..081aa041346 100644 --- a/workspace/state/src/createWorkspaceState.ts +++ b/workspace/state/src/createWorkspaceState.ts @@ -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 @@ -18,7 +18,7 @@ export const createWorkspaceState = (opts: CreateWorkspaceStateOptions): Workspa version: project.manifest.version, }, ])), - pnpmfileExists: opts.pnpmfileExists, + pnpmfiles: opts.pnpmfiles, settings: pick([ 'autoInstallPeers', 'catalogs', diff --git a/workspace/state/src/types.ts b/workspace/state/src/types.ts index e82f330e72f..84adfc0bbc0 100644 --- a/workspace/state/src/types.ts +++ b/workspace/state/src/types.ts @@ -9,7 +9,7 @@ export interface WorkspaceState { name?: string version?: string }> - pnpmfileExists: boolean + pnpmfiles: string[] filteredInstall: boolean configDependencies?: Record settings: WorkspaceStateSettings diff --git a/workspace/state/src/updateWorkspaceState.ts b/workspace/state/src/updateWorkspaceState.ts index 550c1de183d..dbfa6a8ae70 100644 --- a/workspace/state/src/updateWorkspaceState.ts +++ b/workspace/state/src/updateWorkspaceState.ts @@ -9,7 +9,7 @@ export interface UpdateWorkspaceStateOptions { allProjects: ProjectsList settings: WorkspaceStateSettings workspaceDir: string - pnpmfileExists: boolean + pnpmfiles: string[] filteredInstall: boolean configDependencies?: Record } diff --git a/workspace/state/test/createWorkspaceState.test.ts b/workspace/state/test/createWorkspaceState.test.ts index de7c0a0b1fc..3596fe5b348 100644 --- a/workspace/state/test/createWorkspaceState.test.ts +++ b/workspace/state/test/createWorkspaceState.test.ts @@ -9,7 +9,7 @@ test('createWorkspaceState() on empty list', () => { expect( createWorkspaceState({ allProjects: [], - pnpmfileExists: true, + pnpmfiles: [], filteredInstall: false, settings: { autoInstallPeers: true, @@ -54,7 +54,7 @@ test('createWorkspaceState() on non-empty list', () => { }, }, }, - pnpmfileExists: false, + pnpmfiles: [], filteredInstall: false, }) ).toStrictEqual(expect.objectContaining({ diff --git a/workspace/state/test/loadWorkspaceState.test.ts b/workspace/state/test/loadWorkspaceState.test.ts index c24f35c7ad9..862f93536c5 100644 --- a/workspace/state/test/loadWorkspaceState.test.ts +++ b/workspace/state/test/loadWorkspaceState.test.ts @@ -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)) diff --git a/workspace/state/test/updatePackagesList.test.ts b/workspace/state/test/updatePackagesList.test.ts index 2a867473c29..e3a35c601d9 100644 --- a/workspace/state/test/updatePackagesList.test.ts +++ b/workspace/state/test/updatePackagesList.test.ts @@ -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, From 975b28f17e143643619227fe8548cbeb8f75fef5 Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Mon, 7 Jul 2025 15:34:06 +0200 Subject: [PATCH 08/18] test: fix --- workspace/state/test/createWorkspaceState.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workspace/state/test/createWorkspaceState.test.ts b/workspace/state/test/createWorkspaceState.test.ts index 3596fe5b348..df3481eb48f 100644 --- a/workspace/state/test/createWorkspaceState.test.ts +++ b/workspace/state/test/createWorkspaceState.test.ts @@ -22,7 +22,7 @@ test('createWorkspaceState() on empty list', () => { }) ).toStrictEqual(expect.objectContaining({ projects: {}, - pnpmfileExists: true, + pnpmfiles: [], lastValidatedTimestamp: expect.any(Number), })) }) @@ -72,6 +72,6 @@ test('createWorkspaceState() on non-empty list', () => { [path.resolve('packages/c')]: {}, [path.resolve('packages/d')]: {}, }, - pnpmfileExists: false, + pnpmfiles: [], })) }) From b2e666794e6bc1686fdabd1b229e072ef9a8016a Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Mon, 7 Jul 2025 22:20:48 +0200 Subject: [PATCH 09/18] test: fix --- deps/status/src/checkDepsStatus.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/deps/status/src/checkDepsStatus.ts b/deps/status/src/checkDepsStatus.ts index ba9ba699719..504534b085f 100644 --- a/deps/status/src/checkDepsStatus.ts +++ b/deps/status/src/checkDepsStatus.ts @@ -565,8 +565,7 @@ async function patchesOrHooksAreModified (opts: { if (!equals(opts.currentPnpmfiles, opts.previousPnpmfiles)) { return 'The list of pnpmfiles changed.' } - for (let i = 0; i < opts.currentPnpmfiles.length; i++) { - const pnpmfilePath = opts.currentPnpmfiles[0] + for (const pnpmfilePath in opts.currentPnpmfiles) { const pnpmfileStats = safeStatSync(pnpmfilePath) if (pnpmfileStats == null) { return `pnpmfile at "${pnpmfilePath}" was removed` From 74e39906041adbf4d3faf7d82b2494ccf5e5f7b8 Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Mon, 7 Jul 2025 22:23:34 +0200 Subject: [PATCH 10/18] test: fix --- deps/status/src/checkDepsStatus.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/status/src/checkDepsStatus.ts b/deps/status/src/checkDepsStatus.ts index 504534b085f..a0c169ae0f6 100644 --- a/deps/status/src/checkDepsStatus.ts +++ b/deps/status/src/checkDepsStatus.ts @@ -565,7 +565,7 @@ async function patchesOrHooksAreModified (opts: { if (!equals(opts.currentPnpmfiles, opts.previousPnpmfiles)) { return 'The list of pnpmfiles changed.' } - for (const pnpmfilePath in opts.currentPnpmfiles) { + for (const pnpmfilePath of opts.currentPnpmfiles) { const pnpmfileStats = safeStatSync(pnpmfilePath) if (pnpmfileStats == null) { return `pnpmfile at "${pnpmfilePath}" was removed` From da33eb8b9d4deaf9995ebcaf1aae9daa6b6d6421 Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Tue, 8 Jul 2025 00:55:10 +0200 Subject: [PATCH 11/18] test: checkDepStatus --- deps/status/test/checkDepsStatus.test.ts | 87 ++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 deps/status/test/checkDepsStatus.test.ts diff --git a/deps/status/test/checkDepsStatus.test.ts b/deps/status/test/checkDepsStatus.test.ts new file mode 100644 index 00000000000..f4b63aa0273 --- /dev/null +++ b/deps/status/test/checkDepsStatus.test.ts @@ -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') + }) +}) From 69b5bca525d08ddd5e507a00cf51e294f6e9d988 Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Tue, 8 Jul 2025 01:03:12 +0200 Subject: [PATCH 12/18] docs: add changesets --- .changeset/full-trees-strive.md | 12 ++++++++++++ .changeset/major-coats-make.md | 5 +++++ .changeset/rich-sites-notice.md | 5 +++++ 3 files changed, 22 insertions(+) create mode 100644 .changeset/full-trees-strive.md create mode 100644 .changeset/major-coats-make.md create mode 100644 .changeset/rich-sites-notice.md diff --git a/.changeset/full-trees-strive.md b/.changeset/full-trees-strive.md new file mode 100644 index 00000000000..7d2e22c13e7 --- /dev/null +++ b/.changeset/full-trees-strive.md @@ -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). diff --git a/.changeset/major-coats-make.md b/.changeset/major-coats-make.md new file mode 100644 index 00000000000..7b643ec4d2a --- /dev/null +++ b/.changeset/major-coats-make.md @@ -0,0 +1,5 @@ +--- +"@pnpm/crypto.hash": minor +--- + +A new function added: createHashFromFiles. diff --git a/.changeset/rich-sites-notice.md b/.changeset/rich-sites-notice.md new file mode 100644 index 00000000000..aabeaee1ca8 --- /dev/null +++ b/.changeset/rich-sites-notice.md @@ -0,0 +1,5 @@ +--- +"@pnpm/core": major +--- + +`hooks.preResolution` is now an array of functions. From 66472f88891bbe1da743eed63bd7ca41b9efe69e Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Tue, 8 Jul 2025 01:14:23 +0200 Subject: [PATCH 13/18] refactor: pnpmfile --- hooks/pnpmfile/src/index.ts | 2 +- hooks/pnpmfile/src/requirePnpmfile.ts | 6 ++---- hooks/pnpmfile/test/index.ts | 3 ++- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/hooks/pnpmfile/src/index.ts b/hooks/pnpmfile/src/index.ts index af208c8f992..7cd110037a4 100644 --- a/hooks/pnpmfile/src/index.ts +++ b/hooks/pnpmfile/src/index.ts @@ -1,6 +1,6 @@ import type { CookedHooks } from './requireHooks' export { requireHooks } from './requireHooks' -export { requirePnpmfile, BadReadPackageHookError } from './requirePnpmfile' +export { BadReadPackageHookError } from './requirePnpmfile' export type { HookContext } from './Hooks' export type Hooks = CookedHooks diff --git a/hooks/pnpmfile/src/requirePnpmfile.ts b/hooks/pnpmfile/src/requirePnpmfile.ts index bb1da29bc5a..8aedbd69431 100644 --- a/hooks/pnpmfile/src/requirePnpmfile.ts +++ b/hooks/pnpmfile/src/requirePnpmfile.ts @@ -29,12 +29,11 @@ class PnpmFileFailError extends PnpmError { export interface Pnpmfile { hooks?: Hooks - filename: string } export function requirePnpmfile (pnpmFilePath: string, prefix: string): Pnpmfile | 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".`, @@ -65,8 +64,7 @@ export function requirePnpmfile (pnpmFilePath: string, prefix: string): Pnpmfile return newPkg } } - pnpmfile.filename = pnpmFilePath - return pnpmfile as Pnpmfile + return pnpmfile } catch (err: unknown) { if (err instanceof SyntaxError) { console.error(chalk.red('A syntax error in the .pnpmfile.cjs\n')) diff --git a/hooks/pnpmfile/test/index.ts b/hooks/pnpmfile/test/index.ts index 994504c9fad..5c8d24b50d6 100644 --- a/hooks/pnpmfile/test/index.ts +++ b/hooks/pnpmfile/test/index.ts @@ -1,6 +1,7 @@ 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 () {} } From f59a071fa6a82c4b22072c0d0ee60ed821dd5098 Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Tue, 8 Jul 2025 11:56:13 +0200 Subject: [PATCH 14/18] fix: add versioning to the workspace state file --- pnpm/test/install/issue-8959.ts | 2 +- workspace/state/src/filePath.ts | 2 +- workspace/state/test/filePath.test.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pnpm/test/install/issue-8959.ts b/pnpm/test/install/issue-8959.ts index c096c075449..db16490d4b5 100644 --- a/pnpm/test/install/issue-8959.ts +++ b/pnpm/test/install/issue-8959.ts @@ -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 }) diff --git a/workspace/state/src/filePath.ts b/workspace/state/src/filePath.ts index 1534d03aed5..2e92d9870eb 100644 --- a/workspace/state/src/filePath.ts +++ b/workspace/state/src/filePath.ts @@ -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') diff --git a/workspace/state/test/filePath.test.ts b/workspace/state/test/filePath.test.ts index 6ec805d86d1..a2b20f4b3ce 100644 --- a/workspace/state/test/filePath.test.ts +++ b/workspace/state/test/filePath.test.ts @@ -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')) ) }) From 074ccc752e2e812a9d04b697992d32eab60f9773 Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Tue, 8 Jul 2025 12:00:05 +0200 Subject: [PATCH 15/18] refactor: rename createHashFromFiles to createHashFromMultipleFiles --- .changeset/major-coats-make.md | 2 +- crypto/hash/src/index.ts | 2 +- hooks/pnpmfile/src/requireHooks.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.changeset/major-coats-make.md b/.changeset/major-coats-make.md index 7b643ec4d2a..ac5bef52c40 100644 --- a/.changeset/major-coats-make.md +++ b/.changeset/major-coats-make.md @@ -2,4 +2,4 @@ "@pnpm/crypto.hash": minor --- -A new function added: createHashFromFiles. +A new function added: createHashFromMultipleFiles. diff --git a/crypto/hash/src/index.ts b/crypto/hash/src/index.ts index c6f8ad610d9..54e261a6a2b 100644 --- a/crypto/hash/src/index.ts +++ b/crypto/hash/src/index.ts @@ -15,7 +15,7 @@ export function createHash (input: string): string { return `sha256-${crypto.hash('sha256', input, 'base64')}` } -export async function createHashFromFiles (files: string[]): Promise { +export async function createHashFromMultipleFiles (files: string[]): Promise { if (files.length === 1) { return createHashFromFile(files[0]) } diff --git a/hooks/pnpmfile/src/requireHooks.ts b/hooks/pnpmfile/src/requireHooks.ts index 31219b9a2e0..85160f7b115 100644 --- a/hooks/pnpmfile/src/requireHooks.ts +++ b/hooks/pnpmfile/src/requireHooks.ts @@ -1,7 +1,7 @@ import type { PreResolutionHookContext, PreResolutionHookLogger } from '@pnpm/hooks.types' import { PnpmError } from '@pnpm/error' import { hookLogger } from '@pnpm/core-loggers' -import { createHashFromFiles } 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' @@ -110,7 +110,7 @@ export function requireHooks ( } } filesToIncludeInHash.sort() - return createHashFromFiles(filesToIncludeInHash) + return createHashFromMultipleFiles(filesToIncludeInHash) } } From a8dbfc27d2200a5b22b0b09617ffcf12bb217d6d Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Tue, 8 Jul 2025 14:09:22 +0200 Subject: [PATCH 16/18] fix: throw an error if pnpmfile is not found --- hooks/pnpmfile/src/requireHooks.ts | 12 ++++++++---- hooks/pnpmfile/src/requirePnpmfile.ts | 8 ++++---- hooks/pnpmfile/test/index.ts | 12 ++++++++---- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/hooks/pnpmfile/src/requireHooks.ts b/hooks/pnpmfile/src/requireHooks.ts index 85160f7b115..a44b50e272c 100644 --- a/hooks/pnpmfile/src/requireHooks.ts +++ b/hooks/pnpmfile/src/requireHooks.ts @@ -18,6 +18,7 @@ type Cook any> = ( interface PnpmfileEntry { path: string includeInChecksum: boolean + optional?: boolean } interface PnpmfileEntryLoaded { @@ -59,6 +60,7 @@ export function requireHooks ( pnpmfiles.push({ path: '.pnpmfile.cjs', includeInChecksum: true, + optional: true, }) if (opts.pnpmfile) { if (Array.isArray(opts.pnpmfile)) { @@ -77,17 +79,19 @@ export function requireHooks ( } const entries: PnpmfileEntryLoaded[] = [] const loadedFiles: string[] = [] - for (const { path, includeInChecksum } of pnpmfiles) { + for (const { path, includeInChecksum, optional } of pnpmfiles) { const file = pathAbsolute(path, prefix) if (!loadedFiles.includes(file)) { loadedFiles.push(file) - const module = requirePnpmfile(pathAbsolute(path, prefix), prefix) - if (module != null) { + const requirePnpmfileResult = requirePnpmfile(file, prefix) + if (requirePnpmfileResult != null) { entries.push({ file, includeInChecksum, - module, + module: requirePnpmfileResult.pnpmfileModule, }) + } else if (!optional) { + throw new PnpmError('PNPMFILE_NOT_FOUND', `pnpmfile at "${file}" is not found`) } } } diff --git a/hooks/pnpmfile/src/requirePnpmfile.ts b/hooks/pnpmfile/src/requirePnpmfile.ts index 8aedbd69431..61ebaec4ac4 100644 --- a/hooks/pnpmfile/src/requirePnpmfile.ts +++ b/hooks/pnpmfile/src/requirePnpmfile.ts @@ -31,7 +31,7 @@ export interface Pnpmfile { hooks?: Hooks } -export function requirePnpmfile (pnpmFilePath: string, prefix: string): Pnpmfile | undefined { +export function requirePnpmfile (pnpmFilePath: string, prefix: string): { pnpmfileModule: Pnpmfile | undefined } | undefined { try { const pnpmfile: Pnpmfile = require(pnpmFilePath) // eslint-disable-line if (typeof pnpmfile === 'undefined') { @@ -39,7 +39,7 @@ export function requirePnpmfile (pnpmFilePath: string, prefix: string): Pnpmfile 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') @@ -64,10 +64,10 @@ export function requirePnpmfile (pnpmFilePath: string, prefix: string): Pnpmfile return newPkg } } - return 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) } diff --git a/hooks/pnpmfile/test/index.ts b/hooks/pnpmfile/test/index.ts index 5c8d24b50d6..138fca70cb1 100644 --- a/hooks/pnpmfile/test/index.ts +++ b/hooks/pnpmfile/test/index.ts @@ -6,13 +6,13 @@ 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) @@ -21,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.')) @@ -48,7 +48,7 @@ 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() }) @@ -69,3 +69,7 @@ test('updateConfig throws an error if it returns undefined', async () => { 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') +}) From 7af3c1b6890ee17c5dfac81e1bc2a2eaaf9899b7 Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Tue, 8 Jul 2025 14:10:41 +0200 Subject: [PATCH 17/18] refactor: pnpmfile --- hooks/pnpmfile/src/requireHooks.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/hooks/pnpmfile/src/requireHooks.ts b/hooks/pnpmfile/src/requireHooks.ts index a44b50e272c..ef117f9f569 100644 --- a/hooks/pnpmfile/src/requireHooks.ts +++ b/hooks/pnpmfile/src/requireHooks.ts @@ -23,7 +23,7 @@ interface PnpmfileEntry { interface PnpmfileEntryLoaded { file: string - module: Pnpmfile | undefined + hooks: Pnpmfile['hooks'] | undefined includeInChecksum: boolean } @@ -88,7 +88,7 @@ export function requireHooks ( entries.push({ file, includeInChecksum, - module: requirePnpmfileResult.pnpmfileModule, + hooks: requirePnpmfileResult.pnpmfileModule?.hooks, }) } else if (!optional) { throw new PnpmError('PNPMFILE_NOT_FOUND', `pnpmfile at "${file}" is not found`) @@ -105,7 +105,7 @@ export function requireHooks ( } // calculate combined checksum for all included files - if (entries.some((entry) => entry.module != null)) { + if (entries.some((entry) => entry.hooks != null)) { cookedHooks.calculatePnpmfileChecksum = async () => { const filesToIncludeInHash: string[] = [] for (const { includeInChecksum, file } of entries) { @@ -122,8 +122,8 @@ export function requireHooks ( let fetchersProvider: string | undefined // process hooks in order - for (const { module, file } of entries) { - const fileHooks: Hooks = module?.hooks ?? {} + for (const { hooks, file } of entries) { + const fileHooks: Hooks = hooks ?? {} // readPackage & afterAllResolved for (const hookName of ['readPackage', 'afterAllResolved'] as const) { From 32fc4f469e6b5e3891ee4d27ba7d348ffa7e03d9 Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Tue, 8 Jul 2025 14:32:05 +0200 Subject: [PATCH 18/18] fix: type of pnpmfile --- deps/status/src/checkDepsStatus.ts | 6 +++--- exec/build-commands/test/approveBuilds.test.ts | 2 ++ exec/plugin-commands-rebuild/test/utils/index.ts | 2 +- exec/plugin-commands-script-runners/test/utils/index.ts | 4 ++-- lockfile/plugin-commands-audit/test/index.ts | 2 +- patching/plugin-commands-patching/test/utils/index.ts | 2 +- pkg-manager/plugin-commands-installation/src/install.ts | 2 +- .../plugin-commands-installation/src/installDeps.ts | 8 ++++---- pkg-manager/plugin-commands-installation/src/recursive.ts | 2 +- pkg-manager/plugin-commands-installation/src/remove.ts | 2 +- pkg-manager/plugin-commands-installation/test/add.ts | 2 +- pkg-manager/plugin-commands-installation/test/fetch.ts | 2 +- pkg-manager/plugin-commands-installation/test/global.ts | 2 +- .../plugin-commands-installation/test/peerDependencies.ts | 2 +- pkg-manager/plugin-commands-installation/test/prune.ts | 2 +- .../test/update/interactive.ts | 2 +- .../test/update/issue-7415.ts | 2 +- .../plugin-commands-installation/test/utils/index.ts | 2 +- pnpm/src/types.ts | 3 ++- releasing/plugin-commands-deploy/test/utils/index.ts | 2 +- releasing/plugin-commands-publishing/test/utils/index.ts | 2 +- reviewing/plugin-commands-licenses/test/utils/index.ts | 2 +- reviewing/plugin-commands-listing/test/utils/index.ts | 2 +- reviewing/plugin-commands-outdated/test/utils/index.ts | 2 +- 24 files changed, 32 insertions(+), 29 deletions(-) diff --git a/deps/status/src/checkDepsStatus.ts b/deps/status/src/checkDepsStatus.ts index a0c169ae0f6..29c5341dff0 100644 --- a/deps/status/src/checkDepsStatus.ts +++ b/deps/status/src/checkDepsStatus.ts @@ -57,11 +57,11 @@ export type CheckDepsStatusOptions = Pick & { ignoreFilteredInstallCache?: boolean ignoredWorkspaceStateSettings?: Array + pnpmfile: string[] } & WorkspaceStateSettings export interface CheckDepsStatusResult { @@ -230,7 +230,7 @@ async function _checkDepsStatus (opts: CheckDepsStatusOptions, workspaceState: W rootManifestOptions, rootDir: rootProjectManifestDir, lastValidatedTimestamp: workspaceState.lastValidatedTimestamp, - currentPnpmfiles: opts.pnpmfile as string[], + currentPnpmfiles: opts.pnpmfile, previousPnpmfiles: workspaceState.pnpmfiles, }) if (issue) { @@ -373,7 +373,7 @@ async function _checkDepsStatus (opts: CheckDepsStatusOptions, workspaceState: W rootManifestOptions, rootDir: rootProjectManifestDir, lastValidatedTimestamp: wantedLockfileStats.mtime.valueOf(), - currentPnpmfiles: opts.pnpmfile as string[], + currentPnpmfiles: opts.pnpmfile, previousPnpmfiles: workspaceState.pnpmfiles, }) if (issue) { diff --git a/exec/build-commands/test/approveBuilds.test.ts b/exec/build-commands/test/approveBuilds.test.ts index 5bc7a72e132..169fe596dca 100644 --- a/exec/build-commands/test/approveBuilds.test.ts +++ b/exec/build-commands/test/approveBuilds.test.ts @@ -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: [] } }) diff --git a/exec/plugin-commands-rebuild/test/utils/index.ts b/exec/plugin-commands-rebuild/test/utils/index.ts index dbaf0b056ae..9ca4163d280 100644 --- a/exec/plugin-commands-rebuild/test/utils/index.ts +++ b/exec/plugin-commands-rebuild/test/utils/index.ts @@ -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 }, diff --git a/exec/plugin-commands-script-runners/test/utils/index.ts b/exec/plugin-commands-script-runners/test/utils/index.ts index 49e244e7117..5b86a5a7969 100644 --- a/exec/plugin-commands-script-runners/test/utils/index.ts +++ b/exec/plugin-commands-script-runners/test/utils/index.ts @@ -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 }, diff --git a/lockfile/plugin-commands-audit/test/index.ts b/lockfile/plugin-commands-audit/test/index.ts index ece031ca063..55f81b460aa 100644 --- a/lockfile/plugin-commands-audit/test/index.ts +++ b/lockfile/plugin-commands-audit/test/index.ts @@ -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, diff --git a/patching/plugin-commands-patching/test/utils/index.ts b/patching/plugin-commands-patching/test/utils/index.ts index 1a115712df6..0b8d1ede2ce 100644 --- a/patching/plugin-commands-patching/test/utils/index.ts +++ b/patching/plugin-commands-patching/test/utils/index.ts @@ -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, diff --git a/pkg-manager/plugin-commands-installation/src/install.ts b/pkg-manager/plugin-commands-installation/src/install.ts index 8caa24213ea..84021f32bfd 100644 --- a/pkg-manager/plugin-commands-installation/src/install.ts +++ b/pkg-manager/plugin-commands-installation/src/install.ts @@ -282,7 +282,6 @@ export type InstallCommandOptions = Pick> export async function handler (opts: InstallCommandOptions): Promise { diff --git a/pkg-manager/plugin-commands-installation/src/installDeps.ts b/pkg-manager/plugin-commands-installation/src/installDeps.ts index 0ab9b393a9a..07c5a5a3e2e 100644 --- a/pkg-manager/plugin-commands-installation/src/installDeps.ts +++ b/pkg-manager/plugin-commands-installation/src/installDeps.ts @@ -70,7 +70,6 @@ export type InstallDepsOptions = Pick> 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, - pnpmfiles: opts.pnpmfile as string[], + 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, - pnpmfiles: opts.pnpmfile as string[], + 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, - pnpmfiles: opts.pnpmfile as string[], + pnpmfiles: opts.pnpmfile, filteredInstall: allProjects.length !== Object.keys(opts.selectedProjectsGraph ?? {}).length, configDependencies: opts.configDependencies, }) diff --git a/pkg-manager/plugin-commands-installation/src/recursive.ts b/pkg-manager/plugin-commands-installation/src/recursive.ts index dafc1753d84..2775709c2f8 100755 --- a/pkg-manager/plugin-commands-installation/src/recursive.ts +++ b/pkg-manager/plugin-commands-installation/src/recursive.ts @@ -66,7 +66,6 @@ export type RecursiveOptions = CreateStoreControllerOptions & Pick & { recursive?: boolean + pnpmfile: string[] }, params: string[] ): Promise { diff --git a/pkg-manager/plugin-commands-installation/test/add.ts b/pkg-manager/plugin-commands-installation/test/add.ts index 77788467979..205b53ebaab 100644 --- a/pkg-manager/plugin-commands-installation/test/add.ts +++ b/pkg-manager/plugin-commands-installation/test/add.ts @@ -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 }, diff --git a/pkg-manager/plugin-commands-installation/test/fetch.ts b/pkg-manager/plugin-commands-installation/test/fetch.ts index cf41136a681..169e3be9920 100644 --- a/pkg-manager/plugin-commands-installation/test/fetch.ts +++ b/pkg-manager/plugin-commands-installation/test/fetch.ts @@ -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 }, diff --git a/pkg-manager/plugin-commands-installation/test/global.ts b/pkg-manager/plugin-commands-installation/test/global.ts index 1451cca5ffe..d25063ae6ac 100644 --- a/pkg-manager/plugin-commands-installation/test/global.ts +++ b/pkg-manager/plugin-commands-installation/test/global.ts @@ -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 }, diff --git a/pkg-manager/plugin-commands-installation/test/peerDependencies.ts b/pkg-manager/plugin-commands-installation/test/peerDependencies.ts index 5fe963920a8..56415d9cdc1 100644 --- a/pkg-manager/plugin-commands-installation/test/peerDependencies.ts +++ b/pkg-manager/plugin-commands-installation/test/peerDependencies.ts @@ -24,7 +24,7 @@ const DEFAULT_OPTIONS = { optionalDependencies: true, }, lock: true, - pnpmfile: '.pnpmfile.cjs', + pnpmfile: ['.pnpmfile.cjs'], pnpmHomeDir: '', preferWorkspacePackages: true, rawConfig: { registry: REGISTRY_URL }, diff --git a/pkg-manager/plugin-commands-installation/test/prune.ts b/pkg-manager/plugin-commands-installation/test/prune.ts index f1ce5627ae3..6535261ccd0 100644 --- a/pkg-manager/plugin-commands-installation/test/prune.ts +++ b/pkg-manager/plugin-commands-installation/test/prune.ts @@ -27,7 +27,7 @@ const DEFAULT_OPTIONS = { }, lock: true, linkWorkspacePackages: true, - pnpmfile: '.pnpmfile.cjs', + pnpmfile: ['.pnpmfile.cjs'], pnpmHomeDir: '', preferWorkspacePackages: true, rawConfig: { registry: REGISTRY_URL }, diff --git a/pkg-manager/plugin-commands-installation/test/update/interactive.ts b/pkg-manager/plugin-commands-installation/test/update/interactive.ts index c092fad06ed..62069f92b91 100644 --- a/pkg-manager/plugin-commands-installation/test/update/interactive.ts +++ b/pkg-manager/plugin-commands-installation/test/update/interactive.ts @@ -31,7 +31,7 @@ const DEFAULT_OPTIONS = { optionalDependencies: true, }, lock: true, - pnpmfile: '.pnpmfile.cjs', + pnpmfile: ['.pnpmfile.cjs'], pnpmHomeDir: '', preferWorkspacePackages: true, rawConfig: { registry: REGISTRY_URL }, diff --git a/pkg-manager/plugin-commands-installation/test/update/issue-7415.ts b/pkg-manager/plugin-commands-installation/test/update/issue-7415.ts index 7e8849ea8f3..7922ec83181 100644 --- a/pkg-manager/plugin-commands-installation/test/update/issue-7415.ts +++ b/pkg-manager/plugin-commands-installation/test/update/issue-7415.ts @@ -28,7 +28,7 @@ const DEFAULT_OPTIONS = { optionalDependencies: true, }, lock: true, - pnpmfile: '.pnpmfile.cjs', + pnpmfile: ['.pnpmfile.cjs'], pnpmHomeDir: '', preferWorkspacePackages: true, rawConfig: { registry: REGISTRY_URL }, diff --git a/pkg-manager/plugin-commands-installation/test/utils/index.ts b/pkg-manager/plugin-commands-installation/test/utils/index.ts index cb1644c7dfd..8329c1d3c46 100644 --- a/pkg-manager/plugin-commands-installation/test/utils/index.ts +++ b/pkg-manager/plugin-commands-installation/test/utils/index.ts @@ -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, diff --git a/pnpm/src/types.ts b/pnpm/src/types.ts index 7ebe91682cc..bad107c4b36 100644 --- a/pnpm/src/types.ts +++ b/pnpm/src/types.ts @@ -4,7 +4,7 @@ import { type ReadPackageHook, } from '@pnpm/types' -export type PnpmOptions = Omit & { +export type PnpmOptions = Omit & { argv: { cooked: string[] original: string[] @@ -12,6 +12,7 @@ export type PnpmOptions = Omit & { } cliOptions: object reporter?: (logObj: LogBase) => void + pnpmfile: string[] packageManager?: { name: string version: string diff --git a/releasing/plugin-commands-deploy/test/utils/index.ts b/releasing/plugin-commands-deploy/test/utils/index.ts index 87daf95e6f8..49573ee9e22 100644 --- a/releasing/plugin-commands-deploy/test/utils/index.ts +++ b/releasing/plugin-commands-deploy/test/utils/index.ts @@ -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, diff --git a/releasing/plugin-commands-publishing/test/utils/index.ts b/releasing/plugin-commands-publishing/test/utils/index.ts index 38312148fd7..d97441064f2 100644 --- a/releasing/plugin-commands-publishing/test/utils/index.ts +++ b/releasing/plugin-commands-publishing/test/utils/index.ts @@ -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 }, diff --git a/reviewing/plugin-commands-licenses/test/utils/index.ts b/reviewing/plugin-commands-licenses/test/utils/index.ts index 60fac218fed..36ea26bb360 100644 --- a/reviewing/plugin-commands-licenses/test/utils/index.ts +++ b/reviewing/plugin-commands-licenses/test/utils/index.ts @@ -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, diff --git a/reviewing/plugin-commands-listing/test/utils/index.ts b/reviewing/plugin-commands-listing/test/utils/index.ts index c74ee858205..abe686f8af4 100644 --- a/reviewing/plugin-commands-listing/test/utils/index.ts +++ b/reviewing/plugin-commands-listing/test/utils/index.ts @@ -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, diff --git a/reviewing/plugin-commands-outdated/test/utils/index.ts b/reviewing/plugin-commands-outdated/test/utils/index.ts index de5748f77fc..b5995e544f1 100644 --- a/reviewing/plugin-commands-outdated/test/utils/index.ts +++ b/reviewing/plugin-commands-outdated/test/utils/index.ts @@ -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,